From 399be07fcf00d0974d2a83d1af55f0bee9861d54 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 12 Jan 2016 16:34:47 -0800 Subject: [PATCH 001/968] Cretonne README and LICENSE. --- LICENSE | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 9 +++ 2 files changed, 211 insertions(+) create mode 100644 LICENSE create mode 100644 README.rst diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000..10db808462 --- /dev/null +++ b/README.rst @@ -0,0 +1,9 @@ +======================= +Cretonne Code Generator +======================= + +Cretonne is a low-level retargetable code generator. It translates a +target-independent intermediate language into executable machine code. Cretonne +aims to generate code that behaves identically on different target +architectures, and that can be executed safely in a sandbox. + From ae54206e66f735ecb9b5f6d7fe5e7e8668b67222 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 12 Jan 2016 16:46:27 -0800 Subject: [PATCH 002/968] Initial Sphinx configuration. --- docs/.gitignore | 1 + docs/Makefile | 192 ++++++++++++++++++++++++++++++++ docs/conf.py | 289 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 16 +++ docs/make.bat | 263 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 761 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..e35d8850c9 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_build diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000..619d2caf24 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/cretonne.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/cretonne.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/cretonne" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/cretonne" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000000..4f2a236b61 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +# +# cretonne documentation build configuration file, created by +# sphinx-quickstart on Fri Jan 8 10:11:19 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.todo', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'cretonne' +copyright = u'2016, Jakob Stoklund Olesen' +author = u'Jakob Stoklund Olesen' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.0' +# The full version, including alpha/beta/rc tags. +release = u'0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'cretonnedoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'cretonne.tex', u'cretonne Documentation', + u'Jakob Stoklund Olesen', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'cretonne', u'cretonne Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'cretonne', u'cretonne Documentation', + author, 'cretonne', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000000..5f8e7c2df1 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,16 @@ +Cretonne Code Generator +======================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000000..3f6fe2e48d --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\cretonne.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\cretonne.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end From c8f20534d383981f0a4d535edebf01ea0528d277 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 12 Jan 2016 16:53:43 -0800 Subject: [PATCH 003/968] Add a Cretonne domain for Sphinx. Include roles for documenting IL instructions and types, including index cross references. --- .gitignore | 1 + docs/conf.py | 3 +- docs/cton_domain.py | 187 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 docs/cton_domain.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..0d20b6487c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/docs/conf.py b/docs/conf.py index 4f2a236b61..58cc740464 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ import shlex # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ @@ -34,6 +34,7 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', + 'cton_domain' ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/cton_domain.py b/docs/cton_domain.py new file mode 100644 index 0000000000..afa372921f --- /dev/null +++ b/docs/cton_domain.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# +# Sphinx domain for documenting compiler intermediate languages. +# +# This defines a 'cton' Sphinx domain with the following directives and roles: +# +# .. cton::type:: type +# Document an IR type. +# .. cton:inst:: v1, v2 = inst op1, op2 +# Document an IR instruction. +# + +import re + +from docutils import nodes + +from sphinx import addnodes +from sphinx.directives import ObjectDescription +from sphinx.domains import Domain, ObjType +from sphinx.locale import l_, _ +from sphinx.roles import XRefRole +from sphinx.util.docfields import Field, TypedField +from sphinx.util.nodes import make_refnode + +class CtonObject(ObjectDescription): + """ + Any kind of Cretonne IL object. + + This is a shared base class for the different kinds of indexable objects + in the Cretonne IL reference. + """ + + def add_target_and_index(self, name, sig, signode): + """ + Add ``name`` the the index. + + :param name: The object name returned by :func:`handle_signature`. + :param sig: The signature text. + :param signode: The output node. + """ + targetname = self.objtype + '-' + name + if targetname not in self.state.document.ids: + signode['names'].append(targetname) + signode['ids'].append(targetname) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + inv = self.env.domaindata['cton']['objects'] + if name in inv: + self.state_machine.reporter.warning( + 'duplicate Cretonne object description of %s, ' % name + + 'other instance in ' + self.env.doc2path(inv[name][0]), + line=self.lineno) + inv[name] = (self.env.docname, self.objtype) + + indextext = self.get_index_text(name) + if indextext: + self.indexnode['entries'].append(('single', indextext, + targetname, '')) + +class CtonType(CtonObject): + """A Cretonne IL type description.""" + + def handle_signature(self, sig, signode): + """ + Parse type signature in ``sig`` and append description to signode. + + Return a global object name for ``add_target_and_index``. + """ + + name = sig.strip() + signode += addnodes.desc_name(name, name) + return name + + def get_index_text(self, name): + return name + ' (IL type)' + +sep_equal = re.compile('\s*=\s*') +sep_comma = re.compile('\s*,\s*') + +def parse_params(s, signode): + for i,p in enumerate(sep_comma.split(s)): + if i != 0: + signode += nodes.Text(', ') + signode += nodes.emphasis(p, p) + +class CtonInst(CtonObject): + """A Cretonne IL instruction.""" + + doc_field_types = [ + TypedField('argument', label=l_('Arguments'), + names=('in', 'arg'), + typerolename='type', typenames=('type',)), + TypedField('result', label=l_('Results'), + names=('out', 'result'), + typerolename='type', typenames=('type',)), + Field('resulttype', label=l_('Result type'), has_arg=False, + names=('rtype',)), + ] + + def handle_signature(self, sig, signode): + # Look for signatures like + # + # v1, v2 = foo op1, op2 + # v1 = foo + # foo op1 + + parts = re.split(sep_equal, sig, 1) + if len(parts) == 2: + # Outgoing parameters. + parse_params(parts[0], signode) + signode += nodes.Text(' = ') + name = parts[1] + else: + name = parts[0] + + # Parse 'name arg, arg' + parts = name.split(None, 1) + name = parts[0] + signode += addnodes.desc_name(name, name) + + if len(parts) == 2: + # Incoming parameters. + signode += nodes.Text(' ') + parse_params(parts[1], signode) + + return name + + def get_index_text(self, name): + return name + +class CretonneDomain(Domain): + """Cretonne domain for intermediate language objects.""" + name = 'cton' + label = 'Cretonne' + + object_types = { + 'type' : ObjType(l_('type'), 'type'), + 'inst' : ObjType(l_('instruction'), 'inst') + } + + directives = { + 'type' : CtonType, + 'inst' : CtonInst, + } + + roles = { + 'type' : XRefRole(), + 'inst' : XRefRole(), + } + + initial_data = { + 'objects': {}, # fullname -> docname, objtype + } + + def clear_doc(self, docname): + for fullname, (fn, _l) in list(self.data['objects'].items()): + if fn == docname: + del self.data['objects'][fullname] + + def merge_domaindata(self, docnames, otherdata): + for fullname, (fn, objtype) in otherdata['objects'].items(): + if fn in docnames: + self.data['objects'][fullname] = (fn, objtype) + + def resolve_xref(self, env, fromdocname, builder, typ, target, node, + contnode): + objects = self.data['objects'] + if target not in objects: + return None + obj = objects[target] + return make_refnode(builder, fromdocname, obj[0], + obj[1] + '-' + target, contnode, target) + + def resolve_any_xref(self, env, fromdocname, builder, target, + node, contnode): + objects = self.data['objects'] + if target not in objects: + return [] + obj = objects[target] + return [('cton:' + self.role_for_objtype(obj[1]), + make_refnode(builder, fromdocname, obj[0], + obj[1] + '-' + target, contnode, target))] + +def setup(app): + app.add_domain(CretonneDomain) + + return { 'version' : '0.1' } From b5ee157d4653baecc3e90f7bd47dad83a32c12ba Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 12 Jan 2016 16:54:49 -0800 Subject: [PATCH 004/968] Begin the intermediate language reference. --- docs/index.rst | 1 + docs/langref.rst | 585 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 586 insertions(+) create mode 100644 docs/langref.rst diff --git a/docs/index.rst b/docs/index.rst index 5f8e7c2df1..137e7b1e84 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,7 @@ Contents: .. toctree:: :maxdepth: 2 + langref Indices and tables ================== diff --git a/docs/langref.rst b/docs/langref.rst new file mode 100644 index 0000000000..31f79df0c2 --- /dev/null +++ b/docs/langref.rst @@ -0,0 +1,585 @@ +**************************************** +Cretonne Intermediate Language Reference +**************************************** + +Type system +=========== + +.. default-domain:: cton + +All SSA values have a type which determines the size and shape (for SIMD +vectors) of the value. Many instructions are polymorphic -- they can operate on +different types. + +.. type:: bool + + A boolean value that is either true or false. Booleans can't be stored in + memory. + +Integer types +------------- + +Integer values have a fixed size and can be interpreted as either signed or +unsigned. Some instructions will interpret an operand as a signed or unsigned +number, others don't care. + +.. type:: i8 + + A 8-bit integer value taking up 1 byte in memory. + +.. type:: i16 + + A 16-bit integer value taking up 2 bytes in memory. + +.. type:: i32 + + A 32-bit integer value taking up 4 bytes in memory. + +.. type:: i64 + + A 64-bit integer value taking up 8 bytes in memory. + +Floating point types +-------------------- + +The floating point types have the IEEE semantics that are supported by most +hardware. There is no support for higher-precision types like quads or +double-double formats. + +.. type:: f32 + + A 32-bit floating point type represented in the IEEE 754 *Single precision* + format. This corresponds to the :c:type:`float` type in most C + implementations. + +.. type:: f64 + + A 64-bit floating point type represented in the IEEE 754 *Double precision* + format. This corresponds to the :c:type:`double` type in most C + implementations. + +SIMD vector types +----------------- + +A SIMD vector type represents a vector of values from one of the scalar types +(:type:`bool`, integer, and floating point). Each scalar value in a SIMD type is +called a *lane*. The number of lanes must be a power of two in the range 2-256. + +.. type:: vNiB + + A SIMD vector of integers. The lane type :type:`iB` must be one of the + integer types :type:`i8` ... :type:`i64`. + + Some concrete integer vector types are :type:`v4i32`, :type:`v8i64`, and + :type:`v4i16`. + + The size of a SIMD integer vector in memory is :math:`N B\over 8` bytes. + +.. type:: vNf32 + + A SIMD vector of single precision floating point numbers. + + Some concrete :type:`f32` vector types are: :type:`v2f32`, :type:`v4f32`, + and :type:`v8f32`. + + The size of a :type:`f32` vector in memory is :math:`4N` bytes. + +.. type:: vNf64 + + A SIMD vector of double precision floating point numbers. + + Some concrete :type:`f64` vector types are: :type:`v2f64`, :type:`v4f64`, + and :type:`v8f64`. + + The size of a :type:`f64` vector in memory is :math:`8N` bytes. + +.. type:: vNbool + + A boolean SIMD vector. + + Like the :type:`bool` type, a boolean vector cannot be stored in memory. It + can only be used for ephemeral SSA values. + +Instructions +============ + +Control flow instructions +------------------------- + +.. inst:: br EBB(args...) + + Branch. + + Unconditionally branch to an extended basic block, passing the specified + EBB arguments. The number and types of arguments must match the destination + EBB. + +.. inst:: brz x, EBB(args...) + + Branch when zero. + + If ``x`` is a :type:`bool` value, take the branch when ``x`` is false. If + ``x`` is an integer value, take the branch when ``x = 0``. + + :param iN/bool x: Value to test. + :param EBB: Destination extended basic block. + +.. inst:: brnz x, EBB(args...) + + Branch when non-zero. + + If ``x`` is a :type:`bool` value, take the branch when ``x`` is true. If + ``x`` is an integer value, take the branch when ``x != 0``. + + :param iN/bool x: Value to test. + :param EBB: Destination extended basic block. + +Special operations +================== + +Most operations are easily classified as arithmetic or control flow. These +instructions are not so easily classified. + +.. inst:: a = iconst n + + Integer constant. + +.. inst:: a = fconst n + + Floating point constant. + +.. inst:: a = vconst n + + Vector constant (floating point or integer). + +.. inst:: a = select c, x, y + + Conditional select. + + :param c bool: Controlling flag. + :param x: Value to return when ``c`` is true. + :param y: Value to return when ``c`` is false. Must be same type as ``x``. + :rtype: Same type as ``x`` and ``y``. + + This instruction selects whole values. Use :inst:`vselect` for + lane-wise selection. + +Vector operations +================= + +.. inst:: a = vselect c, x, y + + Vector lane select. + + Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean + vector ``c``. + + :arg vNbool c: Controlling flag vector. + :arg x: Vector with lanes selected by the true lanes of ``c``. + Must be a vector type with the same number of lanes as ``c``. + :arg y: Vector with lanes selected by the false lanes of ``c``. + Must be same type as ``x``. + :rtype: Same type as ``x`` and ``y``. + +.. inst:: a = vbuild x, y, z, ... + + Vector build. + + Build a vector value from the provided lanes. + +.. inst:: a = splat x + + Vector splat. + + Return a vector whose lanes are all ``x``. + +.. inst:: a = insertlane x, idx, y + + Insert ``y`` as lane ``idx`` in x. + + The lane index, ``idx``, is an immediate value, not an SSA value. It must + indicate a valid lane index for the type of ``x``. + +.. inst:: a = extractlane x, idx + + Extract lane ``idx`` from ``x``. + + The lane index, ``idx``, is an immediate value, not an SSA value. It must + indicate a valid lane index for the type of ``x``. + +Integer operations +================== + +.. inst:: a = icmp cond, x, y + + Integer comparison. + + :param cond: Condition code determining how ``x`` and ``y`` are compared. + :param x, y: Integer scalar or vector values of the same type. + :rtype: :type:`bool` or :type:`vNbool` with the same number of lanes as + ``x`` and ``y``. + + The condition code determines if the operands are interpreted as signed or + unsigned integers. + + ====== ======== ========= + Signed Unsigned Condition + ====== ======== ========= + eq eq Equal + ne ne Not equal + slt ult Less than + sge uge Greater than or equal + sgt ugt Greater than + sle ule Less than or equal + ====== ======== ========= + +.. inst:: a = iadd x, y + + Wrapping integer addition: :math:`a := x + y \pmod{2^B}`. This instruction + does not depend on the signed/unsigned interpretation of the operands. + +.. inst:: a = isub x, y + + Wrapping integer subtraction: :math:`a := x - y \pmod{2^B}`. This + instruction does not depend on the signed/unsigned interpretation of the + operands. + +.. todo:: Overflow arithmetic + + Add instructions for add with carry out / carry in and so on. Enough to + implement larger integer types efficiently. It should also be possible to + legalize :type:`i64` arithmetic to terms of :type:`i32` operations. + +.. inst:: a = ineg x + + Wrapping integer negation: :math:`a := -x \pmod{2^B}`. This instruction does + not depend on the signed/unsigned interpretation of the operand. + +.. inst:: a = imul x, y + + Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. This + instruction does not depend on the signed/unsigned interpretation of the + operands. + +.. todo:: Larger multiplication results. + + For example, ``smulx`` which multiplies :type:`i32` operands to produce a + :type:`i64` result. Alternatively, ``smulhi`` and ``smullo`` pairs. + +.. inst:: a = udiv x, y + + Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. This + operation traps if the divisor is zero. + + .. todo:: + Add a ``udiv_imm`` variant with an immediate divisor greater than 1. + This is useful for pattern-matching divide-by-constant, and this + instruction would be non-trapping. + +.. inst:: a = sdiv x, y + + Signed integer division rounded toward zero: :math:`a := sign(xy) \lfloor + {|x| \over |y|}\rfloor`. This operation traps if the divisor is zero, or if + the result is not representable in :math:`B` bits two's complement. This only + happens when :math:`x = -2^{B-1}, y = -1`. + + .. todo:: + Add a ``sdiv_imm`` variant with an immediate non-zero divisor. This is + useful for pattern-matching divide-by-constant, and this instruction + would be non-trapping. Don't allow divisors 0, 1, or -1. + +.. inst:: a = urem x, y + + Unsigned integer remainder. This operation traps if the divisor is zero. + + .. todo:: + Add a ``urem_imm`` non-trapping variant. + +.. inst:: a = srem x, y + + Signed integer remainder. This operation traps if the divisor is zero. + + .. todo:: + Clarify whether the result has the sign of the divisor or the dividend. + Should we add a ``smod`` instruction for the case where the result has + the same sign as the divisor? + +.. todo:: Minimum / maximum. + + NEON has ``smin``, ``smax``, ``umin``, and ``umax`` instructions. We should + replicate those for both scalar and vector integer types. Even if the + target ISA doesn't have scalar operations, these are good pattern mtching + targets. + +.. todo:: Saturating arithmetic. + + Mostly for SIMD use, but again these are good paterns to contract. + Something like ``usatadd``, ``usatsub``, ``ssatadd``, and ``ssatsub`` is a + good start. + +Bitwise operations +================== + +.. inst:: a = and x, y + + Bitwise and. + + :rtype: bool, iB, vNiB, vNfB? + +.. inst:: a = or x, y + + Bitwise or. + + :rtype: bool, iB, vNiB, vNfB? + +.. inst:: a = xor x, y + + Bitwise xor. + + :rtype: bool, iB, vNiB, vNfB? + +.. inst:: a = not x + + Bitwise not. + + :rtype: bool, iB, vNiB, vNfB? + +.. todo:: Redundant bitwise operators. + + ARM has instructions like ``bic(x,y) = x & ~y``, ``orn(x,y) = x | ~y``, and + ``eon(x,y) = x ^ ~y``. + +.. inst:: a = rotl x, y + + Rotate left. + + Rotate the bits in ``x`` by ``y`` places. + + :param x: Integer value to be rotated. + :param y: Number of bits to shift. Any integer type, not necessarily the + same type as ``x``. + :rtype: Same type as ``x``. + +.. inst:: a = rotr x, y + + Rotate right. + + Rotate the bits in ``x`` by ``y`` places. + + :param x: Integer value to be rotated. + :param y: Number of bits to shift. Any integer type, not necessarily the + same type as ``x``. + :rtype: Same type as ``x``. + +.. inst:: a = ishl x, y + + Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` + places. Shift in zero bits to the LSB. + + The shift amount is masked to the size of ``x``. + + :param x: Integer value to be shifted. + :param y: Number of bits to shift. Any integer type, not necessarily the + same type as ``x``. + :rtype: Same type as ``x``. + + When shifting a B-bits integer type, this instruction computes: + + .. math:: + s &:= y \pmod B, \\ + a &:= x \cdot 2^s \pmod{2^B}. + + .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. + +.. inst:: a = ushr x, y + + Unsigned shift right. Shift bits in ``x`` towards the LSB by ``y`` places, + shifting in zero bits to the MSB. Also called a *logical shift*. + + The shift amount is masked to the size of the register. + + :param x: Integer value to be shifted. + :param y: Number of bits to shift. Can be any integer type, not necessarily + the same type as ``x``. + :rtype: Same type as ``x``. + + When shifting a B-bits integer type, this instruction computes: + + .. math:: + s &:= y \pmod B, \\ + a &:= \lfloor x \cdot 2^{-s} \rfloor. + + .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. + +.. inst:: a = sshr x, y + + Signed shift right. Shift bits in ``x`` towards the LSB by ``y`` places, + shifting in sign bits to the MSB. Also called an *arithmetic shift*. + + The shift amount is masked to the size of the register. + + :param x: Integer value to be shifted. + :param y: Number of bits to shift. Can be any integer type, not necessarily + the same type as ``x``. + :rtype: Same type as ``x``. + + .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. + +.. inst:: a = clz x + + Count leading zero bits. + + :param x: Integer value. + :rtype: :type:`i8` + + Starting from the MSB in ``x``, count the number of zero bits before + reaching the first one bit. When ``x`` is zero, returns the size of x in + bits. + +.. inst:: a = cls x + + Count leading sign bits. + + :param x: Integer value. + :rtype: :type:`i8` + + Starting from the MSB after the sign bit in ``x``, count the number of + consecutive bits identical to the sign bit. When ``x`` is 0 or -1, returns + one less than the size of x in bits. + +.. inst:: a = ctz x + + Count trailing zeros. + + :param x: Integer value. + :rtype: :type:`i8` + + Starting from the LSB in ``x``, count the number of zero bits before + reaching the first one bit. When ``x`` is zero, returns the size of x in + bits. + +.. inst:: a = popcnt x + + Population count + + :param x: Integer value. + :rtype: :type:`i8` + + Count the number of one bits in ``x``. + + +Floating point operations +========================= + +.. inst:: a = fcmp cond, x, y + + Floating point comparison. + + :param cond: Condition code determining how ``x`` and ``y`` are compared. + :param x, y: Floating point scalar or vector values of the same type. + :rtype: :type:`bool` or :type:`vNbool` with the same number of lanes as + ``x`` and ``y``. + + An 'ordered' condition code yields ``false`` if either operand is Nan. + + An 'unordered' condition code yields ``true`` if either operand is Nan. + + ======= ========= ========= + Ordered Unordered Condition + ======= ========= ========= + ord uno None (ord = no NaNs, uno = some NaNs) + oeq ueq Equal + one une Not equal + olt ult Less than + oge uge Greater than or equal + ogt ugt Greater than + ole ule Less than or equal + ======= ========= ========= + +.. inst:: fadd x,y + + Floating point addition. + +.. inst:: fsub x,y + + Floating point subtraction. + +.. inst:: fneg x + + Floating point negation. + + :returns: ``x`` with its sign bit inverted. + + Note that this is a pure bitwise operation. + +.. inst:: fabs x + + Floating point absolute value. + + :returns: ``x`` with its sign bit cleared. + + Note that this is a pure bitwise operation. + +.. inst:: a = fcopysign x, y + + Floating point copy sign. + + :returns: ``x`` with its sign changed to that of ``y``. + + Note that this is a pure bitwise operation. The sign bit from ``y`` is + copied to the sign bit of ``x``. + +.. inst:: fmul x, y +.. inst:: fdiv x, y +.. inst:: fmin x, y +.. inst:: fminnum x, y +.. inst:: fmax x, y +.. inst:: fmaxnum x, y +.. inst:: ceil x + + Round floating point round to integral, towards positive infinity. + +.. inst:: floor x + + Round floating point round to integral, towards negative infinity. + +.. inst:: trunc x + + Round floating point round to integral, towards zero. + +.. inst:: nearest x + + Round floating point round to integral, towards nearest with ties to even. + +.. inst:: sqrt x + + Floating point square root. + +.. inst:: a = fma x, y, z + + Floating point fused multiply-and-add. + + Computes :math:`a := xy+z` wihtout any intermediate rounding of the + product. + +Conversion operations +===================== + +.. inst:: a = bitcast x + + Reinterpret the bits in ``x`` as a different type. + + The input and output types must be storable to memory and of the same size. + A bitcast is equivalent to storing one type and loading the other type from + the same address. + +.. inst:: a = itrunc x +.. inst:: a = uext x +.. inst:: a = sext x +.. inst:: a = ftrunc x +.. inst:: a = fext x +.. inst:: a = cvt_ftou x +.. inst:: a = cvt_ftos x +.. inst:: a = cvt_utof x +.. inst:: a = cvt_stof x + From edb2440eaf13ac8150ba7c1e1fd9e69b714df389 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 19 Jan 2016 19:51:53 -0800 Subject: [PATCH 005/968] Emit list of todo items. --- docs/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 137e7b1e84..668b055c97 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,3 +15,7 @@ Indices and tables * :ref:`modindex` * :ref:`search` +Todo list +========= + +.. todolist:: From 36cb753c4f20de5c1b2ed731d2c2cc069159cf76 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 19 Jan 2016 20:28:33 -0800 Subject: [PATCH 006/968] Cretonne pygments lexer --- docs/conf.py | 3 ++- docs/cton_lexer.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 docs/cton_lexer.py diff --git a/docs/conf.py b/docs/conf.py index 58cc740464..120d32011a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,7 +34,8 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', - 'cton_domain' + 'cton_domain', + 'cton_lexer', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py new file mode 100644 index 0000000000..02e8945580 --- /dev/null +++ b/docs/cton_lexer.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# Pygments lexer for Cretonne. + +from pygments.lexer import RegexLexer, bygroups +from pygments.token import * + +class CretonneLexer(RegexLexer): + name = 'Cretonne' + aliases = ['cton'] + filenames = ['*.cton'] + + tokens = { + 'root': [ + (r';.*?$', Comment.Single), + (r'\b(function|entry)\b', Keyword), + (r'\b(align)\b', Name.Attribute), + (r'\b(v\d+)?(bool|i\d+|f32|f64)\b', Keyword.Type), + (r'\d+', Number.Integer), + (r'0[xX][0-9a-fA-F]+', Number.Hex), + (r'(v|ss|ebb)\d+', Name.Variable), + (r'(ebb)\d+', Name.Label), + (r'(=)( *)([a-z]\w*)', bygroups(Operator, Whitespace, Name.Function)), + (r'^( +)([a-z]\w*\b)(?! *[,=])', bygroups(Whitespace, Name.Function)), + (r'[a-z]\w*', Name), + (r'->|=|:', Operator), + (r'[{}(),.]', Punctuation), + (r'[ \t]+', Text), + ] + } + +def setup(app): + """Setup Sphinx extension.""" + app.add_lexer('cton', CretonneLexer()) + + return { 'version' : '0.1' } From 2b2b79dcf82791a960aba1c836ed77b7c9806ec3 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Thu, 21 Jan 2016 11:46:30 -0800 Subject: [PATCH 007/968] Add langref example --- docs/langref.rst | 72 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/docs/langref.rst b/docs/langref.rst index 31f79df0c2..364ea20f05 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -2,10 +2,80 @@ Cretonne Intermediate Language Reference **************************************** +.. default-domain:: cton +.. highlight:: cton + +The Cretonne intermediate language has two equivalent representations: an +*in-memory data structure* that the code generator library is using, and +a *text format* which is used for test cases and debug output. Files containing +Cretonne textual IL have the ``.cton`` filename extension. + +This reference uses the text format to describe IL semantics but glosses over +the details of the lexical and syntactic structure of the test format. + +Overall structure +================= + +Cretonne compiles functions independently. A ``.cton`` IL file may contain +multiple functions, and the programmatic API can create multiple function +handles at the same time, but the functions don't share any data or reference +each other directly. + +This is a C function that computes the average of an array of floats: + +.. code-block:: c + + float average(const float *array, size_t count) { + double sum = 0; + for (size_t i = 0; i < count; i++) + sum += array[i]; + return sum / count; + } + +Here it is compiled into Cretonne IL:: + + function average(i32, i32) -> f32 { + ; Preamble. + ss1 = local 8, align 4 + + entry ebb1(v1: i32, v2: i32): + v3 = fconst.f64 0.0 + stack_store v3, ss1 + brz v2, ebb3 ; Handle count == 0. + v4 = iconst.i32 0 + br ebb2(v4) + + ebb2(v5: i32): + ; Compute address of array element. + v6 = imul_imm v5, 4 + v7 = iadd v1, v6 + v8 = heap_load.f32 v7 ; array[i] + v9 = fext.f64 v8 + ; Add to accumulator in ss1. + v10 = stack_load.f64 ss1 + v11 = fadd v9, v10 + stack_store v11, ss1 + ; Increment loop counter. + v12 = iadd_imm v5, 1 + v13 = icmp ult v12, v2 + brnz v13, ebb2(v12) ; Loop backedge. + ; Compute average from sum. + v14 = stack_load.f64 ss1 + v15 = cvt_utof.f64 v2 + v16 = fdiv v14, v15 + v17 = ftrunc.f32 v16 + return v17 + + ebb3: + v100 = fconst.f32 0x7f800000 ; Inf + return v100 + } + + + Type system =========== -.. default-domain:: cton All SSA values have a type which determines the size and shape (for SIMD vectors) of the value. Many instructions are polymorphic -- they can operate on From 401afdc48ccc35bcf6da39baaa0a22bde1a7d32b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 21 Jan 2016 14:25:16 -0800 Subject: [PATCH 008/968] Update language reference. Add a glossary and explain the overall shape of a Cretonne function. --- docs/example.c | 8 +++ docs/example.cton | 31 ++++++++++ docs/langref.rst | 144 +++++++++++++++++++++++++++++++--------------- 3 files changed, 138 insertions(+), 45 deletions(-) create mode 100644 docs/example.c create mode 100644 docs/example.cton diff --git a/docs/example.c b/docs/example.c new file mode 100644 index 0000000000..0523123301 --- /dev/null +++ b/docs/example.c @@ -0,0 +1,8 @@ +float +average(const float *array, size_t count) +{ + double sum = 0; + for (size_t i = 0; i < count; i++) + sum += array[i]; + return sum / count; +} diff --git a/docs/example.cton b/docs/example.cton new file mode 100644 index 0000000000..04c9e77fb4 --- /dev/null +++ b/docs/example.cton @@ -0,0 +1,31 @@ +function average(i32, i32) -> f32 { + ss1 = local 8, align 4 ; Stack slot for ``sum``. + +entry ebb1(v1: i32, v2: i32): + v3 = fconst.f64 0.0 + stack_store v3, ss1 + brz v2, ebb3 ; Handle count == 0. + v4 = iconst.i32 0 + br ebb2(v4) + +ebb2(v5: i32): + v6 = imul_imm v5, 4 + v7 = iadd v1, v6 + v8 = heap_load.f32 v7 ; array[i] + v9 = fext.f64 v8 + v10 = stack_load.f64 ss1 + v11 = fadd v9, v10 + stack_store v11, ss1 + v12 = iadd_imm v5, 1 + v13 = icmp ult v12, v2 + brnz v13, ebb2(v12) ; Loop backedge. + v14 = stack_load.f64 ss1 + v15 = cvt_utof.f64 v2 + v16 = fdiv v14, v15 + v17 = ftrunc.f32 v16 + return v17 + +ebb3: + v100 = fconst.f32 0x7fc00000 ; 0/0 = NaN + return v100 +} diff --git a/docs/langref.rst b/docs/langref.rst index 364ea20f05..aaff27a9f3 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -21,62 +21,53 @@ multiple functions, and the programmatic API can create multiple function handles at the same time, but the functions don't share any data or reference each other directly. -This is a C function that computes the average of an array of floats: +This is a simple C function that computes the average of an array of floats: -.. code-block:: c +.. literalinclude:: example.c + :language: c - float average(const float *array, size_t count) { - double sum = 0; - for (size_t i = 0; i < count; i++) - sum += array[i]; - return sum / count; - } +Here is the same function compiled into Cretonne IL: -Here it is compiled into Cretonne IL:: +.. literalinclude:: example.cton + :language: cton + :linenos: + :emphasize-lines: 2 - function average(i32, i32) -> f32 { - ; Preamble. - ss1 = local 8, align 4 +The first line of a function definition provides the function *name* and +the :term:`function signature` which declares the argument and return types. +Then follows the :term:`function preample` which declares a number of entities +that can be referenced inside the function. In the example above, the preample +declares a single local variable, ``ss1``. - entry ebb1(v1: i32, v2: i32): - v3 = fconst.f64 0.0 - stack_store v3, ss1 - brz v2, ebb3 ; Handle count == 0. - v4 = iconst.i32 0 - br ebb2(v4) +After the preample follows the :term:`function body` which consists of +:term:`extended basic block`\s, one of which is marked as the :term:`entry +block`. Every EBB ends with a :term:`terminator instruction`, and execution +can never fall through to the next EBB without an explicit branch. - ebb2(v5: i32): - ; Compute address of array element. - v6 = imul_imm v5, 4 - v7 = iadd v1, v6 - v8 = heap_load.f32 v7 ; array[i] - v9 = fext.f64 v8 - ; Add to accumulator in ss1. - v10 = stack_load.f64 ss1 - v11 = fadd v9, v10 - stack_store v11, ss1 - ; Increment loop counter. - v12 = iadd_imm v5, 1 - v13 = icmp ult v12, v2 - brnz v13, ebb2(v12) ; Loop backedge. - ; Compute average from sum. - v14 = stack_load.f64 ss1 - v15 = cvt_utof.f64 v2 - v16 = fdiv v14, v15 - v17 = ftrunc.f32 v16 - return v17 +Static single assignment form +----------------------------- - ebb3: - v100 = fconst.f32 0x7f800000 ; Inf - return v100 - } - +The instructions in the function body use and produce *values* in SSA form. This +means that every value is defined exactly once, and every use of a value must be +dominated by the definition. +Cretonne does not have phi instructions but uses *EBB arguments* instead. An EBB +can be defined with a list of typed arguments. Whenever control is transferred +to the EBB, values for the arguments must be provided. When entering a function, +the incoming function arguments are passed as arguments to the entry EBB. -Type system +Instructions define zero, one, or more result values. All SSA values are either +EBB arguments or instruction results. + +In the example above, the loop induction variable ``i`` is represented as three +SSA values: In the entry block, ``v4`` is the initial value. In the loop block +``ebb2``, the EBB argument ``v5`` represents the value of the induction +variable during each iteration. Finally, ``v12`` is computed as the induction +variable value for the next iteration. + +Value types =========== - All SSA values have a type which determines the size and shape (for SIMD vectors) of the value. Many instructions are polymorphic -- they can operate on different types. @@ -653,3 +644,66 @@ Conversion operations .. inst:: a = cvt_utof x .. inst:: a = cvt_stof x +Glossary +======== + +.. glossary:: + + function signature + A function signature describes how to call a function. It consists of: + + - The calling convention. + - The number of arguments and return values. (Functions can return + multiple values.) + - Type and flags of each argument. + - Type and flags of each return value. + + Not all function atributes are part of the signature. For example, a + function that never returns could be marked as ``noreturn``, but that + is not necessary to know when calling it, so it is just an attribute, + and not part of the signature. + + function preample + A list of declarations of entities that are used by the function body. + Some of the entities that can be declared in the preample are: + + - Local variables. + - Functions that are called directly. + - Function signatures for indirect function calls. + - Function flags and attributes that are not part of the signature. + + basic block + A maximal sequence of instructions that can only be entered from the + top, and that contains no branch or terminator instructions except for + the last instruction. + + extended basic block + EBB + A maximal sequence of instructions that can only be entered from the + top, and that contains no :term:`terminator instruction`s except for + the last one. An EBB can contain conditional branches that can fall + through to the following instructions in the block, but only the first + instruction in the EBB can be a branch target. + + The last instrution in an EBB must be a :term:`terminator instruction`, + so execion cannot flow through to the next EBB in the function. (But + there may be a branch to the next EBB.) + + Note that some textbooks define an EBB as a maximal *subtree* in the + control flow graph where only the root can be a join node. This + definition is not equivalent to Cretonne EBBs. + + terminator instruction + A control flow instruction that unconditionally directs the flow of + execution somewhere else. Execution never continues at the instruction + following a terminator instruction. + + The basic terminator instructions are :inst:`br`, :inst:`return`, and + :inst:`trap`. Conditional branches and instructions that trap + conditionally are not terminator instructions. + + entry block + The :term:`EBB` that is executed first in a function. Currently, a + Cretonne function must have exactly one entry block. The types of the + entry block arguments must match the types of arguments in the function + signature. From a3f97e4d1fecc3c511b8982bad4e7b4958981358 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 21 Jan 2016 16:39:45 -0800 Subject: [PATCH 009/968] Switch SIMD type spelling to i32x4. Add support for 'type variables' in type directives. --- docs/cton_domain.py | 29 +++++++++++++++- docs/cton_lexer.py | 8 +++-- docs/langref.rst | 82 +++++++++++++++++++++++++-------------------- 3 files changed, 79 insertions(+), 40 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index afa372921f..9f153f3a50 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -57,6 +57,33 @@ class CtonObject(ObjectDescription): self.indexnode['entries'].append(('single', indextext, targetname, '')) +# Type variables are indicated as %T. +typevar = re.compile('(\%[A-Z])') + +def parse_type(name, signode): + """ + Parse a type with embedded type vars and append to signode. + + Return a a string that can be compiled into a regular expression matching + the type. + """ + + re_str = '' + + for part in typevar.split(name): + if part == '': + continue + if len(part) == 2 and part[0] == '%': + # This is a type parameter. Don't display the %, use emphasis + # instead. + part = part[1] + signode += nodes.emphasis(part, part) + re_str += r'\w+' + else: + signode += addnodes.desc_name(part, part) + re_str += re.escape(part) + return re_str + class CtonType(CtonObject): """A Cretonne IL type description.""" @@ -68,7 +95,7 @@ class CtonType(CtonObject): """ name = sig.strip() - signode += addnodes.desc_name(name, name) + re_str = parse_type(name, signode) return name def get_index_text(self, name): diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 02e8945580..0567b748c7 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -15,10 +15,14 @@ class CretonneLexer(RegexLexer): (r';.*?$', Comment.Single), (r'\b(function|entry)\b', Keyword), (r'\b(align)\b', Name.Attribute), - (r'\b(v\d+)?(bool|i\d+|f32|f64)\b', Keyword.Type), + # Well known value types. + (r'\b(bool|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), (r'\d+', Number.Integer), (r'0[xX][0-9a-fA-F]+', Number.Hex), - (r'(v|ss|ebb)\d+', Name.Variable), + # v = value + # ss = stack slot + (r'(v|ss)\d+', Name.Variable), + # ebb = extended basic block (r'(ebb)\d+', Name.Label), (r'(=)( *)([a-z]\w*)', bygroups(Operator, Whitespace, Name.Function)), (r'^( +)([a-z]\w*\b)(?! *[,=])', bygroups(Whitespace, Name.Function)), diff --git a/docs/langref.rst b/docs/langref.rst index aaff27a9f3..85f50ef32d 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -109,13 +109,13 @@ double-double formats. .. type:: f32 - A 32-bit floating point type represented in the IEEE 754 *Single precision* + A 32-bit floating point type represented in the IEEE 754 *single precision* format. This corresponds to the :c:type:`float` type in most C implementations. .. type:: f64 - A 64-bit floating point type represented in the IEEE 754 *Double precision* + A 64-bit floating point type represented in the IEEE 754 *double precision* format. This corresponds to the :c:type:`double` type in most C implementations. @@ -126,40 +126,42 @@ A SIMD vector type represents a vector of values from one of the scalar types (:type:`bool`, integer, and floating point). Each scalar value in a SIMD type is called a *lane*. The number of lanes must be a power of two in the range 2-256. -.. type:: vNiB +.. type:: i%Bx%N - A SIMD vector of integers. The lane type :type:`iB` must be one of the - integer types :type:`i8` ... :type:`i64`. + A SIMD vector of integers. The lane type :type:`iB` is one of the integer + types :type:`i8` ... :type:`i64`. - Some concrete integer vector types are :type:`v4i32`, :type:`v8i64`, and - :type:`v4i16`. + Some concrete integer vector types are :type:`i32x4`, :type:`i64x8`, and + :type:`i16x4`. The size of a SIMD integer vector in memory is :math:`N B\over 8` bytes. -.. type:: vNf32 +.. type:: f32x%N A SIMD vector of single precision floating point numbers. - Some concrete :type:`f32` vector types are: :type:`v2f32`, :type:`v4f32`, - and :type:`v8f32`. + Some concrete :type:`f32` vector types are: :type:`f32x2`, :type:`f32x4`, + and :type:`f32x8`. The size of a :type:`f32` vector in memory is :math:`4N` bytes. -.. type:: vNf64 +.. type:: f64x%N A SIMD vector of double precision floating point numbers. - Some concrete :type:`f64` vector types are: :type:`v2f64`, :type:`v4f64`, - and :type:`v8f64`. + Some concrete :type:`f64` vector types are: :type:`f64x2`, :type:`f64x4`, + and :type:`f64x8`. The size of a :type:`f64` vector in memory is :math:`8N` bytes. -.. type:: vNbool +.. type:: boolx%N A boolean SIMD vector. - Like the :type:`bool` type, a boolean vector cannot be stored in memory. It - can only be used for ephemeral SSA values. + Boolean vectors are used when comparing SIMD vectors. For example, + comparing two :type:`i32x4` values would produce a :type:`boolx4` result. + + Like the :type:`bool` type, a boolean vector cannot be stored in memory. Instructions ============ @@ -175,6 +177,9 @@ Control flow instructions EBB arguments. The number and types of arguments must match the destination EBB. + :arg EBB: Destination extended basic block. + :result: None. This is a terminator instruction. + .. inst:: brz x, EBB(args...) Branch when zero. @@ -182,8 +187,9 @@ Control flow instructions If ``x`` is a :type:`bool` value, take the branch when ``x`` is false. If ``x`` is an integer value, take the branch when ``x = 0``. - :param iN/bool x: Value to test. - :param EBB: Destination extended basic block. + :arg iN / bool x: Value to test. + :arg EBB: Destination extended basic block. + :result: None. .. inst:: brnz x, EBB(args...) @@ -192,15 +198,13 @@ Control flow instructions If ``x`` is a :type:`bool` value, take the branch when ``x`` is true. If ``x`` is an integer value, take the branch when ``x != 0``. - :param iN/bool x: Value to test. - :param EBB: Destination extended basic block. + :arg iN / bool x: Value to test. + :arg EBB: Destination extended basic block. + :result: None. Special operations ================== -Most operations are easily classified as arithmetic or control flow. These -instructions are not so easily classified. - .. inst:: a = iconst n Integer constant. @@ -217,13 +221,13 @@ instructions are not so easily classified. Conditional select. - :param c bool: Controlling flag. - :param x: Value to return when ``c`` is true. - :param y: Value to return when ``c`` is false. Must be same type as ``x``. - :rtype: Same type as ``x`` and ``y``. + :arg bool c: Controlling flag. + :arg T x: Value to return when ``c`` is true. + :arg T y: Value to return when ``c`` is false. Must be same type as ``x``. + :rtype: T. Same type as ``x`` and ``y``. - This instruction selects whole values. Use :inst:`vselect` for - lane-wise selection. + This instruction selects whole values. Use :inst:`vselect` for lane-wise + selection. Vector operations ================= @@ -235,7 +239,7 @@ Vector operations Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean vector ``c``. - :arg vNbool c: Controlling flag vector. + :arg boolx%N c: Controlling flag vector. :arg x: Vector with lanes selected by the true lanes of ``c``. Must be a vector type with the same number of lanes as ``c``. :arg y: Vector with lanes selected by the false lanes of ``c``. @@ -277,7 +281,7 @@ Integer operations :param cond: Condition code determining how ``x`` and ``y`` are compared. :param x, y: Integer scalar or vector values of the same type. - :rtype: :type:`bool` or :type:`vNbool` with the same number of lanes as + :rtype: :type:`bool` or :type:`boolxN` with the same number of lanes as ``x`` and ``y``. The condition code determines if the operands are interpreted as signed or @@ -385,25 +389,25 @@ Bitwise operations Bitwise and. - :rtype: bool, iB, vNiB, vNfB? + :rtype: bool, iB, iBxN, fBxN? .. inst:: a = or x, y Bitwise or. - :rtype: bool, iB, vNiB, vNfB? + :rtype: bool, iB, iBxN, fBxN? .. inst:: a = xor x, y Bitwise xor. - :rtype: bool, iB, vNiB, vNfB? + :rtype: bool, iB, iBxN, fBxN? .. inst:: a = not x Bitwise not. - :rtype: bool, iB, vNiB, vNfB? + :rtype: bool, iB, iBxN, fBxN? .. todo:: Redundant bitwise operators. @@ -538,7 +542,7 @@ Floating point operations :param cond: Condition code determining how ``x`` and ``y`` are compared. :param x, y: Floating point scalar or vector values of the same type. - :rtype: :type:`bool` or :type:`vNbool` with the same number of lanes as + :rtype: :type:`bool` or :type:`boolxN` with the same number of lanes as ``x`` and ``y``. An 'ordered' condition code yields ``false`` if either operand is Nan. @@ -672,6 +676,10 @@ Glossary - Function signatures for indirect function calls. - Function flags and attributes that are not part of the signature. + function body + The extended basic blocks which contain all the executable code in a + function. The function body follows the function preample. + basic block A maximal sequence of instructions that can only be entered from the top, and that contains no branch or terminator instructions except for @@ -680,7 +688,7 @@ Glossary extended basic block EBB A maximal sequence of instructions that can only be entered from the - top, and that contains no :term:`terminator instruction`s except for + top, and that contains no :term:`terminator instruction`\s except for the last one. An EBB can contain conditional branches that can fall through to the following instructions in the block, but only the first instruction in the EBB can be a branch target. From 45caa52622548061aef593c6ea8638d5ff0939c1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 21 Jan 2016 17:15:20 -0800 Subject: [PATCH 010/968] Document control flow instructions. --- docs/langref.rst | 85 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 73 insertions(+), 12 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 85f50ef32d..70602f232f 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -163,12 +163,9 @@ called a *lane*. The number of lanes must be a power of two in the range 2-256. Like the :type:`bool` type, a boolean vector cannot be stored in memory. -Instructions +Control flow ============ -Control flow instructions -------------------------- - .. inst:: br EBB(args...) Branch. @@ -187,7 +184,7 @@ Control flow instructions If ``x`` is a :type:`bool` value, take the branch when ``x`` is false. If ``x`` is an integer value, take the branch when ``x = 0``. - :arg iN / bool x: Value to test. + :arg iN/bool x: Value to test. :arg EBB: Destination extended basic block. :result: None. @@ -198,12 +195,76 @@ Control flow instructions If ``x`` is a :type:`bool` value, take the branch when ``x`` is true. If ``x`` is an integer value, take the branch when ``x != 0``. - :arg iN / bool x: Value to test. + :arg iN/bool x: Value to test. :arg EBB: Destination extended basic block. :result: None. +.. inst:: br_table x, JT + + Jump table branch. + + Use ``x`` as an index into the jump table ``JT``. If a jump table entry is + found, branch to the corresponding EBB. If no entry was found fall through + to the next instruction. + + Note that this branch instruction can't pass arguments to the targeted + blocks. Split critical edges as needed to work around this. + + :arg iN x: Integer index into jump table. + :arg JT: Jump table which was declared in the preample. + :result: None. + +.. inst:: return args... + + Return from function. + + Unconditionally transfer control to the calling function, passing the + provided return values. + + :arg args: Return values. The list of retur values must match the list if + return value types in the function signature. + :result: None. This is a terminator instruction. + +.. inst:: trap + + Terminate execution. + + :result: None. This is a terminator instruction. + +.. inst:: trapz x + + Trap when zero. + + if ``x`` is non-zero, execution continues at the following instruction. + + :arg iN/bool x: Value to test. + :result: None. + +.. inst:: trapnz x + + Trap when non-zero. + + if ``x`` is zero, execution continues at the following instruction. + + :arg iN/bool x: Value to test. + :result: None. + +Function calls +============== + +A function call needs a target function and a :term:`function signature`. The +target function may be determined dynamically at runtime, but the signature +must be known when the function call is compiled. + + + +Operations +========== + + + Special operations -================== +------------------ .. inst:: a = iconst n @@ -230,7 +291,7 @@ Special operations selection. Vector operations -================= +----------------- .. inst:: a = vselect c, x, y @@ -273,7 +334,7 @@ Vector operations indicate a valid lane index for the type of ``x``. Integer operations -================== +------------------ .. inst:: a = icmp cond, x, y @@ -383,7 +444,7 @@ Integer operations good start. Bitwise operations -================== +------------------ .. inst:: a = and x, y @@ -534,7 +595,7 @@ Bitwise operations Floating point operations -========================= +------------------------- .. inst:: a = fcmp cond, x, y @@ -628,7 +689,7 @@ Floating point operations product. Conversion operations -===================== +--------------------- .. inst:: a = bitcast x From 189fc7dfb75cfb73d91e6de7a3bfe7d122c98ab2 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Fri, 22 Jan 2016 10:31:24 -0800 Subject: [PATCH 011/968] Add hexadecimal numbers to the lexer. Also decimal and hexadecimal exponential notation for float constants. --- docs/cton_lexer.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 0567b748c7..0df2e6208a 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -2,9 +2,12 @@ # # Pygments lexer for Cretonne. -from pygments.lexer import RegexLexer, bygroups +from pygments.lexer import RegexLexer, bygroups, words from pygments.token import * +def keywords(*args): + return words(args, prefix=r'\b', suffix=r'\b') + class CretonneLexer(RegexLexer): name = 'Cretonne' aliases = ['cton'] @@ -13,19 +16,30 @@ class CretonneLexer(RegexLexer): tokens = { 'root': [ (r';.*?$', Comment.Single), - (r'\b(function|entry)\b', Keyword), - (r'\b(align)\b', Name.Attribute), + # Strings are in double quotes, support \xx escapes only. + (r'"([^"\\]+|\\[0-9a-fA-F]{2})*"', String), + # A naked function name following 'function' is also a string. + (r'\b(function)([ \t]+)(\w+)\b', bygroups(Keyword, Whitespace, String.Symbol)), + # Numbers. + (r'[-+]?0[xX][0-9a-fA-F]+', Number.Hex), + (r'[-+]?0[xX][0-9a-fA-F]*\.[0-9a-fA-F]*([pP]\d+)?', Number.Hex), + (r'[-+]?\d+\.\d+([eE]\d+)?', Number.Float), + (r'[-+]?\d+', Number.Integer), + # Reserved words. + (keywords('function', 'entry'), Keyword), + # Known attributes. + (keywords('align'), Name.Attribute), # Well known value types. (r'\b(bool|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), - (r'\d+', Number.Integer), - (r'0[xX][0-9a-fA-F]+', Number.Hex), # v = value # ss = stack slot (r'(v|ss)\d+', Name.Variable), # ebb = extended basic block (r'(ebb)\d+', Name.Label), + # Match instruction names in context. (r'(=)( *)([a-z]\w*)', bygroups(Operator, Whitespace, Name.Function)), (r'^( +)([a-z]\w*\b)(?! *[,=])', bygroups(Whitespace, Name.Function)), + # Other names: results and arguments (r'[a-z]\w*', Name), (r'->|=|:', Operator), (r'[{}(),.]', Punctuation), From 74eb6ce9014f8508be137d5cd35c69e69eacaafc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jan 2016 12:13:38 -0800 Subject: [PATCH 012/968] Clarify local SSA form. Rename 'local' to 'stack_slot'. --- docs/example.cton | 10 +++++----- docs/langref.rst | 42 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/docs/example.cton b/docs/example.cton index 04c9e77fb4..c0001fe7c6 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -1,24 +1,24 @@ function average(i32, i32) -> f32 { - ss1 = local 8, align 4 ; Stack slot for ``sum``. + ss1 = stack_slot 8, align 4 ; Stack slot for ``sum``. entry ebb1(v1: i32, v2: i32): v3 = fconst.f64 0.0 stack_store v3, ss1 - brz v2, ebb3 ; Handle count == 0. + brz v2, ebb3 ; Handle count == 0. v4 = iconst.i32 0 br ebb2(v4) ebb2(v5: i32): v6 = imul_imm v5, 4 v7 = iadd v1, v6 - v8 = heap_load.f32 v7 ; array[i] + v8 = heap_load.f32 v7 ; array[i] v9 = fext.f64 v8 v10 = stack_load.f64 ss1 v11 = fadd v9, v10 stack_store v11, ss1 v12 = iadd_imm v5, 1 v13 = icmp ult v12, v2 - brnz v13, ebb2(v12) ; Loop backedge. + brnz v13, ebb2(v12) ; Loop backedge. v14 = stack_load.f64 ss1 v15 = cvt_utof.f64 v2 v16 = fdiv v14, v15 @@ -26,6 +26,6 @@ ebb2(v5: i32): return v17 ebb3: - v100 = fconst.f32 0x7fc00000 ; 0/0 = NaN + v100 = fconst.f32 0x7fc00000 ; 0/0 = NaN return v100 } diff --git a/docs/langref.rst b/docs/langref.rst index 70602f232f..6a9bcc60eb 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -11,7 +11,8 @@ a *text format* which is used for test cases and debug output. Files containing Cretonne textual IL have the ``.cton`` filename extension. This reference uses the text format to describe IL semantics but glosses over -the details of the lexical and syntactic structure of the test format. +the finer details of the lexical and syntactic structure of the format. + Overall structure ================= @@ -41,8 +42,8 @@ declares a single local variable, ``ss1``. After the preample follows the :term:`function body` which consists of :term:`extended basic block`\s, one of which is marked as the :term:`entry -block`. Every EBB ends with a :term:`terminator instruction`, and execution -can never fall through to the next EBB without an explicit branch. +block`. Every EBB ends with a :term:`terminator instruction`, so execution can +never fall through to the next EBB without an explicit branch. Static single assignment form ----------------------------- @@ -65,6 +66,20 @@ SSA values: In the entry block, ``v4`` is the initial value. In the loop block variable during each iteration. Finally, ``v12`` is computed as the induction variable value for the next iteration. +It can be difficult to generate correct SSA form if the program being converted +into Cretonne IL contains multiple assignments to the same variables. Such +variables can be presented to Cretonne as :term:`stack slot`\s instead. Stack +slots are accessed with the :inst:`stack_store` and :inst:`stack_load` +instructions which behave more like variable accesses in a typical programming +language. Cretonne can perform the necessary dataflow analysis to convert stack +slots to SSA form. + +If all values are only used in the same EBB where they are defined, the +function is said to be in :term:`local SSA form`. It is much faster for +Cretonne to verify the correctness of a function in local SSA form since no +complicated control flow analysis is required. + + Value types =========== @@ -180,7 +195,7 @@ Control flow .. inst:: brz x, EBB(args...) Branch when zero. - + If ``x`` is a :type:`bool` value, take the branch when ``x`` is false. If ``x`` is an integer value, take the branch when ``x = 0``. @@ -191,7 +206,7 @@ Control flow .. inst:: brnz x, EBB(args...) Branch when non-zero. - + If ``x`` is a :type:`bool` value, take the branch when ``x`` is true. If ``x`` is an integer value, take the branch when ``x != 0``. @@ -776,3 +791,20 @@ Glossary Cretonne function must have exactly one entry block. The types of the entry block arguments must match the types of arguments in the function signature. + + stack slot + A fixed size memory allocation in the current function's activation + frame. Also called a local variable. + + local SSA form + A restricted version of SSA form where all values are defined and used + in the same EBB. A function is in local SSA form iff it is in SSA form + and: + + - No branches pass arguments to their target EBB. + - Only the entry EBB may have arguments. + + This also implies that there are no branches to the entry EBB. + + Local SSA form is easy to generate and fast to verify. It passes data + between EBBs by using stack slots. From 43b4f7f4e74b4c952cee122107f0a0317dcacd8f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jan 2016 13:08:18 -0800 Subject: [PATCH 013/968] Expand on control flow and direct function calls. Define the syntax for function signatures. --- docs/cton_lexer.py | 2 +- docs/langref.rst | 142 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 0df2e6208a..4c309d779b 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -28,7 +28,7 @@ class CretonneLexer(RegexLexer): # Reserved words. (keywords('function', 'entry'), Keyword), # Known attributes. - (keywords('align'), Name.Attribute), + (keywords('align', 'uext', 'sext', 'inreg'), Name.Attribute), # Well known value types. (r'\b(bool|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value diff --git a/docs/langref.rst b/docs/langref.rst index 6a9bcc60eb..b3f8b14ea1 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -178,9 +178,27 @@ called a *lane*. The number of lanes must be a power of two in the range 2-256. Like the :type:`bool` type, a boolean vector cannot be stored in memory. +Pseudo-types +------------ + +These are not concrete types, but convenient names uses to refer to real types +in this reference. + +.. type:: iPtr + + A Pointer-sized integer. + + This is either :type:`i32`, or :type:`i64`, depending on whether the target + platform has 32-bit or 64-bit pointers. + Control flow ============ +Branches transfer control to a new EBB and provide values for the target EBB's +arguments, if it has any. Conditional branches only take the branch if their +condition is satisfied, otherwise execution continues at the following +instruction in the EBB. + .. inst:: br EBB(args...) Branch. @@ -190,6 +208,7 @@ Control flow EBB. :arg EBB: Destination extended basic block. + :arg args...: Zero or more arguments passed to EBB. :result: None. This is a terminator instruction. .. inst:: brz x, EBB(args...) @@ -201,6 +220,7 @@ Control flow :arg iN/bool x: Value to test. :arg EBB: Destination extended basic block. + :arg args...: Arguments passed to EBB. :result: None. .. inst:: brnz x, EBB(args...) @@ -212,15 +232,16 @@ Control flow :arg iN/bool x: Value to test. :arg EBB: Destination extended basic block. + :arg args...: Zero or more arguments passed to EBB. :result: None. .. inst:: br_table x, JT Jump table branch. - Use ``x`` as an index into the jump table ``JT``. If a jump table entry is - found, branch to the corresponding EBB. If no entry was found fall through - to the next instruction. + Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table + entry is found, branch to the corresponding EBB. If no entry was found fall + through to the next instruction. Note that this branch instruction can't pass arguments to the targeted blocks. Split critical edges as needed to work around this. @@ -229,20 +250,31 @@ Control flow :arg JT: Jump table which was declared in the preample. :result: None. -.. inst:: return args... +.. inst:: JT = jump_table EBB0, EBB1, ..., EBBn - Return from function. + Declare a jump table in the :term:`function preample`. - Unconditionally transfer control to the calling function, passing the - provided return values. + This declares a jump table for use by the :inst:`br_table` indirect branch + instruction. Entries in the table are either EBB names, or ``0`` which + indicates an absent entry. - :arg args: Return values. The list of retur values must match the list if - return value types in the function signature. - :result: None. This is a terminator instruction. + The EBBs listed must belong to the current function, and they can't have + any arguments. + + :arg EBB0: Target EBB when ``x = 0``. + :arg EBB1: Target EBB when ``x = 1``. + :arg EBBn: Target EBB when ``x = n``. + :result: A jump table identifier. (Not an SSA value). + +Traps stop the program because something went wrong. The exact behavior depends +on the target instruction set architecture and operating system. There are +explicit trap instructions defined below, but some instructions may also cause +traps for certain input value. For example, :inst:`udiv` traps when the divisor +is zero. .. inst:: trap - Terminate execution. + Terminate execution unconditionally. :result: None. This is a terminator instruction. @@ -264,12 +296,98 @@ Control flow :arg iN/bool x: Value to test. :result: None. + Function calls ============== A function call needs a target function and a :term:`function signature`. The target function may be determined dynamically at runtime, but the signature -must be known when the function call is compiled. +must be known when the function call is compiled. The function signature +describes how to call the function, including arguments, return values, and the +calling convention: + +.. productionlist:: + signature : "(" [arglist] ")" ["->" retlist] [call_conv] + arglist : arg + : arglist "," arg + retlist : arglist + arg : type + : arg flag + flag : "uext" | "sext" | "inreg" + callconv : `string` + +Arguments and return values have flags whose meaning is mostly target +dependent. They make it possible to call native functions on the target +platform. When calling other Cretonne functions, the flags are not necessary. + +Functions that are called directly must be declared in the :term:`function +preample`: + +.. inst:: F = function NAME signature + + Declare a function so it can be called directly. + + :arg NAME: Name of the function, passed to the linker for resolution. + :arg signature: Function signature. See below. + :result F: A function identifier that can be used with :inst:`call`. + +.. inst:: a, b, ... = call F(args...) + + Direct function call. + + :arg F: Function identifier to call, declared by :inst:`function`. + :arg args...: Function arguments matching the signature of F. + :result a,b,...: Return values matching the signature of F. + +.. inst:: return args... + + Return from function. + + Unconditionally transfer control to the calling function, passing the + provided return values. + + :arg args: Return values. The list of return values must match the list of + return value types in the function signature. + :result: None. This is a terminator instruction. + +This simple example illustrates direct function calls and signatures:: + + function gcd(i32 uext, i32 uext) -> i32 uext "C" { + f1 = function divmod(i32 uext, i32 uext) -> i32 uext, i32 uext + + entry ebb1(v1: i32, v2: i32): + brz v2, ebb2 + v3, v4 = call f1(v1, v2) + br ebb1(v2, v4) + + ebb2: + return v1 + } + +Indirect function calls use a signature declared in the preample. + +.. inst:: SIG = signature signature + + Declare a function signature for use with indirect calls. + + :arg signature: Function signature. See :token:`signature`. + :result SIG: A signature identifier. + +.. inst:: a, b, ... = call_indirect SIG, x(args...) + + Indirect function call. + + :arg SIG: A function signature identifier declared with :inst:`signature`. + :arg iPtr x: The address of the function to call. + :arg args...: Function arguments matching SIG. + :result a,b,...: Return values matching SIG. + +.. todo:: Define safe indirect function calls. + + The :inst:`call_indirect` instruction is dangerous to use in a sandboxed + environment since it is not easy to verify the callee address. + We need a table-driven indirect call instruction, similar to + :inst:`br_table`. From 095190b1a747b03fec93957632c6ecbf75a8ed42 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jan 2016 15:20:10 -0800 Subject: [PATCH 014/968] Load, store, local variables. --- docs/cton_domain.py | 3 +- docs/cton_lexer.py | 4 +- docs/langref.rst | 124 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 9f153f3a50..e1dc08a67b 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -19,7 +19,7 @@ from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType from sphinx.locale import l_, _ from sphinx.roles import XRefRole -from sphinx.util.docfields import Field, TypedField +from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_refnode class CtonObject(ObjectDescription): @@ -120,6 +120,7 @@ class CtonInst(CtonObject): TypedField('result', label=l_('Results'), names=('out', 'result'), typerolename='type', typenames=('type',)), + GroupedField('flag', names=('flag',), label=l_('Flags')), Field('resulttype', label=l_('Result type'), has_arg=False, names=('rtype',)), ] diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 4c309d779b..33f769dfc6 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -28,7 +28,7 @@ class CretonneLexer(RegexLexer): # Reserved words. (keywords('function', 'entry'), Keyword), # Known attributes. - (keywords('align', 'uext', 'sext', 'inreg'), Name.Attribute), + (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), Name.Attribute), # Well known value types. (r'\b(bool|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value @@ -38,7 +38,7 @@ class CretonneLexer(RegexLexer): (r'(ebb)\d+', Name.Label), # Match instruction names in context. (r'(=)( *)([a-z]\w*)', bygroups(Operator, Whitespace, Name.Function)), - (r'^( +)([a-z]\w*\b)(?! *[,=])', bygroups(Whitespace, Name.Function)), + (r'^( *)([a-z]\w*\b)(?! *[,=])', bygroups(Whitespace, Name.Function)), # Other names: results and arguments (r'[a-z]\w*', Name), (r'->|=|:', Operator), diff --git a/docs/langref.rst b/docs/langref.rst index b3f8b14ea1..6d64b42f03 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -390,6 +390,130 @@ Indirect function calls use a signature declared in the preample. :inst:`br_table`. +Memory +====== + +Cretonne provides fully general :inst:`load` and :inst:`store` instructions for +accessing memory. However, it can be very complicated to verify the safety of +general loads and stores when compiling code for a sandboxed environment, so +Cretonne also provides more restricted memory operations that are always safe. + +.. inst:: a = load p, Offset, Flags... + + Load from memory at ``p + Offset``. + + This is a polymorphic instruction that can load any value type which has a + memory representation (i.e., everything except :type:`bool` and boolean + vectors). + + :arg iPtr p: Base address. + :arg Offset: Immediate signed offset. + :flag align(N): Expected alignment of ``p + Offset``. Power of two. + :flag aligntrap: Always trap if the memory access is misaligned. + :result T a: Loaded value. + +.. inst:: store x, p, Offset, Flags... + + Store ``x`` to memory at ``p + Offset``. + + This is a polymorphic instruction that can store any value type with a + memory representation. + + :arg T x: Value to store. + :arg iPtr p: Base address. + :arg Offset: Immediate signed offset. + :flag align(N): Expected alignment of ``p + Offset``. Power of two. + :flag aligntrap: Always trap if the memory access is misaligned. + +Loads and stores are *misaligned* if the resultant address is not a multiple of +the expected alignment. Depending on the target architecture, misaligned memory +accesses may trap, or they may work. Sometimes, operating systems catch +alignment traps and emulate the misaligned memory access. + +On target architectures like x86 that don't check alignment, Cretonne expands +the aligntrap flag into a conditional trap instruction:: + + v5 = load.i32 v1, 4, align(4), aligntrap + ; Becomes: + v10 = and_imm v1, 3 + trapnz v10 + v5 = load.i32 v1, 4 + + +Local variables +--------------- + +One set of restricted memory operations access the current function's stack +frame. The stack frame is divided into fixed-size stack slots that are +allocated in the :term:`function preample`. Stack slots are not typed, they +simply represent a contiguous sequence of bytes in the stack frame. + +.. inst:: SS = stack_slot Bytes, Flags... + + Allocate a stack slot in the preample. + + If no alignment is specified, Cretonne will pick an appropriate alignment + for the stack slot based on its size and access patterns. + + :arg Bytes: Stack slot size on bytes. + :flag align(N): Request at least N bytes alignment. + :result SS: Stack slot index. + +.. inst:: a = stack_load SS, Offset + + Load a value from a stack slot at the constant offset. + + This is a polymorphic instruction that can load any value type which has a + memory representation. + + The offset is an immediate constant, not an SSA value. The memory access + cannot go out of bounds, i.e. ``sizeof(a) + Offset <= sizeof(SS)``. + + :arg SS: Stack slot declared with :inst:`stack_slot`. + :arg Offset: Immediate non-negative offset. + :result T a: Value loaded. + +.. inst:: stack_store x, SS, Offset + + Store a value to a stack slot at a constant offset. + + This is a polymorphic instruction that can store any value type with a + memory representation. + + The offset is an immediate constant, not an SSA value. The memory access + cannot go out of bounds, i.e. ``sizeof(a) + Offset <= sizeof(SS)``. + + :arg T x: Value to be stored. + :arg SS: Stack slot declared with :inst:`stack_slot`. + :arg Offset: Immediate non-negative offset. + +The dedicated stack access instructions are easy ofr the compiler to reason +about because stack slots and offsets are fixed at compile time. For example, +the alignment of these stack memory accesses can be inferred from the offsets +and stack slot alignments. + +It can be necessary to escape from the safety of the restricted instructions by +taking the address of a stack slot. + +.. inst:: a = stack_addr SS, Offset + + Get the address of a stack slot. + + Compute the absolute address of a byte in a stack slot. The offset must + refer to a byte inside the stack slot: ``0 <= Offset < sizeof(SS)``. + + :arg SS: Stack slot declared with :inst:`stack_slot`. + :arg Offset: Immediate non-negative offset. + :result iPtr a: Address. + +The :inst:`stack_addr` instruction can be used to macro-expand the stack access +instructions before instruction selection:: + + v1 = stack_load.f64 ss3, 16 + ; Expands to: + v9 = stack_addr ss3, 16 + v1 = load.f64 v9 + Operations ========== From 519fed894b95cf90d859100daf5895cd39129403 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jan 2016 16:48:11 -0800 Subject: [PATCH 015/968] Document heaps. --- docs/langref.rst | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/docs/langref.rst b/docs/langref.rst index 6d64b42f03..d2896722c4 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -191,6 +191,10 @@ in this reference. This is either :type:`i32`, or :type:`i64`, depending on whether the target platform has 32-bit or 64-bit pointers. +.. type:: iN + + Any of the scalar integer types :type:`i8` -- :type:`i64`. + Control flow ============ @@ -514,6 +518,89 @@ instructions before instruction selection:: v9 = stack_addr ss3, 16 v1 = load.f64 v9 +Heaps +----- + +Code compiled from WebAssembly or asm.js runs in a sandbox where it can't access +all process memory. Instead, it is given a small set of memory areas to work +in, and all accesses are bounds checked. Cretonne models this through the +concept of *heaps*. + +A heap is declared in the function preample and can be accessed with restricted +instructions that trap on out-of-bounds accesses. Heap addresses can be smaller +than the native pointer size, for example unsigned :type:`i32` offsets on a +64-bit architecture. + +.. inst:: H = heap Name + + Declare a heap in the function preample. + + This doesn't allocate memory, it just retrieves a handle to a sandbox from + the runtime environment. + + :arg Name: String identifying the heap in the runtime environment. + :result H: Heap identifier. + +.. inst:: a = heap_load H, p, Offset + + Load a value at the address ``p + Offset`` in the heap H. + + Trap if the heap access would be out of bounds. + + :arg H: Heap identifier created by :inst:`heap`. + :arg iN p: Unsigned base address in heap. + :arg Offset: Immediate signed offset. + :flag align(N): Expected alignment of ``p + Offset``. Power of two. + :flag aligntrap: Always trap if the memory access is misaligned. + :result T a: Loaded value. + +.. inst:: a = heap_store H, x, p, Offset + + Store a value at the address ``p + Offset`` in the heap H. + + Trap if the heap access would be out of bounds. + + :arg H: Heap indetifier created by :inst:`heap`. + :arg T x: Value to be stored. + :arg iN p: Unsigned base address in heap. + :arg Offset: Immediate signed offset. + :flag align(N): Expected alignment of ``p + Offset``. Power of two. + :flag aligntrap: Always trap if the memory access is misaligned. + +When optimizing heap accesses, Cretonne may separate the heap bounds checking +and address computations from the memory accesses. + +.. inst:: a = heap_addr H, p, Size + + Bounds check and compute absolute address of heap memory. + + Verify that the address range ``p .. p + Size - 1`` is valid in the heap H, + and trap if not. + + Convert the heap-relative address in ``p`` to a real absolute address and + return it. + + :arg H: Heap identifier created by :inst:`heap`. + :arg iN p: Unsigned base address in heap. + :arg Size: Immediate unsigned byte count for range to verify. + :result iPtr a: Absolute address corresponding to ``p``. + +A small example using heaps:: + + function vdup(i32, i32) { + h1 = heap "main" + + entry ebb1(v1: i32, v2: i32): + v3 = heap_load.i32x4 h1, v1, 0 + v4 = heap_addr h1, v2, 32 ; Shared range check for two stores. + store v3, v4, 0 + store v3, v4, 16 + return + } + +The final expansion of the :inst:`heap_addr` range check and address conversion +depends on the runtime environment. + Operations ========== From ec7d65d8f130b93853c886dd77082e1bd38fee22 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jan 2016 17:30:30 -0800 Subject: [PATCH 016/968] Clean up the list of operations somewhat. --- docs/langref.rst | 184 +++++++++++++++++++++++++++++++---------------- 1 file changed, 122 insertions(+), 62 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index d2896722c4..3aad746bfe 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -605,23 +605,32 @@ depends on the runtime environment. Operations ========== +The remaining instruction set is mostly arithmetic. +A few instructions have variants that take immediate operands (e.g., +:inst:`and` / :inst:`and_imm`), but in general an instruction is required to +load a constant into an SSA value. -Special operations ------------------- - -.. inst:: a = iconst n +.. inst:: a = iconst N Integer constant. -.. inst:: a = fconst n + Create a scalar integer SSA value with an immediate constant value, or an + integer vector where all the lanes have the same value. + +.. inst:: a = fconst N Floating point constant. -.. inst:: a = vconst n + Create a :type:`f32` or :type:`f64` SSA value with an immediate constant + value, or a floating point vector where all the lanes have the same value. + +.. inst:: a = vconst N Vector constant (floating point or integer). + Create a SIMD vector value where the lanes don't have to be identical. + .. inst:: a = select c, x, y Conditional select. @@ -680,12 +689,13 @@ Vector operations Integer operations ------------------ -.. inst:: a = icmp cond, x, y +.. inst:: a = icmp Cond, x, y Integer comparison. - :param cond: Condition code determining how ``x`` and ``y`` are compared. - :param x, y: Integer scalar or vector values of the same type. + :arg Cond: Condition code determining how ``x`` and ``y`` are compared. + :arg x: First value to compare. + :arg y: Second value to compare. :rtype: :type:`bool` or :type:`boolxN` with the same number of lanes as ``x`` and ``y``. @@ -708,29 +718,59 @@ Integer operations Wrapping integer addition: :math:`a := x + y \pmod{2^B}`. This instruction does not depend on the signed/unsigned interpretation of the operands. + Polymorphic over all integer types (vector and scalar). + +.. inst:: a = iadd_imm x, Imm + + Add immediate integer. + + Same as :inst:`iadd`, but one operand is an immediate constant. + + :arg iN x: Dynamic addend. + :arg Imm: Immediate addend. + + Polymorphic over all scalar integer types. + .. inst:: a = isub x, y Wrapping integer subtraction: :math:`a := x - y \pmod{2^B}`. This instruction does not depend on the signed/unsigned interpretation of the operands. -.. todo:: Overflow arithmetic + Polymorphic over all integer types (vector and scalar). + +.. inst:: a = isub_imm Imm, x + + Immediate subtraction. + + Also works as integer negation when :math:`Imm = 0`. Use :inst:`iadd_imm` with a + negative immediate operand for the reverse immediate subtraction. + + :arg Imm: Immediate minuend. + :arg iN x: Dynamic subtrahend. + + Polymorphic over all scalar integer types. + +.. todo:: Integer overflow arithmetic Add instructions for add with carry out / carry in and so on. Enough to implement larger integer types efficiently. It should also be possible to legalize :type:`i64` arithmetic to terms of :type:`i32` operations. -.. inst:: a = ineg x - - Wrapping integer negation: :math:`a := -x \pmod{2^B}`. This instruction does - not depend on the signed/unsigned interpretation of the operand. - .. inst:: a = imul x, y Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. This instruction does not depend on the signed/unsigned interpretation of the operands. + Polymorphic over all integer types (vector and scalar). + +.. inst:: a = imul_imm x, Imm + + Integer multiplication by immediate constant. + + Polymorphic over all scalar integer types. + .. todo:: Larger multiplication results. For example, ``smulx`` which multiplies :type:`i32` operands to produce a @@ -741,10 +781,11 @@ Integer operations Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. This operation traps if the divisor is zero. - .. todo:: - Add a ``udiv_imm`` variant with an immediate divisor greater than 1. - This is useful for pattern-matching divide-by-constant, and this - instruction would be non-trapping. +.. inst:: a = udiv_imm x, Imm + + Unsigned integer division by an immediate constant. + + This instruction never traps because a divisor of zero is not allowed. .. inst:: a = sdiv x, y @@ -753,37 +794,52 @@ Integer operations the result is not representable in :math:`B` bits two's complement. This only happens when :math:`x = -2^{B-1}, y = -1`. - .. todo:: - Add a ``sdiv_imm`` variant with an immediate non-zero divisor. This is - useful for pattern-matching divide-by-constant, and this instruction - would be non-trapping. Don't allow divisors 0, 1, or -1. +.. inst:: a = sdiv_imm x, Imm + + Signed integer division by an immediate constant. + + This instruction never traps because a divisor of -1 or 0 is not allowed. .. inst:: a = urem x, y - Unsigned integer remainder. This operation traps if the divisor is zero. + Unsigned integer remainder. - .. todo:: - Add a ``urem_imm`` non-trapping variant. + This operation traps if the divisor is zero. + +.. inst:: a = urem_imm x, Imm + + Unsigned integer remainder with immediate divisor. + + This instruction never traps because a divisor of zero is not allowed. .. inst:: a = srem x, y - Signed integer remainder. This operation traps if the divisor is zero. + Signed integer remainder. + + This operation traps if the divisor is zero. + + .. todo:: Integer remainder vs modulus. - .. todo:: Clarify whether the result has the sign of the divisor or the dividend. Should we add a ``smod`` instruction for the case where the result has the same sign as the divisor? +.. inst:: a = srem_imm x, Imm + + Signed integer remainder with immediate divisor. + + This instruction never traps because a divisor of 0 or -1 is not allowed. + .. todo:: Minimum / maximum. NEON has ``smin``, ``smax``, ``umin``, and ``umax`` instructions. We should replicate those for both scalar and vector integer types. Even if the - target ISA doesn't have scalar operations, these are good pattern mtching + target ISA doesn't have scalar operations, these are good pattern matching targets. .. todo:: Saturating arithmetic. - Mostly for SIMD use, but again these are good paterns to contract. + Mostly for SIMD use, but again these are good patterns for contraction. Something like ``usatadd``, ``usatsub``, ``ssatadd``, and ``ssatsub`` is a good start. @@ -825,9 +881,9 @@ Bitwise operations Rotate the bits in ``x`` by ``y`` places. - :param x: Integer value to be rotated. - :param y: Number of bits to shift. Any integer type, not necessarily the - same type as ``x``. + :arg T x: Integer value to be rotated. + :arg iN y: Number of bits to shift. Any scalar integer type, not necessarily + the same type as ``x``. :rtype: Same type as ``x``. .. inst:: a = rotr x, y @@ -836,9 +892,9 @@ Bitwise operations Rotate the bits in ``x`` by ``y`` places. - :param x: Integer value to be rotated. - :param y: Number of bits to shift. Any integer type, not necessarily the - same type as ``x``. + :arg T x: Integer value to be rotated. + :arg iN y: Number of bits to shift. Any scalar integer type, not necessarily + the same type as ``x``. :rtype: Same type as ``x``. .. inst:: a = ishl x, y @@ -848,9 +904,9 @@ Bitwise operations The shift amount is masked to the size of ``x``. - :param x: Integer value to be shifted. - :param y: Number of bits to shift. Any integer type, not necessarily the - same type as ``x``. + :arg T x: Integer value to be shifted. + :arg iN y: Number of bits to shift. Any scalar integer type, not necessarily + the same type as ``x``. :rtype: Same type as ``x``. When shifting a B-bits integer type, this instruction computes: @@ -868,9 +924,9 @@ Bitwise operations The shift amount is masked to the size of the register. - :param x: Integer value to be shifted. - :param y: Number of bits to shift. Can be any integer type, not necessarily - the same type as ``x``. + :arg T x: Integer value to be shifted. + :arg iN y: Number of bits to shift. Can be any scalar integer type, not + necessarily the same type as ``x``. :rtype: Same type as ``x``. When shifting a B-bits integer type, this instruction computes: @@ -888,9 +944,9 @@ Bitwise operations The shift amount is masked to the size of the register. - :param x: Integer value to be shifted. - :param y: Number of bits to shift. Can be any integer type, not necessarily - the same type as ``x``. + :arg T x: Integer value to be shifted. + :arg iN y: Number of bits to shift. Can be any scalar integer type, not + necessarily the same type as ``x``. :rtype: Same type as ``x``. .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. @@ -899,7 +955,7 @@ Bitwise operations Count leading zero bits. - :param x: Integer value. + :arg x: Integer value. :rtype: :type:`i8` Starting from the MSB in ``x``, count the number of zero bits before @@ -910,7 +966,7 @@ Bitwise operations Count leading sign bits. - :param x: Integer value. + :arg x: Integer value. :rtype: :type:`i8` Starting from the MSB after the sign bit in ``x``, count the number of @@ -921,7 +977,7 @@ Bitwise operations Count trailing zeros. - :param x: Integer value. + :arg x: Integer value. :rtype: :type:`i8` Starting from the LSB in ``x``, count the number of zero bits before @@ -932,7 +988,7 @@ Bitwise operations Population count - :param x: Integer value. + :arg x: Integer value. :rtype: :type:`i8` Count the number of one bits in ``x``. @@ -941,12 +997,14 @@ Bitwise operations Floating point operations ------------------------- -.. inst:: a = fcmp cond, x, y +These operations generally follow IEEE 754-2008 semantics. + +.. inst:: a = fcmp Cond, x, y Floating point comparison. - :param cond: Condition code determining how ``x`` and ``y`` are compared. - :param x, y: Floating point scalar or vector values of the same type. + :arg Cond: Condition code determining how ``x`` and ``y`` are compared. + :arg x,y: Floating point scalar or vector values of the same type. :rtype: :type:`bool` or :type:`boolxN` with the same number of lanes as ``x`` and ``y``. @@ -978,7 +1036,7 @@ Floating point operations Floating point negation. - :returns: ``x`` with its sign bit inverted. + :result: ``x`` with its sign bit inverted. Note that this is a pure bitwise operation. @@ -986,7 +1044,7 @@ Floating point operations Floating point absolute value. - :returns: ``x`` with its sign bit cleared. + :result: ``x`` with its sign bit cleared. Note that this is a pure bitwise operation. @@ -994,18 +1052,19 @@ Floating point operations Floating point copy sign. - :returns: ``x`` with its sign changed to that of ``y``. + :result: ``x`` with its sign changed to that of ``y``. Note that this is a pure bitwise operation. The sign bit from ``y`` is copied to the sign bit of ``x``. -.. inst:: fmul x, y -.. inst:: fdiv x, y -.. inst:: fmin x, y -.. inst:: fminnum x, y -.. inst:: fmax x, y -.. inst:: fmaxnum x, y -.. inst:: ceil x +.. inst:: a = fmul x, y +.. inst:: a = fdiv x, y +.. inst:: a = fmin x, y +.. inst:: a = fminnum x, y +.. inst:: a = fmax x, y +.. inst:: a = fmaxnum x, y + +.. inst:: a = ceil x Round floating point round to integral, towards positive infinity. @@ -1053,6 +1112,7 @@ Conversion operations .. inst:: a = cvt_utof x .. inst:: a = cvt_stof x + Glossary ======== From 5997554aed237fabe52e9b0b6d93d50231340eff Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Sat, 23 Jan 2016 18:03:23 -0800 Subject: [PATCH 017/968] Update README. --- README.rst | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 10db808462..895247eb42 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,39 @@ Cretonne Code Generator ======================= Cretonne is a low-level retargetable code generator. It translates a -target-independent intermediate language into executable machine code. Cretonne -aims to generate code that behaves identically on different target -architectures, and that can be executed safely in a sandbox. +target-independent intermediate language into executable machine code. + +Cretonne is designed to be a code generator for WebAssembly with these design +goals: + +No undefined behavior + Cretonne does not have a nasal demons clause, and it won't generate code + with unexpected behavior if invariants are broken. +Portable semantics + As far as possible, Cretonne's input language has well-defined semantics + that are the same on all target architectures. The semantics are usually + the same as WebAssembly's. +Fast sandbox verification + Cretonne's input language has a safe subset for sandboxed code. No advanced + analysis is required to verify memory safety as long as only the safe + instructions are used. The safe instruction set is expressive enough to + implement WebAssembly. +Scalable performance + Cretonne can be configured to generate code as quickly as possible, or it + can generate very good code at the cost of slower compile times. +Predictable performance + When optimizing, Cretonne focuses on adapting the target-independent IL to + the quirks of the target architecture. There are no advanced optimizations + that sometimes work, somtimes fail. + +Building the documentation +-------------------------- + +To build the Cretonne documentation, you need the `Sphinx documentation +generator `_:: + + $ pip install sphinx + $ cd cretonne/docs + $ make html + $ open _build/html/index.html From d1ed09183b2d6854f83b611d865d725577605097 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Thu, 4 Feb 2016 11:47:25 -0800 Subject: [PATCH 018/968] Add ReadTheDocs badge with link to documentation. --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 895247eb42..8e8105f83b 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,10 @@ Cretonne Code Generator Cretonne is a low-level retargetable code generator. It translates a target-independent intermediate language into executable machine code. +.. image:: https://readthedocs.org/projects/cretonne/badge/?version=latest + :target: http://cretonne.readthedocs.org/en/latest/?badge=latest + :alt: Documentation Status + Cretonne is designed to be a code generator for WebAssembly with these design goals: From f66d2a26a2b365ad6086e3c15fa5dcac24abef49 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Thu, 4 Feb 2016 12:19:08 -0800 Subject: [PATCH 019/968] Add some more type classes. --- docs/langref.rst | 89 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 22 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 3aad746bfe..0f12a7489e 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -178,8 +178,8 @@ called a *lane*. The number of lanes must be a power of two in the range 2-256. Like the :type:`bool` type, a boolean vector cannot be stored in memory. -Pseudo-types ------------- +Pseudo-types and type classes +----------------------------- These are not concrete types, but convenient names uses to refer to real types in this reference. @@ -191,10 +191,38 @@ in this reference. This is either :type:`i32`, or :type:`i64`, depending on whether the target platform has 32-bit or 64-bit pointers. -.. type:: iN +.. type:: iB Any of the scalar integer types :type:`i8` -- :type:`i64`. +.. type:: Int + + Any scalar *or vector* integer type: :type:`iB` or :type:`iBxN`. + +.. type:: fB + + Either of the floating point scalar types: :type:`f32` or :type:`f64. + +.. type:: Float + + Any scalar *or vector* floating point type: :type:`fB` or :type:`fBxN`. + +.. type:: %Tx%N + + Any SIMD vector type. + +.. type:: Mem + + Any type that can be stored in memory: :type:`Int` or :type:`Float`. + +.. type:: Logic + + Either :type:`bool` or :type:`boolxN`. + +.. type:: Testable + + Either :type:`bool` or :type:`iN`. + Control flow ============ @@ -222,7 +250,7 @@ instruction in the EBB. If ``x`` is a :type:`bool` value, take the branch when ``x`` is false. If ``x`` is an integer value, take the branch when ``x = 0``. - :arg iN/bool x: Value to test. + :arg Testable x: Value to test. :arg EBB: Destination extended basic block. :arg args...: Arguments passed to EBB. :result: None. @@ -234,7 +262,7 @@ instruction in the EBB. If ``x`` is a :type:`bool` value, take the branch when ``x`` is true. If ``x`` is an integer value, take the branch when ``x != 0``. - :arg iN/bool x: Value to test. + :arg Testable x: Value to test. :arg EBB: Destination extended basic block. :arg args...: Zero or more arguments passed to EBB. :result: None. @@ -288,7 +316,7 @@ is zero. if ``x`` is non-zero, execution continues at the following instruction. - :arg iN/bool x: Value to test. + :arg Testable x: Value to test. :result: None. .. inst:: trapnz x @@ -297,7 +325,7 @@ is zero. if ``x`` is zero, execution continues at the following instruction. - :arg iN/bool x: Value to test. + :arg Testable x: Value to test. :result: None. @@ -618,6 +646,8 @@ load a constant into an SSA value. Create a scalar integer SSA value with an immediate constant value, or an integer vector where all the lanes have the same value. + :result Int a: Constant value. + .. inst:: a = fconst N Floating point constant. @@ -625,12 +655,16 @@ load a constant into an SSA value. Create a :type:`f32` or :type:`f64` SSA value with an immediate constant value, or a floating point vector where all the lanes have the same value. + :result Float a: Constant value. + .. inst:: a = vconst N Vector constant (floating point or integer). Create a SIMD vector value where the lanes don't have to be identical. + :result TxN a: Constant value. + .. inst:: a = select c, x, y Conditional select. @@ -638,7 +672,7 @@ load a constant into an SSA value. :arg bool c: Controlling flag. :arg T x: Value to return when ``c`` is true. :arg T y: Value to return when ``c`` is false. Must be same type as ``x``. - :rtype: T. Same type as ``x`` and ``y``. + :result T a: Same type as ``x`` and ``y``. This instruction selects whole values. Use :inst:`vselect` for lane-wise selection. @@ -653,12 +687,12 @@ Vector operations Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean vector ``c``. - :arg boolx%N c: Controlling flag vector. - :arg x: Vector with lanes selected by the true lanes of ``c``. + :arg boolxN c: Controlling flag vector. + :arg TxN x: Vector with lanes selected by the true lanes of ``c``. Must be a vector type with the same number of lanes as ``c``. - :arg y: Vector with lanes selected by the false lanes of ``c``. + :arg TxN y: Vector with lanes selected by the false lanes of ``c``. Must be same type as ``x``. - :rtype: Same type as ``x`` and ``y``. + :result TxN a: Same type as ``x`` and ``y``. .. inst:: a = vbuild x, y, z, ... @@ -672,20 +706,32 @@ Vector operations Return a vector whose lanes are all ``x``. -.. inst:: a = insertlane x, idx, y + :arg T x: Scalar value to be replicated. + :result TxN a: Vector with identical lanes. - Insert ``y`` as lane ``idx`` in x. +.. inst:: a = insertlane x, Idx, y - The lane index, ``idx``, is an immediate value, not an SSA value. It must + Insert ``y`` as lane ``Idx`` in x. + + The lane index, ``Idx``, is an immediate value, not an SSA value. It must indicate a valid lane index for the type of ``x``. -.. inst:: a = extractlane x, idx + :arg TxN x: Vector to modify. + :arg Idx: Lane index smaller than N. + :arg T y: New lane value. + :result TxN y: Updated vector. - Extract lane ``idx`` from ``x``. +.. inst:: a = extractlane x, Idx - The lane index, ``idx``, is an immediate value, not an SSA value. It must + Extract lane ``Idx`` from ``x``. + + The lane index, ``Idx``, is an immediate value, not an SSA value. It must indicate a valid lane index for the type of ``x``. + :arg TxN x: Source vector + :arg Idx: Lane index + :result T a: Lane value. + Integer operations ------------------ @@ -694,10 +740,9 @@ Integer operations Integer comparison. :arg Cond: Condition code determining how ``x`` and ``y`` are compared. - :arg x: First value to compare. - :arg y: Second value to compare. - :rtype: :type:`bool` or :type:`boolxN` with the same number of lanes as - ``x`` and ``y``. + :arg Int x: First value to compare. + :arg Int y: Second value to compare. + :result Logic a: With the same number of lanes as ``x`` and ``y``. The condition code determines if the operands are interpreted as signed or unsigned integers. From ec918fe33280ea9dabf64f378752eabab9e027ed Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Thu, 4 Feb 2016 17:25:32 -0800 Subject: [PATCH 020/968] Begin defining the meta language. The Cretonne meta language is used to describe Cretonne instructions, both the target independent ones in the base instruction set and real target instructions. Start by providing type definitions matching langref, and begin the meta language reference using autodoc to pull in the PYthon definitions. --- docs/conf.py | 4 ++ docs/index.rst | 1 + docs/metaref.rst | 33 ++++++++++++++++ meta/cretonne/__init__.py | 83 +++++++++++++++++++++++++++++++++++++++ meta/cretonne/types.py | 20 ++++++++++ 5 files changed, 141 insertions(+) create mode 100644 docs/metaref.rst create mode 100644 meta/cretonne/__init__.py create mode 100644 meta/cretonne/types.py diff --git a/docs/conf.py b/docs/conf.py index 120d32011a..0b7f7667d8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,10 @@ import shlex # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('.')) +# Also add the meta directory to sys.path so autodoc can find the Cretonne meta +# language definitions. +sys.path.insert(0, os.path.abspath('../meta')) + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. diff --git a/docs/index.rst b/docs/index.rst index 668b055c97..88d9257213 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ Contents: :maxdepth: 2 langref + metaref Indices and tables ================== diff --git a/docs/metaref.rst b/docs/metaref.rst new file mode 100644 index 0000000000..42a471cc93 --- /dev/null +++ b/docs/metaref.rst @@ -0,0 +1,33 @@ +******************************** +Cretonne Meta Language Reference +******************************** + +.. default-domain:: py +.. highlight:: python + +The Cretonne meta language is used to define instructions for Cretonne. It is a +domain specific language embedded in Python. + +An instruction set is described by a Python module under the :file:`meta` +directory that has a global variable called ``instructions``. The basic +Cretonne instruction set described in :doc:`langref` is defined by the Python +module :mod:`cretonne.instrs`. + +Types +===== + +Concrete value types are represented as instances of :class:`cretonne.Type`. There are +subclasses to represent scalar and vector types. + +.. autoclass:: cretonne.Type +.. autoclass:: cretonne.ScalarType + :members: +.. autoclass:: cretonne.VectorType + :members: +.. autoclass:: cretonne.IntType + :members: +.. autoclass:: cretonne.FloatType + :members: +.. automodule:: cretonne.types + :members: + diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py new file mode 100644 index 0000000000..9133f8e062 --- /dev/null +++ b/meta/cretonne/__init__.py @@ -0,0 +1,83 @@ +""" +Cretonne meta language module. + +This module provides classes and functions used to describe Cretonne +instructions. +""" + +# Concrete types. +# +# Instances (i8, i32, ...) are provided in the cretonne.types module. + +class Type(object): + """A concrete value type.""" + + def __str__(self): + return self.name + +class ScalarType(Type): + """ + A concrete scalar (not vector) type. + + Also tracks a unique set of :class:`VectorType` instances with this type as + the lane type. + """ + + def __init__(self, name): + self.name = name + self._vectors = dict() + + def __repr__(self): + return 'ScalarType({})'.format(self.name) + + def by(self, lanes): + """ + Get a vector type with this type as the lane type. + + For example, ``i32.by(4)`` returns the :obj:`i32x4` type. + """ + if lanes in self._vectors: + return self._vectors[lanes] + else: + v = VectorType(self, lanes) + self._vectors[lanes] = v + return v + +class VectorType(Type): + """ + A concrete SIMD vector type. + + A vector type has a lane type which is an instance of :class:`ScalarType`, + and a positive number of lanes. + """ + + def __init__(self, base, lanes): + assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' + self.base = base + self.lanes = lanes + self.name = '{}x{}'.format(base.name, lanes) + + def __repr__(self): + return 'VectorType(base={}, lanes={})'.format(self.base.name, self.lanes) + +class IntType(ScalarType): + """A concrete scalar integer type.""" + + def __init__(self, bits): + assert bits > 0, 'IntType must have positive number of bits' + super(IntType, self).__init__('i{:d}'.format(bits)) + self.bits = bits + + def __repr__(self): + return 'IntType(bits={})'.format(self.bits) + +class FloatType(ScalarType): + """A concrete scalar floating point type.""" + + def __init__(self, bits): + assert bits > 0, 'FloatType must have positive number of bits' + super(FloatType, self).__init__('f{:d}'.format(bits)) + self.bits = bits + + def __repr__(self): + return 'FloatType(bits={})'.format(self.bits) diff --git a/meta/cretonne/types.py b/meta/cretonne/types.py new file mode 100644 index 0000000000..382fe0def2 --- /dev/null +++ b/meta/cretonne/types.py @@ -0,0 +1,20 @@ +"""Predefined types.""" + +from . import ScalarType, IntType, FloatType + +#: A boolean value. +bool = ScalarType('bool') + +i8 = IntType(8) #: 8-bit int. +i16 = IntType(16) #: 16-bit int. +i32 = IntType(32) #: 32-bit int. +i64 = IntType(64) #: 64-bit int. + +f32 = FloatType(32) #: IEEE 32-bit float. +f64 = FloatType(64) #: IEEE 64-bit float + +i8x16 = i8.by(16) #: Vector of 16 i8 lanes. + +f32x4 = f32.by(4) #: Vector of 4 f32 lanes. +f64x2 = f64.by(2) #: Vector of 2 f64 lanes. + From 06f9b65e12bea83a974c865dd3e3a70cf63778dc Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Mon, 8 Feb 2016 18:21:58 -0800 Subject: [PATCH 021/968] Add a TypeDocumenter for Cretonne types. Use the autodoc Sphinx module to add a .. autoctontype:: directive which generates documentation for one of the types in the cretonne.types module. --- docs/cton_domain.py | 34 ++++++++++++++++++++++++++++++++++ docs/langref.rst | 39 ++++++++------------------------------- meta/cretonne/__init__.py | 29 +++++++++++++++++++++-------- meta/cretonne/types.py | 35 ++++++++++++++++++++++++----------- 4 files changed, 87 insertions(+), 50 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index e1dc08a67b..823ed9c8a5 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -13,6 +13,7 @@ import re from docutils import nodes +from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.directives import ObjectDescription @@ -22,6 +23,8 @@ from sphinx.roles import XRefRole from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_refnode +import sphinx.ext.autodoc + class CtonObject(ObjectDescription): """ Any kind of Cretonne IL object. @@ -29,6 +32,11 @@ class CtonObject(ObjectDescription): This is a shared base class for the different kinds of indexable objects in the Cretonne IL reference. """ + option_spec = { + 'noindex': directives.flag, + 'module': directives.unchanged, + 'annotation': directives.unchanged, + } def add_target_and_index(self, name, sig, signode): """ @@ -209,7 +217,33 @@ class CretonneDomain(Domain): make_refnode(builder, fromdocname, obj[0], obj[1] + '-' + target, contnode, target))] + +class TypeDocumenter(sphinx.ext.autodoc.Documenter): + # Invoke with .. autoctontype:: + objtype = 'ctontype' + # Convert into cton:type directives + domain = 'cton' + directivetype = 'type' + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + return False + + def resolve_name(self, modname, parents, path, base): + return 'cretonne.types', [ base ] + + def add_content(self, more_content, no_docstring=False): + super(TypeDocumenter, self).add_content(more_content, no_docstring) + sourcename = self.get_sourcename() + membytes = self.object.membytes + if membytes: + self.add_line(u':bytes: {}'.format(membytes), sourcename) + else: + self.add_line(u':bytes: Can\'t be stored in memory', sourcename) + + def setup(app): app.add_domain(CretonneDomain) + app.add_autodocumenter(TypeDocumenter) return { 'version' : '0.1' } diff --git a/docs/langref.rst b/docs/langref.rst index 0f12a7489e..ad2ccb4299 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -87,10 +87,7 @@ All SSA values have a type which determines the size and shape (for SIMD vectors) of the value. Many instructions are polymorphic -- they can operate on different types. -.. type:: bool - - A boolean value that is either true or false. Booleans can't be stored in - memory. +.. autoctontype:: bool Integer types ------------- @@ -99,21 +96,10 @@ Integer values have a fixed size and can be interpreted as either signed or unsigned. Some instructions will interpret an operand as a signed or unsigned number, others don't care. -.. type:: i8 - - A 8-bit integer value taking up 1 byte in memory. - -.. type:: i16 - - A 16-bit integer value taking up 2 bytes in memory. - -.. type:: i32 - - A 32-bit integer value taking up 4 bytes in memory. - -.. type:: i64 - - A 64-bit integer value taking up 8 bytes in memory. +.. autoctontype:: i8 +.. autoctontype:: i16 +.. autoctontype:: i32 +.. autoctontype:: i64 Floating point types -------------------- @@ -122,17 +108,8 @@ The floating point types have the IEEE semantics that are supported by most hardware. There is no support for higher-precision types like quads or double-double formats. -.. type:: f32 - - A 32-bit floating point type represented in the IEEE 754 *single precision* - format. This corresponds to the :c:type:`float` type in most C - implementations. - -.. type:: f64 - - A 64-bit floating point type represented in the IEEE 754 *double precision* - format. This corresponds to the :c:type:`double` type in most C - implementations. +.. autoctontype:: f32 +.. autoctontype:: f64 SIMD vector types ----------------- @@ -201,7 +178,7 @@ in this reference. .. type:: fB - Either of the floating point scalar types: :type:`f32` or :type:`f64. + Either of the floating point scalar types: :type:`f32` or :type:`f64`. .. type:: Float diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 9133f8e062..216607c9dc 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -12,6 +12,11 @@ instructions. class Type(object): """A concrete value type.""" + def __init__(self, name, membytes, doc): + self.name = name + self.membytes = membytes + self.__doc__ = doc + def __str__(self): return self.name @@ -19,12 +24,12 @@ class ScalarType(Type): """ A concrete scalar (not vector) type. - Also tracks a unique set of :class:`VectorType` instances with this type as - the lane type. + Also tracks a unique set of :py:class:`VectorType` instances with this type + as the lane type. """ - def __init__(self, name): - self.name = name + def __init__(self, name, membytes, doc): + super(ScalarType, self).__init__(name, membytes, doc) self._vectors = dict() def __repr__(self): @@ -53,9 +58,14 @@ class VectorType(Type): def __init__(self, base, lanes): assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' + super(VectorType, self).__init__( + name='{}x{}'.format(base.name, lanes), + membytes=lanes*base.membytes, + doc=""" + A SIMD vector with {} lanes containing a {} each. + """.format(lanes, base.name)) self.base = base self.lanes = lanes - self.name = '{}x{}'.format(base.name, lanes) def __repr__(self): return 'VectorType(base={}, lanes={})'.format(self.base.name, self.lanes) @@ -65,7 +75,10 @@ class IntType(ScalarType): def __init__(self, bits): assert bits > 0, 'IntType must have positive number of bits' - super(IntType, self).__init__('i{:d}'.format(bits)) + super(IntType, self).__init__( + name='i{:d}'.format(bits), + membytes=bits/8, + doc="An integer type with {} bits.".format(bits)) self.bits = bits def __repr__(self): @@ -74,9 +87,9 @@ class IntType(ScalarType): class FloatType(ScalarType): """A concrete scalar floating point type.""" - def __init__(self, bits): + def __init__(self, bits, doc): assert bits > 0, 'FloatType must have positive number of bits' - super(FloatType, self).__init__('f{:d}'.format(bits)) + super(FloatType, self).__init__( name='f{:d}'.format(bits), membytes=bits/8, doc=doc) self.bits = bits def __repr__(self): diff --git a/meta/cretonne/types.py b/meta/cretonne/types.py index 382fe0def2..27a74aec67 100644 --- a/meta/cretonne/types.py +++ b/meta/cretonne/types.py @@ -2,19 +2,32 @@ from . import ScalarType, IntType, FloatType -#: A boolean value. -bool = ScalarType('bool') +bool = ScalarType('bool', 0, + """ + A boolean value that is either true or false. + """) -i8 = IntType(8) #: 8-bit int. -i16 = IntType(16) #: 16-bit int. -i32 = IntType(32) #: 32-bit int. -i64 = IntType(64) #: 64-bit int. +i8 = IntType(8) +i16 = IntType(16) +i32 = IntType(32) +i64 = IntType(64) -f32 = FloatType(32) #: IEEE 32-bit float. -f64 = FloatType(64) #: IEEE 64-bit float +f32 = FloatType(32, + """ + A 32-bit floating point type represented in the IEEE 754-2008 *binary32* + interchange format. This corresponds to the :c:type:`float` type in most + C implementations. + """) -i8x16 = i8.by(16) #: Vector of 16 i8 lanes. +f64 = FloatType(64, + """ + A 64-bit floating point type represented in the IEEE 754-2008 *binary64* + interchange format. This corresponds to the :c:type:`double` type in + most C implementations. + """) -f32x4 = f32.by(4) #: Vector of 4 f32 lanes. -f64x2 = f64.by(2) #: Vector of 2 f64 lanes. +i8x16 = i8.by(16) + +f32x4 = f32.by(4) +f64x2 = f64.by(2) From 29cc36c8d93d548496ec84601e6fc27b355a7e9b Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Tue, 9 Feb 2016 08:11:20 -0800 Subject: [PATCH 022/968] Enable inheritance diagrams. --- docs/conf.py | 9 +++++++++ docs/metaref.rst | 17 ++++++++++++----- meta/cretonne/types.py | 21 ++++++++++----------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0b7f7667d8..c4ab93eff0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,6 +38,8 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', + 'sphinx.ext.graphviz', + 'sphinx.ext.inheritance_diagram', 'cton_domain', 'cton_lexer', ] @@ -293,3 +295,10 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False + + +# -- Options for Graphviz ------------------------------------------------- + +graphviz_output_format = 'svg' + +inheritance_graph_attrs = dict(rankdir='TD') diff --git a/docs/metaref.rst b/docs/metaref.rst index 42a471cc93..8cdece065c 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -13,21 +13,28 @@ directory that has a global variable called ``instructions``. The basic Cretonne instruction set described in :doc:`langref` is defined by the Python module :mod:`cretonne.instrs`. +.. module:: cretonne + Types ===== Concrete value types are represented as instances of :class:`cretonne.Type`. There are subclasses to represent scalar and vector types. -.. autoclass:: cretonne.Type -.. autoclass:: cretonne.ScalarType +.. inheritance-diagram:: Type ScalarType VectorType IntType FloatType + :parts: 1 +.. autoclass:: Type +.. autoclass:: ScalarType :members: -.. autoclass:: cretonne.VectorType +.. autoclass:: VectorType :members: -.. autoclass:: cretonne.IntType +.. autoclass:: IntType :members: -.. autoclass:: cretonne.FloatType +.. autoclass:: FloatType :members: + +Predefined types +---------------- .. automodule:: cretonne.types :members: diff --git a/meta/cretonne/types.py b/meta/cretonne/types.py index 27a74aec67..3d369a60e4 100644 --- a/meta/cretonne/types.py +++ b/meta/cretonne/types.py @@ -1,17 +1,21 @@ -"""Predefined types.""" +""" +The cretonne.types module predefines all the Cretonne scalar types. +""" from . import ScalarType, IntType, FloatType +#: Boolean. bool = ScalarType('bool', 0, """ A boolean value that is either true or false. """) -i8 = IntType(8) -i16 = IntType(16) -i32 = IntType(32) -i64 = IntType(64) +i8 = IntType(8) #: 8-bit int. +i16 = IntType(16) #: 16-bit int. +i32 = IntType(32) #: 32-bit int. +i64 = IntType(64) #: 64-bit int. +#: IEEE single precision. f32 = FloatType(32, """ A 32-bit floating point type represented in the IEEE 754-2008 *binary32* @@ -19,15 +23,10 @@ f32 = FloatType(32, C implementations. """) +#: IEEE double precision. f64 = FloatType(64, """ A 64-bit floating point type represented in the IEEE 754-2008 *binary64* interchange format. This corresponds to the :c:type:`double` type in most C implementations. """) - -i8x16 = i8.by(16) - -f32x4 = f32.by(4) -f64x2 = f64.by(2) - From 82e05bcbc90381e3128dadb8a0935eb802e02d46 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Mar 2016 15:54:28 -0800 Subject: [PATCH 023/968] Implement type representation in Rust. Start the Cretonne library as a Rust crate. --- .gitignore | 2 + src/libcretonne/Cargo.toml | 10 ++ src/libcretonne/lib.rs | 8 ++ src/libcretonne/types.rs | 230 +++++++++++++++++++++++++++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 src/libcretonne/Cargo.toml create mode 100644 src/libcretonne/lib.rs create mode 100644 src/libcretonne/types.rs diff --git a/.gitignore b/.gitignore index 0d20b6487c..25eadf4e60 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.pyc +Cargo.lock +target diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml new file mode 100644 index 0000000000..58a67fe569 --- /dev/null +++ b/src/libcretonne/Cargo.toml @@ -0,0 +1,10 @@ +[package] +authors = ["The cwCretonneRust Project Developers"] +name = "cretonne" +version = "0.0.0" + +[lib] +name = "cretonne" +path = "lib.rs" + +[dependencies] diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs new file mode 100644 index 0000000000..e2adcaec67 --- /dev/null +++ b/src/libcretonne/lib.rs @@ -0,0 +1,8 @@ + +//====--------------------------------------------------------------------------------------====// +// +// Cretonne code generation library. +// +//====--------------------------------------------------------------------------------------====// + +mod types; diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs new file mode 100644 index 0000000000..49d7cc29ce --- /dev/null +++ b/src/libcretonne/types.rs @@ -0,0 +1,230 @@ + +//! Common types for the Cretonne code generator. + +use std::fmt::{self, Display, Formatter, Write}; + +/// The type of an SSA value. +/// +/// The VOID type is only used for instructions that produce no value. It can't be part of a SIMD +/// vector. +/// +/// Basic integer types: `I8`, `I16`, `I32`, and `I64`. These types are sign-agnostic. +/// +/// Basic floating point types: `F32` and `F64`. IEEE single and double precision. +/// +/// Boolean types: `B1`, `B8`, `B16`, `B32`, and `B64`. These all encode 'true' or 'false'. The +/// larger types use redundant bits. +/// +/// SIMD vector types have power-of-two lanes, up to 256. Lanes can be any int/float/bool type. +/// +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Type(u8); + +pub const VOID: Type = Type(0); + +pub const I8: Type = Type(1); +pub const I16: Type = Type(2); +pub const I32: Type = Type(3); +pub const I64: Type = Type(4); + +pub const F32: Type = Type(5); +pub const F64: Type = Type(6); + +pub const B1: Type = Type(7); +pub const B8: Type = Type(8); +pub const B16: Type = Type(9); +pub const B32: Type = Type(10); +pub const B64: Type = Type(11); + +impl Type { + /// Get the lane type of this SIMD vector type. + /// + /// A scalar type is the same as a SIMD vector type with one lane, so it returns itself. + pub fn lane_type(self) -> Type { + Type(self.0 & 0x0f) + } + + /// Get the number of bits in a lane. + pub fn lane_bits(self) -> u8 { + match self.lane_type() { + B1 => 1, + B8 | I8 => 8, + B16 | I16 => 16, + B32 | I32 | F32 => 32, + B64 | I64 | F64 => 64, + _ => 0, + } + } + + /// Is this the VOID type? + pub fn is_void(self) -> bool { + self == VOID + } + + /// Is this a scalar boolean type? + pub fn is_bool(self) -> bool { + match self { + B1 | B8 | B16 | B32 | B64 => true, + _ => false, + } + } + + /// Is this a scalar integer type? + pub fn is_int(self) -> bool { + match self { + I8 | I16 | I32 | I64 => true, + _ => false, + } + } + + /// Is this a scalar floating point type? + pub fn is_float(self) -> bool { + match self { + F32 | F64 => true, + _ => false, + } + } + + /// Get log2 of the number of lanes in this SIMD vector type. + /// + /// All SIMD types have a lane count that is a power of two and no larger than 256, so this + /// will be a number in the range 0-8. + /// + /// A scalar type is the same as a SIMD vector type with one lane, so it return 0. + pub fn log2_lane_count(self) -> u8 { + self.0 >> 4 + } + + /// Is this a scalar type? (That is, not a SIMD vector type). + /// + /// A scalar type is the same as a SIMD vector type with one lane. + pub fn is_scalar(self) -> bool { + self.log2_lane_count() == 0 + } + + /// Get the number of lanes in this SIMD vector type. + /// + /// A scalar type is the same as a SIMD vector type with one lane, so it returns 1. + pub fn lane_count(self) -> u16 { + 1 << self.log2_lane_count() + } + + /// Get the total number of bits used to represent this type. + pub fn bits(self) -> u16 { + self.lane_bits() as u16 * self.lane_count() + } + + /// Get a SIMD vector type with `n` times more lanes than this one. + /// + /// If this is a scalar type, this produces a SIMD type with this as a lane type and `n` lanes. + /// + /// If this is already a SIMD vector type, this produces a SIMD vector type with `n * + /// self.lane_count()` lanes. + pub fn by(self, n: u16) -> Type { + debug_assert!(self.lane_bits() > 0, + "Can't make SIMD vectors with void lanes."); + debug_assert!(n.is_power_of_two(), + "Number of SIMD lanes must be a power of two"); + let log2_lanes: u32 = n.trailing_zeros(); + let new_type = self.0 as u32 + (log2_lanes << 4); + assert!(new_type < 0x90, "No more than 256 SIMD lanes supported"); + Type(new_type as u8) + } + + /// Get a SIMD vector with half the number of lanes. + pub fn half_vector(self) -> Type { + assert!(!self.is_scalar(), "Expecting a proper SIMD vector type."); + Type(self.0 - 0x10) + } +} + +impl Display for Type { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if self.is_void() { + write!(f, "void") + } else if self.is_bool() { + write!(f, "b{}", self.lane_bits()) + } else if self.is_int() { + write!(f, "i{}", self.lane_bits()) + } else if self.is_float() { + write!(f, "f{}", self.lane_bits()) + } else if !self.is_scalar() { + write!(f, "{}x{}", self.lane_type(), self.lane_count()) + } else { + panic!("Invalid Type(0x{:x})", self.0) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_scalars() { + assert_eq!(VOID, VOID.lane_type()); + assert_eq!(0, VOID.bits()); + assert_eq!(B1, B1.lane_type()); + assert_eq!(B8, B8.lane_type()); + assert_eq!(B16, B16.lane_type()); + assert_eq!(B32, B32.lane_type()); + assert_eq!(B64, B64.lane_type()); + assert_eq!(I8, I8.lane_type()); + assert_eq!(I16, I16.lane_type()); + assert_eq!(I32, I32.lane_type()); + assert_eq!(I64, I64.lane_type()); + assert_eq!(F32, F32.lane_type()); + assert_eq!(F64, F64.lane_type()); + + assert_eq!(VOID.lane_bits(), 0); + assert_eq!(B1.lane_bits(), 1); + assert_eq!(B8.lane_bits(), 8); + assert_eq!(B16.lane_bits(), 16); + assert_eq!(B32.lane_bits(), 32); + assert_eq!(B64.lane_bits(), 64); + assert_eq!(I8.lane_bits(), 8); + assert_eq!(I16.lane_bits(), 16); + assert_eq!(I32.lane_bits(), 32); + assert_eq!(I64.lane_bits(), 64); + assert_eq!(F32.lane_bits(), 32); + assert_eq!(F64.lane_bits(), 64); + } + + #[test] + fn vectors() { + let big = F64.by(256); + assert_eq!(big.lane_bits(), 64); + assert_eq!(big.lane_count(), 256); + assert_eq!(big.bits(), 64 * 256); + + assert_eq!(format!("{}", big.half_vector()), "f64x128"); + assert_eq!(format!("{}", B1.by(2).half_vector()), "b1"); + } + + #[test] + fn format_scalars() { + assert_eq!(format!("{}", VOID), "void"); + assert_eq!(format!("{}", B1), "b1"); + assert_eq!(format!("{}", B8), "b8"); + assert_eq!(format!("{}", B16), "b16"); + assert_eq!(format!("{}", B32), "b32"); + assert_eq!(format!("{}", B64), "b64"); + assert_eq!(format!("{}", I8), "i8"); + assert_eq!(format!("{}", I16), "i16"); + assert_eq!(format!("{}", I32), "i32"); + assert_eq!(format!("{}", I64), "i64"); + assert_eq!(format!("{}", F32), "f32"); + assert_eq!(format!("{}", F64), "f64"); + } + + #[test] + fn format_vectors() { + assert_eq!(format!("{}", B1.by(8)), "b1x8"); + assert_eq!(format!("{}", B8.by(1)), "b8"); + assert_eq!(format!("{}", B16.by(256)), "b16x256"); + assert_eq!(format!("{}", B32.by(4).by(2)), "b32x8"); + assert_eq!(format!("{}", B64.by(8)), "b64x8"); + assert_eq!(format!("{}", I8.by(64)), "i8x64"); + assert_eq!(format!("{}", F64.by(2)), "f64x2"); + } +} From 4ba29e594fc21ba19a828e819ca5dee1c1391b3e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Mar 2016 16:06:14 -0800 Subject: [PATCH 024/968] Make the types module public, add documentation comments. --- src/libcretonne/lib.rs | 6 +++--- src/libcretonne/types.rs | 23 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index e2adcaec67..380209e7f6 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -1,8 +1,8 @@ -//====--------------------------------------------------------------------------------------====// +// ====------------------------------------------------------------------------------------==== // // // Cretonne code generation library. // -//====--------------------------------------------------------------------------------------====// +// ====------------------------------------------------------------------------------------==== // -mod types; +pub mod types; diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 49d7cc29ce..88a2b6cad2 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Display, Formatter, Write}; /// The type of an SSA value. /// -/// The VOID type is only used for instructions that produce no value. It can't be part of a SIMD +/// The `VOID` type is only used for instructions that produce no value. It can't be part of a SIMD /// vector. /// /// Basic integer types: `I8`, `I16`, `I32`, and `I64`. These types are sign-agnostic. @@ -20,20 +20,41 @@ use std::fmt::{self, Display, Formatter, Write}; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Type(u8); +/// No type. Used for functions without a return value. Can't be loaded or stored. Can't be part of +/// a SIMD vector. pub const VOID: Type = Type(0); +/// Integer type with 8 bits. pub const I8: Type = Type(1); + +/// Integer type with 16 bits. pub const I16: Type = Type(2); + +/// Integer type with 32 bits. pub const I32: Type = Type(3); + +/// Integer type with 64 bits. pub const I64: Type = Type(4); +/// IEEE single precision floating point type. pub const F32: Type = Type(5); + +/// IEEE double precision floating point type. pub const F64: Type = Type(6); +/// Boolean type. Can't be loaded or stored, but can be used to form SIMD vectors. pub const B1: Type = Type(7); + +/// Boolean type using 8 bits to represent true/false. pub const B8: Type = Type(8); + +/// Boolean type using 16 bits to represent true/false. pub const B16: Type = Type(9); + +/// Boolean type using 32 bits to represent true/false. pub const B32: Type = Type(10); + +/// Boolean type using 64 bits to represent true/false. pub const B64: Type = Type(11); impl Type { From 29481a5851d234bfeb82b2cfb07588c64f38c679 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Fri, 12 Feb 2016 10:11:52 -0800 Subject: [PATCH 025/968] Add type variables. --- docs/metaref.rst | 10 ++++++++++ meta/cretonne/__init__.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/docs/metaref.rst b/docs/metaref.rst index 8cdece065c..7a8c156142 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -38,3 +38,13 @@ Predefined types .. automodule:: cretonne.types :members: +Parametric polymorphism +----------------------- +.. currentmodule:: cretonne + +Instruction operands can be defined with *type variables* instead of concrete +types for their operands. This makes the instructions polymorphic. + +.. autoclass:: TypeVar + + diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 216607c9dc..e8cdfe3f55 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -94,3 +94,19 @@ class FloatType(ScalarType): def __repr__(self): return 'FloatType(bits={})'.format(self.bits) + +# +# Parametric polymorphism. +# + +class TypeVar(object): + """ + A Type Variable. + + Type variables can be used in place of concrete types when defining + instructions. This makes the instructions *polymorphic*. + """ + + def __init__(self, name): + self.name = name + From 10903503c48a67f8c022eeb8191afd97b91cc077 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 30 Mar 2016 11:36:23 -0700 Subject: [PATCH 026/968] Add ImmediateType for declaring immediate operands. --- docs/langref.rst | 12 ++++++++++++ docs/metaref.rst | 17 ++++++++++++++++- meta/cretonne/__init__.py | 20 ++++++++++++++++++++ meta/cretonne/immediates.py | 12 ++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 meta/cretonne/immediates.py diff --git a/docs/langref.rst b/docs/langref.rst index ad2ccb4299..845bc4da37 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -200,6 +200,18 @@ in this reference. Either :type:`bool` or :type:`iN`. +Immediate operand types +----------------------- + +These types are not part of the normal SSA type system. They are used to +indicate the different kinds of immediate operands on an instruction. + +.. type:: imm64 + + A 64-bit immediate integer. The value of this operand is interpreted as a + signed two's complement integer. Instruction encodings may limit the valid + range. + Control flow ============ diff --git a/docs/metaref.rst b/docs/metaref.rst index 7a8c156142..b4dda9576d 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -38,13 +38,28 @@ Predefined types .. automodule:: cretonne.types :members: +.. currentmodule:: cretonne + Parametric polymorphism ----------------------- -.. currentmodule:: cretonne Instruction operands can be defined with *type variables* instead of concrete types for their operands. This makes the instructions polymorphic. .. autoclass:: TypeVar +Immediates +---------- +Immediate instruction operands don't correspond to SSA values, but have values +that are encoded directly in the instruction. Immediate operands don't +have types from the :class:`cretonne.Type` type system; they often have +enumerated values of a specific type. The type of an immediate operand is +indicated with an instance of :class:`ImmediateType`. + +.. autoclass:: ImmediateType + +.. automodule:: cretonne.immediates + :members: + +.. currentmodule:: cretonne diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index e8cdfe3f55..60c3748106 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -110,3 +110,23 @@ class TypeVar(object): def __init__(self, name): self.name = name +# +# Immediate operands. +# +# Instances of immediate operand types are provided in the cretonne.immediates +# module. + +class ImmediateType(object): + """ + The type of an immediate instruction operand. + """ + + def __init__(self, name, doc): + self.name = name + self.__doc__ = doc + + def __str__(self): + return self.name + + def __repr__(self): + return 'ImmediateType({})'.format(self.name) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py new file mode 100644 index 0000000000..6b499d5215 --- /dev/null +++ b/meta/cretonne/immediates.py @@ -0,0 +1,12 @@ +""" +The cretonne.immdiates module predefines all the Cretonne immediate operand +types. +""" + +from . import ImmediateType + +#: A 64-bit immediate integer operand. +#: +#: This type of immediate integer can interact with SSA values with any +#: :py:class:`cretonne.IntType` type. +imm64 = ImmediateType('imm64', 'A 64-bit immediate integer.') From 38d0f626a7d93cb00238314fcd348972fde41fb8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 1 Apr 2016 09:54:49 -0700 Subject: [PATCH 027/968] Implement Imm64 in an 'immediates.rs' module. Format larger immediates as hexadecimal with a multiple of 4 digits and '_' group separators. --- src/libcretonne/immediates.rs | 53 +++++++++++++++++++++++++++++++++++ src/libcretonne/lib.rs | 1 + 2 files changed, 54 insertions(+) create mode 100644 src/libcretonne/immediates.rs diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs new file mode 100644 index 0000000000..a60291b164 --- /dev/null +++ b/src/libcretonne/immediates.rs @@ -0,0 +1,53 @@ + +//! Immediate operands for Cretonne instructions +//! +//! This module defines the types of immediate operands that can appear on Cretonne instructions. +//! Each type here should have a corresponding definition in the `cretonne.immediates` Python +//! module in the meta language. + +use std::fmt::{self, Display, Formatter}; + +/// 64-bit immediate integer operand. +/// +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Imm64(i64); + +impl Display for Imm64 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let x = self.0; + if -10_000 < x && x < 10_000 { + // Use decimal for small numbers. + write!(f, "{}", x) + } else { + // Hexadecimal with a multiple of 4 digits and group separators: + // + // 0xfff0 + // 0x0001_ffff + // 0xffff_ffff_fff8_4400 + // + let mut pos = (64 - x.leading_zeros() - 1) & 0xf0; + try!(write!(f, "0x{:04x}", (x >> pos) & 0xffff)); + while pos > 0 { + pos -= 16; + try!(write!(f, "_{:04x}", (x >> pos) & 0xffff)); + } + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn format_imm64() { + assert_eq!(format!("{}", Imm64(0)), "0"); + assert_eq!(format!("{}", Imm64(9999)), "9999"); + assert_eq!(format!("{}", Imm64(10000)), "0x2710"); + assert_eq!(format!("{}", Imm64(-9999)), "-9999"); + assert_eq!(format!("{}", Imm64(-10000)), "0xffff_ffff_ffff_d8f0"); + assert_eq!(format!("{}", Imm64(0xffff)), "0xffff"); + assert_eq!(format!("{}", Imm64(0x10000)), "0x0001_0000"); + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 380209e7f6..9c837eb0f1 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -6,3 +6,4 @@ // ====------------------------------------------------------------------------------------==== // pub mod types; +pub mod immediates; From a76a0da826407e65615128a734870df7cc37c583 Mon Sep 17 00:00:00 2001 From: Jakob Olesen Date: Fri, 12 Feb 2016 14:24:01 -0800 Subject: [PATCH 028/968] Add Instruction and Operand classes to the meta language. --- docs/cton_domain.py | 44 ++++++++++++++++++++++++ docs/metaref.rst | 9 +++++ meta/cretonne/__init__.py | 72 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 823ed9c8a5..6f20cf8d32 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -242,8 +242,52 @@ class TypeDocumenter(sphinx.ext.autodoc.Documenter): self.add_line(u':bytes: Can\'t be stored in memory', sourcename) +class InstDocumenter(sphinx.ext.autodoc.Documenter): + # Invoke with .. autoinst:: + objtype = 'inst' + # Convert into cton:inst directives + domain = 'cton' + directivetype = 'inst' + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + return False + + def resolve_name(self, modname, parents, path, base): + return 'cretonne.base', [ base ] + + def format_signature(self): + inst = self.object + sig = self.format_name() + if len(inst.outs) > 0: + sig = ', '.join([op.name for op in inst.outs]) + ' = ' + sig + if len(inst.ins) > 0: + sig = sig + ' ' + ', '.join([op.name for op in inst.ins]) + return sig + + def add_directive_header(self, sig): + """Add the directive header and options to the generated content.""" + domain = getattr(self, 'domain', 'cton') + directive = getattr(self, 'directivetype', self.objtype) + sourcename = self.get_sourcename() + self.add_line(u'.. %s:%s:: %s' % (domain, directive, sig), sourcename) + if self.options.noindex: + self.add_line(u' :noindex:', sourcename) + + def add_content(self, more_content, no_docstring=False): + super(InstDocumenter, self).add_content(more_content, no_docstring) + sourcename = self.get_sourcename() + + # Add inputs and outputs. + for op in self.object.ins: + self.add_line(u':in {} {}: {}'.format(op.typ.name, op.name, op.get_doc()), sourcename) + for op in self.object.outs: + self.add_line(u':out {} {}: {}'.format(op.typ.name, op.name, op.get_doc()), sourcename) + + def setup(app): app.add_domain(CretonneDomain) app.add_autodocumenter(TypeDocumenter) + app.add_autodocumenter(InstDocumenter) return { 'version' : '0.1' } diff --git a/docs/metaref.rst b/docs/metaref.rst index b4dda9576d..8d58757b81 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -63,3 +63,12 @@ indicated with an instance of :class:`ImmediateType`. :members: .. currentmodule:: cretonne + +Instructions +============ + +New instructions are defined as instances of the :class:`cretonne.Instruction` +class. + +.. autoclass:: Operand +.. autoclass:: Instruction diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 60c3748106..fd0c4664dd 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -107,8 +107,9 @@ class TypeVar(object): instructions. This makes the instructions *polymorphic*. """ - def __init__(self, name): + def __init__(self, name, doc): self.name = name + self.__doc__ = doc # # Immediate operands. @@ -130,3 +131,72 @@ class ImmediateType(object): def __repr__(self): return 'ImmediateType({})'.format(self.name) + +# +# Defining instructions. +# + +class Operand(object): + """ + An instruction operand. + + An instruction operand can be either an *immediate* or an *SSA value*. The + type of the operand is one of: + + 1. A :py:class:`Type` instance indicates an SSA value operand with a + concrete type. + + 2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the + instruction is polymorphic over the possible concrete types that the type + variable can assume. + + 3. An :py:class:`ImmediateType` instance indicates an immediate operand + whose value is encoded in the instruction itself rather than being passed + as an SSA value. + + """ + def __init__(self, name, typ, doc=''): + self.name = name + self.typ = typ + self.__doc__ = doc + + def get_doc(self): + if self.__doc__: + return self.__doc__ + else: + return self.typ.__doc__ + +class Instruction(object): + """ + An instruction. + + The operands to the instruction are specified as two tuples: ``ins`` and + ``outs``. Since the Python singleton tuple syntax is a bit awkward, it is + allowed to specify a singleton as just the operand itself, i.e., `ins=x` and + `ins=(x,)` are both allowed and mean the same thing. + + :param name: Instruction mnemonic, also becomes opcode name. + :param doc: Documentation string. + :param ins: Tuple of input operands. This can be a mix of SSA value operands + and immediate operands. + :param outs: Tuple of output operands. The output operands can't be + immediates. + """ + + def __init__(self, name, doc, ins=(), outs=(), **kwargs): + self.name = name + self.__doc__ = doc + self.ins = self._to_operand_tuple(ins) + self.outs = self._to_operand_tuple(outs) + + @staticmethod + def _to_operand_tuple(x): + # Allow a single Operand instance instead of the awkward singleton tuple + # syntax. + if isinstance(x, Operand): + x = (x,) + else: + x = tuple(x) + for op in x: + assert isinstance(op, Operand) + return x From c3233fb332e62581b258f5c728d1eae802898738 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 29 Mar 2016 15:22:16 -0700 Subject: [PATCH 029/968] Move instruction definitions into meta. Use the meta language to define instructions, just insert 'autoinst' references in langref. --- docs/langref.rst | 279 ++++++------------------------------------ meta/cretonne/base.py | 275 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+), 240 deletions(-) create mode 100644 meta/cretonne/base.py diff --git a/docs/langref.rst b/docs/langref.rst index 845bc4da37..fed88a819b 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -625,7 +625,7 @@ Operations The remaining instruction set is mostly arithmetic. A few instructions have variants that take immediate operands (e.g., -:inst:`and` / :inst:`and_imm`), but in general an instruction is required to +:inst:`band` / :inst:`band_imm`), but in general an instruction is required to load a constant into an SSA value. .. inst:: a = iconst N @@ -747,43 +747,10 @@ Integer operations sle ule Less than or equal ====== ======== ========= -.. inst:: a = iadd x, y - - Wrapping integer addition: :math:`a := x + y \pmod{2^B}`. This instruction - does not depend on the signed/unsigned interpretation of the operands. - - Polymorphic over all integer types (vector and scalar). - -.. inst:: a = iadd_imm x, Imm - - Add immediate integer. - - Same as :inst:`iadd`, but one operand is an immediate constant. - - :arg iN x: Dynamic addend. - :arg Imm: Immediate addend. - - Polymorphic over all scalar integer types. - -.. inst:: a = isub x, y - - Wrapping integer subtraction: :math:`a := x - y \pmod{2^B}`. This - instruction does not depend on the signed/unsigned interpretation of the - operands. - - Polymorphic over all integer types (vector and scalar). - -.. inst:: a = isub_imm Imm, x - - Immediate subtraction. - - Also works as integer negation when :math:`Imm = 0`. Use :inst:`iadd_imm` with a - negative immediate operand for the reverse immediate subtraction. - - :arg Imm: Immediate minuend. - :arg iN x: Dynamic subtrahend. - - Polymorphic over all scalar integer types. +.. autoinst:: iadd +.. autoinst:: iadd_imm +.. autoinst:: isub +.. autoinst:: isub_imm .. todo:: Integer overflow arithmetic @@ -791,78 +758,22 @@ Integer operations implement larger integer types efficiently. It should also be possible to legalize :type:`i64` arithmetic to terms of :type:`i32` operations. -.. inst:: a = imul x, y - - Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. This - instruction does not depend on the signed/unsigned interpretation of the - operands. - - Polymorphic over all integer types (vector and scalar). - -.. inst:: a = imul_imm x, Imm - - Integer multiplication by immediate constant. - - Polymorphic over all scalar integer types. +.. autoinst:: imul +.. autoinst:: imul_imm .. todo:: Larger multiplication results. For example, ``smulx`` which multiplies :type:`i32` operands to produce a :type:`i64` result. Alternatively, ``smulhi`` and ``smullo`` pairs. -.. inst:: a = udiv x, y - - Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. This - operation traps if the divisor is zero. - -.. inst:: a = udiv_imm x, Imm - - Unsigned integer division by an immediate constant. - - This instruction never traps because a divisor of zero is not allowed. - -.. inst:: a = sdiv x, y - - Signed integer division rounded toward zero: :math:`a := sign(xy) \lfloor - {|x| \over |y|}\rfloor`. This operation traps if the divisor is zero, or if - the result is not representable in :math:`B` bits two's complement. This only - happens when :math:`x = -2^{B-1}, y = -1`. - -.. inst:: a = sdiv_imm x, Imm - - Signed integer division by an immediate constant. - - This instruction never traps because a divisor of -1 or 0 is not allowed. - -.. inst:: a = urem x, y - - Unsigned integer remainder. - - This operation traps if the divisor is zero. - -.. inst:: a = urem_imm x, Imm - - Unsigned integer remainder with immediate divisor. - - This instruction never traps because a divisor of zero is not allowed. - -.. inst:: a = srem x, y - - Signed integer remainder. - - This operation traps if the divisor is zero. - - .. todo:: Integer remainder vs modulus. - - Clarify whether the result has the sign of the divisor or the dividend. - Should we add a ``smod`` instruction for the case where the result has - the same sign as the divisor? - -.. inst:: a = srem_imm x, Imm - - Signed integer remainder with immediate divisor. - - This instruction never traps because a divisor of 0 or -1 is not allowed. +.. autoinst:: udiv +.. autoinst:: udiv_imm +.. autoinst:: sdiv +.. autoinst:: sdiv_imm +.. autoinst:: urem +.. autoinst:: urem_imm +.. autoinst:: srem +.. autoinst:: srem_imm .. todo:: Minimum / maximum. @@ -880,153 +791,41 @@ Integer operations Bitwise operations ------------------ -.. inst:: a = and x, y +The bitwise operations and operate on any value type: Integers, floating point +numbers, and booleans. When operating on integer or floating point types, the +bitwise operations are working on the binary representation of the values. When +operating on boolean values, the bitwise operations work as logical operators. - Bitwise and. - - :rtype: bool, iB, iBxN, fBxN? - -.. inst:: a = or x, y - - Bitwise or. - - :rtype: bool, iB, iBxN, fBxN? - -.. inst:: a = xor x, y - - Bitwise xor. - - :rtype: bool, iB, iBxN, fBxN? - -.. inst:: a = not x - - Bitwise not. - - :rtype: bool, iB, iBxN, fBxN? +.. autoinst:: band +.. autoinst:: bor +.. autoinst:: bxor +.. autoinst:: bnot .. todo:: Redundant bitwise operators. ARM has instructions like ``bic(x,y) = x & ~y``, ``orn(x,y) = x | ~y``, and ``eon(x,y) = x ^ ~y``. -.. inst:: a = rotl x, y +The shift and rotate operations only work on integer types (scalar and vector). +The shift amount does not have to be the same type as the value being shifted. +Only the low `B` bits of the shift amount is significant. - Rotate left. +When operating on an integer vector type, the shift amount is still a scalar +type, and all the lanes are shifted the same amount. The shift amount is masked +to the number of bits in a *lane*, not the full size of the vector type. - Rotate the bits in ``x`` by ``y`` places. +.. autoinst:: rotl +.. autoinst:: rotr +.. autoinst:: ishl +.. autoinst:: ushr +.. autoinst:: sshr - :arg T x: Integer value to be rotated. - :arg iN y: Number of bits to shift. Any scalar integer type, not necessarily - the same type as ``x``. - :rtype: Same type as ``x``. - -.. inst:: a = rotr x, y - - Rotate right. - - Rotate the bits in ``x`` by ``y`` places. - - :arg T x: Integer value to be rotated. - :arg iN y: Number of bits to shift. Any scalar integer type, not necessarily - the same type as ``x``. - :rtype: Same type as ``x``. - -.. inst:: a = ishl x, y - - Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` - places. Shift in zero bits to the LSB. - - The shift amount is masked to the size of ``x``. - - :arg T x: Integer value to be shifted. - :arg iN y: Number of bits to shift. Any scalar integer type, not necessarily - the same type as ``x``. - :rtype: Same type as ``x``. - - When shifting a B-bits integer type, this instruction computes: - - .. math:: - s &:= y \pmod B, \\ - a &:= x \cdot 2^s \pmod{2^B}. - - .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. - -.. inst:: a = ushr x, y - - Unsigned shift right. Shift bits in ``x`` towards the LSB by ``y`` places, - shifting in zero bits to the MSB. Also called a *logical shift*. - - The shift amount is masked to the size of the register. - - :arg T x: Integer value to be shifted. - :arg iN y: Number of bits to shift. Can be any scalar integer type, not - necessarily the same type as ``x``. - :rtype: Same type as ``x``. - - When shifting a B-bits integer type, this instruction computes: - - .. math:: - s &:= y \pmod B, \\ - a &:= \lfloor x \cdot 2^{-s} \rfloor. - - .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. - -.. inst:: a = sshr x, y - - Signed shift right. Shift bits in ``x`` towards the LSB by ``y`` places, - shifting in sign bits to the MSB. Also called an *arithmetic shift*. - - The shift amount is masked to the size of the register. - - :arg T x: Integer value to be shifted. - :arg iN y: Number of bits to shift. Can be any scalar integer type, not - necessarily the same type as ``x``. - :rtype: Same type as ``x``. - - .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. - -.. inst:: a = clz x - - Count leading zero bits. - - :arg x: Integer value. - :rtype: :type:`i8` - - Starting from the MSB in ``x``, count the number of zero bits before - reaching the first one bit. When ``x`` is zero, returns the size of x in - bits. - -.. inst:: a = cls x - - Count leading sign bits. - - :arg x: Integer value. - :rtype: :type:`i8` - - Starting from the MSB after the sign bit in ``x``, count the number of - consecutive bits identical to the sign bit. When ``x`` is 0 or -1, returns - one less than the size of x in bits. - -.. inst:: a = ctz x - - Count trailing zeros. - - :arg x: Integer value. - :rtype: :type:`i8` - - Starting from the LSB in ``x``, count the number of zero bits before - reaching the first one bit. When ``x`` is zero, returns the size of x in - bits. - -.. inst:: a = popcnt x - - Population count - - :arg x: Integer value. - :rtype: :type:`i8` - - Count the number of one bits in ``x``. +The bit-counting instructions below are scalar only. +.. autoinst:: clz +.. autoinst:: cls +.. autoinst:: ctz +.. autoinst:: popcnt Floating point operations ------------------------- diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py new file mode 100644 index 0000000000..24488e7cc2 --- /dev/null +++ b/meta/cretonne/base.py @@ -0,0 +1,275 @@ +""" +Cretonne base instruction set. + +This module defines the basic Cretonne instruction set that all targets support. +""" +from . import TypeVar, Operand, Instruction +from types import i8 +from immediates import imm64 + +Int = TypeVar('Int', 'A scalar or vector integer type') +iB = TypeVar('iB', 'A scalar integer type') + +a = Operand('a', Int) +x = Operand('x', Int) +y = Operand('y', Int) + +iadd = Instruction('iadd', r""" + Wrapping integer addition: :math:`a := x + y \pmod{2^B}`. + + This instruction does not depend on the signed/unsigned interpretation of + the operands. + """, + ins=(x,y), outs=a) + +isub = Instruction('isub', r""" + Wrapping integer subtraction: :math:`a := x - y \pmod{2^B}`. + + This instruction does not depend on the signed/unsigned interpretation of + the operands. + """, + ins=(x,y), outs=a) + +imul = Instruction('imul', r""" + Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. + + This instruction does not depend on the signed/unsigned interpretation of + the + operands. + + Polymorphic over all integer types (vector and scalar). + """, + ins=(x,y), outs=a) + +udiv = Instruction('udiv', r""" + Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. + + This operation traps if the divisor is zero. + """, + ins=(x,y), outs=a) + +sdiv = Instruction('sdiv', r""" + Signed integer division rounded toward zero: :math:`a := sign(xy) \lfloor + {|x| \over |y|}\rfloor`. + + This operation traps if the divisor is zero, or if the result is not + representable in :math:`B` bits two's complement. This only happens when + :math:`x = -2^{B-1}, y = -1`. + """, + ins=(x,y), outs=a) + +urem = Instruction('urem', """ + Unsigned integer remainder. + + This operation traps if the divisor is zero. + """, + ins=(x,y), outs=a) + +srem = Instruction('srem', """ + Signed integer remainder. + + This operation traps if the divisor is zero. + + .. todo:: Integer remainder vs modulus. + + Clarify whether the result has the sign of the divisor or the dividend. + Should we add a ``smod`` instruction for the case where the result has + the same sign as the divisor? + """, + ins=(x,y), outs=a) + +a = Operand('a', iB) +x = Operand('x', iB) +Y = Operand('Y', imm64) + +iadd_imm = Instruction('iadd_imm', """ + Add immediate integer. + + Same as :inst:`iadd`, but one operand is an immediate constant. + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x,Y), outs=a) + +imul_imm = Instruction('imul_imm', """ + Integer multiplication by immediate constant. + + Polymorphic over all scalar integer types. + """, + ins=(x,Y), outs=a) + +udiv_imm = Instruction('udiv_imm', """ + Unsigned integer division by an immediate constant. + + This instruction never traps because a divisor of zero is not allowed. + """, + ins=(x,Y), outs=a) + +sdiv_imm = Instruction('sdiv_imm', """ + Signed integer division by an immediate constant. + + This instruction never traps because a divisor of -1 or 0 is not allowed. + """, + ins=(x,Y), outs=a) + +urem_imm = Instruction('urem_imm', """ + Unsigned integer remainder with immediate divisor. + + This instruction never traps because a divisor of zero is not allowed. + """, + ins=(x,Y), outs=a) + +srem_imm = Instruction('srem_imm', """ + Signed integer remainder with immediate divisor. + + This instruction never traps because a divisor of 0 or -1 is not allowed. + """, + ins=(x,Y), outs=a) + +# Swap x and y for isub_imm. +X = Operand('X', imm64) +y = Operand('y', iB) + +isub_imm = Instruction('isub_imm', """ + Immediate wrapping subtraction: :math:`a := X - y \pmod{2^B}`. + + Also works as integer negation when :math:`X = 0`. Use :inst:`iadd_imm` with a + negative immediate operand for the reverse immediate subtraction. + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(X,y), outs=a) + +# +# Bitwise operations. +# + +# TODO: Which types should permit boolean operations? Any reason to restrict? +bits = TypeVar('bits', 'Any integer, float, or boolean scalar or vector type') +x = Operand('x', bits) +y = Operand('y', bits) +a = Operand('a', bits) + +band = Instruction('band', """ + Bitwise and. + """, + ins=(x,y), outs=a) + +bor = Instruction('bor', """ + Bitwise or. + """, + ins=(x,y), outs=a) + +bxor = Instruction('bxor', """ + Bitwise xor. + """, + ins=(x,y), outs=a) + +bnot = Instruction('bnot', """ + Bitwise not. + """, + ins=x, outs=a) + +# Shift/rotate. +x = Operand('x', Int, doc='Scalar or vector value to shift') +y = Operand('y', iB, doc='Number of bits to shift') +a = Operand('a', Int) + +rotl = Instruction('rotl', r""" + Rotate left. + + Rotate the bits in ``x`` by ``y`` places. + """, + ins=(x,y), outs=a) + +rotr = Instruction('rotr', r""" + Rotate right. + + Rotate the bits in ``x`` by ``y`` places. + """, + ins=(x,y), outs=a) + +ishl = Instruction('ishl', r""" + Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` + places. Shift in zero bits to the LSB. + + The shift amount is masked to the size of ``x``. + + When shifting a B-bits integer type, this instruction computes: + + .. math:: + s &:= y \pmod B, \\ + a &:= x \cdot 2^s \pmod{2^B}. + + .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. + """, + ins=(x,y), outs=a) + +ushr = Instruction('ushr', r""" + Unsigned shift right. Shift bits in ``x`` towards the LSB by ``y`` places, + shifting in zero bits to the MSB. Also called a *logical shift*. + + The shift amount is masked to the size of the register. + + When shifting a B-bits integer type, this instruction computes: + + .. math:: + s &:= y \pmod B, \\ + a &:= \lfloor x \cdot 2^{-s} \rfloor. + + .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. + """, + ins=(x,y), outs=a) + +sshr = Instruction('sshr', r""" + Signed shift right. Shift bits in ``x`` towards the LSB by ``y`` places, + shifting in sign bits to the MSB. Also called an *arithmetic shift*. + + The shift amount is masked to the size of the register. + + .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. + """, + ins=(x,y), outs=a) + +# +# Bit counting. +# + +x = Operand('x', iB) +a = Operand('a', i8) + +clz = Instruction('clz', r""" + Count leading zero bits. + + Starting from the MSB in ``x``, count the number of zero bits before + reaching the first one bit. When ``x`` is zero, returns the size of x in + bits. + """, + ins=x, outs=a) + +cls = Instruction('cls', r""" + Count leading sign bits. + + Starting from the MSB after the sign bit in ``x``, count the number of + consecutive bits identical to the sign bit. When ``x`` is 0 or -1, returns + one less than the size of x in bits. + """, + ins=x, outs=a) + +ctz = Instruction('ctz', r""" + Count trailing zeros. + + Starting from the LSB in ``x``, count the number of zero bits before + reaching the first one bit. When ``x`` is zero, returns the size of x in + bits. + """, + ins=x, outs=a) + +popcnt = Instruction('popcnt', r""" + Population count + + Count the number of one bits in ``x``. + """, + ins=x, outs=a) From e5305c249bb2aebbda5f619027e8b360284d7f7c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 31 Mar 2016 14:18:02 -0700 Subject: [PATCH 030/968] Move constant instructions into meta. Add new immediate types for floating point and vector immediates. Use new immediates to define the constant value instructions in meta. Split the fconst instruction into two: f32const and f64const. This prevents confusion about the interpretation of 64 immediate bits when generating an f32 constant. Add an immvector ImmediateType. This immediate type is variable length, and provides all the bits of a SIMD vector directly. --- docs/cton_lexer.py | 2 +- docs/example.cton | 4 +-- docs/langref.rst | 46 +++++++++++++++----------------- meta/cretonne/base.py | 52 +++++++++++++++++++++++++++++++++++-- meta/cretonne/immediates.py | 13 ++++++++++ 5 files changed, 87 insertions(+), 30 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 33f769dfc6..c12db5aea7 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -23,7 +23,7 @@ class CretonneLexer(RegexLexer): # Numbers. (r'[-+]?0[xX][0-9a-fA-F]+', Number.Hex), (r'[-+]?0[xX][0-9a-fA-F]*\.[0-9a-fA-F]*([pP]\d+)?', Number.Hex), - (r'[-+]?\d+\.\d+([eE]\d+)?', Number.Float), + (r'[-+]?(\d+\.\d+([eE]\d+)?|[sq]NaN|Inf)', Number.Float), (r'[-+]?\d+', Number.Integer), # Reserved words. (keywords('function', 'entry'), Keyword), diff --git a/docs/example.cton b/docs/example.cton index c0001fe7c6..190d3a25fd 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -2,7 +2,7 @@ function average(i32, i32) -> f32 { ss1 = stack_slot 8, align 4 ; Stack slot for ``sum``. entry ebb1(v1: i32, v2: i32): - v3 = fconst.f64 0.0 + v3 = f64const 0x0.0 stack_store v3, ss1 brz v2, ebb3 ; Handle count == 0. v4 = iconst.i32 0 @@ -26,6 +26,6 @@ ebb2(v5: i32): return v17 ebb3: - v100 = fconst.f32 0x7fc00000 ; 0/0 = NaN + v100 = f32const qNaN return v100 } diff --git a/docs/langref.rst b/docs/langref.rst index fed88a819b..3ef460de06 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -212,6 +212,23 @@ indicate the different kinds of immediate operands on an instruction. signed two's complement integer. Instruction encodings may limit the valid range. +.. type:: ieee32 + + A 32-bit immediate floating point number in the IEEE 754-2008 binary32 + interchange format. All bit patterns are allowed. + +.. type:: ieee64 + + A 64-bit immediate floating point number in the IEEE 754-2008 binary64 + interchange format. All bit patterns are allowed. + +.. type:: immvector + + An immediate SIMD vector. This operand supplies all the bits of a SIMD + type, so it can have different sizes depending on the type produced. The + bits of the operand are interpreted as if the SIMD vector was loaded from + memory containing the immediate. + Control flow ============ @@ -628,31 +645,10 @@ A few instructions have variants that take immediate operands (e.g., :inst:`band` / :inst:`band_imm`), but in general an instruction is required to load a constant into an SSA value. -.. inst:: a = iconst N - - Integer constant. - - Create a scalar integer SSA value with an immediate constant value, or an - integer vector where all the lanes have the same value. - - :result Int a: Constant value. - -.. inst:: a = fconst N - - Floating point constant. - - Create a :type:`f32` or :type:`f64` SSA value with an immediate constant - value, or a floating point vector where all the lanes have the same value. - - :result Float a: Constant value. - -.. inst:: a = vconst N - - Vector constant (floating point or integer). - - Create a SIMD vector value where the lanes don't have to be identical. - - :result TxN a: Constant value. +.. autoinst:: iconst +.. autoinst:: f32const +.. autoinst:: f64const +.. autoinst:: vconst .. inst:: a = select c, x, y diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 24488e7cc2..a926829d7c 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -4,11 +4,59 @@ Cretonne base instruction set. This module defines the basic Cretonne instruction set that all targets support. """ from . import TypeVar, Operand, Instruction -from types import i8 -from immediates import imm64 +from types import i8, f32, f64 +from immediates import imm64, ieee32, ieee64, immvector Int = TypeVar('Int', 'A scalar or vector integer type') iB = TypeVar('iB', 'A scalar integer type') +TxN = TypeVar('%Tx%N', 'A SIMD vector type') + +# +# Materializing constants. +# + +N = Operand('N', imm64) +a = Operand('a', Int, doc='A constant integer scalar or vector value') +iconst = Instruction('iconst', r""" + Integer constant. + + Create a scalar integer SSA value with an immediate constant value, or an + integer vector where all the lanes have the same value. + """, + ins=N, outs=a) + +N = Operand('N', ieee32) +a = Operand('a', f32, doc='A constant integer scalar or vector value') +f32const = Instruction('f32const', r""" + Floating point constant. + + Create a :type:`f32` SSA value with an immediate constant value, or a + floating point vector where all the lanes have the same value. + """, + ins=N, outs=a) + +N = Operand('N', ieee64) +a = Operand('a', f64, doc='A constant integer scalar or vector value') +f64const = Instruction('f64const', r""" + Floating point constant. + + Create a :type:`f64` SSA value with an immediate constant value, or a + floating point vector where all the lanes have the same value. + """, + ins=N, outs=a) + +N = Operand('N', immvector) +a = Operand('a', TxN, doc='A constant vector value') +vconst = Instruction('vconst', r""" + Vector constant (floating point or integer). + + Create a SIMD vector value where the lanes don't have to be identical. + """, + ins=N, outs=a) + +# +# Integer arithmetic +# a = Operand('a', Int) x = Operand('x', Int) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index 6b499d5215..2735cd6be1 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -10,3 +10,16 @@ from . import ImmediateType #: This type of immediate integer can interact with SSA values with any #: :py:class:`cretonne.IntType` type. imm64 = ImmediateType('imm64', 'A 64-bit immediate integer.') + +#: A 32-bit immediate floating point operand. +#: +#: IEEE 754-2008 binary32 interchange format. +ieee32 = ImmediateType('ieee32', 'A 32-bit immediate floating point number.') + +#: A 64-bit immediate floating point operand. +#: +#: IEEE 754-2008 binary64 interchange format. +ieee64 = ImmediateType('ieee64', 'A 64-bit immediate floating point number.') + +#: A large SIMD vector constant. +immvector = ImmediateType('immvector', 'An immediate SIMD vector.') From 5f706b0a1fcd6f4170e0204d864dec52a66d9cf6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 31 Mar 2016 15:22:23 -0700 Subject: [PATCH 031/968] Implement IEEE immediates for binary32 and binary64. Clarify the textual encoding of floating point numbers. Don't allow decimal floating point since conversion to/from binary can produce rounding problems on some (buggy) systems. --- docs/langref.rst | 35 +++++++ src/libcretonne/immediates.rs | 184 ++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) diff --git a/docs/langref.rst b/docs/langref.rst index 3ef460de06..8acb5e793a 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -212,6 +212,9 @@ indicate the different kinds of immediate operands on an instruction. signed two's complement integer. Instruction encodings may limit the valid range. + In the textual format, :type:`imm64` immediates appear as decimal or + hexadecimal literals using the same syntax as C. + .. type:: ieee32 A 32-bit immediate floating point number in the IEEE 754-2008 binary32 @@ -229,6 +232,38 @@ indicate the different kinds of immediate operands on an instruction. bits of the operand are interpreted as if the SIMD vector was loaded from memory containing the immediate. +The two IEEE floating point immediate types :type:`ieee32` and :type:`ieee64` +are displayed as hexadecimal floating point literals in the textual IL format. +Decimal floating point literals are not allowed because some computer systems +can round differently when converting to binary. The hexadecimal floating point +format is mostly the same as the one used by C99, but extended to represent all +NaN bit patterns: + +Normal numbers + Compatible with C99: ``-0x1.Tpe`` where ``T`` are the trailing + significand bits encoded as hexadecimal, and ``e`` is the unbiased exponent + as a decimal number. :type:`ieee32` has 23 trailing significand bits. They + are padded with an extra LSB to produce 6 hexadecimal digits. This is not + necessary for :type:`ieee64` which has 52 trailing significand bits + forming 13 hexadecimal digits with no padding. + +Subnormal numbers + Compatible with C99: ``-0x0.Tpemin`` where ``T`` are the trailing + significand bits encoded as hexadecimal, and ``emin`` is the minimum exponent + as a decimal number. + +Infinities + Either ``-Inf`` or ``Inf``. + +Quiet NaNs + Quiet NaNs have the MSB of the trailing significand set. If the remaining + bits of the trailing significand are all zero, the value is displayed as + ``-qNaN`` or ``qNaN``. Otherwise, ``-qNaN:0xT`` where ``T`` are the + trailing significand bits encoded as hexadecimal. + +Signaling NaNs + Displayed as ``-sNaN:0xT``. + Control flow ============ diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index a60291b164..0e1e94ba07 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -6,6 +6,7 @@ //! module in the meta language. use std::fmt::{self, Display, Formatter}; +use std::mem; /// 64-bit immediate integer operand. /// @@ -36,9 +37,123 @@ impl Display for Imm64 { } } + +/// An IEEE binary32 immediate floating point value. +/// +/// All bit patterns are allowed. +pub struct Ieee32(f32); + +/// An IEEE binary64 immediate floating point value. +/// +/// All bit patterns are allowed. +pub struct Ieee64(f64); + +// Format a floating point number in a way that is reasonably human-readable, and that can be +// converted back to binary without any rounding issues. The hexadecimal formatting of normal and +// subnormal numbers is compatible with C99 and the printf "%a" format specifier. The NaN and Inf +// formats are not supported by C99. +// +// The encoding parameters are: +// +// w - exponent field width in bits +// t - trailing significand field width in bits +// +fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { + assert!(w > 0 && w <= 16, "Invalid exponent range"); + assert!(1 + w + t <= 64, "Too large IEEE format for u64"); + + let max_e_bits = (1u64 << w) - 1; + let t_bits = bits & ((1u64 << t) - 1); // Trailing significand. + let e_bits = (bits >> t) & max_e_bits; // Biased exponent. + let sign_bit = (bits >> w + t) & 1; + + let bias: i32 = (1 << (w - 1)) - 1; + let e = e_bits as i32 - bias; // Unbiased exponent. + let emin = 1 - bias; // Minimum exponent. + + // How many hexadecimal digits are needed for the trailing significand? + let digits = (t + 3) / 4; + // Trailing significand left-aligned in `digits` hexadecimal digits. + let left_t_bits = t_bits << (4 * digits - t); + + // All formats share the leading sign. + if sign_bit != 0 { + try!(write!(f, "-")); + } + + if e_bits == 0 { + if t_bits == 0 { + // Zero. + write!(f, "0.0") + } else { + // Subnormal. + write!(f, "0x0.{0:01$x}p{2}", left_t_bits, digits as usize, emin) + } + } else if e_bits == max_e_bits { + if t_bits == 0 { + // Infinity. + write!(f, "Inf") + } else { + // NaN. + let payload = t_bits & ((1 << (t - 1)) - 1); + if t_bits & (1 << (t - 1)) != 0 { + // Quiet NaN. + if payload != 0 { + write!(f, "qNaN:0x{:x}", payload) + } else { + write!(f, "qNaN") + } + } else { + // Signaling NaN. + write!(f, "sNaN:0x{:x}", payload) + } + } + } else { + // Normal number. + write!(f, "0x1.{0:01$x}p{2}", left_t_bits, digits as usize, e) + } +} + +impl Ieee32 { + pub fn new(x: f32) -> Ieee32 { + Ieee32(x) + } + + /// Construct Ieee32 immediate from raw bits. + pub fn new_from_bits(x: u32) -> Ieee32 { + Ieee32(unsafe { mem::transmute(x) }) + } +} + +impl Display for Ieee32 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let bits: u32 = unsafe { mem::transmute(self.0) }; + format_float(bits as u64, 8, 23, f) + } +} + +impl Ieee64 { + pub fn new(x: f64) -> Ieee64 { + Ieee64(x) + } + + /// Construct Ieee64 immediate from raw bits. + pub fn new_from_bits(x: u64) -> Ieee64 { + Ieee64(unsafe { mem::transmute(x) }) + } +} + +impl Display for Ieee64 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let bits: u64 = unsafe { mem::transmute(self.0) }; + format_float(bits, 11, 52, f) + } +} + #[cfg(test)] mod tests { use super::*; + use std::{f32, f64}; #[test] fn format_imm64() { @@ -50,4 +165,73 @@ mod tests { assert_eq!(format!("{}", Imm64(0xffff)), "0xffff"); assert_eq!(format!("{}", Imm64(0x10000)), "0x0001_0000"); } + + #[test] + fn format_ieee32() { + assert_eq!(format!("{}", Ieee32::new(0.0)), "0.0"); + assert_eq!(format!("{}", Ieee32::new(-0.0)), "-0.0"); + assert_eq!(format!("{}", Ieee32::new(1.0)), "0x1.000000p0"); + assert_eq!(format!("{}", Ieee32::new(1.5)), "0x1.800000p0"); + assert_eq!(format!("{}", Ieee32::new(0.5)), "0x1.000000p-1"); + assert_eq!(format!("{}", Ieee32::new(f32::EPSILON)), "0x1.000000p-23"); + assert_eq!(format!("{}", Ieee32::new(f32::MIN)), "-0x1.fffffep127"); + assert_eq!(format!("{}", Ieee32::new(f32::MAX)), "0x1.fffffep127"); + // Smallest positive normal number. + assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE)), + "0x1.000000p-126"); + // Subnormals. + assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE / 2.0)), + "0x0.800000p-126"); + assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON)), + "0x0.000002p-126"); + assert_eq!(format!("{}", Ieee32::new(f32::INFINITY)), "Inf"); + assert_eq!(format!("{}", Ieee32::new(f32::NEG_INFINITY)), "-Inf"); + assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "qNaN"); + assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-qNaN"); + // Construct some qNaNs with payloads. + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "qNaN:0x1"); + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7ff00001)), + "qNaN:0x300001"); + // Signaling NaNs. + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7f800001)), "sNaN:0x1"); + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fa00001)), + "sNaN:0x200001"); + } + + #[test] + fn format_ieee64() { + assert_eq!(format!("{}", Ieee64::new(0.0)), "0.0"); + assert_eq!(format!("{}", Ieee64::new(-0.0)), "-0.0"); + assert_eq!(format!("{}", Ieee64::new(1.0)), "0x1.0000000000000p0"); + assert_eq!(format!("{}", Ieee64::new(1.5)), "0x1.8000000000000p0"); + assert_eq!(format!("{}", Ieee64::new(0.5)), "0x1.0000000000000p-1"); + assert_eq!(format!("{}", Ieee64::new(f64::EPSILON)), + "0x1.0000000000000p-52"); + assert_eq!(format!("{}", Ieee64::new(f64::MIN)), + "-0x1.fffffffffffffp1023"); + assert_eq!(format!("{}", Ieee64::new(f64::MAX)), + "0x1.fffffffffffffp1023"); + // Smallest positive normal number. + assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE)), + "0x1.0000000000000p-1022"); + // Subnormals. + assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE / 2.0)), + "0x0.8000000000000p-1022"); + assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON)), + "0x0.0000000000001p-1022"); + assert_eq!(format!("{}", Ieee64::new(f64::INFINITY)), "Inf"); + assert_eq!(format!("{}", Ieee64::new(f64::NEG_INFINITY)), "-Inf"); + assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "qNaN"); + assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-qNaN"); + // Construct some qNaNs with payloads. + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff8000000000001)), + "qNaN:0x1"); + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ffc000000000001)), + "qNaN:0x4000000000001"); + // Signaling NaNs. + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff0000000000001)), + "sNaN:0x1"); + assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff4000000000001)), + "sNaN:0x4000000000001"); + } } From 79e765a1834964010bdaa13d317eab4951fb341b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Apr 2016 10:20:32 -0700 Subject: [PATCH 032/968] Display quiet NaNs as 'NaN'. This is recommended by IEEE 754-2008. We still distinguish signaling NaNs with 'sNaN'. --- docs/langref.rst | 7 +++++-- src/libcretonne/immediates.rs | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 8acb5e793a..47f7fa8833 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -247,6 +247,9 @@ Normal numbers necessary for :type:`ieee64` which has 52 trailing significand bits forming 13 hexadecimal digits with no padding. +Zeros + Positive and negative zero are displayed as ``0.0`` and ``-0.0`` respectively. + Subnormal numbers Compatible with C99: ``-0x0.Tpemin`` where ``T`` are the trailing significand bits encoded as hexadecimal, and ``emin`` is the minimum exponent @@ -258,8 +261,8 @@ Infinities Quiet NaNs Quiet NaNs have the MSB of the trailing significand set. If the remaining bits of the trailing significand are all zero, the value is displayed as - ``-qNaN`` or ``qNaN``. Otherwise, ``-qNaN:0xT`` where ``T`` are the - trailing significand bits encoded as hexadecimal. + ``-NaN`` or ``NaN``. Otherwise, ``-NaN:0xT`` where ``T`` are the trailing + significand bits encoded as hexadecimal. Signaling NaNs Displayed as ``-sNaN:0xT``. diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 0e1e94ba07..efd84cd606 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -99,9 +99,9 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { if t_bits & (1 << (t - 1)) != 0 { // Quiet NaN. if payload != 0 { - write!(f, "qNaN:0x{:x}", payload) + write!(f, "NaN:0x{:x}", payload) } else { - write!(f, "qNaN") + write!(f, "NaN") } } else { // Signaling NaN. @@ -186,12 +186,12 @@ mod tests { "0x0.000002p-126"); assert_eq!(format!("{}", Ieee32::new(f32::INFINITY)), "Inf"); assert_eq!(format!("{}", Ieee32::new(f32::NEG_INFINITY)), "-Inf"); - assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "qNaN"); - assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-qNaN"); + assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "NaN"); + assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "qNaN:0x1"); + assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "NaN:0x1"); assert_eq!(format!("{}", Ieee32::new_from_bits(0x7ff00001)), - "qNaN:0x300001"); + "NaN:0x300001"); // Signaling NaNs. assert_eq!(format!("{}", Ieee32::new_from_bits(0x7f800001)), "sNaN:0x1"); assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fa00001)), @@ -221,13 +221,13 @@ mod tests { "0x0.0000000000001p-1022"); assert_eq!(format!("{}", Ieee64::new(f64::INFINITY)), "Inf"); assert_eq!(format!("{}", Ieee64::new(f64::NEG_INFINITY)), "-Inf"); - assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "qNaN"); - assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-qNaN"); + assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "NaN"); + assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-NaN"); // Construct some qNaNs with payloads. assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff8000000000001)), - "qNaN:0x1"); + "NaN:0x1"); assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ffc000000000001)), - "qNaN:0x4000000000001"); + "NaN:0x4000000000001"); // Signaling NaNs. assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff0000000000001)), "sNaN:0x1"); From 043bb1aba546ace26bd383403977df946767d863 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 1 Apr 2016 15:32:00 -0700 Subject: [PATCH 033/968] Replace bool with b1, b8, b16, ... The b1 type is an abstract boolean value. The others are concrete representations. --- docs/cton_lexer.py | 2 +- docs/langref.rst | 56 +++++++++++++++++++++++++++++---------- meta/cretonne/__init__.py | 14 ++++++++++ meta/cretonne/types.py | 9 +++++-- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index c12db5aea7..0139de337c 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -30,7 +30,7 @@ class CretonneLexer(RegexLexer): # Known attributes. (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), Name.Attribute), # Well known value types. - (r'\b(bool|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), + (r'\b(b\d+|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value # ss = stack slot (r'(v|ss)\d+', Name.Variable), diff --git a/docs/langref.rst b/docs/langref.rst index 47f7fa8833..df0e19b20a 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -87,7 +87,36 @@ All SSA values have a type which determines the size and shape (for SIMD vectors) of the value. Many instructions are polymorphic -- they can operate on different types. -.. autoctontype:: bool +Boolean types +------------- + +Boolean values are either true or false. While this only requires a single bit +to represent, more bits are often used when holding a boolean value in a +register or in memory. The :type:`b1` type represents an abstract boolean +value. It can only exist as an SSA value, it can't be stored in memory or +converted to another type. The larger boolean types can be stored in memory. + +.. todo:: Clarify the representation of larger boolean types. + + The multi-bit boolean types can be interpreted in different ways. We could + declare that zero means false and non-zero means true. This may require + unwanted normalization code in some places. + + We could specify a fixed encoding like all ones for true. This would then + lead to undefined behavior if untrusted code uses the multibit booleans + incorrectly. + + Something like this: + + - External code is not allowed to load/store multi-bit booleans or + otherwise expose the representation. + - Each target specifies the exact representation of a multi-bit boolean. + +.. autoctontype:: b1 +.. autoctontype:: b8 +.. autoctontype:: b16 +.. autoctontype:: b32 +.. autoctontype:: b64 Integer types ------------- @@ -115,7 +144,7 @@ SIMD vector types ----------------- A SIMD vector type represents a vector of values from one of the scalar types -(:type:`bool`, integer, and floating point). Each scalar value in a SIMD type is +(boolean, integer, and floating point). Each scalar value in a SIMD type is called a *lane*. The number of lanes must be a power of two in the range 2-256. .. type:: i%Bx%N @@ -146,14 +175,14 @@ called a *lane*. The number of lanes must be a power of two in the range 2-256. The size of a :type:`f64` vector in memory is :math:`8N` bytes. -.. type:: boolx%N +.. type:: b1x%N A boolean SIMD vector. Boolean vectors are used when comparing SIMD vectors. For example, - comparing two :type:`i32x4` values would produce a :type:`boolx4` result. + comparing two :type:`i32x4` values would produce a :type:`b1x4` result. - Like the :type:`bool` type, a boolean vector cannot be stored in memory. + Like the :type:`b1` type, a boolean vector cannot be stored in memory. Pseudo-types and type classes ----------------------------- @@ -194,11 +223,11 @@ in this reference. .. type:: Logic - Either :type:`bool` or :type:`boolxN`. + Either :type:`b1` or :type:`b1xN`. .. type:: Testable - Either :type:`bool` or :type:`iN`. + Either :type:`b1` or :type:`iN`. Immediate operand types ----------------------- @@ -291,7 +320,7 @@ instruction in the EBB. Branch when zero. - If ``x`` is a :type:`bool` value, take the branch when ``x`` is false. If + If ``x`` is a :type:`b1` value, take the branch when ``x`` is false. If ``x`` is an integer value, take the branch when ``x = 0``. :arg Testable x: Value to test. @@ -303,7 +332,7 @@ instruction in the EBB. Branch when non-zero. - If ``x`` is a :type:`bool` value, take the branch when ``x`` is true. If + If ``x`` is a :type:`b1` value, take the branch when ``x`` is true. If ``x`` is an integer value, take the branch when ``x != 0``. :arg Testable x: Value to test. @@ -479,8 +508,7 @@ Cretonne also provides more restricted memory operations that are always safe. Load from memory at ``p + Offset``. This is a polymorphic instruction that can load any value type which has a - memory representation (i.e., everything except :type:`bool` and boolean - vectors). + memory representation. :arg iPtr p: Base address. :arg Offset: Immediate signed offset. @@ -692,7 +720,7 @@ load a constant into an SSA value. Conditional select. - :arg bool c: Controlling flag. + :arg b1 c: Controlling flag. :arg T x: Value to return when ``c`` is true. :arg T y: Value to return when ``c`` is false. Must be same type as ``x``. :result T a: Same type as ``x`` and ``y``. @@ -710,7 +738,7 @@ Vector operations Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean vector ``c``. - :arg boolxN c: Controlling flag vector. + :arg b1xN c: Controlling flag vector. :arg TxN x: Vector with lanes selected by the true lanes of ``c``. Must be a vector type with the same number of lanes as ``c``. :arg TxN y: Vector with lanes selected by the false lanes of ``c``. @@ -872,7 +900,7 @@ These operations generally follow IEEE 754-2008 semantics. :arg Cond: Condition code determining how ``x`` and ``y`` are compared. :arg x,y: Floating point scalar or vector values of the same type. - :rtype: :type:`bool` or :type:`boolxN` with the same number of lanes as + :rtype: :type:`b1` or :type:`b1xN` with the same number of lanes as ``x`` and ``y``. An 'ordered' condition code yields ``false`` if either operand is Nan. diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index fd0c4664dd..bbf0e74063 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -95,6 +95,20 @@ class FloatType(ScalarType): def __repr__(self): return 'FloatType(bits={})'.format(self.bits) +class BoolType(ScalarType): + """A concrete scalar boolean type.""" + + def __init__(self, bits): + assert bits > 0, 'BoolType must have positive number of bits' + super(BoolType, self).__init__( + name='b{:d}'.format(bits), + membytes=bits/8, + doc="A boolean type with {} bits.".format(bits)) + self.bits = bits + + def __repr__(self): + return 'BoolType(bits={})'.format(self.bits) + # # Parametric polymorphism. # diff --git a/meta/cretonne/types.py b/meta/cretonne/types.py index 3d369a60e4..c639d89050 100644 --- a/meta/cretonne/types.py +++ b/meta/cretonne/types.py @@ -2,14 +2,19 @@ The cretonne.types module predefines all the Cretonne scalar types. """ -from . import ScalarType, IntType, FloatType +from . import ScalarType, IntType, FloatType, BoolType #: Boolean. -bool = ScalarType('bool', 0, +b1 = ScalarType('b1', 0, """ A boolean value that is either true or false. """) +b8 = BoolType(8) #: 8-bit bool. +b16 = BoolType(16) #: 16-bit bool. +b32 = BoolType(32) #: 32-bit bool. +b64 = BoolType(64) #: 64-bit bool. + i8 = IntType(8) #: 8-bit int. i16 = IntType(16) #: 16-bit int. i32 = IntType(32) #: 32-bit int. From d197f8a104e34353d0f8edb08b6bc952a3bdeb42 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Apr 2016 11:11:53 -0700 Subject: [PATCH 034/968] Add an autohtml target to docs/Makefile. This runs the convenient sphinx-autobuild web server for docs development. --- docs/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Makefile b/docs/Makefile index 619d2caf24..598a13b496 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,6 +4,7 @@ # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build +SPHINXABUILD = sphinx-autobuild PAPER = BUILDDIR = _build @@ -56,6 +57,9 @@ html: @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." +autohtml: html + $(SPHINXABUILD) -z ../meta -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html + dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo From d32d78d16c51fc172b19426896738b7177603247 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Apr 2016 13:21:46 -0700 Subject: [PATCH 035/968] Add scaffolding for a Python build script. Hook up a Cargo build script that runs a Python script in the meta directory. --- meta/build.py | 11 +++++++++ src/libcretonne/Cargo.toml | 1 + src/libcretonne/build.rs | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 meta/build.py create mode 100644 src/libcretonne/build.rs diff --git a/meta/build.py b/meta/build.py new file mode 100644 index 0000000000..2cdda53403 --- /dev/null +++ b/meta/build.py @@ -0,0 +1,11 @@ +# Second-level build script. +# +# This script is run from src/libcretonne/build.rs to generate Rust files. + +import argparse + +parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') +parser.add_argument('--out-dir', help='set output directory') + +args = parser.parse_args() +out_dir = args.out_dir diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index 58a67fe569..38edc811d1 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -2,6 +2,7 @@ authors = ["The cwCretonneRust Project Developers"] name = "cretonne" version = "0.0.0" +build = "build.rs" [lib] name = "cretonne" diff --git a/src/libcretonne/build.rs b/src/libcretonne/build.rs new file mode 100644 index 0000000000..523ddb3548 --- /dev/null +++ b/src/libcretonne/build.rs @@ -0,0 +1,49 @@ + +// Build script. +// +// This program is run by Cargo when building libcretonne. It is used to generate Rust code from +// the language definitions in the meta directory. +// +// Environment: +// +// OUT_DIR +// Directory where generated files should be placed. +// +// The build script expects to be run from the directory where this build.rs file lives. The +// current directory is used to find the sources. + + +use std::env; +use std::process; + +fn main() { + let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); + + println!("Build script generating files in {}", out_dir); + + let mut cur_dir = env::current_dir().expect("Can't access current working directory"); + + // We're in src/libcretonne. Find the top-level directory. + assert!(cur_dir.pop(), "No parent 'src' directory"); + assert!(cur_dir.pop(), "No top-level directory"); + let top_dir = cur_dir.as_path(); + + // Scripts are in $top_dir/meta. + let meta_dir = top_dir.join("meta"); + let build_script = meta_dir.join("build.py"); + + // Let Cargo known that this script should be rerun if anything changes in the meta directory. + println!("cargo:rerun-if-changed={}", meta_dir.display()); + + // Launch build script with Python. We'll just find python in the path. + let status = process::Command::new("python") + .current_dir(top_dir) + .arg(build_script) + .arg("--out-dir") + .arg(out_dir) + .status() + .expect("Failed to launch second-level build script"); + if !status.success() { + process::exit(status.code().unwrap()); + } +} From 6f083a310ad267cab38eab45116a49b81eceaeff Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Apr 2016 14:35:50 -0700 Subject: [PATCH 036/968] Collect all instructions into instruction groups. --- docs/metaref.rst | 2 ++ meta/cretonne/__init__.py | 45 +++++++++++++++++++++++++++++++++++++++ meta/cretonne/base.py | 6 +++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index 8d58757b81..e9b7c9a6ec 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -72,3 +72,5 @@ class. .. autoclass:: Operand .. autoclass:: Instruction +.. autoclass:: InstructionGroup + :members: diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index bbf0e74063..a5ebb50f04 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -150,6 +150,50 @@ class ImmediateType(object): # Defining instructions. # +class InstructionGroup(object): + """ + An instruction group. + + Every instruction must belong to exactly one instruction group. A given + target architecture can support instructions from multiple groups, and it + does not necessarily support all instructions in a group. + + New instructions are automatically added to the currently open instruction + group. + """ + + # The currently open instruction group. + _current = None + + def open(self): + """ + Open this instruction group such that future new instructions are + added to this group. + """ + assert InstructionGroup._current is None, ( + "Can't open {} since {} is already open".format(self, _current)) + InstructionGroup._current = self + + def close(self): + """ + Close this instruction group. This function should be called before + opening another instruction group. + """ + assert InstructionGroup._current is self, ( + "Can't close {}, the open instuction group is {}".format(self, _current)) + InstructionGroup._current = None + + def __init__(self, name, doc): + self.name = name + self.__doc__ = doc + self.instructions = [] + self.open() + + @staticmethod + def append(inst): + assert InstructionGroup._current, "Open an instruction group before defining instructions." + InstructionGroup._current.instructions.append(inst) + class Operand(object): """ An instruction operand. @@ -202,6 +246,7 @@ class Instruction(object): self.__doc__ = doc self.ins = self._to_operand_tuple(ins) self.outs = self._to_operand_tuple(outs) + InstructionGroup.append(self) @staticmethod def _to_operand_tuple(x): diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index a926829d7c..604687829e 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -3,10 +3,12 @@ Cretonne base instruction set. This module defines the basic Cretonne instruction set that all targets support. """ -from . import TypeVar, Operand, Instruction +from . import TypeVar, Operand, Instruction, InstructionGroup from types import i8, f32, f64 from immediates import imm64, ieee32, ieee64, immvector +instructions = InstructionGroup("base", "Shared base instruction set") + Int = TypeVar('Int', 'A scalar or vector integer type') iB = TypeVar('iB', 'A scalar integer type') TxN = TypeVar('%Tx%N', 'A SIMD vector type') @@ -321,3 +323,5 @@ popcnt = Instruction('popcnt', r""" Count the number of one bits in ``x``. """, ins=x, outs=a) + +instructions.close() From 936d6e523a73f8c20fcef0301b4f1ff5e76ce249 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Apr 2016 15:28:08 -0700 Subject: [PATCH 037/968] Give instructions a CamelCase name. This will be used as the instruction name in Rust code. By making this a property of the instruction, it can be changed by the user if desired. --- meta/cretonne/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index a5ebb50f04..ff03ed198c 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -5,6 +5,13 @@ This module provides classes and functions used to describe Cretonne instructions. """ +import re + +camel_re = re.compile('(^|_)([a-z])') +def camel_case(s): + """Convert the string s to CamelCase""" + return camel_re.sub(lambda m: m.group(2).upper(), s) + # Concrete types. # # Instances (i8, i32, ...) are provided in the cretonne.types module. @@ -243,6 +250,7 @@ class Instruction(object): def __init__(self, name, doc, ins=(), outs=(), **kwargs): self.name = name + self.camel_name = camel_case(name) self.__doc__ = doc self.ins = self._to_operand_tuple(ins) self.outs = self._to_operand_tuple(outs) From 7bf4570ba1d7e5df07a5a793e963b8a3b711a521 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 6 Apr 2016 11:32:43 -0700 Subject: [PATCH 038/968] Add a RISC-V target. Flesh out the directory structure for defining target instruction set architectures. Use RISC-V as a startgin point because it is so simple. --- .gitignore | 2 -- docs/metaref.rst | 18 +++++++++++++++++- meta/cretonne/__init__.py | 18 ++++++++++++++++++ meta/target/__init__.py | 16 ++++++++++++++++ meta/target/riscv/__init__.py | 30 ++++++++++++++++++++++++++++++ src/.gitignore | 2 ++ 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 meta/target/__init__.py create mode 100644 meta/target/riscv/__init__.py create mode 100644 src/.gitignore diff --git a/.gitignore b/.gitignore index 25eadf4e60..0d20b6487c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ *.pyc -Cargo.lock -target diff --git a/docs/metaref.rst b/docs/metaref.rst index e9b7c9a6ec..c8f8b0e265 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -11,7 +11,7 @@ domain specific language embedded in Python. An instruction set is described by a Python module under the :file:`meta` directory that has a global variable called ``instructions``. The basic Cretonne instruction set described in :doc:`langref` is defined by the Python -module :mod:`cretonne.instrs`. +module :mod:`cretonne.base`. .. module:: cretonne @@ -74,3 +74,19 @@ class. .. autoclass:: Instruction .. autoclass:: InstructionGroup :members: + +Targets +======= + +Cretonne can be compiled with support for multiple target instruction set +architectures. Each ISA is represented by a :py:class`cretonne.Target` instance. + +.. autoclass:: Target + +The definitions for each supported target live in a package under +:file:`meta/target`. + +.. automodule:: target + :members: + +.. automodule:: target.riscv diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index ff03ed198c..d69d5c35c4 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -267,3 +267,21 @@ class Instruction(object): for op in x: assert isinstance(op, Operand) return x + +# +# Defining targets +# +class Target(object): + """ + A target instruction set architecture. + + The `Target` class collects everything known about a target ISA. + + :param name: Short mnemonic name for the ISA. + :param instruction_groups: List of `InstructionGroup` instances that are + relevant for this ISA. + """ + + def __init__(self, name, instrution_groups): + self.name = name + self.instruction_groups = instrution_groups diff --git a/meta/target/__init__.py b/meta/target/__init__.py new file mode 100644 index 0000000000..bed730b207 --- /dev/null +++ b/meta/target/__init__.py @@ -0,0 +1,16 @@ +""" +Cretonne target definitions +--------------------------- + +The :py:mod:`target` package contains sub-packages for each target instruction +set architecture supported by Cretonne. +""" + +from . import riscv + +def all_targets(): + """ + Get a list of all the supported targets. Each target is represented as a + :py:class:`cretonne.Target` instance. + """ + return [riscv.target] diff --git a/meta/target/riscv/__init__.py b/meta/target/riscv/__init__.py new file mode 100644 index 0000000000..e9b9926e31 --- /dev/null +++ b/meta/target/riscv/__init__.py @@ -0,0 +1,30 @@ +""" +RISC-V Target +------------- + +`RISC-V `_ is an open instruction set architecture originally +developed at UC Berkeley. It is a RISC-style ISA with either a 32-bit (RV32I) or +64-bit (RV32I) base instruction set and a number of optional extensions: + +RV32M / RV64M + Integer multiplication and division. + +RV32A / RV64A + Atomics. + +RV32F / RV64F + Single-precision IEEE floating point. + +RV32D / RV64D + Double-precision IEEE floating point. + +RV32G / RV64G + General purpose instruction sets. This represents the union of the I, M, A, + F, and D instruction sets listed above. + +""" + +from cretonne import Target +import cretonne.base + +target = Target('riscv', [cretonne.base.instructions]) diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000000..a9d37c560c --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock From 15f39c776c38493280223a4ed197b77e73e2f412 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 6 Apr 2016 10:45:06 -0700 Subject: [PATCH 039/968] Begin source generation. Start out easy by emiting an opcodes.rs file containing an opcode enumeration. --- meta/build.py | 6 ++++ meta/gen_instr.py | 31 ++++++++++++++++++++ meta/srcgen.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 meta/gen_instr.py create mode 100644 meta/srcgen.py diff --git a/meta/build.py b/meta/build.py index 2cdda53403..76673fa62b 100644 --- a/meta/build.py +++ b/meta/build.py @@ -3,9 +3,15 @@ # This script is run from src/libcretonne/build.rs to generate Rust files. import argparse +import target +import gen_instr parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') args = parser.parse_args() out_dir = args.out_dir + +targets = target.all_targets() + +gen_instr.generate(targets, out_dir) diff --git a/meta/gen_instr.py b/meta/gen_instr.py new file mode 100644 index 0000000000..a92609c861 --- /dev/null +++ b/meta/gen_instr.py @@ -0,0 +1,31 @@ +""" +Generate sources with instruction info. +""" + +import srcgen + +def collect_instr_groups(targets): + seen = set() + groups = [] + for t in targets: + for g in t.instruction_groups: + if g not in seen: + groups.append(g) + seen.add(g) + return groups + +def gen_opcodes(groups, out_dir): + """Generate opcode enumerations.""" + fmt = srcgen.Formatter() + fmt.line('enum Opcode {') + fmt.indent_push() + for g in groups: + for i in g.instructions: + fmt.line(i.camel_name + ',') + fmt.indent_pop() + fmt.line('}') + fmt.update_file('opcodes.rs', out_dir) + +def generate(targets, out_dir): + groups = collect_instr_groups(targets) + gen_opcodes(groups, out_dir) diff --git a/meta/srcgen.py b/meta/srcgen.py new file mode 100644 index 0000000000..9778e2c518 --- /dev/null +++ b/meta/srcgen.py @@ -0,0 +1,73 @@ +""" +Source code generator. + +The `srcgen` module contains generic helper routines and classes for generating +source code. + +""" + +import sys +import os + +class Formatter(object): + """ + Source code formatter class. + + - Collect source code to be written to a file. + - Keep track of indentation. + + Indentation example: + + >>> f = Formatter() + >>> f.line('Hello line 1') + >>> f.writelines() + Hello line 1 + >>> f.indent_push() + >>> f.comment('Nested comment') + >>> f.indent_pop() + >>> f.line('Back again') + >>> f.writelines() + Hello line 1 + // Nested comment + Back again + + """ + + shiftwidth = 2 + + def __init__(self): + self.indent = '' + self.lines = [] + + def indent_push(self): + """Increase current indentation level by one.""" + self.indent += ' ' * self.shiftwidth + + def indent_pop(self): + """Decrease indentation by one level.""" + assert self.indent != '', 'Already at top level indentation' + self.indent = self.indent[0:-self.shiftwidth] + + def line(self, s): + """And an indented line.""" + self.lines.append('{}{}\n'.format(self.indent, s)) + + def writelines(self, f=None): + """Write all lines to `f`.""" + if not f: + f = sys.stdout + f.writelines(self.lines) + + def update_file(self, filename, directory): + if directory is not None: + filename = os.path.join(directory, filename) + with open(filename, 'w') as f: + self.writelines(f) + + def comment(self, s): + """Add a comment line.""" + self.line('// ' + s) + +if __name__ == "__main__": + import doctest + doctest.testmod() From d650d551a07e25faa716f15264a3f5a7b45c8b73 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 6 Apr 2016 14:55:21 -0700 Subject: [PATCH 040/968] Include generated Opcode enum in the immediates module. Generate nice doc comments for the Opcode enum variants that 'cargo doc' will pick up. Include a `Display` trait implementation that prints the lower snake-case version of the opcode name. --- meta/gen_instr.py | 32 ++++++++++++++++++------ meta/srcgen.py | 47 ++++++++++++++++++++++++++++++++--- src/libcretonne/immediates.rs | 18 ++++++++++++++ 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index a92609c861..cd50b4ee8a 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -17,13 +17,31 @@ def collect_instr_groups(targets): def gen_opcodes(groups, out_dir): """Generate opcode enumerations.""" fmt = srcgen.Formatter() - fmt.line('enum Opcode {') - fmt.indent_push() - for g in groups: - for i in g.instructions: - fmt.line(i.camel_name + ',') - fmt.indent_pop() - fmt.line('}') + + fmt.doc_comment('An instruction opcode.') + fmt.doc_comment('') + fmt.doc_comment('All instructions from all supported targets are present.') + fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') + instrs = [] + with fmt.indented('pub enum Opcode {', '}'): + for g in groups: + for i in g.instructions: + instrs.append(i) + # Build a doc comment. + prefix = ', '.join(o.name for o in i.outs) + if prefix: + prefix = prefix + ' = ' + suffix = ', '.join(o.name for o in i.ins) + fmt.doc_comment('`{}{} {}`.'.format(prefix, i.name, suffix)) + # Enum variant itself. + fmt.line(i.camel_name + ',') + + with fmt.indented('impl Display for Opcode {', '}'): + with fmt.indented('fn fmt(&self, f: &mut Formatter) -> fmt::Result {', '}'): + with fmt.indented('f.write_str(match *self {', '})'): + for i in instrs: + fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) + fmt.update_file('opcodes.rs', out_dir) def generate(targets, out_dir): diff --git a/meta/srcgen.py b/meta/srcgen.py index 9778e2c518..125ae0a63a 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -25,15 +25,15 @@ class Formatter(object): >>> f.indent_push() >>> f.comment('Nested comment') >>> f.indent_pop() - >>> f.line('Back again') + >>> f.format('Back {} again', 'home') >>> f.writelines() Hello line 1 - // Nested comment - Back again + // Nested comment + Back home again """ - shiftwidth = 2 + shiftwidth = 4 def __init__(self): self.indent = '' @@ -64,10 +64,49 @@ class Formatter(object): with open(filename, 'w') as f: self.writelines(f) + class _IndentedScope(object): + def __init__(self, fmt, after): + self.fmt = fmt + self.after = after + + def __enter__(self): + self.fmt.indent_push(); + + def __exit__(self, t, v, tb): + self.fmt.indent_pop() + if self.after: + self.fmt.line(self.after) + + def indented(self, before=None, after=None): + """ + Return a scope object for use with a `with` statement: + + >>> f = Formatter() + >>> with f.indented('prefix {', '} suffix'): + ... f.line('hello') + >>> f.writelines() + prefix { + hello + } suffix + + The optional `before` and `after` parameters are surrounding lines + which are *not* indented. + """ + if before: + self.line(before) + return self._IndentedScope(self, after) + + def format(self, fmt, *args): + self.line(fmt.format(*args)) + def comment(self, s): """Add a comment line.""" self.line('// ' + s) + def doc_comment(self, s): + """Add a documentation comment line.""" + self.line('/// ' + s) + if __name__ == "__main__": import doctest doctest.testmod() diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index efd84cd606..0034139f50 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -8,6 +8,9 @@ use std::fmt::{self, Display, Formatter}; use std::mem; +// The `Opcode` enum is generated from the meta instruction descriptions. +include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); + /// 64-bit immediate integer operand. /// #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -41,11 +44,13 @@ impl Display for Imm64 { /// An IEEE binary32 immediate floating point value. /// /// All bit patterns are allowed. +#[derive(Copy, Clone, Debug)] pub struct Ieee32(f32); /// An IEEE binary64 immediate floating point value. /// /// All bit patterns are allowed. +#[derive(Copy, Clone, Debug)] pub struct Ieee64(f64); // Format a floating point number in a way that is reasonably human-readable, and that can be @@ -155,6 +160,19 @@ mod tests { use super::*; use std::{f32, f64}; + #[test] + fn opcodes() { + let x = Opcode::Iadd; + let mut y = Opcode::Isub; + + assert!(x != y); + y = Opcode::Iadd; + assert_eq!(x, y); + + assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); + assert_eq!(format!("{}", Opcode::IaddImm), "iadd_imm"); + } + #[test] fn format_imm64() { assert_eq!(format!("{}", Imm64(0)), "0"); From 3a570e8b2168364c1c1e97c485aff4fc89dc6aaa Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Apr 2016 11:09:36 -0700 Subject: [PATCH 041/968] Add repr.rs module containing the representation of functions. A function owns instructions and extended basic blocks. References to these entities are implemented as opaque structs indexing into the functions internal tables. This avoids fighting Rust's ownership checking and it also makes references 4 bytes on all platforms. SSA values are identified similarly, but with an optimization for the first value produced by an instruction. Very few instructions will produce more than one value, and there is an extended value table for those. --- src/libcretonne/lib.rs | 1 + src/libcretonne/repr.rs | 341 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 src/libcretonne/repr.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 9c837eb0f1..b67ca5df10 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -7,3 +7,4 @@ pub mod types; pub mod immediates; +pub mod repr; diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs new file mode 100644 index 0000000000..1318a0936a --- /dev/null +++ b/src/libcretonne/repr.rs @@ -0,0 +1,341 @@ + +//! Representation of Cretonne IL functions. + +use types::Type; +use immediates::*; +use std::fmt::{self, Display, Formatter, Write}; +use std::u32; + +// ====--------------------------------------------------------------------------------------====// +// +// Public data types. +// +// ====--------------------------------------------------------------------------------------====// + +/// An opaque reference to an extended basic block in a function. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Ebb(u32); + +/// A guaranteed invalid EBB reference. +pub const NO_EBB: Ebb = Ebb(u32::MAX); + +/// An opaque reference to an instruction in a function. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Inst(u32); + +/// A guaranteed invalid instruction reference. +pub const NO_INST: Inst = Inst(u32::MAX); + +/// An opaque reference to an SSA value. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Value(u32); + +/// A guaranteed invalid value reference. +pub const NO_VALUE: Value = Value(u32::MAX); + +/// A function. +/// +/// The `Function` struct owns all of its instructions and extended basic blocks, and it works as a +/// container for those objects by implementing both `Index` and `Index`. +/// +pub struct Function { + /// Data about all of the instructions in the function. The instructions in this vector is not + /// necessarily in program order. The `Inst` reference indexes into this vector. + instructions: Vec, + + /// Extended basic blocks in the function, not necessarily in program order. The `Ebb` + /// reference indexes into this vector. + extended_basic_blocks: Vec, + + /// Extended value table. Most `Value` references refer directly to their defining instruction. + /// Others index into this table. + extended_values: Vec, + + /// Return type(s). A function may return zero or more values. + pub return_types: Vec, +} + +/// Contents of an extended basic block. +pub struct EbbData { + /// Arguments for this extended basic block. These values dominate everything in the EBB. + /// All branches to this EBB must provide matching arguments, and the arguments to the entry + /// EBB must match the function arguments. + pub arguments: Vec, +} + +/// Contents on an instruction. +/// +/// Every variant must contain `opcode` and `ty` fields. An instruction that doesn't produce a +/// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at +/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a +/// `Box` to store the additional information out of line. +pub enum InstructionData { + Nullary { + opcode: Opcode, + ty: Type, + }, + Unary { + opcode: Opcode, + ty: Type, + arg: Value, + }, + UnaryImm { + opcode: Opcode, + ty: Type, + imm: Imm64, + }, + Binary { + opcode: Opcode, + ty: Type, + args: [Value; 2], + }, + BinaryImm { + opcode: Opcode, + ty: Type, + arg: Value, + imm: Imm64, + }, + Call { + opcode: Opcode, + ty: Type, + data: Box, + }, +} + +/// Payload of a call instruction. +pub struct CallData { + // Number of result values. + results: u8, + + // Dynamically sized array containing `results-1` result values (not including the first value) + // followed by the argument values. + values: Vec, +} + + +// ====--------------------------------------------------------------------------------------====// +// +// Extended basic block implementation. +// +// ====--------------------------------------------------------------------------------------====// + +impl Ebb { + fn new(index: usize) -> Ebb { + assert!(index < (u32::MAX as usize)); + Ebb(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display an `Ebb` reference as "ebb12". +impl Display for Ebb { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "ebb{}", self.0) + } +} + +impl EbbData { + fn new() -> EbbData { + EbbData { arguments: Vec::new() } + } +} + +// ====--------------------------------------------------------------------------------------====// +// +// Instruction implementation. +// +// ====--------------------------------------------------------------------------------------====// + +impl Inst { + fn new(index: usize) -> Inst { + assert!(index < (u32::MAX as usize)); + Inst(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display an `Inst` reference as "inst7". +impl Display for Inst { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "inst{}", self.0) + } +} + +// ====--------------------------------------------------------------------------------------====// +// +// Value implementation. +// +// ====--------------------------------------------------------------------------------------====// + +// Value references can either reference an instruction directly, or they can refer to the +// extended value table. +enum ExpandedValue { + // This is the first value produced by the referenced instruction. + Direct(Inst), + + // This value is described in the extended value table. + Table(usize), +} + +impl Value { + fn new_direct(i: Inst) -> Value { + let encoding = i.index() * 2; + assert!(encoding < u32::MAX as usize); + Value(encoding as u32) + } + + fn new_table(index: usize) -> Value { + let encoding = index * 2 + 1; + assert!(encoding < u32::MAX as usize); + Value(encoding as u32) + } + + // Expand the internal representation into something useful. + fn expand(&self) -> ExpandedValue { + use self::ExpandedValue::*; + let index = (self.0 / 2) as usize; + if self.0 % 2 == 0 { + Direct(Inst::new(index)) + } else { + Table(index) + } + } +} + +/// Display a `Value` reference as "v7" or "v2x". +impl Display for Value { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + use self::ExpandedValue::*; + match self.expand() { + Direct(i) => write!(fmt, "v{}", i.0), + Table(i) => write!(fmt, "v{}x", i), + } + } +} + +// Most values are simply the first value produced by an instruction. +// Other values have an entry in the value table. +enum ValueData { + // An unused entry in the value table. No instruction should be defining or using this value. + Unused, + + // Value is defined by an instruction, but it is not the first result. + Def { + ty: Type, + num: u8, + def: Inst, + }, + + // Value is an EBB argument. + Argument { + ty: Type, + num: u8, + ebb: Ebb, + }, +} + +impl InstructionData { + /// Get the opcode of this instruction. + pub fn opcode(&self) -> Opcode { + use self::InstructionData::*; + match *self { + Nullary { opcode, .. } => opcode, + Unary { opcode, .. } => opcode, + UnaryImm { opcode, .. } => opcode, + Binary { opcode, .. } => opcode, + BinaryImm { opcode, .. } => opcode, + Call { opcode, .. } => opcode, + } + } + + /// Type of the first result. + pub fn first_type(&self) -> Type { + use self::InstructionData::*; + match *self { + Nullary { ty, .. } => ty, + Unary { ty, .. } => ty, + UnaryImm { ty, .. } => ty, + Binary { ty, .. } => ty, + BinaryImm { ty, .. } => ty, + Call { ty, .. } => ty, + } + } +} + +impl Function { + /// Create a new empty function. + pub fn new() -> Function { + Function { + instructions: Vec::new(), + extended_basic_blocks: Vec::new(), + extended_values: Vec::new(), + return_types: Vec::new(), + } + } + + /// Resolve an instruction reference. + pub fn inst(&self, i: Inst) -> &InstructionData { + &self.instructions[i.0 as usize] + } + + /// Create a new instruction. + pub fn make_inst(&mut self, data: InstructionData) -> Inst { + let iref = Inst::new(self.instructions.len()); + self.instructions.push(data); + // FIXME: Allocate extended value table entries if needed. + iref + } + + /// Create a new basic block. + pub fn make_ebb(&mut self) -> Ebb { + let ebb = Ebb::new(self.extended_basic_blocks.len()); + self.extended_basic_blocks.push(EbbData::new()); + ebb + } + + /// Get the type of a value. + pub fn value_type(&self, v: Value) -> Type { + use self::ExpandedValue::*; + use self::ValueData::*; + match v.expand() { + Direct(i) => self.inst(i).first_type(), + Table(i) => { + match self.extended_values[i] { + Unused => panic!("Can't get type of Unused value {}", v), + Def { ty, .. } => ty, + Argument { ty, .. } => ty, + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use types; + use immediates::*; + + #[test] + fn make_inst() { + let mut func = Function::new(); + + let idata = InstructionData::Nullary { + opcode: Opcode::Iconst, + ty: types::I32, + }; + let inst = func.make_inst(idata); + assert_eq!(format!("{}", inst), "inst0"); + + // Immutable reference resolution. + let ins = func.inst(inst); + assert_eq!(ins.opcode(), Opcode::Iconst); + assert_eq!(ins.first_type(), types::I32); + } +} From 3dcd2f8e58f7cca639b0b4cae905db18c87f40d7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Apr 2016 13:49:35 -0700 Subject: [PATCH 042/968] Generate an opcode_name() function. This function returning a &'static str is more primitive that the Display implementation. It allows the opcode strings to be reused by the parser. --- meta/gen_instr.py | 10 +++++----- src/libcretonne/immediates.rs | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index cd50b4ee8a..b0fe4686f4 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -36,11 +36,11 @@ def gen_opcodes(groups, out_dir): # Enum variant itself. fmt.line(i.camel_name + ',') - with fmt.indented('impl Display for Opcode {', '}'): - with fmt.indented('fn fmt(&self, f: &mut Formatter) -> fmt::Result {', '}'): - with fmt.indented('f.write_str(match *self {', '})'): - for i in instrs: - fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) + # Generate a private opcode_name function. + with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): + with fmt.indented('match opc {', '}'): + for i in instrs: + fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) fmt.update_file('opcodes.rs', out_dir) diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 0034139f50..4b9adcc43a 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -8,9 +8,16 @@ use std::fmt::{self, Display, Formatter}; use std::mem; -// The `Opcode` enum is generated from the meta instruction descriptions. +// The `Opcode` enum and the `opcode_name` function are generated from the meta instruction +// descriptions. include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); +impl Display for Opcode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", opcode_name(*self)) + } +} + /// 64-bit immediate integer operand. /// #[derive(Copy, Clone, PartialEq, Eq, Debug)] From 24e0828d204e7bce1e44ca0a9f731283f7cf95ad Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Apr 2016 20:24:21 -0700 Subject: [PATCH 043/968] Generate a constant hash table for recognizing opcodes. Use a simple quadratically probed, open addressed hash table. We could use a parfect hash function, but it would take longer to compute in Python, and this is not in the critical path performancewise. --- meta/constant_hash.py | 76 +++++++++++++++++++++++++++++++++++ meta/gen_instr.py | 13 ++++++ src/libcretonne/immediates.rs | 55 ++++++++++++++++++++++++- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 meta/constant_hash.py diff --git a/meta/constant_hash.py b/meta/constant_hash.py new file mode 100644 index 0000000000..b1b6104231 --- /dev/null +++ b/meta/constant_hash.py @@ -0,0 +1,76 @@ +""" +Generate constant hash tables. + +The `constant_hash` module can generate constant pre-populated hash tables. We +don't attempt parfect hashing, but simply generate an open addressed +quadratically probed hash table. +""" + +def simple_hash(s): + """ + Compute a primitive hash of a string. + + Example: + >>> hex(simple_hash("Hello")) + '0x2fa70c01' + >>> hex(simple_hash("world")) + '0x5b0c31d5' + """ + h = 5381 + for c in s: + h = ((h ^ ord(c)) + ((h >> 6) + (h << 26))) & 0xffffffff + return h + +def next_power_of_two(x): + """ + Compute the next power of two that is greater than `x`: + >>> next_power_of_two(0) + 1 + >>> next_power_of_two(1) + 2 + >>> next_power_of_two(2) + 4 + >>> next_power_of_two(3) + 4 + >>> next_power_of_two(4) + 8 + """ + s = 1 + while x & (x + 1) != 0: + x |= x >> s + s *= 2 + return x + 1 + +def compute_quadratic(items, hash_function): + """ + Compute an open addressed, quadratically probed hash table containing + `items`. The returned table is a list containing the elements of the + iterable `items` and `None` in unused slots. + + :param items: Iterable set of items to place in hash table. + :param hash_function: Hash function which takes an item and returns a + number. + + Simple example (see hash values above, they collide on slot 1): + >>> compute_quadratic(['Hello', 'world'], simple_hash) + [None, 'Hello', 'world', None] + """ + + items = list(items) + # Table size must be a power of two. Aim for >20% unused slots. + size = next_power_of_two(int(1.20*len(items))) + table = [None] * size + + for i in items: + h = hash_function(i) % size + s = 0 + while table[h] is not None: + s += 1 + h = (h + s) % size + table[h] = i + + return table + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/meta/gen_instr.py b/meta/gen_instr.py index b0fe4686f4..eb1b0a361f 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -3,6 +3,7 @@ Generate sources with instruction info. """ import srcgen +import constant_hash def collect_instr_groups(targets): seen = set() @@ -24,6 +25,7 @@ def gen_opcodes(groups, out_dir): fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') instrs = [] with fmt.indented('pub enum Opcode {', '}'): + fmt.line('NotAnOpcode,') for g in groups: for i in g.instructions: instrs.append(i) @@ -39,9 +41,20 @@ def gen_opcodes(groups, out_dir): # Generate a private opcode_name function. with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): with fmt.indented('match opc {', '}'): + fmt.line('Opcode::NotAnOpcode => "",') for i in instrs: fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) + # Generate an opcode hash table for looking up opcodes by name. + hash_table = constant_hash.compute_quadratic(instrs, + lambda i: constant_hash.simple_hash(i.name)) + with fmt.indented('const OPCODE_HASH_TABLE: [Opcode; {}] = ['.format(len(hash_table)), '];'): + for i in hash_table: + if i is None: + fmt.line('Opcode::NotAnOpcode,') + else: + fmt.format('Opcode::{},', i.camel_name) + fmt.update_file('opcodes.rs', out_dir) def generate(targets, out_dir): diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 4b9adcc43a..911197c349 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -8,8 +8,12 @@ use std::fmt::{self, Display, Formatter}; use std::mem; -// The `Opcode` enum and the `opcode_name` function are generated from the meta instruction -// descriptions. +// Include code generated by `meta/gen_instr.py`. This file contains: +// +// - The `pub enum Opcode` definition with all known opcodes, +// - The private `fn opcode_name(Opcode) -> &'static str` function, and +// - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`. +// include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); impl Display for Opcode { @@ -18,6 +22,46 @@ impl Display for Opcode { } } +// A primitive hash function for matching opcodes. +// Must match `meta/constant_hash.py`. +fn simple_hash(s: &str) -> u32 { + let mut h: u32 = 5381; + for c in s.chars() { + h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); + } + h +} + +impl Opcode { + /// Parse an Opcode name from a string. + pub fn from_str(s: &str) -> Option { + let tlen = OPCODE_HASH_TABLE.len(); + assert!(tlen.is_power_of_two()); + let mut idx = simple_hash(s) as usize; + let mut step: usize = 0; + loop { + idx = idx % tlen; + let entry = OPCODE_HASH_TABLE[idx]; + + if entry == Opcode::NotAnOpcode { + return None; + } + + if *opcode_name(entry) == *s { + return Some(entry); + } + + // Quadratic probing. + step += 1; + // When `tlen` is a power of two, it can be proven that idx will visit all entries. + // This means that this loop will always terminate if the hash table has even one + // unused entry. + assert!(step < tlen); + idx += step; + } + } +} + /// 64-bit immediate integer operand. /// #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -178,6 +222,13 @@ mod tests { assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); assert_eq!(format!("{}", Opcode::IaddImm), "iadd_imm"); + + // Check the matcher. + assert_eq!(Opcode::from_str("iadd"), Some(Opcode::Iadd)); + assert_eq!(Opcode::from_str("iadd_imm"), Some(Opcode::IaddImm)); + assert_eq!(Opcode::from_str("iadd\0"), None); + assert_eq!(Opcode::from_str(""), None); + assert_eq!(Opcode::from_str("\0"), None); } #[test] From 66778dc23cbc9daeb2b7e7841c8de871b731b89c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Apr 2016 10:31:14 -0700 Subject: [PATCH 044/968] Typo. --- src/libcretonne/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index 38edc811d1..eccd757c7c 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The cwCretonneRust Project Developers"] +authors = ["The Cretonne Project Developers"] name = "cretonne" version = "0.0.0" build = "build.rs" From 49ae98a1e9f4b5f9d6cacecc092eb92262c0722a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Apr 2016 11:06:33 -0700 Subject: [PATCH 045/968] Implement std::str::FromStr for matching opcodes. Replace the home-grown from_str function. --- src/libcretonne/immediates.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 911197c349..79326d9a0b 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -7,6 +7,7 @@ use std::fmt::{self, Display, Formatter}; use std::mem; +use std::str::FromStr; // Include code generated by `meta/gen_instr.py`. This file contains: // @@ -32,9 +33,11 @@ fn simple_hash(s: &str) -> u32 { h } -impl Opcode { +impl FromStr for Opcode { + type Err = &'static str; + /// Parse an Opcode name from a string. - pub fn from_str(s: &str) -> Option { + fn from_str(s: &str) -> Result { let tlen = OPCODE_HASH_TABLE.len(); assert!(tlen.is_power_of_two()); let mut idx = simple_hash(s) as usize; @@ -44,11 +47,11 @@ impl Opcode { let entry = OPCODE_HASH_TABLE[idx]; if entry == Opcode::NotAnOpcode { - return None; + return Err("Unknown opcode"); } if *opcode_name(entry) == *s { - return Some(entry); + return Ok(entry); } // Quadratic probing. @@ -224,11 +227,11 @@ mod tests { assert_eq!(format!("{}", Opcode::IaddImm), "iadd_imm"); // Check the matcher. - assert_eq!(Opcode::from_str("iadd"), Some(Opcode::Iadd)); - assert_eq!(Opcode::from_str("iadd_imm"), Some(Opcode::IaddImm)); - assert_eq!(Opcode::from_str("iadd\0"), None); - assert_eq!(Opcode::from_str(""), None); - assert_eq!(Opcode::from_str("\0"), None); + assert_eq!("iadd".parse::(), Ok(Opcode::Iadd)); + assert_eq!("iadd_imm".parse::(), Ok(Opcode::IaddImm)); + assert_eq!("iadd\0".parse::(), Err("Unknown opcode")); + assert_eq!("".parse::(), Err("Unknown opcode")); + assert_eq!("\0".parse::(), Err("Unknown opcode")); } #[test] From 4f40a2ae797e018ca8b2a899c83a0081724b9dc4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Apr 2016 11:22:30 -0700 Subject: [PATCH 046/968] Add some scaffolding for building more crates. The src/tools directory contains the cretonne-tools crate which will build binaries for testing cretonne. The src/libctonfile directory contains the ctonfile library crate which provides reading and writing of .cton files. --- src/.gitignore | 2 +- src/libcretonne/Cargo.toml | 4 ++++ src/libctonfile/Cargo.toml | 12 ++++++++++++ src/libctonfile/lib.rs | 12 ++++++++++++ src/libctonfile/parser.rs | 2 ++ src/tools/Cargo.lock | 19 +++++++++++++++++++ src/tools/Cargo.toml | 14 ++++++++++++++ src/tools/main.rs | 5 +++++ 8 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/libctonfile/Cargo.toml create mode 100644 src/libctonfile/lib.rs create mode 100644 src/libctonfile/parser.rs create mode 100644 src/tools/Cargo.lock create mode 100644 src/tools/Cargo.toml create mode 100644 src/tools/main.rs diff --git a/src/.gitignore b/src/.gitignore index a9d37c560c..d6168f7c42 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,2 +1,2 @@ target -Cargo.lock +lib*/Cargo.lock diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index eccd757c7c..ba7edec6d9 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -2,6 +2,10 @@ authors = ["The Cretonne Project Developers"] name = "cretonne" version = "0.0.0" +description = "Low-level code generator library" +documentation = "https://cretonne.readthedocs.org/" +repository = "https://github.com/stoklund/cretonne" +publish = false build = "build.rs" [lib] diff --git a/src/libctonfile/Cargo.toml b/src/libctonfile/Cargo.toml new file mode 100644 index 0000000000..7b2826b072 --- /dev/null +++ b/src/libctonfile/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["The Cretonne Project Developers"] +name = "ctonfile" +version = "0.0.0" +publish = false + +[lib] +name = "ctonfile" +path = "lib.rs" + +[dependencies] +cretonne = { path = "../libcretonne" } diff --git a/src/libctonfile/lib.rs b/src/libctonfile/lib.rs new file mode 100644 index 0000000000..3271ad035c --- /dev/null +++ b/src/libctonfile/lib.rs @@ -0,0 +1,12 @@ + +// ====------------------------------------------------------------------------------------==== // +// +// Cretonne file read/write library +// +// ====------------------------------------------------------------------------------------==== // +// +// The libctonfile library supports reading and writing .cton files. This functionality is needed +// for testing Cretonne, but is not essential for a JIT compiler. + +extern crate cretonne; +pub mod parser; diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs new file mode 100644 index 0000000000..44cf3a2500 --- /dev/null +++ b/src/libctonfile/parser.rs @@ -0,0 +1,2 @@ + +use cretonne; diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock new file mode 100644 index 0000000000..90722ef5e7 --- /dev/null +++ b/src/tools/Cargo.lock @@ -0,0 +1,19 @@ +[root] +name = "cretonne-tools" +version = "0.0.0" +dependencies = [ + "cretonne 0.0.0", + "ctonfile 0.0.0", +] + +[[package]] +name = "cretonne" +version = "0.0.0" + +[[package]] +name = "ctonfile" +version = "0.0.0" +dependencies = [ + "cretonne 0.0.0", +] + diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml new file mode 100644 index 0000000000..b4477bd1e8 --- /dev/null +++ b/src/tools/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["The Cretonne Project Developers"] +name = "cretonne-tools" +version = "0.0.0" +description = "Binaries for testing the Cretonne library" +publish = false + +[[bin]] +name = "cretonne" +path = "main.rs" + +[dependencies] +cretonne = { path = "../libcretonne" } +ctonfile = { path = "../libctonfile" } diff --git a/src/tools/main.rs b/src/tools/main.rs new file mode 100644 index 0000000000..33cc1afe81 --- /dev/null +++ b/src/tools/main.rs @@ -0,0 +1,5 @@ + +extern crate cretonne; +extern crate ctonfile; + +fn main() {} From f1d43652ad1f4d2fc641aeadb52c43af0849bb3f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 27 Apr 2016 16:08:08 -0700 Subject: [PATCH 047/968] Migrate to readthedocs.io --- README.rst | 4 ++-- src/libcretonne/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 8e8105f83b..4e87f615e8 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ Cretonne Code Generator Cretonne is a low-level retargetable code generator. It translates a target-independent intermediate language into executable machine code. -.. image:: https://readthedocs.org/projects/cretonne/badge/?version=latest - :target: http://cretonne.readthedocs.org/en/latest/?badge=latest +.. image:: https://readthedocs.io/projects/cretonne/badge/?version=latest + :target: http://cretonne.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status Cretonne is designed to be a code generator for WebAssembly with these design diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index ba7edec6d9..e6158af94f 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -3,7 +3,7 @@ authors = ["The Cretonne Project Developers"] name = "cretonne" version = "0.0.0" description = "Low-level code generator library" -documentation = "https://cretonne.readthedocs.org/" +documentation = "https://cretonne.readthedocs.io/" repository = "https://github.com/stoklund/cretonne" publish = false build = "build.rs" From 6a1f74125fb641f3007d2fad96a241599efd94f9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 12 Apr 2016 14:53:57 -0700 Subject: [PATCH 048/968] Implement FromStr for Imm64, Ieee32, Ieee64. These are bitwise exact conversions from string to immediates, implementing the inverse of the Display trait. Only accept hexadecimal floating point numbers to avoid issues with rounding when converting decimal numbers to binary. --- src/libcretonne/immediates.rs | 493 +++++++++++++++++++++++++++++++++- 1 file changed, 480 insertions(+), 13 deletions(-) diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 79326d9a0b..4fefdbb9d0 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -70,6 +70,12 @@ impl FromStr for Opcode { #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Imm64(i64); +impl Imm64 { + pub fn from_bits(x: u64) -> Imm64 { + Imm64(x as i64) + } +} + impl Display for Imm64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let x = self.0; @@ -94,6 +100,80 @@ impl Display for Imm64 { } } +impl FromStr for Imm64 { + type Err = &'static str; + + // Parse a decimal or hexadecimal Imm64, formatted as above. + fn from_str(s: &str) -> Result { + let mut value: u64 = 0; + let mut digits = 0; + let negative = s.starts_with('-'); + let s2 = if negative { + &s[1..] + } else { + s + }; + + if s2.starts_with("0x") { + // Hexadecimal. + for ch in s2[2..].chars() { + match ch.to_digit(16) { + Some(digit) => { + digits += 1; + if digits > 16 { + return Err("Too many hexadecimal digits in Imm64"); + } + // This can't overflow given the digit limit. + value = (value << 4) | digit as u64; + } + None => { + // Allow embedded underscores, but fail on anything else. + if ch != '_' { + return Err("Invalid character in hexadecimal Imm64"); + } + } + } + } + } else { + // Decimal number, possibly negative. + for ch in s2.chars() { + match ch.to_digit(16) { + Some(digit) => { + digits += 1; + match value.checked_mul(10) { + None => return Err("Too large decimal Imm64"), + Some(v) => value = v, + } + match value.checked_add(digit as u64) { + None => return Err("Too large decimal Imm64"), + Some(v) => value = v, + } + } + None => { + // Allow embedded underscores, but fail on anything else. + if ch != '_' { + return Err("Invalid character in decimal Imm64"); + } + } + } + } + } + + if digits == 0 { + return Err("No digits in Imm64"); + } + + // We support the range-and-a-half from -2^63 .. 2^64-1. + if negative { + value = value.wrapping_neg(); + // Don't allow large negative values to wrap around and become positive. + if value as i64 > 0 { + return Err("Negative number too small for Imm64"); + } + } + Ok(Imm64::from_bits(value)) + } +} /// An IEEE binary32 immediate floating point value. /// @@ -118,8 +198,9 @@ pub struct Ieee64(f64); // t - trailing significand field width in bits // fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { - assert!(w > 0 && w <= 16, "Invalid exponent range"); - assert!(1 + w + t <= 64, "Too large IEEE format for u64"); + debug_assert!(w > 0 && w <= 16, "Invalid exponent range"); + debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64"); + debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size"); let max_e_bits = (1u64 << w) - 1; let t_bits = bits & ((1u64 << t) - 1); // Trailing significand. @@ -173,13 +254,173 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { } } +// Parse a float using the same format as `format_float` above. +// +// The encoding parameters are: +// +// w - exponent field width in bits +// t - trailing significand field width in bits +// +fn parse_float(s: &str, w: u8, t: u8) -> Result { + debug_assert!(w > 0 && w <= 16, "Invalid exponent range"); + debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64"); + debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size"); + + let (sign_bit, s2) = if s.starts_with('-') { + (1u64 << t + w, &s[1..]) + } else { + (0, s) + }; + + if !s2.starts_with("0x") { + let max_e_bits = ((1u64 << w) - 1) << t; + let quiet_bit = 1u64 << (t - 1); + + // The only decimal encoding allowed is 0. + if s2 == "0.0" { + return Ok(sign_bit); + } + + if s2 == "Inf" { + // +/- infinity: e = max, t = 0. + return Ok(sign_bit | max_e_bits); + } + if s2 == "NaN" { + // Canonical quiet NaN: e = max, t = quiet. + return Ok(sign_bit | max_e_bits | quiet_bit); + } + if s2.starts_with("NaN:0x") { + // Quiet NaN with payload. + return match u64::from_str_radix(&s2[6..], 16) { + Ok(payload) if payload < quiet_bit => { + Ok(sign_bit | max_e_bits | quiet_bit | payload) + } + _ => Err("Invalid NaN payload"), + }; + } + if s2.starts_with("sNaN:0x") { + // Signaling NaN with payload. + return match u64::from_str_radix(&s2[7..], 16) { + Ok(payload) if 0 < payload && payload < quiet_bit => { + Ok(sign_bit | max_e_bits | payload) + } + _ => Err("Invalid sNaN payload"), + }; + } + + return Err("Float must be hexadecimal"); + } + let s3 = &s2[2..]; + + let mut digits = 0u8; + let mut digits_before_period: Option = None; + let mut significand = 0u64; + let mut exponent = 0i32; + + for (idx, ch) in s3.char_indices() { + match ch { + '.' => { + // This is the radix point. There can only be one. + if digits_before_period != None { + return Err("Multiple radix points"); + } else { + digits_before_period = Some(digits); + } + } + 'p' => { + // The following exponent is a decimal number. + let exp_str = &s3[1 + idx..]; + match exp_str.parse::() { + Ok(e) => { + exponent = e as i32; + break; + } + Err(_) => return Err("Bad exponent"), + } + } + _ => { + match ch.to_digit(16) { + Some(digit) => { + digits += 1; + if digits > 16 { + return Err("Too many digits"); + } + significand = (significand << 4) | digit as u64; + } + None => return Err("Invalid character"), + } + } + + } + } + + if digits == 0 { + return Err("No digits"); + } + + if significand == 0 { + // This is +/- 0.0. + return Ok(sign_bit); + } + + // Number of bits appearing after the radix point. + match digits_before_period { + None => {} // No radix point present. + Some(d) => exponent -= 4 * (digits - d) as i32, + }; + + // Normalize the significand and exponent. + let significant_bits = (64 - significand.leading_zeros()) as u8; + if significant_bits > t + 1 { + let adjust = significant_bits - (t + 1); + if significand & ((1u64 << adjust) - 1) != 0 { + return Err("Too many significant bits"); + } + // Adjust significand down. + significand >>= adjust; + exponent += adjust as i32; + } else { + let adjust = t + 1 - significant_bits; + significand <<= adjust; + exponent -= adjust as i32; + } + assert_eq!(significand >> t, 1); + + // Trailing significand excludes the high bit. + let t_bits = significand & ((1 << t) - 1); + + let max_exp = (1i32 << w) - 2; + let bias: i32 = (1 << (w - 1)) - 1; + exponent += bias + t as i32; + + if exponent > max_exp { + Err("Magnitude too large") + } else if exponent > 0 { + // This is a normal number. + let e_bits = (exponent as u64) << t; + Ok(sign_bit | e_bits | t_bits) + } else if 1 - exponent <= t as i32 { + // This is a subnormal number: e = 0, t = significand bits. + // Renormalize significand for exponent = 1. + let adjust = 1 - exponent; + if significand & ((1u64 << adjust) - 1) != 0 { + Err("Subnormal underflow") + } else { + significand >>= adjust; + Ok(sign_bit | significand) + } + } else { + Err("Magnitude too small") + } +} + impl Ieee32 { pub fn new(x: f32) -> Ieee32 { Ieee32(x) } /// Construct Ieee32 immediate from raw bits. - pub fn new_from_bits(x: u32) -> Ieee32 { + pub fn from_bits(x: u32) -> Ieee32 { Ieee32(unsafe { mem::transmute(x) }) } } @@ -191,13 +432,24 @@ impl Display for Ieee32 { } } +impl FromStr for Ieee32 { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match parse_float(s, 8, 23) { + Ok(b) => Ok(Ieee32::from_bits(b as u32)), + Err(s) => Err(s), + } + } +} + impl Ieee64 { pub fn new(x: f64) -> Ieee64 { Ieee64(x) } /// Construct Ieee64 immediate from raw bits. - pub fn new_from_bits(x: u64) -> Ieee64 { + pub fn from_bits(x: u64) -> Ieee64 { Ieee64(unsafe { mem::transmute(x) }) } } @@ -209,10 +461,23 @@ impl Display for Ieee64 { } } +impl FromStr for Ieee64 { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match parse_float(s, 11, 52) { + Ok(b) => Ok(Ieee64::from_bits(b)), + Err(s) => Err(s), + } + } +} + #[cfg(test)] mod tests { use super::*; use std::{f32, f64}; + use std::str::FromStr; + use std::fmt::Display; #[test] fn opcodes() { @@ -245,6 +510,74 @@ mod tests { assert_eq!(format!("{}", Imm64(0x10000)), "0x0001_0000"); } + // Verify that `text` can be parsed as a `T` into a value that displays as `want`. + fn parse_ok(text: &str, want: &str) + where ::Err: Display + { + match text.parse::() { + Err(s) => panic!("\"{}\".parse() error: {}", text, s), + Ok(x) => assert_eq!(format!("{}", x), want), + } + } + + // Verify that `text` fails to parse as `T` with the error `msg`. + fn parse_err(text: &str, msg: &str) + where ::Err: Display + { + match text.parse::() { + Err(s) => assert_eq!(format!("{}", s), msg), + Ok(x) => panic!("Wanted Err({}), but got {}", msg, x), + } + } + + #[test] + fn parse_imm64() { + parse_ok::("0", "0"); + parse_ok::("1", "1"); + parse_ok::("-0", "0"); + parse_ok::("-1", "-1"); + parse_ok::("0x0", "0"); + parse_ok::("0xf", "15"); + parse_ok::("-0x9", "-9"); + + // Probe limits. + parse_ok::("0xffffffff_ffffffff", "-1"); + parse_ok::("0x80000000_00000000", "0x8000_0000_0000_0000"); + parse_ok::("-0x80000000_00000000", "0x8000_0000_0000_0000"); + parse_err::("-0x80000000_00000001", + "Negative number too small for Imm64"); + parse_ok::("18446744073709551615", "-1"); + parse_ok::("-9223372036854775808", "0x8000_0000_0000_0000"); + // Overflow both the checked_add and checked_mul. + parse_err::("18446744073709551616", "Too large decimal Imm64"); + parse_err::("184467440737095516100", "Too large decimal Imm64"); + parse_err::("-9223372036854775809", + "Negative number too small for Imm64"); + + // Underscores are allowed where digits go. + parse_ok::("0_0", "0"); + parse_ok::("-_10_0", "-100"); + parse_ok::("_10_", "10"); + parse_ok::("0x97_88_bb", "0x0097_88bb"); + parse_ok::("0x_97_", "151"); + + parse_err::("", "No digits in Imm64"); + parse_err::("-", "No digits in Imm64"); + parse_err::("_", "No digits in Imm64"); + parse_err::("0x", "No digits in Imm64"); + parse_err::("0x_", "No digits in Imm64"); + parse_err::("-0x", "No digits in Imm64"); + parse_err::(" ", "Invalid character in decimal Imm64"); + parse_err::("0 ", "Invalid character in decimal Imm64"); + parse_err::(" 0", "Invalid character in decimal Imm64"); + parse_err::("--", "Invalid character in decimal Imm64"); + parse_err::("-0x-", "Invalid character in hexadecimal Imm64"); + + // Hex count overflow. + parse_err::("0x0_0000_0000_0000_0000", + "Too many hexadecimal digits in Imm64"); + } + #[test] fn format_ieee32() { assert_eq!(format!("{}", Ieee32::new(0.0)), "0.0"); @@ -268,15 +601,81 @@ mod tests { assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "NaN"); assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "NaN:0x1"); - assert_eq!(format!("{}", Ieee32::new_from_bits(0x7ff00001)), - "NaN:0x300001"); + assert_eq!(format!("{}", Ieee32::from_bits(0x7fc00001)), "NaN:0x1"); + assert_eq!(format!("{}", Ieee32::from_bits(0x7ff00001)), "NaN:0x300001"); // Signaling NaNs. - assert_eq!(format!("{}", Ieee32::new_from_bits(0x7f800001)), "sNaN:0x1"); - assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fa00001)), + assert_eq!(format!("{}", Ieee32::from_bits(0x7f800001)), "sNaN:0x1"); + assert_eq!(format!("{}", Ieee32::from_bits(0x7fa00001)), "sNaN:0x200001"); } + #[test] + fn parse_ieee32() { + parse_ok::("0.0", "0.0"); + parse_ok::("-0.0", "-0.0"); + parse_ok::("0x0", "0.0"); + parse_ok::("0x0.0", "0.0"); + parse_ok::("0x.0", "0.0"); + parse_ok::("0x0.", "0.0"); + parse_ok::("0x1", "0x1.000000p0"); + parse_ok::("-0x1", "-0x1.000000p0"); + parse_ok::("0x10", "0x1.000000p4"); + parse_ok::("0x10.0", "0x1.000000p4"); + parse_err::("0.", "Float must be hexadecimal"); + parse_err::(".0", "Float must be hexadecimal"); + parse_err::("0", "Float must be hexadecimal"); + parse_err::("-0", "Float must be hexadecimal"); + parse_err::(".", "Float must be hexadecimal"); + parse_err::("", "Float must be hexadecimal"); + parse_err::("-", "Float must be hexadecimal"); + parse_err::("0x", "No digits"); + parse_err::("0x..", "Multiple radix points"); + + // Check significant bits. + parse_ok::("0x0.ffffff", "0x1.fffffep-1"); + parse_ok::("0x1.fffffe", "0x1.fffffep0"); + parse_ok::("0x3.fffffc", "0x1.fffffep1"); + parse_ok::("0x7.fffff8", "0x1.fffffep2"); + parse_ok::("0xf.fffff0", "0x1.fffffep3"); + parse_err::("0x1.ffffff", "Too many significant bits"); + parse_err::("0x1.fffffe0000000000", "Too many digits"); + + // Exponents. + parse_ok::("0x1p3", "0x1.000000p3"); + parse_ok::("0x1p-3", "0x1.000000p-3"); + parse_ok::("0x1.0p3", "0x1.000000p3"); + parse_ok::("0x2.0p3", "0x1.000000p4"); + parse_ok::("0x1.0p127", "0x1.000000p127"); + parse_ok::("0x1.0p-126", "0x1.000000p-126"); + parse_ok::("0x0.1p-122", "0x1.000000p-126"); + parse_err::("0x2.0p127", "Magnitude too large"); + + // Subnormals. + parse_ok::("0x1.0p-127", "0x0.800000p-126"); + parse_ok::("0x1.0p-149", "0x0.000002p-126"); + parse_ok::("0x0.000002p-126", "0x0.000002p-126"); + parse_err::("0x0.100001p-126", "Subnormal underflow"); + parse_err::("0x1.8p-149", "Subnormal underflow"); + parse_err::("0x1.0p-150", "Magnitude too small"); + + // NaNs and Infs. + parse_ok::("Inf", "Inf"); + parse_ok::("-Inf", "-Inf"); + parse_ok::("NaN", "NaN"); + parse_ok::("-NaN", "-NaN"); + parse_ok::("NaN:0x0", "NaN"); + parse_err::("NaN:", "Float must be hexadecimal"); + parse_err::("NaN:0", "Float must be hexadecimal"); + parse_err::("NaN:0x", "Invalid NaN payload"); + parse_ok::("NaN:0x000001", "NaN:0x1"); + parse_ok::("NaN:0x300001", "NaN:0x300001"); + parse_err::("NaN:0x400001", "Invalid NaN payload"); + parse_ok::("sNaN:0x1", "sNaN:0x1"); + parse_err::("sNaN:0x0", "Invalid sNaN payload"); + parse_ok::("sNaN:0x200001", "sNaN:0x200001"); + parse_err::("sNaN:0x400001", "Invalid sNaN payload"); + } + #[test] fn format_ieee64() { assert_eq!(format!("{}", Ieee64::new(0.0)), "0.0"); @@ -303,14 +702,82 @@ mod tests { assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "NaN"); assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff8000000000001)), + assert_eq!(format!("{}", Ieee64::from_bits(0x7ff8000000000001)), "NaN:0x1"); - assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ffc000000000001)), + assert_eq!(format!("{}", Ieee64::from_bits(0x7ffc000000000001)), "NaN:0x4000000000001"); // Signaling NaNs. - assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff0000000000001)), + assert_eq!(format!("{}", Ieee64::from_bits(0x7ff0000000000001)), "sNaN:0x1"); - assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff4000000000001)), + assert_eq!(format!("{}", Ieee64::from_bits(0x7ff4000000000001)), "sNaN:0x4000000000001"); } + + #[test] + fn parse_ieee64() { + parse_ok::("0.0", "0.0"); + parse_ok::("-0.0", "-0.0"); + parse_ok::("0x0", "0.0"); + parse_ok::("0x0.0", "0.0"); + parse_ok::("0x.0", "0.0"); + parse_ok::("0x0.", "0.0"); + parse_ok::("0x1", "0x1.0000000000000p0"); + parse_ok::("-0x1", "-0x1.0000000000000p0"); + parse_ok::("0x10", "0x1.0000000000000p4"); + parse_ok::("0x10.0", "0x1.0000000000000p4"); + parse_err::("0.", "Float must be hexadecimal"); + parse_err::(".0", "Float must be hexadecimal"); + parse_err::("0", "Float must be hexadecimal"); + parse_err::("-0", "Float must be hexadecimal"); + parse_err::(".", "Float must be hexadecimal"); + parse_err::("", "Float must be hexadecimal"); + parse_err::("-", "Float must be hexadecimal"); + parse_err::("0x", "No digits"); + parse_err::("0x..", "Multiple radix points"); + + // Check significant bits. + parse_ok::("0x0.fffffffffffff8", "0x1.fffffffffffffp-1"); + parse_ok::("0x1.fffffffffffff", "0x1.fffffffffffffp0"); + parse_ok::("0x3.ffffffffffffe", "0x1.fffffffffffffp1"); + parse_ok::("0x7.ffffffffffffc", "0x1.fffffffffffffp2"); + parse_ok::("0xf.ffffffffffff8", "0x1.fffffffffffffp3"); + parse_err::("0x3.fffffffffffff", "Too many significant bits"); + parse_err::("0x001.fffffe00000000", "Too many digits"); + + // Exponents. + parse_ok::("0x1p3", "0x1.0000000000000p3"); + parse_ok::("0x1p-3", "0x1.0000000000000p-3"); + parse_ok::("0x1.0p3", "0x1.0000000000000p3"); + parse_ok::("0x2.0p3", "0x1.0000000000000p4"); + parse_ok::("0x1.0p1023", "0x1.0000000000000p1023"); + parse_ok::("0x1.0p-1022", "0x1.0000000000000p-1022"); + parse_ok::("0x0.1p-1018", "0x1.0000000000000p-1022"); + parse_err::("0x2.0p1023", "Magnitude too large"); + + // Subnormals. + parse_ok::("0x1.0p-1023", "0x0.8000000000000p-1022"); + parse_ok::("0x1.0p-1074", "0x0.0000000000001p-1022"); + parse_ok::("0x0.0000000000001p-1022", "0x0.0000000000001p-1022"); + parse_err::("0x0.10000000000008p-1022", "Subnormal underflow"); + parse_err::("0x1.8p-1074", "Subnormal underflow"); + parse_err::("0x1.0p-1075", "Magnitude too small"); + + // NaNs and Infs. + parse_ok::("Inf", "Inf"); + parse_ok::("-Inf", "-Inf"); + parse_ok::("NaN", "NaN"); + parse_ok::("-NaN", "-NaN"); + parse_ok::("NaN:0x0", "NaN"); + parse_err::("NaN:", "Float must be hexadecimal"); + parse_err::("NaN:0", "Float must be hexadecimal"); + parse_err::("NaN:0x", "Invalid NaN payload"); + parse_ok::("NaN:0x000001", "NaN:0x1"); + parse_ok::("NaN:0x4000000000001", "NaN:0x4000000000001"); + parse_err::("NaN:0x8000000000001", "Invalid NaN payload"); + parse_ok::("sNaN:0x1", "sNaN:0x1"); + parse_err::("sNaN:0x0", "Invalid sNaN payload"); + parse_ok::("sNaN:0x4000000000001", "sNaN:0x4000000000001"); + parse_err::("sNaN:0x8000000000001", "Invalid sNaN payload"); + } + } From 04d3b10564de43741fc6af1e0cfdfd47cf0cd961 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 27 Apr 2016 14:25:54 -0700 Subject: [PATCH 049/968] Add function signatures. Describe function argument and return value types along with flags for passing values in an ABI-compliant way. --- src/libcretonne/types.rs | 131 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 88a2b6cad2..6c8fb90d90 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -3,6 +3,12 @@ use std::fmt::{self, Display, Formatter, Write}; +// ====--------------------------------------------------------------------------------------====// +// +// Value types +// +// ====--------------------------------------------------------------------------------------====// + /// The type of an SSA value. /// /// The `VOID` type is only used for instructions that produce no value. It can't be part of a SIMD @@ -177,6 +183,107 @@ impl Display for Type { } } +// ====--------------------------------------------------------------------------------------====// +// +// Function signatures +// +// ====--------------------------------------------------------------------------------------====// + +/// Function argument extension options. +/// +/// On some architectures, small integer function arguments are extended to the width of a +/// general-purpose register. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ArgumentExtension { + /// No extension, high bits are indeterminate. + None, + /// Unsigned extension: high bits in register are 0. + Uext, + /// Signed extension: high bits in register replicate sign bit. + Sext, +} + +/// Function argument or return value type. +/// +/// This describes the value type being passed to or from a function along with flags that affect +/// how the argument is passed. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct ArgumentType { + pub value_type: Type, + pub extension: ArgumentExtension, + /// Place this argument in a register if possible. + pub inreg: bool, +} + +/// Function signature. +/// +/// The function signature describes the types of arguments and return values along with other +/// details that are needed to call a function correctly. +pub struct Signature { + pub argument_types: Vec, + pub return_types: Vec, +} + +impl ArgumentType { + pub fn new(vt: Type) -> ArgumentType { + ArgumentType { + value_type: vt, + extension: ArgumentExtension::None, + inreg: false, + } + } +} + +impl Display for ArgumentType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + try!(write!(f, "{}", self.value_type)); + match self.extension { + ArgumentExtension::None => {} + ArgumentExtension::Uext => try!(write!(f, " uext")), + ArgumentExtension::Sext => try!(write!(f, " sext")), + } + if self.inreg { + try!(write!(f, " inreg")); + } + Ok(()) + } +} + +impl Signature { + pub fn new() -> Signature { + Signature { + argument_types: Vec::new(), + return_types: Vec::new(), + } + } +} + +fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { + match args.split_first() { + None => {} + Some((first, rest)) => { + try!(write!(f, "{}", first)); + for arg in rest { + try!(write!(f, ", {}", arg)); + } + } + } + Ok(()) +} + +impl Display for Signature { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + try!(write!(f, "(")); + try!(write_list(f, &self.argument_types)); + try!(write!(f, ")")); + if !self.return_types.is_empty() { + try!(write!(f, " -> ")); + try!(write_list(f, &self.return_types)); + } + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -248,4 +355,28 @@ mod tests { assert_eq!(format!("{}", I8.by(64)), "i8x64"); assert_eq!(format!("{}", F64.by(2)), "f64x2"); } + + #[test] + fn argument_type() { + let mut t = ArgumentType::new(I32); + assert_eq!(format!("{}", t), "i32"); + t.extension = ArgumentExtension::Uext; + assert_eq!(format!("{}", t), "i32 uext"); + t.inreg = true; + assert_eq!(format!("{}", t), "i32 uext inreg"); + } + + #[test] + fn signatures() { + let mut sig = Signature::new(); + assert_eq!(format!("{}", sig), "()"); + sig.argument_types.push(ArgumentType::new(I32)); + assert_eq!(format!("{}", sig), "(i32)"); + sig.return_types.push(ArgumentType::new(F32)); + assert_eq!(format!("{}", sig), "(i32) -> f32"); + sig.argument_types.push(ArgumentType::new(I32.by(4))); + assert_eq!(format!("{}", sig), "(i32, i32x4) -> f32"); + sig.return_types.push(ArgumentType::new(B8)); + assert_eq!(format!("{}", sig), "(i32, i32x4) -> f32, b8"); + } } From 9e00ce5081b2cf77353c3869a71f195ea25dcaf9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 27 Apr 2016 14:51:16 -0700 Subject: [PATCH 050/968] Type::by() returns an Optional. Don't use assertions to enforce the limits on SIMD lanes in a type, Type is too fundamental for that. Instead, the Vector-forming by() method returns an Optional, and None if the requested SIMD vector is not valid. Same for Type::half_vector(). --- src/libcretonne/types.rs | 53 ++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 6c8fb90d90..0aaa30841b 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -147,21 +147,26 @@ impl Type { /// /// If this is already a SIMD vector type, this produces a SIMD vector type with `n * /// self.lane_count()` lanes. - pub fn by(self, n: u16) -> Type { - debug_assert!(self.lane_bits() > 0, - "Can't make SIMD vectors with void lanes."); - debug_assert!(n.is_power_of_two(), - "Number of SIMD lanes must be a power of two"); + pub fn by(self, n: u16) -> Option { + if self.lane_bits() == 0 || !n.is_power_of_two() { + return None; + } let log2_lanes: u32 = n.trailing_zeros(); let new_type = self.0 as u32 + (log2_lanes << 4); - assert!(new_type < 0x90, "No more than 256 SIMD lanes supported"); - Type(new_type as u8) + if new_type < 0x90 { + Some(Type(new_type as u8)) + } else { + None + } } /// Get a SIMD vector with half the number of lanes. - pub fn half_vector(self) -> Type { - assert!(!self.is_scalar(), "Expecting a proper SIMD vector type."); - Type(self.0 - 0x10) + pub fn half_vector(self) -> Option { + if self.is_scalar() { + None + } else { + Some(Type(self.0 - 0x10)) + } } } @@ -320,13 +325,16 @@ mod tests { #[test] fn vectors() { - let big = F64.by(256); + let big = F64.by(256).unwrap(); assert_eq!(big.lane_bits(), 64); assert_eq!(big.lane_count(), 256); assert_eq!(big.bits(), 64 * 256); - assert_eq!(format!("{}", big.half_vector()), "f64x128"); - assert_eq!(format!("{}", B1.by(2).half_vector()), "b1"); + assert_eq!(format!("{}", big.half_vector().unwrap()), "f64x128"); + assert_eq!(format!("{}", B1.by(2).unwrap().half_vector().unwrap()), + "b1"); + assert_eq!(I32.half_vector(), None); + assert_eq!(VOID.half_vector(), None); } #[test] @@ -347,13 +355,16 @@ mod tests { #[test] fn format_vectors() { - assert_eq!(format!("{}", B1.by(8)), "b1x8"); - assert_eq!(format!("{}", B8.by(1)), "b8"); - assert_eq!(format!("{}", B16.by(256)), "b16x256"); - assert_eq!(format!("{}", B32.by(4).by(2)), "b32x8"); - assert_eq!(format!("{}", B64.by(8)), "b64x8"); - assert_eq!(format!("{}", I8.by(64)), "i8x64"); - assert_eq!(format!("{}", F64.by(2)), "f64x2"); + assert_eq!(format!("{}", B1.by(8).unwrap()), "b1x8"); + assert_eq!(format!("{}", B8.by(1).unwrap()), "b8"); + assert_eq!(format!("{}", B16.by(256).unwrap()), "b16x256"); + assert_eq!(format!("{}", B32.by(4).unwrap().by(2).unwrap()), "b32x8"); + assert_eq!(format!("{}", B64.by(8).unwrap()), "b64x8"); + assert_eq!(format!("{}", I8.by(64).unwrap()), "i8x64"); + assert_eq!(format!("{}", F64.by(2).unwrap()), "f64x2"); + assert_eq!(I8.by(3), None); + assert_eq!(I8.by(512), None); + assert_eq!(VOID.by(4), None); } #[test] @@ -374,7 +385,7 @@ mod tests { assert_eq!(format!("{}", sig), "(i32)"); sig.return_types.push(ArgumentType::new(F32)); assert_eq!(format!("{}", sig), "(i32) -> f32"); - sig.argument_types.push(ArgumentType::new(I32.by(4))); + sig.argument_types.push(ArgumentType::new(I32.by(4).unwrap())); assert_eq!(format!("{}", sig), "(i32, i32x4) -> f32"); sig.return_types.push(ArgumentType::new(B8)); assert_eq!(format!("{}", sig), "(i32, i32x4) -> f32, b8"); From ab74770ffeda58cc20bc9855e2b1bfd2f92626b2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 11 Apr 2016 16:41:00 -0700 Subject: [PATCH 051/968] Begin lexer implementation. Add a lexer module which implements the lexical analysis of .cton files. --- src/libctonfile/lexer.rs | 425 +++++++++++++++++++++++++++++++++++++++ src/libctonfile/lib.rs | 6 +- 2 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 src/libctonfile/lexer.rs diff --git a/src/libctonfile/lexer.rs b/src/libctonfile/lexer.rs new file mode 100644 index 0000000000..69d9c98580 --- /dev/null +++ b/src/libctonfile/lexer.rs @@ -0,0 +1,425 @@ + +// ====--------------------------------------------------------------------------------------====// +// +// Lexical analysis for .cton files. +// +// ====--------------------------------------------------------------------------------------====// + +use std::str::CharIndices; + +/// The location of a `Token` or `Error`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Location { + pub line_number: usize, +} + +/// A Token returned from the `Lexer`. +/// +/// Some variants may contains references to the original source text, so the `Token` has the same +/// lifetime as the source. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Token<'a> { + Comment(&'a str), + LPar, // '(' + RPar, // ')' + LBrace, // '{' + RBrace, // '}' + Comma, // ',' + Dot, // '.' + Colon, // ':' + Equal, // '=' + Arrow, // '->' + Function, // 'function' + Entry, // 'entry' + Float(&'a str), // Floating point immediate + Integer(&'a str), // Integer immediate + ValueDirect(u32), // v12 + ValueExtended(u32), // vx7 + Ebb(u32), // ebb3 + StackSlot(u32), // ss3 + Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) +} + +/// A `Token` with an associated location. +#[derive(Debug, PartialEq, Eq)] +pub struct LocatedToken<'a> { + pub token: Token<'a>, + pub location: Location, +} + +/// Wrap up a `Token` with the given location. +fn token<'a>(token: Token<'a>, loc: Location) -> Result, LocatedError> { + Ok(LocatedToken { + token: token, + location: loc, + }) +} + +/// An error from the lexical analysis. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + InvalidChar, +} + +/// An `Error` with an associated Location. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LocatedError { + pub error: Error, + pub location: Location, +} + +/// Wrap up an `Error` with the given location. +fn error<'a>(error: Error, loc: Location) -> Result, LocatedError> { + Err(LocatedError { + error: error, + location: loc, + }) +} + +/// Lexical analysis. +/// +/// A `Lexer` reads text from a `&str` and provides a sequence of tokens. +/// +/// Also keep track of a line number for error reporting. +/// +pub struct Lexer<'a> { + // Complete source being processed. + source: &'a str, + + // Iterator into `source`. + chars: CharIndices<'a>, + + // Next character to be processed, or `None` at the end. + lookahead: Option, + + // Index into `source` of lookahead character. + pos: usize, + + // Current line number. + line_number: usize, +} + +impl<'a> Lexer<'a> { + pub fn new(s: &'a str) -> Lexer { + let mut lex = Lexer { + source: s, + chars: s.char_indices(), + lookahead: None, + pos: 0, + line_number: 1, + }; + // Advance to the first char. + lex.next_ch(); + lex + } + + // Advance to the next character. + // Return the next lookahead character, or None when the end is encountered. + // Always update cur_ch to reflect + fn next_ch(&mut self) -> Option { + if self.lookahead == Some('\n') { + self.line_number += 1; + } + match self.chars.next() { + Some((idx, ch)) => { + self.pos = idx; + self.lookahead = Some(ch); + } + None => { + self.pos = self.source.len(); + self.lookahead = None; + } + } + self.lookahead + } + + // Get the location corresponding to `lookahead`. + fn loc(&self) -> Location { + Location { line_number: self.line_number } + } + + // Starting from `lookahead`, are we looking at `prefix`? + fn looking_at(&self, prefix: &str) -> bool { + self.source[self.pos..].starts_with(prefix) + } + + // Scan a single-char token. + fn scan_char(&mut self, tok: Token<'a>) -> Result, LocatedError> { + assert!(self.lookahead != None); + let loc = self.loc(); + self.next_ch(); + token(tok, loc) + } + + // Scan a multi-char token. + fn scan_chars(&mut self, + count: usize, + tok: Token<'a>) + -> Result, LocatedError> { + let loc = self.loc(); + for _ in 0..count { + assert!(self.lookahead != None); + self.next_ch(); + } + token(tok, loc) + } + + // Scan a comment extending to the end of the current line. + fn scan_comment(&mut self) -> Result, LocatedError> { + let begin = self.pos; + let loc = self.loc(); + loop { + match self.next_ch() { + None | Some('\n') => { + let text = &self.source[begin..self.pos]; + return token(Token::Comment(text), loc); + } + _ => {} + } + } + } + + // Scan a number token which can represent either an integer or floating point number. + // + // Accept the following forms: + // + // - `10`: Integer + // - `-10`: Integer + // - `0xff_00`: Integer + // - `0.0`: Float + // - `0x1.f`: Float + // - `-0x2.4`: Float + // - `0x0.4p-34`: Float + // + // This function does not filter out all invalid numbers. It depends in the context-sensitive + // decoding of the text for that. For example, the number of allowed digits an an Ieee32` and + // an `Ieee64` constant are different. + fn scan_number(&mut self) -> Result, LocatedError> { + let begin = self.pos; + let loc = self.loc(); + let mut is_float = false; + + // Skip a leading sign. + if self.lookahead == Some('-') { + self.next_ch(); + } + + // Check for NaNs with payloads. + if self.looking_at("NaN:") || self.looking_at("sNaN:") { + // Skip the `NaN:` prefix, the loop below won't accept it. + // We expect a hexadecimal number to follow the colon. + while self.next_ch() != Some(':') {} + is_float = true; + } else if self.looking_at("NaN") || self.looking_at("Inf") { + // This is Inf or a default quiet NaN. + is_float = true; + } + + // Look for the end of this number. Detect the radix point if there is one. + loop { + match self.next_ch() { + Some('-') | Some('_') => {} + Some('.') => is_float = true, + Some(ch) if ch.is_alphanumeric() => {} + _ => break, + } + } + let text = &self.source[begin..self.pos]; + if is_float { + token(Token::Float(text), loc) + } else { + token(Token::Integer(text), loc) + } + } + + // Scan a 'word', which is an identifier-like sequence of characters beginning with '_' or an + // alphabetic char, followed by zero or more alphanumeric or '_' characters. + // + // + fn scan_word(&mut self) -> Result, LocatedError> { + let begin = self.pos; + let loc = self.loc(); + let mut trailing_digits = 0usize; + + assert!(self.lookahead == Some('_') || self.lookahead.unwrap().is_alphabetic()); + loop { + match self.next_ch() { + Some(ch) if ch.is_digit(10) => trailing_digits += 1, + Some('_') => trailing_digits = 0, + Some(ch) if ch.is_alphabetic() => trailing_digits = 0, + _ => break, + } + } + let text = &self.source[begin..self.pos]; + + match if trailing_digits == 0 { + Self::keyword(text) + } else { + // Look for numbered well-known entities like ebb15, v45, ... + let (prefix, suffix) = text.split_at(text.len() - trailing_digits); + Self::numbered_entity(prefix, suffix) + } { + Some(t) => token(t, loc), + None => token(Token::Identifier(text), loc), + } + } + + // Recognize a keyword. + fn keyword(text: &str) -> Option> { + match text { + "function" => Some(Token::Function), + "entry" => Some(Token::Entry), + _ => None, + } + } + + // If prefix is a well-known entity prefix and suffix is a valid entity number, return the + // decoded token. + fn numbered_entity(prefix: &str, suffix: &str) -> Option> { + // Reject non-canonical numbers like v0001. + if suffix.len() > 1 && suffix.starts_with('0') { + return None; + } + + let value: u32 = match suffix.parse() { + Ok(v) => v, + _ => return None, + }; + + match prefix { + "v" => Some(Token::ValueDirect(value)), + "vx" => Some(Token::ValueExtended(value)), + "ebb" => Some(Token::Ebb(value)), + "ss" => Some(Token::StackSlot(value)), + _ => None, + } + } + + /// Get the next token or a lexical error. + /// + /// Return None when the end of the source is encountered. + pub fn next(&mut self) -> Option, LocatedError>> { + loop { + let loc = self.loc(); + return match self.lookahead { + None => None, + Some(';') => Some(self.scan_comment()), + Some('(') => Some(self.scan_char(Token::LPar)), + Some(')') => Some(self.scan_char(Token::RPar)), + Some('{') => Some(self.scan_char(Token::LBrace)), + Some('}') => Some(self.scan_char(Token::RBrace)), + Some(',') => Some(self.scan_char(Token::Comma)), + Some('.') => Some(self.scan_char(Token::Dot)), + Some(':') => Some(self.scan_char(Token::Colon)), + Some('=') => Some(self.scan_char(Token::Equal)), + Some('-') => { + if self.looking_at("->") { + Some(self.scan_chars(2, Token::Arrow)) + } else { + Some(self.scan_number()) + } + } + Some(ch) if ch.is_digit(10) => Some(self.scan_number()), + Some(ch) if ch.is_alphabetic() => Some(self.scan_word()), + Some(ch) if ch.is_whitespace() => { + self.next_ch(); + continue; + } + _ => { + // Skip invalid char, return error. + self.next_ch(); + Some(error(Error::InvalidChar, loc)) + } + }; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { + Some(super::token(token, Location { line_number: line })) + } + + fn error<'a>(error: Error, line: usize) -> Option, LocatedError>> { + Some(super::error(error, Location { line_number: line })) + } + + #[test] + fn make_lexer() { + let mut l1 = Lexer::new(""); + let mut l2 = Lexer::new(" "); + let mut l3 = Lexer::new("\n "); + + assert_eq!(l1.next(), None); + assert_eq!(l2.next(), None); + assert_eq!(l3.next(), None); + } + + #[test] + fn lex_comment() { + let mut lex = Lexer::new("; hello"); + assert_eq!(lex.next(), token(Token::Comment("; hello"), 1)); + assert_eq!(lex.next(), None); + + lex = Lexer::new("\n ;hello\n;foo"); + assert_eq!(lex.next(), token(Token::Comment(";hello"), 2)); + assert_eq!(lex.next(), token(Token::Comment(";foo"), 3)); + assert_eq!(lex.next(), None); + + // Scan a comment after an invalid char. + let mut lex = Lexer::new("#; hello"); + assert_eq!(lex.next(), error(Error::InvalidChar, 1)); + assert_eq!(lex.next(), token(Token::Comment("; hello"), 1)); + assert_eq!(lex.next(), None); + } + + #[test] + fn lex_chars() { + let mut lex = Lexer::new("(); hello\n = :{, }."); + assert_eq!(lex.next(), token(Token::LPar, 1)); + assert_eq!(lex.next(), token(Token::RPar, 1)); + assert_eq!(lex.next(), token(Token::Comment("; hello"), 1)); + assert_eq!(lex.next(), token(Token::Equal, 2)); + assert_eq!(lex.next(), token(Token::Colon, 2)); + assert_eq!(lex.next(), token(Token::LBrace, 2)); + assert_eq!(lex.next(), token(Token::Comma, 2)); + assert_eq!(lex.next(), token(Token::RBrace, 2)); + assert_eq!(lex.next(), token(Token::Dot, 2)); + assert_eq!(lex.next(), None); + } + + #[test] + fn lex_numbers() { + let mut lex = Lexer::new(" 0 2_000 -1,0xf -0x0 0.0 0x0.4p-34"); + assert_eq!(lex.next(), token(Token::Integer("0"), 1)); + assert_eq!(lex.next(), token(Token::Integer("2_000"), 1)); + assert_eq!(lex.next(), token(Token::Integer("-1"), 1)); + assert_eq!(lex.next(), token(Token::Comma, 1)); + assert_eq!(lex.next(), token(Token::Integer("0xf"), 1)); + assert_eq!(lex.next(), token(Token::Integer("-0x0"), 1)); + assert_eq!(lex.next(), token(Token::Float("0.0"), 1)); + assert_eq!(lex.next(), token(Token::Float("0x0.4p-34"), 1)); + assert_eq!(lex.next(), None); + } + + #[test] + fn lex_identifiers() { + let mut lex = Lexer::new("v0 v00 vx01 ebb1234567890 ebb5234567890 entry v1x vx1 vxvx4 \ + function0 function"); + assert_eq!(lex.next(), token(Token::ValueDirect(0), 1)); + assert_eq!(lex.next(), token(Token::Identifier("v00"), 1)); + assert_eq!(lex.next(), token(Token::Identifier("vx01"), 1)); + assert_eq!(lex.next(), token(Token::Ebb(1234567890), 1)); + assert_eq!(lex.next(), token(Token::Identifier("ebb5234567890"), 1)); + assert_eq!(lex.next(), token(Token::Entry, 1)); + assert_eq!(lex.next(), token(Token::Identifier("v1x"), 1)); + assert_eq!(lex.next(), token(Token::ValueExtended(1), 1)); + assert_eq!(lex.next(), token(Token::Identifier("vxvx4"), 1)); + assert_eq!(lex.next(), token(Token::Identifier("function0"), 1)); + assert_eq!(lex.next(), token(Token::Function, 1)); + assert_eq!(lex.next(), None); + } +} diff --git a/src/libctonfile/lib.rs b/src/libctonfile/lib.rs index 3271ad035c..9dcb169b7d 100644 --- a/src/libctonfile/lib.rs +++ b/src/libctonfile/lib.rs @@ -1,12 +1,16 @@ // ====------------------------------------------------------------------------------------==== // // -// Cretonne file read/write library +// Cretonne file read/write library. // // ====------------------------------------------------------------------------------------==== // // // The libctonfile library supports reading and writing .cton files. This functionality is needed // for testing Cretonne, but is not essential for a JIT compiler. +// +// ====------------------------------------------------------------------------------------==== // extern crate cretonne; + +pub mod lexer; pub mod parser; From c712ddc7766b0ddc8d5c2de613b3ae39b0892f68 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 27 Apr 2016 15:38:54 -0700 Subject: [PATCH 052/968] Handle value type names in the lexer. --- src/libctonfile/lexer.rs | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/libctonfile/lexer.rs b/src/libctonfile/lexer.rs index 69d9c98580..855dd34056 100644 --- a/src/libctonfile/lexer.rs +++ b/src/libctonfile/lexer.rs @@ -6,6 +6,7 @@ // ====--------------------------------------------------------------------------------------====// use std::str::CharIndices; +use cretonne::types; /// The location of a `Token` or `Error`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,6 +34,7 @@ pub enum Token<'a> { Entry, // 'entry' Float(&'a str), // Floating point immediate Integer(&'a str), // Integer immediate + Type(types::Type), // i32, f32, b32x4, ... ValueDirect(u32), // v12 ValueExtended(u32), // vx7 Ebb(u32), // ebb3 @@ -257,7 +259,7 @@ impl<'a> Lexer<'a> { } else { // Look for numbered well-known entities like ebb15, v45, ... let (prefix, suffix) = text.split_at(text.len() - trailing_digits); - Self::numbered_entity(prefix, suffix) + Self::numbered_entity(prefix, suffix).or_else(|| Self::value_type(text, prefix, suffix)) } { Some(t) => token(t, loc), None => token(Token::Identifier(text), loc), @@ -295,6 +297,39 @@ impl<'a> Lexer<'a> { } } + // Recognize a scalar or vector type. + fn value_type(text: &str, prefix: &str, suffix: &str) -> Option> { + let is_vector = prefix.ends_with('x'); + let scalar = if is_vector { + &prefix[0..prefix.len() - 1] + } else { + text + }; + let base_type = match scalar { + "i8" => types::I8, + "i16" => types::I16, + "i32" => types::I32, + "i64" => types::I64, + "f32" => types::F32, + "f64" => types::F64, + "b1" => types::B1, + "b8" => types::B8, + "b16" => types::B16, + "b32" => types::B32, + "b64" => types::B64, + _ => return None, + }; + if is_vector { + let lanes: u16 = match suffix.parse() { + Ok(v) => v, + _ => return None, + }; + base_type.by(lanes).map(|t| Token::Type(t)) + } else { + Some(Token::Type(base_type)) + } + } + /// Get the next token or a lexical error. /// /// Return None when the end of the source is encountered. @@ -338,6 +373,7 @@ impl<'a> Lexer<'a> { #[cfg(test)] mod tests { use super::*; + use cretonne::types; fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) @@ -408,7 +444,7 @@ mod tests { #[test] fn lex_identifiers() { let mut lex = Lexer::new("v0 v00 vx01 ebb1234567890 ebb5234567890 entry v1x vx1 vxvx4 \ - function0 function"); + function0 function b1 i32x4 f32x5"); assert_eq!(lex.next(), token(Token::ValueDirect(0), 1)); assert_eq!(lex.next(), token(Token::Identifier("v00"), 1)); assert_eq!(lex.next(), token(Token::Identifier("vx01"), 1)); @@ -420,6 +456,9 @@ mod tests { assert_eq!(lex.next(), token(Token::Identifier("vxvx4"), 1)); assert_eq!(lex.next(), token(Token::Identifier("function0"), 1)); assert_eq!(lex.next(), token(Token::Function, 1)); + assert_eq!(lex.next(), token(Token::Type(types::B1), 1)); + assert_eq!(lex.next(), token(Token::Type(types::I32.by(4).unwrap()), 1)); + assert_eq!(lex.next(), token(Token::Identifier("f32x5"), 1)); assert_eq!(lex.next(), None); } } From f1a4b28d3f4c9f762e682fe67340df5cd34b24fc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 27 Apr 2016 13:38:50 -0700 Subject: [PATCH 053/968] Parser for .cton files. Recursive descent parser, although with this simple grammar there won't be any recursion. --- src/libctonfile/parser.rs | 202 +++++++++++++++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 1 deletion(-) diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index 44cf3a2500..d75d21ee24 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -1,2 +1,202 @@ -use cretonne; +// ====--------------------------------------------------------------------------------------====// +// +// Parser for .cton files. +// +// ====--------------------------------------------------------------------------------------====// + +use std::result; +use lexer::{self, Lexer, Token}; +use cretonne::{types, repr}; + +pub use lexer::Location; + +/// A parse error is returned when the parse failed. +pub struct Error { + pub location: Location, + pub message: String, +} + +pub type Result = result::Result; + +pub struct Parser<'a> { + lex: Lexer<'a>, + + lex_error: Option, + + // Current lookahead token. + lookahead: Option>, + + // Location of lookahead. + location: Location, +} + +impl<'a> Parser<'a> { + // Consume the current lookahead token and return it. + fn consume(&mut self) -> Token<'a> { + self.lookahead.take().expect("No token to consume") + } + + // Get the current lookahead token, after making sure there is one. + fn token(&mut self) -> Option> { + if self.lookahead == None { + match self.lex.next() { + Some(Ok(lexer::LocatedToken { token, location })) => { + self.lookahead = Some(token); + self.location = location; + } + Some(Err(lexer::LocatedError { error, location })) => { + self.lex_error = Some(error); + self.location = location; + } + None => {} + } + } + return self.lookahead; + } + + // Generate an error. + fn error(&self, message: &str) -> Error { + Error { + location: self.location, + message: + // If we have a lexer error latched, report that. + match self.lex_error { + Some(lexer::Error::InvalidChar) => "Invalid character".to_string(), + None => message.to_string(), + } + } + } + + // Match and consume a token without payload. + fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result> { + match self.token() { + Some(ref t) if *t == want => Ok(self.consume()), + _ => Err(self.error(err_msg)), + } + } + + // if the next token is a `want`, consume it, otherwise do nothing. + fn optional(&mut self, want: Token<'a>) -> bool { + match self.token() { + Some(t) if t == want => { + self.consume(); + true + } + _ => false, + } + } + + // Parse a whole function definition. + // + // function ::= * "function" name signature { ... } + // + fn parse_function(&mut self) -> Result { + try!(self.match_token(Token::Function, "Expected 'function' keyword")); + + // function ::= "function" * name signature { ... } + let name = try!(self.parse_function_name()); + + // function ::= "function" name * signature { ... } + let sig = try!(self.parse_signature()); + + let mut func = repr::Function::new(); + + try!(self.match_token(Token::LBrace, "Expected '{' before function body")); + try!(self.match_token(Token::RBrace, "Expected '}' after function body")); + + Ok(func) + } + + // Parse a function name. + // + // function ::= "function" * name signature { ... } + // + fn parse_function_name(&mut self) -> Result { + match self.token() { + Some(Token::Identifier(s)) => { + self.consume(); + Ok(s.to_string()) + } + _ => Err(self.error("Expected function name")), + } + } + + // Parse a function signature. + // + // signature ::= * "(" arglist ")" ["->" retlist] [call_conv] + // callconv ::= string + // + // function ::= "function" * name signature { ... } + // + fn parse_signature(&mut self) -> Result { + let mut sig = types::Signature::new(); + + try!(self.match_token(Token::LPar, "Expected function signature: '(' args... ')'")); + // signature ::= "(" * arglist ")" ["->" retlist] [call_conv] + sig.argument_types = try!(self.parse_argument_list()); + try!(self.match_token(Token::RPar, "Expected ')' after function arguments")); + if self.optional(Token::Arrow) { + sig.return_types = try!(self.parse_argument_list()); + if sig.return_types.is_empty() { + return Err(self.error("Missing return type after '->'")); + } + } + + // TBD: calling convention. + + Ok(sig) + } + + // Parse (possibly empty) list of function argument / return value types. + // + // arglist ::= * + // * arg + // * arglist "," arg + fn parse_argument_list(&mut self) -> Result> { + let mut list = Vec::new(); + // arglist ::= * + // * arg + match self.token() { + Some(Token::Type(_)) => list.push(try!(self.parse_argument_type())), + _ => return Ok(list), + } + + // arglist ::= arg * + // arglist * "," arg + while self.token() == Some(Token::Comma) { + // arglist ::= arglist * "," arg + self.consume(); + // arglist ::= arglist "," * arg + list.push(try!(self.parse_argument_type())); + } + + Ok(list) + } + + // Parse a single argument type with flags. + fn parse_argument_type(&mut self) -> Result { + // arg ::= * type + // * arg flag + let mut arg = match self.token() { + Some(Token::Type(t)) => types::ArgumentType::new(t), + _ => return Err(self.error("Expected argument type")), + }; + loop { + self.consume(); + // arg ::= arg * flag + match self.token() { + Some(Token::Identifier(s)) => { + match s { + "uext" => arg.extension = types::ArgumentExtension::Uext, + "sext" => arg.extension = types::ArgumentExtension::Sext, + "inreg" => arg.inreg = true, + _ => break, + } + } + _ => break, + } + } + Ok(arg) + } +} From 8d0311b6428b12c362cafdb6d0d4afdf06aea3b5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 08:06:30 -0700 Subject: [PATCH 054/968] Simplify parser. Use 'if let' instead of 'match' where it makes sense. Use EBNF notation for the grammar rules. This simplifies repetition a lot. --- docs/langref.rst | 6 +-- src/libctonfile/parser.rs | 91 +++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 56 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index df0e19b20a..2cad2999c6 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -413,11 +413,9 @@ calling convention: .. productionlist:: signature : "(" [arglist] ")" ["->" retlist] [call_conv] - arglist : arg - : arglist "," arg + arglist : arg { "," arg } retlist : arglist - arg : type - : arg flag + arg : type { flag } flag : "uext" | "sext" | "inreg" callconv : `string` diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index d75d21ee24..060883b77e 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -70,20 +70,20 @@ impl<'a> Parser<'a> { // Match and consume a token without payload. fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result> { - match self.token() { - Some(ref t) if *t == want => Ok(self.consume()), - _ => Err(self.error(err_msg)), + if self.token() == Some(want) { + Ok(self.consume()) + } else { + Err(self.error(err_msg)) } } - // if the next token is a `want`, consume it, otherwise do nothing. + // If the next token is a `want`, consume it, otherwise do nothing. fn optional(&mut self, want: Token<'a>) -> bool { - match self.token() { - Some(t) if t == want => { - self.consume(); - true - } - _ => false, + if self.token() == Some(want) { + self.consume(); + true + } else { + false } } @@ -124,23 +124,19 @@ impl<'a> Parser<'a> { // Parse a function signature. // - // signature ::= * "(" arglist ")" ["->" retlist] [call_conv] - // callconv ::= string - // - // function ::= "function" * name signature { ... } + // signature ::= * "(" [arglist] ")" ["->" retlist] [call_conv] // fn parse_signature(&mut self) -> Result { let mut sig = types::Signature::new(); try!(self.match_token(Token::LPar, "Expected function signature: '(' args... ')'")); - // signature ::= "(" * arglist ")" ["->" retlist] [call_conv] - sig.argument_types = try!(self.parse_argument_list()); + // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] + if self.token() != Some(Token::RPar) { + sig.argument_types = try!(self.parse_argument_list()); + } try!(self.match_token(Token::RPar, "Expected ')' after function arguments")); if self.optional(Token::Arrow) { sig.return_types = try!(self.parse_argument_list()); - if sig.return_types.is_empty() { - return Err(self.error("Missing return type after '->'")); - } } // TBD: calling convention. @@ -148,26 +144,19 @@ impl<'a> Parser<'a> { Ok(sig) } - // Parse (possibly empty) list of function argument / return value types. + // Parse list of function argument / return value types. + // + // arglist ::= * arg { "," arg } // - // arglist ::= * - // * arg - // * arglist "," arg fn parse_argument_list(&mut self) -> Result> { let mut list = Vec::new(); - // arglist ::= * - // * arg - match self.token() { - Some(Token::Type(_)) => list.push(try!(self.parse_argument_type())), - _ => return Ok(list), - } - // arglist ::= arg * - // arglist * "," arg - while self.token() == Some(Token::Comma) { - // arglist ::= arglist * "," arg - self.consume(); - // arglist ::= arglist "," * arg + // arglist ::= * arg { "," arg } + list.push(try!(self.parse_argument_type())); + + // arglist ::= arg * { "," arg } + while self.optional(Token::Comma) { + // arglist ::= arg { "," * arg } list.push(try!(self.parse_argument_type())); } @@ -176,27 +165,25 @@ impl<'a> Parser<'a> { // Parse a single argument type with flags. fn parse_argument_type(&mut self) -> Result { - // arg ::= * type - // * arg flag - let mut arg = match self.token() { - Some(Token::Type(t)) => types::ArgumentType::new(t), - _ => return Err(self.error("Expected argument type")), + // arg ::= * type { flag } + let mut arg = if let Some(Token::Type(t)) = self.token() { + types::ArgumentType::new(t) + } else { + return Err(self.error("Expected argument type")); }; - loop { - self.consume(); - // arg ::= arg * flag - match self.token() { - Some(Token::Identifier(s)) => { - match s { - "uext" => arg.extension = types::ArgumentExtension::Uext, - "sext" => arg.extension = types::ArgumentExtension::Sext, - "inreg" => arg.inreg = true, - _ => break, - } - } + self.consume(); + + // arg ::= type * { flag } + while let Some(Token::Identifier(s)) = self.token() { + match s { + "uext" => arg.extension = types::ArgumentExtension::Uext, + "sext" => arg.extension = types::ArgumentExtension::Sext, + "inreg" => arg.inreg = true, _ => break, } + self.consume(); } + Ok(arg) } } From 07afc6e8da8efdd7e4af0692a506657030a74b0b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 09:05:11 -0700 Subject: [PATCH 055/968] Begin parser unit tests, add public interface. The main entry point is Parser::parse(). --- src/libctonfile/parser.rs | 64 ++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index 060883b77e..5069520518 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -12,6 +12,7 @@ use cretonne::{types, repr}; pub use lexer::Location; /// A parse error is returned when the parse failed. +#[derive(Debug)] pub struct Error { pub location: Location, pub message: String, @@ -32,6 +33,21 @@ pub struct Parser<'a> { } impl<'a> Parser<'a> { + /// Create a new `Parser` which reads `text`. The referenced text must outlive the parser. + pub fn new(text: &'a str) -> Parser { + Parser { + lex: Lexer::new(text), + lex_error: None, + lookahead: None, + location: Location { line_number: 0 }, + } + } + + /// Parse the entire string into a list of functions. + pub fn parse(text: &'a str) -> Result> { + Self::new(text).parse_function_list() + } + // Consume the current lookahead token and return it. fn consume(&mut self) -> Token<'a> { self.lookahead.take().expect("No token to consume") @@ -62,7 +78,7 @@ impl<'a> Parser<'a> { message: // If we have a lexer error latched, report that. match self.lex_error { - Some(lexer::Error::InvalidChar) => "Invalid character".to_string(), + Some(lexer::Error::InvalidChar) => "invalid character".to_string(), None => message.to_string(), } } @@ -87,12 +103,23 @@ impl<'a> Parser<'a> { } } + /// Parse a list of function definitions. + /// + /// This is the top-level parse function matching the whole contents of a file. + pub fn parse_function_list(&mut self) -> Result> { + let mut list = Vec::new(); + while self.token().is_some() { + list.push(try!(self.parse_function())); + } + Ok(list) + } + // Parse a whole function definition. // // function ::= * "function" name signature { ... } // fn parse_function(&mut self) -> Result { - try!(self.match_token(Token::Function, "Expected 'function' keyword")); + try!(self.match_token(Token::Function, "expected 'function' keyword")); // function ::= "function" * name signature { ... } let name = try!(self.parse_function_name()); @@ -102,8 +129,8 @@ impl<'a> Parser<'a> { let mut func = repr::Function::new(); - try!(self.match_token(Token::LBrace, "Expected '{' before function body")); - try!(self.match_token(Token::RBrace, "Expected '}' after function body")); + try!(self.match_token(Token::LBrace, "expected '{' before function body")); + try!(self.match_token(Token::RBrace, "expected '}' after function body")); Ok(func) } @@ -118,7 +145,7 @@ impl<'a> Parser<'a> { self.consume(); Ok(s.to_string()) } - _ => Err(self.error("Expected function name")), + _ => Err(self.error("expected function name")), } } @@ -129,12 +156,12 @@ impl<'a> Parser<'a> { fn parse_signature(&mut self) -> Result { let mut sig = types::Signature::new(); - try!(self.match_token(Token::LPar, "Expected function signature: '(' args... ')'")); + try!(self.match_token(Token::LPar, "expected function signature: '(' args... ')'")); // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] if self.token() != Some(Token::RPar) { sig.argument_types = try!(self.parse_argument_list()); } - try!(self.match_token(Token::RPar, "Expected ')' after function arguments")); + try!(self.match_token(Token::RPar, "expected ')' after function arguments")); if self.optional(Token::Arrow) { sig.return_types = try!(self.parse_argument_list()); } @@ -169,7 +196,7 @@ impl<'a> Parser<'a> { let mut arg = if let Some(Token::Type(t)) = self.token() { types::ArgumentType::new(t) } else { - return Err(self.error("Expected argument type")); + return Err(self.error("expected argument type")); }; self.consume(); @@ -187,3 +214,24 @@ impl<'a> Parser<'a> { Ok(arg) } } + +#[cfg(test)] +mod tests { + use super::*; + use cretonne::types::{self, ArgumentType, ArgumentExtension}; + + #[test] + fn argument_type() { + let mut p = Parser::new("i32 sext"); + let arg = p.parse_argument_type().unwrap(); + assert_eq!(arg, + ArgumentType { + value_type: types::I32, + extension: ArgumentExtension::Sext, + inreg: false, + }); + let Error { location, message } = p.parse_argument_type().unwrap_err(); + assert_eq!(location.line_number, 1); + assert_eq!(message, "expected argument type"); + } +} From cda7943b0bc22fc09f8b9457670381f735fc43c7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 09:09:34 -0700 Subject: [PATCH 056/968] Fix documentation badge. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4e87f615e8..6fae53072f 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ Cretonne Code Generator Cretonne is a low-level retargetable code generator. It translates a target-independent intermediate language into executable machine code. -.. image:: https://readthedocs.io/projects/cretonne/badge/?version=latest - :target: http://cretonne.readthedocs.io/en/latest/?badge=latest +.. image:: https://readthedocs.org/projects/cretonne/badge/?version=latest + :target: https://cretonne.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status Cretonne is designed to be a code generator for WebAssembly with these design From 42364fda59804183430aca21495538b565908da7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 12:57:40 -0700 Subject: [PATCH 057/968] Add top-level productions to language reference. --- docs/langref.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 2cad2999c6..0851ea8fcd 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -1,6 +1,6 @@ -**************************************** -Cretonne Intermediate Language Reference -**************************************** +*************************** +Cretonne Language Reference +*************************** .. default-domain:: cton .. highlight:: cton @@ -45,6 +45,15 @@ After the preample follows the :term:`function body` which consists of block`. Every EBB ends with a :term:`terminator instruction`, so execution can never fall through to the next EBB without an explicit branch. +A ``.cton`` file consists of a sequence of independent function definitions: + +.. productionlist:: + function-list : { function } + function : function-spec "{" preample function-body "}" + function-spec : "function" function-name signature + preamble : { preamble-decl } + function-body : { extended-basic-block } + Static single assignment form ----------------------------- From 41d95c0342fccbec4ed9a2761f6a62d8f6655a70 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 13:16:13 -0700 Subject: [PATCH 058/968] Tests for signature parser. --- src/libcretonne/types.rs | 1 + src/libctonfile/parser.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 0aaa30841b..d54b3c51d0 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -224,6 +224,7 @@ pub struct ArgumentType { /// /// The function signature describes the types of arguments and return values along with other /// details that are needed to call a function correctly. +#[derive(Clone, PartialEq, Eq, Debug)] pub struct Signature { pub argument_types: Vec, pub return_types: Vec, diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index 5069520518..a56d69df00 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -6,6 +6,7 @@ // ====--------------------------------------------------------------------------------------====// use std::result; +use std::fmt::{self, Display, Formatter, Write}; use lexer::{self, Lexer, Token}; use cretonne::{types, repr}; @@ -18,6 +19,12 @@ pub struct Error { pub message: String, } +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}: {}", self.location.line_number, self.message) + } +} + pub type Result = result::Result; pub struct Parser<'a> { @@ -156,7 +163,7 @@ impl<'a> Parser<'a> { fn parse_signature(&mut self) -> Result { let mut sig = types::Signature::new(); - try!(self.match_token(Token::LPar, "expected function signature: '(' args... ')'")); + try!(self.match_token(Token::LPar, "expected function signature: ( args... )")); // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] if self.token() != Some(Token::RPar) { sig.argument_types = try!(self.parse_argument_list()); @@ -234,4 +241,27 @@ mod tests { assert_eq!(location.line_number, 1); assert_eq!(message, "expected argument type"); } + + #[test] + fn signature() { + let sig = Parser::new("()").parse_signature().unwrap(); + assert_eq!(sig.argument_types.len(), 0); + assert_eq!(sig.return_types.len(), 0); + + let sig2 = Parser::new("(i8 inreg uext, f32, f64) -> i32 sext, f64") + .parse_signature() + .unwrap(); + assert_eq!(format!("{}", sig2), + "(i8 uext inreg, f32, f64) -> i32 sext, f64"); + + // `void` is not recognized as a type by the lexer. It should not appear in files. + assert_eq!(format!("{}", + Parser::new("() -> void").parse_signature().unwrap_err()), + "1: expected argument type"); + assert_eq!(format!("{}", Parser::new("i8 -> i8").parse_signature().unwrap_err()), + "1: expected function signature: ( args... )"); + assert_eq!(format!("{}", + Parser::new("(i8 -> i8").parse_signature().unwrap_err()), + "1: expected ')' after function arguments"); + } } From 021bde11918e1a25c601ac81284b35bf1f515bba Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 14:02:16 -0700 Subject: [PATCH 059/968] Add FunctionName, Signature to repr::Function. Simplify the uses in parser.rs to avoid too many module qualifiers. --- src/libcretonne/repr.rs | 19 +++++++++++--- src/libcretonne/types.rs | 6 +++++ src/libctonfile/parser.rs | 54 ++++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 1318a0936a..7d11b62bd1 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,7 +1,7 @@ //! Representation of Cretonne IL functions. -use types::Type; +use types::{Type, FunctionName, Signature}; use immediates::*; use std::fmt::{self, Display, Formatter, Write}; use std::u32; @@ -39,6 +39,12 @@ pub const NO_VALUE: Value = Value(u32::MAX); /// container for those objects by implementing both `Index` and `Index`. /// pub struct Function { + /// Name of this function. Mostly used by `.cton` files. + name: FunctionName, + + /// Signature of this function. + signature: Signature, + /// Data about all of the instructions in the function. The instructions in this vector is not /// necessarily in program order. The `Inst` reference indexes into this vector. instructions: Vec, @@ -269,9 +275,11 @@ impl InstructionData { } impl Function { - /// Create a new empty function. - pub fn new() -> Function { + /// Create a function with the given name and signature. + pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function { Function { + name: name, + signature: sig, instructions: Vec::new(), extended_basic_blocks: Vec::new(), extended_values: Vec::new(), @@ -279,6 +287,11 @@ impl Function { } } + /// Create a new empty, anomymous function. + pub fn new() -> Function { + Self::with_name_signature(FunctionName::new(), Signature::new()) + } + /// Resolve an instruction reference. pub fn inst(&self, i: Inst) -> &InstructionData { &self.instructions[i.0 as usize] diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index d54b3c51d0..9d4f2d93af 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -194,6 +194,12 @@ impl Display for Type { // // ====--------------------------------------------------------------------------------------====// +/// The name of a function can be any UTF-8 string. +/// +/// Function names are mostly a testing and debugging tool. In partucular, `.cton` files use +/// function names to identify functions. +pub type FunctionName = String; + /// Function argument extension options. /// /// On some architectures, small integer function arguments are extended to the width of a diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index a56d69df00..22b9adcc93 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -8,7 +8,8 @@ use std::result; use std::fmt::{self, Display, Formatter, Write}; use lexer::{self, Lexer, Token}; -use cretonne::{types, repr}; +use cretonne::types::{FunctionName, Signature, ArgumentType, ArgumentExtension}; +use cretonne::repr::Function; pub use lexer::Location; @@ -51,7 +52,7 @@ impl<'a> Parser<'a> { } /// Parse the entire string into a list of functions. - pub fn parse(text: &'a str) -> Result> { + pub fn parse(text: &'a str) -> Result> { Self::new(text).parse_function_list() } @@ -113,7 +114,7 @@ impl<'a> Parser<'a> { /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. - pub fn parse_function_list(&mut self) -> Result> { + pub fn parse_function_list(&mut self) -> Result> { let mut list = Vec::new(); while self.token().is_some() { list.push(try!(self.parse_function())); @@ -123,25 +124,36 @@ impl<'a> Parser<'a> { // Parse a whole function definition. // - // function ::= * "function" name signature { ... } + // function ::= * function-spec "{" preample function-body "}" // - fn parse_function(&mut self) -> Result { - try!(self.match_token(Token::Function, "expected 'function' keyword")); - - // function ::= "function" * name signature { ... } - let name = try!(self.parse_function_name()); - - // function ::= "function" name * signature { ... } - let sig = try!(self.parse_signature()); - - let mut func = repr::Function::new(); + fn parse_function(&mut self) -> Result { + let (name, sig) = try!(self.parse_function_spec()); + let mut func = Function::with_name_signature(name, sig); + // function ::= function-spec * "{" preample function-body "}" try!(self.match_token(Token::LBrace, "expected '{' before function body")); + // function ::= function-spec "{" preample function-body * "}" try!(self.match_token(Token::RBrace, "expected '}' after function body")); Ok(func) } + // Parse a function spec. + // + // function-spec ::= * "function" name signature + // + fn parse_function_spec(&mut self) -> Result<(FunctionName, Signature)> { + try!(self.match_token(Token::Function, "expected 'function' keyword")); + + // function-spec ::= "function" * name signature + let name = try!(self.parse_function_name()); + + // function-spec ::= "function" name * signature + let sig = try!(self.parse_signature()); + + Ok((name, sig)) + } + // Parse a function name. // // function ::= "function" * name signature { ... } @@ -160,8 +172,8 @@ impl<'a> Parser<'a> { // // signature ::= * "(" [arglist] ")" ["->" retlist] [call_conv] // - fn parse_signature(&mut self) -> Result { - let mut sig = types::Signature::new(); + fn parse_signature(&mut self) -> Result { + let mut sig = Signature::new(); try!(self.match_token(Token::LPar, "expected function signature: ( args... )")); // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] @@ -182,7 +194,7 @@ impl<'a> Parser<'a> { // // arglist ::= * arg { "," arg } // - fn parse_argument_list(&mut self) -> Result> { + fn parse_argument_list(&mut self) -> Result> { let mut list = Vec::new(); // arglist ::= * arg { "," arg } @@ -198,10 +210,10 @@ impl<'a> Parser<'a> { } // Parse a single argument type with flags. - fn parse_argument_type(&mut self) -> Result { + fn parse_argument_type(&mut self) -> Result { // arg ::= * type { flag } let mut arg = if let Some(Token::Type(t)) = self.token() { - types::ArgumentType::new(t) + ArgumentType::new(t) } else { return Err(self.error("expected argument type")); }; @@ -210,8 +222,8 @@ impl<'a> Parser<'a> { // arg ::= type * { flag } while let Some(Token::Identifier(s)) = self.token() { match s { - "uext" => arg.extension = types::ArgumentExtension::Uext, - "sext" => arg.extension = types::ArgumentExtension::Sext, + "uext" => arg.extension = ArgumentExtension::Uext, + "sext" => arg.extension = ArgumentExtension::Sext, "inreg" => arg.inreg = true, _ => break, } From 88931983a81f574016691171718ee42e8fadff69 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 28 Apr 2016 14:35:52 -0700 Subject: [PATCH 060/968] Parse stack slot decls. Add a stack slot array to repr::Function, use repr::StackSlot to reference them. Parse stack slot declarations in the function preamble, add them to the function. Add a new `Context` struct which keeps track of mappings between identifiers used in the file and real references. --- src/libcretonne/immediates.rs | 4 + src/libcretonne/repr.rs | 121 ++++++++++++++++++++++++++++- src/libctonfile/parser.rs | 141 +++++++++++++++++++++++++++++++++- 3 files changed, 262 insertions(+), 4 deletions(-) diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 4fefdbb9d0..072a8dcb91 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -74,6 +74,10 @@ impl Imm64 { pub fn from_bits(x: u64) -> Imm64 { Imm64(x as i64) } + + pub fn to_bits(&self) -> u64 { + self.0 as u64 + } } impl Display for Imm64 { diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 7d11b62bd1..e982a65ddd 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -4,6 +4,7 @@ use types::{Type, FunctionName, Signature}; use immediates::*; use std::fmt::{self, Display, Formatter, Write}; +use std::ops::Index; use std::u32; // ====--------------------------------------------------------------------------------------====// @@ -33,18 +34,29 @@ pub struct Value(u32); /// A guaranteed invalid value reference. pub const NO_VALUE: Value = Value(u32::MAX); +/// An opaque reference to a stack slot. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct StackSlot(u32); + +/// A guaranteed invalid stack slot reference. +pub const NO_STACK_SLOT: StackSlot = StackSlot(u32::MAX); + /// A function. /// /// The `Function` struct owns all of its instructions and extended basic blocks, and it works as a /// container for those objects by implementing both `Index` and `Index`. /// +#[derive(Debug)] pub struct Function { /// Name of this function. Mostly used by `.cton` files. - name: FunctionName, + pub name: FunctionName, /// Signature of this function. signature: Signature, + /// Stack slots allocated in this function. + stack_slots: Vec, + /// Data about all of the instructions in the function. The instructions in this vector is not /// necessarily in program order. The `Inst` reference indexes into this vector. instructions: Vec, @@ -61,7 +73,15 @@ pub struct Function { pub return_types: Vec, } +/// Contents of a stack slot. +#[derive(Debug)] +pub struct StackSlotData { + /// Size of stack slot in bytes. + pub size: u32, +} + /// Contents of an extended basic block. +#[derive(Debug)] pub struct EbbData { /// Arguments for this extended basic block. These values dominate everything in the EBB. /// All branches to this EBB must provide matching arguments, and the arguments to the entry @@ -75,6 +95,7 @@ pub struct EbbData { /// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a /// `Box` to store the additional information out of line. +#[derive(Debug)] pub enum InstructionData { Nullary { opcode: Opcode, @@ -109,6 +130,7 @@ pub enum InstructionData { } /// Payload of a call instruction. +#[derive(Debug)] pub struct CallData { // Number of result values. results: u8, @@ -119,6 +141,72 @@ pub struct CallData { } +// ====--------------------------------------------------------------------------------------====// +// +// Stack slot implementation. +// +// ====--------------------------------------------------------------------------------------====// + +impl StackSlot { + fn new(index: usize) -> StackSlot { + assert!(index < (u32::MAX as usize)); + StackSlot(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display a `StackSlot` reference as "ss12". +impl Display for StackSlot { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "ss{}", self.0) + } +} + +impl StackSlotData { + /// Create a stack slot with the specified byte size. + pub fn new(size: u32) -> StackSlotData { + StackSlotData { size: size } + } +} + +impl Display for StackSlotData { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "stack_slot {}", self.size) + } +} + +/// Allow immutable access to stack slots via function indexing. +impl Index for Function { + type Output = StackSlotData; + + fn index<'a>(&'a self, ss: StackSlot) -> &'a StackSlotData { + &self.stack_slots[ss.index()] + } +} + +/// Stack slot iterator visits all stack slots in a function, returning `StackSlot` references. +pub struct StackSlotIter { + cur: usize, + end: usize, +} + +impl Iterator for StackSlotIter { + type Item = StackSlot; + + fn next(&mut self) -> Option { + if self.cur < self.end { + let ss = StackSlot::new(self.cur); + self.cur += 1; + Some(ss) + } else { + None + } + } +} + // ====--------------------------------------------------------------------------------------====// // // Extended basic block implementation. @@ -227,6 +315,7 @@ impl Display for Value { // Most values are simply the first value produced by an instruction. // Other values have an entry in the value table. +#[derive(Debug)] enum ValueData { // An unused entry in the value table. No instruction should be defining or using this value. Unused, @@ -280,6 +369,7 @@ impl Function { Function { name: name, signature: sig, + stack_slots: Vec::new(), instructions: Vec::new(), extended_basic_blocks: Vec::new(), extended_values: Vec::new(), @@ -292,6 +382,21 @@ impl Function { Self::with_name_signature(FunctionName::new(), Signature::new()) } + /// Allocate a new stack slot. + pub fn make_stack_slot(&mut self, data: StackSlotData) -> StackSlot { + let ss = StackSlot::new(self.stack_slots.len()); + self.stack_slots.push(data); + ss + } + + /// Iterate over all stack slots in function. + pub fn stack_slot_iter(&self) -> StackSlotIter { + StackSlotIter { + cur: 0, + end: self.stack_slots.len(), + } + } + /// Resolve an instruction reference. pub fn inst(&self, i: Inst) -> &InstructionData { &self.instructions[i.0 as usize] @@ -351,4 +456,18 @@ mod tests { assert_eq!(ins.opcode(), Opcode::Iconst); assert_eq!(ins.first_type(), types::I32); } + + #[test] + fn stack_slot() { + let mut func = Function::new(); + + let ss0 = func.make_stack_slot(StackSlotData::new(4)); + let ss1 = func.make_stack_slot(StackSlotData::new(8)); + assert_eq!(format!("{}", ss0), "ss0"); + assert_eq!(format!("{}", ss1), "ss1"); + + assert_eq!(func[ss0].size, 4); + assert_eq!(func[ss1].size, 8); + } + } diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index 22b9adcc93..a014ca0253 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -5,11 +5,14 @@ // // ====--------------------------------------------------------------------------------------====// +use std::collections::HashMap; use std::result; use std::fmt::{self, Display, Formatter, Write}; +use std::u32; use lexer::{self, Lexer, Token}; use cretonne::types::{FunctionName, Signature, ArgumentType, ArgumentExtension}; -use cretonne::repr::Function; +use cretonne::immediates::Imm64; +use cretonne::repr::{Function, StackSlot, StackSlotData}; pub use lexer::Location; @@ -40,6 +43,35 @@ pub struct Parser<'a> { location: Location, } +// Context for resolving references when parsing a single function. +// +// Many entities like values, stack slots, and function signatures are referenced in the `.cton` +// file by number. We need to map these numbers to real references. +struct Context { + function: Function, + stack_slots: HashMap, +} + +impl Context { + fn new(f: Function) -> Context { + Context { + function: f, + stack_slots: HashMap::new(), + } + } + + fn add(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { + if self.stack_slots.insert(number, self.function.make_stack_slot(data)).is_some() { + Err(Error { + location: loc.clone(), + message: format!("duplicate stack slot: ss{}", number), + }) + } else { + Ok(()) + } + } +} + impl<'a> Parser<'a> { /// Create a new `Parser` which reads `text`. The referenced text must outlive the parser. pub fn new(text: &'a str) -> Parser { @@ -111,6 +143,38 @@ impl<'a> Parser<'a> { } } + // Match and consume a specific identifier string. + // Used for pseudo-keywords like "stack_slot" that only appear in certain contexts. + fn match_identifier(&mut self, want: &'static str, err_msg: &str) -> Result> { + if self.token() == Some(Token::Identifier(want)) { + Ok(self.consume()) + } else { + Err(self.error(err_msg)) + } + } + + // Match and consume a stack slot reference. + fn match_ss(&mut self, err_msg: &str) -> Result { + if let Some(Token::StackSlot(ss)) = self.token() { + self.consume(); + Ok(ss) + } else { + Err(self.error(err_msg)) + } + } + + // Match and consume an Imm64 immediate. + fn match_imm64(&mut self, err_msg: &str) -> Result { + if let Some(Token::Integer(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like an integer. + // Parse it as an Imm64 to check for overflow and other issues. + text.parse().map_err(|e| self.error(e)) + } else { + Err(self.error(err_msg)) + } + } + /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. @@ -128,14 +192,16 @@ impl<'a> Parser<'a> { // fn parse_function(&mut self) -> Result { let (name, sig) = try!(self.parse_function_spec()); - let mut func = Function::with_name_signature(name, sig); + let mut ctx = Context::new(Function::with_name_signature(name, sig)); // function ::= function-spec * "{" preample function-body "}" try!(self.match_token(Token::LBrace, "expected '{' before function body")); + // function ::= function-spec "{" * preample function-body "}" + try!(self.parse_preamble(&mut ctx)); // function ::= function-spec "{" preample function-body * "}" try!(self.match_token(Token::RBrace, "expected '}' after function body")); - Ok(func) + Ok(ctx.function) } // Parse a function spec. @@ -232,6 +298,46 @@ impl<'a> Parser<'a> { Ok(arg) } + + // Parse the function preamble. + // + // preamble ::= * { preamble-decl } + // preamble-decl ::= * stack-slot-decl + // * function-decl + // * signature-decl + // + // The parsed decls are added to `ctx` rather than returned. + fn parse_preamble(&mut self, ctx: &mut Context) -> Result<()> { + loop { + try!(match self.token() { + Some(Token::StackSlot(..)) => { + self.parse_stack_slot_decl() + .and_then(|(num, dat)| ctx.add(num, dat, &self.location)) + } + // More to come.. + _ => return Ok(()), + }); + } + } + + // Parse a stack slot decl, add to `func`. + // + // stack-slot-decl ::= * StackSlot(ss) "=" "stack_slot" Bytes {"," stack-slot-flag} + fn parse_stack_slot_decl(&mut self) -> Result<(u32, StackSlotData)> { + let number = try!(self.match_ss("expected stack slot number: ss«n»")); + try!(self.match_token(Token::Equal, "expected '=' in stack_slot decl")); + try!(self.match_identifier("stack_slot", "expected 'stack_slot'")); + + // stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag} + let bytes = try!(self.match_imm64("expected byte-size in stack_slot decl")).to_bits(); + if bytes > u32::MAX as u64 { + return Err(self.error("stack slot too large")); + } + let data = StackSlotData::new(bytes as u32); + + // TBD: stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" Bytes * {"," stack-slot-flag} + Ok((number, data)) + } } #[cfg(test)] @@ -276,4 +382,33 @@ mod tests { Parser::new("(i8 -> i8").parse_signature().unwrap_err()), "1: expected ')' after function arguments"); } + + #[test] + fn stack_slot_decl() { + let func = Parser::new("function foo() { + ss3 = stack_slot 13 + ss1 = stack_slot 1 + }") + .parse_function() + .unwrap(); + assert_eq!(func.name, "foo"); + let mut iter = func.stack_slot_iter(); + let ss0 = iter.next().unwrap(); + assert_eq!(format!("{}", ss0), "ss0"); + assert_eq!(func[ss0].size, 13); + let ss1 = iter.next().unwrap(); + assert_eq!(format!("{}", ss1), "ss1"); + assert_eq!(func[ss1].size, 1); + assert_eq!(iter.next(), None); + + // Catch suplicate definitions. + assert_eq!(format!("{}", + Parser::new("function bar() { + ss1 = stack_slot 13 + ss1 = stack_slot 1 + }") + .parse_function() + .unwrap_err()), + "3: duplicate stack slot: ss1"); + } } From 5c4f3d01e2bd03de9c61e80c0c469fcf0c56182f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 11:55:40 -0700 Subject: [PATCH 061/968] Use x.to_string() instead of format!("{}", x). Both use the Display trait. --- src/libcretonne/immediates.rs | 100 ++++++++++++++++------------------ src/libcretonne/repr.rs | 6 +- src/libcretonne/types.rs | 59 ++++++++++---------- src/libctonfile/parser.rs | 26 ++++----- src/test-all.sh | 4 ++ 5 files changed, 96 insertions(+), 99 deletions(-) create mode 100755 src/test-all.sh diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 072a8dcb91..619d790bfe 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -493,7 +493,7 @@ mod tests { assert_eq!(x, y); assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); - assert_eq!(format!("{}", Opcode::IaddImm), "iadd_imm"); + assert_eq!(Opcode::IaddImm.to_string(), "iadd_imm"); // Check the matcher. assert_eq!("iadd".parse::(), Ok(Opcode::Iadd)); @@ -505,13 +505,13 @@ mod tests { #[test] fn format_imm64() { - assert_eq!(format!("{}", Imm64(0)), "0"); - assert_eq!(format!("{}", Imm64(9999)), "9999"); - assert_eq!(format!("{}", Imm64(10000)), "0x2710"); - assert_eq!(format!("{}", Imm64(-9999)), "-9999"); - assert_eq!(format!("{}", Imm64(-10000)), "0xffff_ffff_ffff_d8f0"); - assert_eq!(format!("{}", Imm64(0xffff)), "0xffff"); - assert_eq!(format!("{}", Imm64(0x10000)), "0x0001_0000"); + assert_eq!(Imm64(0).to_string(), "0"); + assert_eq!(Imm64(9999).to_string(), "9999"); + assert_eq!(Imm64(10000).to_string(), "0x2710"); + assert_eq!(Imm64(-9999).to_string(), "-9999"); + assert_eq!(Imm64(-10000).to_string(), "0xffff_ffff_ffff_d8f0"); + assert_eq!(Imm64(0xffff).to_string(), "0xffff"); + assert_eq!(Imm64(0x10000).to_string(), "0x0001_0000"); } // Verify that `text` can be parsed as a `T` into a value that displays as `want`. @@ -520,7 +520,7 @@ mod tests { { match text.parse::() { Err(s) => panic!("\"{}\".parse() error: {}", text, s), - Ok(x) => assert_eq!(format!("{}", x), want), + Ok(x) => assert_eq!(x.to_string(), want), } } @@ -529,7 +529,7 @@ mod tests { where ::Err: Display { match text.parse::() { - Err(s) => assert_eq!(format!("{}", s), msg), + Err(s) => assert_eq!(s.to_string(), msg), Ok(x) => panic!("Wanted Err({}), but got {}", msg, x), } } @@ -584,33 +584,32 @@ mod tests { #[test] fn format_ieee32() { - assert_eq!(format!("{}", Ieee32::new(0.0)), "0.0"); - assert_eq!(format!("{}", Ieee32::new(-0.0)), "-0.0"); - assert_eq!(format!("{}", Ieee32::new(1.0)), "0x1.000000p0"); - assert_eq!(format!("{}", Ieee32::new(1.5)), "0x1.800000p0"); - assert_eq!(format!("{}", Ieee32::new(0.5)), "0x1.000000p-1"); - assert_eq!(format!("{}", Ieee32::new(f32::EPSILON)), "0x1.000000p-23"); - assert_eq!(format!("{}", Ieee32::new(f32::MIN)), "-0x1.fffffep127"); - assert_eq!(format!("{}", Ieee32::new(f32::MAX)), "0x1.fffffep127"); + assert_eq!(Ieee32::new(0.0).to_string(), "0.0"); + assert_eq!(Ieee32::new(-0.0).to_string(), "-0.0"); + assert_eq!(Ieee32::new(1.0).to_string(), "0x1.000000p0"); + assert_eq!(Ieee32::new(1.5).to_string(), "0x1.800000p0"); + assert_eq!(Ieee32::new(0.5).to_string(), "0x1.000000p-1"); + assert_eq!(Ieee32::new(f32::EPSILON).to_string(), "0x1.000000p-23"); + assert_eq!(Ieee32::new(f32::MIN).to_string(), "-0x1.fffffep127"); + assert_eq!(Ieee32::new(f32::MAX).to_string(), "0x1.fffffep127"); // Smallest positive normal number. - assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE)), + assert_eq!(Ieee32::new(f32::MIN_POSITIVE).to_string(), "0x1.000000p-126"); // Subnormals. - assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE / 2.0)), + assert_eq!(Ieee32::new(f32::MIN_POSITIVE / 2.0).to_string(), "0x0.800000p-126"); - assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON)), + assert_eq!(Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON).to_string(), "0x0.000002p-126"); - assert_eq!(format!("{}", Ieee32::new(f32::INFINITY)), "Inf"); - assert_eq!(format!("{}", Ieee32::new(f32::NEG_INFINITY)), "-Inf"); - assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "NaN"); - assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-NaN"); + assert_eq!(Ieee32::new(f32::INFINITY).to_string(), "Inf"); + assert_eq!(Ieee32::new(f32::NEG_INFINITY).to_string(), "-Inf"); + assert_eq!(Ieee32::new(f32::NAN).to_string(), "NaN"); + assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(format!("{}", Ieee32::from_bits(0x7fc00001)), "NaN:0x1"); - assert_eq!(format!("{}", Ieee32::from_bits(0x7ff00001)), "NaN:0x300001"); + assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "NaN:0x1"); + assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "NaN:0x300001"); // Signaling NaNs. - assert_eq!(format!("{}", Ieee32::from_bits(0x7f800001)), "sNaN:0x1"); - assert_eq!(format!("{}", Ieee32::from_bits(0x7fa00001)), - "sNaN:0x200001"); + assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "sNaN:0x1"); + assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "sNaN:0x200001"); } #[test] @@ -682,38 +681,35 @@ mod tests { #[test] fn format_ieee64() { - assert_eq!(format!("{}", Ieee64::new(0.0)), "0.0"); - assert_eq!(format!("{}", Ieee64::new(-0.0)), "-0.0"); - assert_eq!(format!("{}", Ieee64::new(1.0)), "0x1.0000000000000p0"); - assert_eq!(format!("{}", Ieee64::new(1.5)), "0x1.8000000000000p0"); - assert_eq!(format!("{}", Ieee64::new(0.5)), "0x1.0000000000000p-1"); - assert_eq!(format!("{}", Ieee64::new(f64::EPSILON)), + assert_eq!(Ieee64::new(0.0).to_string(), "0.0"); + assert_eq!(Ieee64::new(-0.0).to_string(), "-0.0"); + assert_eq!(Ieee64::new(1.0).to_string(), "0x1.0000000000000p0"); + assert_eq!(Ieee64::new(1.5).to_string(), "0x1.8000000000000p0"); + assert_eq!(Ieee64::new(0.5).to_string(), "0x1.0000000000000p-1"); + assert_eq!(Ieee64::new(f64::EPSILON).to_string(), "0x1.0000000000000p-52"); - assert_eq!(format!("{}", Ieee64::new(f64::MIN)), - "-0x1.fffffffffffffp1023"); - assert_eq!(format!("{}", Ieee64::new(f64::MAX)), - "0x1.fffffffffffffp1023"); + assert_eq!(Ieee64::new(f64::MIN).to_string(), "-0x1.fffffffffffffp1023"); + assert_eq!(Ieee64::new(f64::MAX).to_string(), "0x1.fffffffffffffp1023"); // Smallest positive normal number. - assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE)), + assert_eq!(Ieee64::new(f64::MIN_POSITIVE).to_string(), "0x1.0000000000000p-1022"); // Subnormals. - assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE / 2.0)), + assert_eq!(Ieee64::new(f64::MIN_POSITIVE / 2.0).to_string(), "0x0.8000000000000p-1022"); - assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON)), + assert_eq!(Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON).to_string(), "0x0.0000000000001p-1022"); - assert_eq!(format!("{}", Ieee64::new(f64::INFINITY)), "Inf"); - assert_eq!(format!("{}", Ieee64::new(f64::NEG_INFINITY)), "-Inf"); - assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "NaN"); - assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-NaN"); + assert_eq!(Ieee64::new(f64::INFINITY).to_string(), "Inf"); + assert_eq!(Ieee64::new(f64::NEG_INFINITY).to_string(), "-Inf"); + assert_eq!(Ieee64::new(f64::NAN).to_string(), "NaN"); + assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(format!("{}", Ieee64::from_bits(0x7ff8000000000001)), - "NaN:0x1"); - assert_eq!(format!("{}", Ieee64::from_bits(0x7ffc000000000001)), + assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), "NaN:0x1"); + assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(), "NaN:0x4000000000001"); // Signaling NaNs. - assert_eq!(format!("{}", Ieee64::from_bits(0x7ff0000000000001)), + assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(), "sNaN:0x1"); - assert_eq!(format!("{}", Ieee64::from_bits(0x7ff4000000000001)), + assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(), "sNaN:0x4000000000001"); } diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index e982a65ddd..702a371609 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -449,7 +449,7 @@ mod tests { ty: types::I32, }; let inst = func.make_inst(idata); - assert_eq!(format!("{}", inst), "inst0"); + assert_eq!(inst.to_string(), "inst0"); // Immutable reference resolution. let ins = func.inst(inst); @@ -463,8 +463,8 @@ mod tests { let ss0 = func.make_stack_slot(StackSlotData::new(4)); let ss1 = func.make_stack_slot(StackSlotData::new(8)); - assert_eq!(format!("{}", ss0), "ss0"); - assert_eq!(format!("{}", ss1), "ss1"); + assert_eq!(ss0.to_string(), "ss0"); + assert_eq!(ss1.to_string(), "ss1"); assert_eq!(func[ss0].size, 4); assert_eq!(func[ss1].size, 8); diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 9d4f2d93af..967879c14d 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -337,38 +337,37 @@ mod tests { assert_eq!(big.lane_count(), 256); assert_eq!(big.bits(), 64 * 256); - assert_eq!(format!("{}", big.half_vector().unwrap()), "f64x128"); - assert_eq!(format!("{}", B1.by(2).unwrap().half_vector().unwrap()), - "b1"); + assert_eq!(big.half_vector().unwrap().to_string(), "f64x128"); + assert_eq!(B1.by(2).unwrap().half_vector().unwrap().to_string(), "b1"); assert_eq!(I32.half_vector(), None); assert_eq!(VOID.half_vector(), None); } #[test] fn format_scalars() { - assert_eq!(format!("{}", VOID), "void"); - assert_eq!(format!("{}", B1), "b1"); - assert_eq!(format!("{}", B8), "b8"); - assert_eq!(format!("{}", B16), "b16"); - assert_eq!(format!("{}", B32), "b32"); - assert_eq!(format!("{}", B64), "b64"); - assert_eq!(format!("{}", I8), "i8"); - assert_eq!(format!("{}", I16), "i16"); - assert_eq!(format!("{}", I32), "i32"); - assert_eq!(format!("{}", I64), "i64"); - assert_eq!(format!("{}", F32), "f32"); - assert_eq!(format!("{}", F64), "f64"); + assert_eq!(VOID.to_string(), "void"); + assert_eq!(B1.to_string(), "b1"); + assert_eq!(B8.to_string(), "b8"); + assert_eq!(B16.to_string(), "b16"); + assert_eq!(B32.to_string(), "b32"); + assert_eq!(B64.to_string(), "b64"); + assert_eq!(I8.to_string(), "i8"); + assert_eq!(I16.to_string(), "i16"); + assert_eq!(I32.to_string(), "i32"); + assert_eq!(I64.to_string(), "i64"); + assert_eq!(F32.to_string(), "f32"); + assert_eq!(F64.to_string(), "f64"); } #[test] fn format_vectors() { - assert_eq!(format!("{}", B1.by(8).unwrap()), "b1x8"); - assert_eq!(format!("{}", B8.by(1).unwrap()), "b8"); - assert_eq!(format!("{}", B16.by(256).unwrap()), "b16x256"); - assert_eq!(format!("{}", B32.by(4).unwrap().by(2).unwrap()), "b32x8"); - assert_eq!(format!("{}", B64.by(8).unwrap()), "b64x8"); - assert_eq!(format!("{}", I8.by(64).unwrap()), "i8x64"); - assert_eq!(format!("{}", F64.by(2).unwrap()), "f64x2"); + assert_eq!(B1.by(8).unwrap().to_string(), "b1x8"); + assert_eq!(B8.by(1).unwrap().to_string(), "b8"); + assert_eq!(B16.by(256).unwrap().to_string(), "b16x256"); + assert_eq!(B32.by(4).unwrap().by(2).unwrap().to_string(), "b32x8"); + assert_eq!(B64.by(8).unwrap().to_string(), "b64x8"); + assert_eq!(I8.by(64).unwrap().to_string(), "i8x64"); + assert_eq!(F64.by(2).unwrap().to_string(), "f64x2"); assert_eq!(I8.by(3), None); assert_eq!(I8.by(512), None); assert_eq!(VOID.by(4), None); @@ -377,24 +376,24 @@ mod tests { #[test] fn argument_type() { let mut t = ArgumentType::new(I32); - assert_eq!(format!("{}", t), "i32"); + assert_eq!(t.to_string(), "i32"); t.extension = ArgumentExtension::Uext; - assert_eq!(format!("{}", t), "i32 uext"); + assert_eq!(t.to_string(), "i32 uext"); t.inreg = true; - assert_eq!(format!("{}", t), "i32 uext inreg"); + assert_eq!(t.to_string(), "i32 uext inreg"); } #[test] fn signatures() { let mut sig = Signature::new(); - assert_eq!(format!("{}", sig), "()"); + assert_eq!(sig.to_string(), "()"); sig.argument_types.push(ArgumentType::new(I32)); - assert_eq!(format!("{}", sig), "(i32)"); + assert_eq!(sig.to_string(), "(i32)"); sig.return_types.push(ArgumentType::new(F32)); - assert_eq!(format!("{}", sig), "(i32) -> f32"); + assert_eq!(sig.to_string(), "(i32) -> f32"); sig.argument_types.push(ArgumentType::new(I32.by(4).unwrap())); - assert_eq!(format!("{}", sig), "(i32, i32x4) -> f32"); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32"); sig.return_types.push(ArgumentType::new(B8)); - assert_eq!(format!("{}", sig), "(i32, i32x4) -> f32, b8"); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8"); } } diff --git a/src/libctonfile/parser.rs b/src/libctonfile/parser.rs index a014ca0253..e74ae22c38 100644 --- a/src/libctonfile/parser.rs +++ b/src/libctonfile/parser.rs @@ -369,17 +369,15 @@ mod tests { let sig2 = Parser::new("(i8 inreg uext, f32, f64) -> i32 sext, f64") .parse_signature() .unwrap(); - assert_eq!(format!("{}", sig2), + assert_eq!(sig2.to_string(), "(i8 uext inreg, f32, f64) -> i32 sext, f64"); // `void` is not recognized as a type by the lexer. It should not appear in files. - assert_eq!(format!("{}", - Parser::new("() -> void").parse_signature().unwrap_err()), + assert_eq!(Parser::new("() -> void").parse_signature().unwrap_err().to_string(), "1: expected argument type"); - assert_eq!(format!("{}", Parser::new("i8 -> i8").parse_signature().unwrap_err()), + assert_eq!(Parser::new("i8 -> i8").parse_signature().unwrap_err().to_string(), "1: expected function signature: ( args... )"); - assert_eq!(format!("{}", - Parser::new("(i8 -> i8").parse_signature().unwrap_err()), + assert_eq!(Parser::new("(i8 -> i8").parse_signature().unwrap_err().to_string(), "1: expected ')' after function arguments"); } @@ -394,21 +392,21 @@ mod tests { assert_eq!(func.name, "foo"); let mut iter = func.stack_slot_iter(); let ss0 = iter.next().unwrap(); - assert_eq!(format!("{}", ss0), "ss0"); + assert_eq!(ss0.to_string(), "ss0"); assert_eq!(func[ss0].size, 13); let ss1 = iter.next().unwrap(); - assert_eq!(format!("{}", ss1), "ss1"); + assert_eq!(ss1.to_string(), "ss1"); assert_eq!(func[ss1].size, 1); assert_eq!(iter.next(), None); // Catch suplicate definitions. - assert_eq!(format!("{}", - Parser::new("function bar() { - ss1 = stack_slot 13 - ss1 = stack_slot 1 + assert_eq!(Parser::new("function bar() { + ss1 = stack_slot 13 + ss1 = stack_slot 1 }") - .parse_function() - .unwrap_err()), + .parse_function() + .unwrap_err() + .to_string(), "3: duplicate stack slot: ss1"); } } diff --git a/src/test-all.sh b/src/test-all.sh new file mode 100755 index 0000000000..6838b1544f --- /dev/null +++ b/src/test-all.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd $(dirname "$0")/tools +cargo test -p cretonne -p ctonfile -p cretonne-tools +cargo doc -p cretonne -p ctonfile -p cretonne-tools From ddea422cebdeb7c8c678da8e0f901f105afde920 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 13:30:07 -0700 Subject: [PATCH 062/968] Add a write.rs module. Convert a function to text. --- src/libcretonne/lib.rs | 1 + src/libcretonne/repr.rs | 5 ++ src/libcretonne/write.rs | 113 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 src/libcretonne/write.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index b67ca5df10..f47b049156 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -8,3 +8,4 @@ pub mod types; pub mod immediates; pub mod repr; +pub mod write; diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 702a371609..3917fa8f48 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -382,6 +382,11 @@ impl Function { Self::with_name_signature(FunctionName::new(), Signature::new()) } + /// Get the signature of this function. + pub fn own_signature(&self) -> &Signature { + &self.signature + } + /// Allocate a new stack slot. pub fn make_stack_slot(&mut self, data: StackSlotData) -> StackSlot { let ss = StackSlot::new(self.stack_slots.len()); diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs new file mode 100644 index 0000000000..7281294fe6 --- /dev/null +++ b/src/libcretonne/write.rs @@ -0,0 +1,113 @@ +//! Converting Cretonne IL to text. +//! +//! The `write` module provides the `write_function` function which converts an IL `Function` to an +//! equivalent textual representation. This textual representation can be read back by the +//! `ctonfile` crate. + +use std::io::{self, Write}; +use repr::Function; + +pub type Result = io::Result<()>; + +/// Write `func` to `w` as equivalent text. +pub fn write_function(w: &mut Write, func: &Function) -> Result { + try!(write_spec(w, func)); + try!(writeln!(w, " {{")); + try!(write_preamble(w, func)); + writeln!(w, "}}") +} + +/// Convert `func` to a string. +pub fn function_to_string(func: &Function) -> String { + let mut buffer: Vec = Vec::new(); + // Any errors here would be out-of-memory, which should not happen with normal functions. + write_function(&mut buffer, func).unwrap(); + // A UTF-8 conversion error is a real bug. + String::from_utf8(buffer).unwrap() +} + +// ====--------------------------------------------------------------------------------------====// +// +// Function spec. +// +// ====--------------------------------------------------------------------------------------====// + +// The function name may need quotes if it doesn't parse as an identifier. +fn needs_quotes(name: &str) -> bool { + let mut iter = name.chars(); + if let Some(ch) = iter.next() { + !ch.is_alphabetic() || !iter.all(char::is_alphanumeric) + } else { + // A blank function name needs quotes. + true + } +} + +// Use Rust's escape_default which provides a few simple \t \r \n \' \" \\ escapes and uses +// \u{xxxx} for anything else outside the ASCII printable range. +fn escaped(name: &str) -> String { + name.chars().flat_map(char::escape_default).collect() +} + +fn write_spec(w: &mut Write, func: &Function) -> Result { + let sig = func.own_signature(); + if !needs_quotes(&func.name) { + write!(w, "function {}{}", func.name, sig) + } else { + write!(w, "function \"{}\" {}", escaped(&func.name), sig) + } +} + +fn write_preamble(w: &mut Write, func: &Function) -> Result { + let mut any = false; + + for ss in func.stack_slot_iter() { + any = true; + try!(writeln!(w, " {} = {}", ss, func[ss])); + } + + // Put a blank line after the preamble unless it was empty. + if any { + writeln!(w, "") + } else { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::{needs_quotes, escaped}; + use repr::{Function, StackSlotData}; + + #[test] + fn quoting() { + assert_eq!(needs_quotes(""), true); + assert_eq!(needs_quotes("x"), false); + assert_eq!(needs_quotes(" "), true); + assert_eq!(needs_quotes("0"), true); + assert_eq!(needs_quotes("x0"), false); + } + + #[test] + fn escaping() { + assert_eq!(escaped(""), ""); + assert_eq!(escaped("x"), "x"); + assert_eq!(escaped(" "), " "); + assert_eq!(escaped(" \n"), " \\n"); + assert_eq!(escaped("a\u{1000}v"), "a\\u{1000}v"); + } + + #[test] + fn basic() { + let mut f = Function::new(); + assert_eq!(function_to_string(&f), "function \"\" () {\n}\n"); + + f.name.push_str("foo"); + assert_eq!(function_to_string(&f), "function foo() {\n}\n"); + + f.make_stack_slot(StackSlotData::new(4)); + assert_eq!(function_to_string(&f), + "function foo() {\n ss0 = stack_slot 4\n\n}\n"); + } +} From 810a90e3222e33af36433a799627098e71df8198 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 14:32:10 -0700 Subject: [PATCH 063/968] Rename libraries libctonfile -> libreader. This library will only provide .cton file reading/parsing services which are not needed after deployment. Code for writing .cton files lives in the main cretonne library because it is fairly small, and because it is useful for extracting test cases from a deployed library. --- src/libcretonne/write.rs | 2 +- src/{libctonfile => libreader}/Cargo.toml | 4 ++-- src/{libctonfile => libreader}/lexer.rs | 0 src/{libctonfile => libreader}/lib.rs | 4 ++-- src/{libctonfile => libreader}/parser.rs | 0 src/test-all.sh | 4 ++-- src/tools/Cargo.lock | 4 ++-- src/tools/Cargo.toml | 2 +- src/tools/main.rs | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) rename src/{libctonfile => libreader}/Cargo.toml (78%) rename src/{libctonfile => libreader}/lexer.rs (100%) rename src/{libctonfile => libreader}/lib.rs (82%) rename src/{libctonfile => libreader}/parser.rs (100%) diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 7281294fe6..f245d977c2 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -2,7 +2,7 @@ //! //! The `write` module provides the `write_function` function which converts an IL `Function` to an //! equivalent textual representation. This textual representation can be read back by the -//! `ctonfile` crate. +//! `cretonne-reader` crate. use std::io::{self, Write}; use repr::Function; diff --git a/src/libctonfile/Cargo.toml b/src/libreader/Cargo.toml similarity index 78% rename from src/libctonfile/Cargo.toml rename to src/libreader/Cargo.toml index 7b2826b072..478ee3b586 100644 --- a/src/libctonfile/Cargo.toml +++ b/src/libreader/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ["The Cretonne Project Developers"] -name = "ctonfile" +name = "cretonne-reader" version = "0.0.0" publish = false [lib] -name = "ctonfile" +name = "cton_reader" path = "lib.rs" [dependencies] diff --git a/src/libctonfile/lexer.rs b/src/libreader/lexer.rs similarity index 100% rename from src/libctonfile/lexer.rs rename to src/libreader/lexer.rs diff --git a/src/libctonfile/lib.rs b/src/libreader/lib.rs similarity index 82% rename from src/libctonfile/lib.rs rename to src/libreader/lib.rs index 9dcb169b7d..2d63e68264 100644 --- a/src/libctonfile/lib.rs +++ b/src/libreader/lib.rs @@ -1,11 +1,11 @@ // ====------------------------------------------------------------------------------------==== // // -// Cretonne file read/write library. +// Cretonne file reader library. // // ====------------------------------------------------------------------------------------==== // // -// The libctonfile library supports reading and writing .cton files. This functionality is needed +// The cton_reader library supports reading and writing .cton files. This functionality is needed // for testing Cretonne, but is not essential for a JIT compiler. // // ====------------------------------------------------------------------------------------==== // diff --git a/src/libctonfile/parser.rs b/src/libreader/parser.rs similarity index 100% rename from src/libctonfile/parser.rs rename to src/libreader/parser.rs diff --git a/src/test-all.sh b/src/test-all.sh index 6838b1544f..4fe15bdca2 100755 --- a/src/test-all.sh +++ b/src/test-all.sh @@ -1,4 +1,4 @@ #!/bin/bash cd $(dirname "$0")/tools -cargo test -p cretonne -p ctonfile -p cretonne-tools -cargo doc -p cretonne -p ctonfile -p cretonne-tools +cargo test -p cretonne -p cretonne-reader -p cretonne-tools +cargo doc -p cretonne -p cretonne-reader -p cretonne-tools diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index 90722ef5e7..91e9a5fae4 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -3,7 +3,7 @@ name = "cretonne-tools" version = "0.0.0" dependencies = [ "cretonne 0.0.0", - "ctonfile 0.0.0", + "cretonne-reader 0.0.0", ] [[package]] @@ -11,7 +11,7 @@ name = "cretonne" version = "0.0.0" [[package]] -name = "ctonfile" +name = "cretonne-reader" version = "0.0.0" dependencies = [ "cretonne 0.0.0", diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index b4477bd1e8..5811ca9a1a 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -11,4 +11,4 @@ path = "main.rs" [dependencies] cretonne = { path = "../libcretonne" } -ctonfile = { path = "../libctonfile" } +cretonne-reader = { path = "../libreader" } diff --git a/src/tools/main.rs b/src/tools/main.rs index 33cc1afe81..97b261d22d 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -1,5 +1,5 @@ extern crate cretonne; -extern crate ctonfile; +extern crate cton_reader; fn main() {} From d34ced543774df694cb849c5a35125cfe70a6570 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 15:13:09 -0700 Subject: [PATCH 064/968] Rename the 'cretonne' binary. It soome that 'cargo doc' gets confused when there is both a library crate and a binary called 'cretonne'. --- src/test-all.sh | 6 ++++-- src/tools/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test-all.sh b/src/test-all.sh index 4fe15bdca2..f6dca84322 100755 --- a/src/test-all.sh +++ b/src/test-all.sh @@ -1,4 +1,6 @@ #!/bin/bash cd $(dirname "$0")/tools -cargo test -p cretonne -p cretonne-reader -p cretonne-tools -cargo doc -p cretonne -p cretonne-reader -p cretonne-tools +PKGS="-p cretonne -p cretonne-reader -p cretonne-tools" +cargo build $PKGS +cargo doc $PKGS +cargo test $PKGS diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index 5811ca9a1a..702bd635e6 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -6,7 +6,7 @@ description = "Binaries for testing the Cretonne library" publish = false [[bin]] -name = "cretonne" +name = "cton-util" path = "main.rs" [dependencies] From 4e4c6346668b2fce1789ec2f59eecf9504f06f77 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 15:31:25 -0700 Subject: [PATCH 065/968] Set up Travis CI --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..041073c206 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: rust +rust: + - stable + - beta + - nightly +script: src/test-all.sh From 3c0e2f6e9d8d5562f3c24289db625603eacd3b70 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 15:33:24 -0700 Subject: [PATCH 066/968] Build status badge. --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 6fae53072f..0a26eae385 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,10 @@ target-independent intermediate language into executable machine code. :target: https://cretonne.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status +.. image:: https://travis-ci.org/stoklund/cretonne.svg?branch=master + :target: https://travis-ci.org/stoklund/cretonne + :alt: Build Status + Cretonne is designed to be a code generator for WebAssembly with these design goals: From e414ce6315a8e20bf906f9cc8b4e79e6d8c08517 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 15:45:48 -0700 Subject: [PATCH 067/968] Exit test script on errors. --- src/test-all.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test-all.sh b/src/test-all.sh index f6dca84322..c0d9977ed1 100755 --- a/src/test-all.sh +++ b/src/test-all.sh @@ -1,5 +1,11 @@ #!/bin/bash + +# Exit immediately on errors. +set -e + +# Run from the src/tools directory which includes all our crates. cd $(dirname "$0")/tools + PKGS="-p cretonne -p cretonne-reader -p cretonne-tools" cargo build $PKGS cargo doc $PKGS From e026b36db416081ba08015cc64ad08cb951667b7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Apr 2016 17:23:34 -0700 Subject: [PATCH 068/968] Implement Index for Function. When Function serves as a container for IL entities, use the Index trait to translate a reference class to a Data object. Works for: - StackSlot -> StackSlotData - Inst -> InstructionData --- src/libcretonne/repr.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 3917fa8f48..c219ecb4b3 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -261,6 +261,15 @@ impl Display for Inst { } } +/// Allow immutable access to instructions via function indexing. +impl Index for Function { + type Output = InstructionData; + + fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData { + &self.instructions[inst.index()] + } +} + // ====--------------------------------------------------------------------------------------====// // // Value implementation. @@ -402,11 +411,6 @@ impl Function { } } - /// Resolve an instruction reference. - pub fn inst(&self, i: Inst) -> &InstructionData { - &self.instructions[i.0 as usize] - } - /// Create a new instruction. pub fn make_inst(&mut self, data: InstructionData) -> Inst { let iref = Inst::new(self.instructions.len()); @@ -427,7 +431,7 @@ impl Function { use self::ExpandedValue::*; use self::ValueData::*; match v.expand() { - Direct(i) => self.inst(i).first_type(), + Direct(i) => self[i].first_type(), Table(i) => { match self.extended_values[i] { Unused => panic!("Can't get type of Unused value {}", v), @@ -457,7 +461,7 @@ mod tests { assert_eq!(inst.to_string(), "inst0"); // Immutable reference resolution. - let ins = func.inst(inst); + let ins = &func[inst]; assert_eq!(ins.opcode(), Opcode::Iconst); assert_eq!(ins.first_type(), types::I32); } From 1d768ff734212f1159fbed57020da948f3ce12d7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 2 May 2016 15:25:43 -0700 Subject: [PATCH 069/968] Implement value lists. Values that are defined together are represented as a singly linked list. These lists appear in: - Instructions with multiple result values. The first result value is special, and the following results form a linked list of Def extended_value table entries. - EBB arguments are represented as a linked list of Argument extended_value table entries. The EbbData struct has pointers to the first and last argument to allow fast insertion at both ends. Add a Values iterator type whicih can enumerate both kinds of value lists. --- src/libcretonne/repr.rs | 287 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 265 insertions(+), 22 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index c219ecb4b3..807be698ac 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -3,6 +3,7 @@ use types::{Type, FunctionName, Signature}; use immediates::*; +use std::default::Default; use std::fmt::{self, Display, Formatter, Write}; use std::ops::Index; use std::u32; @@ -81,12 +82,20 @@ pub struct StackSlotData { } /// Contents of an extended basic block. +/// +/// Arguments for an extended basic block are values that dominate everything in the EBB. All +/// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must +/// match the function arguments. #[derive(Debug)] pub struct EbbData { - /// Arguments for this extended basic block. These values dominate everything in the EBB. - /// All branches to this EBB must provide matching arguments, and the arguments to the entry - /// EBB must match the function arguments. - pub arguments: Vec, + /// First argument to this EBB, or `NO_VALUE` if the block has no arguments. + /// + /// The arguments are all ValueData::Argument entries that form a linked list from `first_arg` + /// to `last_arg`. + first_arg: Value, + + /// Last argument to this EBB, or `NO_VALUE` if the block has no arguments. + last_arg: Value, } /// Contents on an instruction. @@ -132,12 +141,11 @@ pub enum InstructionData { /// Payload of a call instruction. #[derive(Debug)] pub struct CallData { - // Number of result values. - results: u8, + /// Second result value for a call producing multiple return values. + second_result: Value, - // Dynamically sized array containing `results-1` result values (not including the first value) - // followed by the argument values. - values: Vec, + // Dynamically sized array containing call argument values. + arguments: Vec, } @@ -233,7 +241,10 @@ impl Display for Ebb { impl EbbData { fn new() -> EbbData { - EbbData { arguments: Vec::new() } + EbbData { + first_arg: NO_VALUE, + last_arg: NO_VALUE, + } } } @@ -284,6 +295,9 @@ enum ExpandedValue { // This value is described in the extended value table. Table(usize), + + // This is NO_VALUE. + None, } impl Value { @@ -302,6 +316,9 @@ impl Value { // Expand the internal representation into something useful. fn expand(&self) -> ExpandedValue { use self::ExpandedValue::*; + if *self == NO_VALUE { + return None; + } let index = (self.0 / 2) as usize; if self.0 % 2 == 0 { Direct(Inst::new(index)) @@ -311,13 +328,20 @@ impl Value { } } +impl Default for Value { + fn default() -> Value { + NO_VALUE + } +} + /// Display a `Value` reference as "v7" or "v2x". impl Display for Value { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { use self::ExpandedValue::*; match self.expand() { Direct(i) => write!(fmt, "v{}", i.0), - Table(i) => write!(fmt, "v{}x", i), + Table(i) => write!(fmt, "vx{}", i), + None => write!(fmt, "NO_VALUE"), } } } @@ -326,25 +350,67 @@ impl Display for Value { // Other values have an entry in the value table. #[derive(Debug)] enum ValueData { - // An unused entry in the value table. No instruction should be defining or using this value. - Unused, - // Value is defined by an instruction, but it is not the first result. Def { ty: Type, - num: u8, def: Inst, + next: Value, // Next result defined by `def`. }, // Value is an EBB argument. Argument { ty: Type, - num: u8, ebb: Ebb, + next: Value, // Next argument to `ebb`. }, } +/// Iterate through a list of related value references, such as: +/// +/// - All results defined by an instruction. +/// - All arguments to an EBB +/// +/// A value iterator borrows a Function reference. +pub struct Values<'a> { + func: &'a Function, + cur: Value, +} + +impl<'a> Iterator for Values<'a> { + type Item = Value; + + fn next(&mut self) -> Option { + let prev = self.cur; + + // Advance self.cur to the next value, or NO_VALUE. + self.cur = match prev.expand() { + ExpandedValue::Direct(inst) => self.func[inst].second_result().unwrap_or_default(), + ExpandedValue::Table(index) => { + match self.func.extended_values[index] { + ValueData::Def { next, .. } => next, + ValueData::Argument { next, .. } => next, + } + } + ExpandedValue::None => return None, + }; + + Some(prev) + } +} + impl InstructionData { + /// Create data for a call instruction. + pub fn call(opc: Opcode, return_type: Type) -> InstructionData { + InstructionData::Call { + opcode: opc, + ty: return_type, + data: Box::new(CallData { + second_result: NO_VALUE, + arguments: Vec::new(), + }), + } + } + /// Get the opcode of this instruction. pub fn opcode(&self) -> Opcode { use self::InstructionData::*; @@ -370,6 +436,31 @@ impl InstructionData { Call { ty, .. } => ty, } } + + /// Second result value, if any. + fn second_result(&self) -> Option { + use self::InstructionData::*; + match *self { + Nullary { .. } => None, + Unary { .. } => None, + UnaryImm { .. } => None, + Binary { .. } => None, + BinaryImm { .. } => None, + Call { ref data, .. } => Some(data.second_result), + } + } + + fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> { + use self::InstructionData::*; + match *self { + Nullary { .. } => None, + Unary { .. } => None, + UnaryImm { .. } => None, + Binary { .. } => None, + BinaryImm { .. } => None, + Call { ref mut data, .. } => Some(&mut data.second_result), + } + } } impl Function { @@ -396,6 +487,8 @@ impl Function { &self.signature } + // Stack slots. + /// Allocate a new stack slot. pub fn make_stack_slot(&mut self, data: StackSlotData) -> StackSlot { let ss = StackSlot::new(self.stack_slots.len()); @@ -411,14 +504,71 @@ impl Function { } } + // Instructions. + /// Create a new instruction. - pub fn make_inst(&mut self, data: InstructionData) -> Inst { - let iref = Inst::new(self.instructions.len()); + /// + /// The instruction is allowed to produce at most one result as indicated by `data.ty`. Use + /// `make_multi_inst()` to create instructions with multiple results. + pub fn make_inst(&mut self, data: InstructionData, extra_result_types: &[Type]) -> Inst { + let inst = Inst::new(self.instructions.len()); self.instructions.push(data); - // FIXME: Allocate extended value table entries if needed. - iref + inst } + /// Make an instruction that may produce multiple results. + /// + /// The type of the first result is `data.ty`. If the instruction generates more than one + /// result, additional result types are in `extra_result_types`. + /// + /// Not all instruction formats can represent multiple result values. This function will panic + /// if the format of `data` is insufficient. + pub fn make_multi_inst(&mut self, data: InstructionData, extra_result_types: &[Type]) -> Inst { + let inst = self.make_inst(data); + + if !extra_result_types.is_empty() { + // Additional values form a linked list starting from the second result value. Generate + // the list backwards so we don't have to modify value table entries in place. (This + // causes additional result values to be numbered backwards which is not the aestetic + // choice, but since it is only visible in extremely rare instructions with 3+ results, + // we don't care). + let mut head = NO_VALUE; + for ty in extra_result_types.into_iter().rev() { + head = self.make_value(ValueData::Def { + ty: *ty, + def: inst, + next: head, + }); + } + + // Update the second_result pointer in `inst`. + if let Some(second_result_ref) = self.instructions[inst.index()].second_result_mut() { + *second_result_ref = head; + } else { + panic!("Instruction format doesn't allow multiple results."); + } + } + + inst + } + + /// Get the first result of an instruction. + /// + /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. + pub fn first_result(&self, inst: Inst) -> Value { + Value::new_direct(inst) + } + + /// Iterate through all the results of an instruction. + pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> { + Values { + func: self, + cur: Value::new_direct(inst), + } + } + + // Basic blocks + /// Create a new basic block. pub fn make_ebb(&mut self) -> Ebb { let ebb = Ebb::new(self.extended_basic_blocks.len()); @@ -426,6 +576,60 @@ impl Function { ebb } + /// Reference the representation of an EBB. + fn ebb(&self, ebb: Ebb) -> &EbbData { + &self.extended_basic_blocks[ebb.index()] + } + + /// Mutably reference the representation of an EBB. + fn ebb_mut(&mut self, ebb: Ebb) -> &mut EbbData { + &mut self.extended_basic_blocks[ebb.index()] + } + + /// Append an argument with type `ty` to `ebb`. + pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { + let val = self.make_value(ValueData::Argument { + ty: ty, + ebb: ebb, + next: NO_VALUE, + }); + + let last_arg = self.ebb(ebb).last_arg; + match last_arg.expand() { + // If last_arg = NO_VALUE, we're adding the first EBB argument. + ExpandedValue::None => self.ebb_mut(ebb).first_arg = val, + ExpandedValue::Table(index) => { + // Append to linked list of arguments. + if let ValueData::Argument { ref mut next, .. } = self.extended_values[index] { + *next = val; + } else { + panic!("wrong type of extended value referenced by Ebb::last_arg"); + } + } + ExpandedValue::Direct(_) => panic!("Direct value cannot appear as EBB argument"), + } + self.ebb_mut(ebb).last_arg = val; + + val + } + + /// Iterate through the arguments to an EBB. + pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> { + Values { + func: self, + cur: self.ebb(ebb).first_arg, + } + } + + // Values. + + /// Allocate an extended value entry. + fn make_value(&mut self, data: ValueData) -> Value { + let vref = Value::new_table(self.extended_values.len()); + self.extended_values.push(data); + vref + } + /// Get the type of a value. pub fn value_type(&self, v: Value) -> Type { use self::ExpandedValue::*; @@ -434,11 +638,11 @@ impl Function { Direct(i) => self[i].first_type(), Table(i) => { match self.extended_values[i] { - Unused => panic!("Can't get type of Unused value {}", v), Def { ty, .. } => ty, Argument { ty, .. } => ty, } } + None => panic!("NO_VALUE has no type"), } } } @@ -457,7 +661,7 @@ mod tests { opcode: Opcode::Iconst, ty: types::I32, }; - let inst = func.make_inst(idata); + let inst = func.make_inst(idata, &[]); assert_eq!(inst.to_string(), "inst0"); // Immutable reference resolution. @@ -466,6 +670,21 @@ mod tests { assert_eq!(ins.first_type(), types::I32); } + #[test] + fn multiple_results() { + use types::*; + let mut func = Function::new(); + + let idata = InstructionData::call(Opcode::Vconst, I64); + let inst = func.make_inst(idata, &[I8, F64]); + assert_eq!(inst.to_string(), "inst0"); + let results: Vec = func.inst_results(inst).collect(); + assert_eq!(results.len(), 3); + assert_eq!(func.value_type(results[0]), I64); + assert_eq!(func.value_type(results[1]), I8); + assert_eq!(func.value_type(results[2]), F64); + } + #[test] fn stack_slot() { let mut func = Function::new(); @@ -479,4 +698,28 @@ mod tests { assert_eq!(func[ss1].size, 8); } + #[test] + fn ebb() { + let mut func = Function::new(); + + let ebb = func.make_ebb(); + assert_eq!(ebb.to_string(), "ebb0"); + assert_eq!(func.ebb_args(ebb).next(), None); + + let arg1 = func.append_ebb_arg(ebb, types::F32); + assert_eq!(arg1.to_string(), "vx0"); + { + let mut args1 = func.ebb_args(ebb); + assert_eq!(args1.next(), Some(arg1)); + assert_eq!(args1.next(), None); + } + let arg2 = func.append_ebb_arg(ebb, types::I16); + assert_eq!(arg2.to_string(), "vx1"); + { + let mut args2 = func.ebb_args(ebb); + assert_eq!(args2.next(), Some(arg1)); + assert_eq!(args2.next(), Some(arg2)); + assert_eq!(args2.next(), None); + } + } } From 21b0eae044d43dea83dcb71e369a56a61138b661 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 2 May 2016 16:04:21 -0700 Subject: [PATCH 070/968] Fix build. --- src/libcretonne/repr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 807be698ac..534fd1a7cb 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -510,7 +510,7 @@ impl Function { /// /// The instruction is allowed to produce at most one result as indicated by `data.ty`. Use /// `make_multi_inst()` to create instructions with multiple results. - pub fn make_inst(&mut self, data: InstructionData, extra_result_types: &[Type]) -> Inst { + pub fn make_inst(&mut self, data: InstructionData) -> Inst { let inst = Inst::new(self.instructions.len()); self.instructions.push(data); inst @@ -661,7 +661,7 @@ mod tests { opcode: Opcode::Iconst, ty: types::I32, }; - let inst = func.make_inst(idata, &[]); + let inst = func.make_inst(idata); assert_eq!(inst.to_string(), "inst0"); // Immutable reference resolution. @@ -676,7 +676,7 @@ mod tests { let mut func = Function::new(); let idata = InstructionData::call(Opcode::Vconst, I64); - let inst = func.make_inst(idata, &[I8, F64]); + let inst = func.make_multi_inst(idata, &[I8, F64]); assert_eq!(inst.to_string(), "inst0"); let results: Vec = func.inst_results(inst).collect(); assert_eq!(results.len(), 3); From f1e2dffa8180c3ec2c2a51bb2207415f1174287b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 12 May 2016 13:37:03 -0700 Subject: [PATCH 071/968] PEP8 formatting. --- meta/constant_hash.py | 3 + meta/cretonne/__init__.py | 63 ++++-- meta/cretonne/base.py | 405 ++++++++++++++++++---------------- meta/cretonne/types.py | 39 ++-- meta/gen_instr.py | 10 +- meta/srcgen.py | 3 +- meta/target/__init__.py | 1 + meta/target/riscv/__init__.py | 7 +- 8 files changed, 299 insertions(+), 232 deletions(-) diff --git a/meta/constant_hash.py b/meta/constant_hash.py index b1b6104231..27a30476a4 100644 --- a/meta/constant_hash.py +++ b/meta/constant_hash.py @@ -6,6 +6,7 @@ don't attempt parfect hashing, but simply generate an open addressed quadratically probed hash table. """ + def simple_hash(s): """ Compute a primitive hash of a string. @@ -21,6 +22,7 @@ def simple_hash(s): h = ((h ^ ord(c)) + ((h >> 6) + (h << 26))) & 0xffffffff return h + def next_power_of_two(x): """ Compute the next power of two that is greater than `x`: @@ -41,6 +43,7 @@ def next_power_of_two(x): s *= 2 return x + 1 + def compute_quadratic(items, hash_function): """ Compute an open addressed, quadratically probed hash table containing diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index d69d5c35c4..a4ef2830c0 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -7,15 +7,20 @@ instructions. import re + camel_re = re.compile('(^|_)([a-z])') + + def camel_case(s): """Convert the string s to CamelCase""" return camel_re.sub(lambda m: m.group(2).upper(), s) + # Concrete types. # # Instances (i8, i32, ...) are provided in the cretonne.types module. + class Type(object): """A concrete value type.""" @@ -27,6 +32,7 @@ class Type(object): def __str__(self): return self.name + class ScalarType(Type): """ A concrete scalar (not vector) type. @@ -55,6 +61,7 @@ class ScalarType(Type): self._vectors[lanes] = v return v + class VectorType(Type): """ A concrete SIMD vector type. @@ -75,7 +82,9 @@ class VectorType(Type): self.lanes = lanes def __repr__(self): - return 'VectorType(base={}, lanes={})'.format(self.base.name, self.lanes) + return ('VectorType(base={}, lanes={})' + .format(self.base.name, self.lanes)) + class IntType(ScalarType): """A concrete scalar integer type.""" @@ -91,17 +100,20 @@ class IntType(ScalarType): def __repr__(self): return 'IntType(bits={})'.format(self.bits) + class FloatType(ScalarType): """A concrete scalar floating point type.""" def __init__(self, bits, doc): assert bits > 0, 'FloatType must have positive number of bits' - super(FloatType, self).__init__( name='f{:d}'.format(bits), membytes=bits/8, doc=doc) + super(FloatType, self).__init__(name='f{:d}'.format(bits), + membytes=bits/8, doc=doc) self.bits = bits def __repr__(self): return 'FloatType(bits={})'.format(self.bits) + class BoolType(ScalarType): """A concrete scalar boolean type.""" @@ -116,9 +128,9 @@ class BoolType(ScalarType): def __repr__(self): return 'BoolType(bits={})'.format(self.bits) -# + # Parametric polymorphism. -# + class TypeVar(object): """ @@ -132,12 +144,13 @@ class TypeVar(object): self.name = name self.__doc__ = doc -# + # Immediate operands. # # Instances of immediate operand types are provided in the cretonne.immediates # module. + class ImmediateType(object): """ The type of an immediate instruction operand. @@ -153,9 +166,9 @@ class ImmediateType(object): def __repr__(self): return 'ImmediateType({})'.format(self.name) -# + # Defining instructions. -# + class InstructionGroup(object): """ @@ -178,7 +191,8 @@ class InstructionGroup(object): added to this group. """ assert InstructionGroup._current is None, ( - "Can't open {} since {} is already open".format(self, _current)) + "Can't open {} since {} is already open" + .format(self, InstructionGroup._current)) InstructionGroup._current = self def close(self): @@ -187,7 +201,8 @@ class InstructionGroup(object): opening another instruction group. """ assert InstructionGroup._current is self, ( - "Can't close {}, the open instuction group is {}".format(self, _current)) + "Can't close {}, the open instuction group is {}" + .format(self, InstructionGroup._current)) InstructionGroup._current = None def __init__(self, name, doc): @@ -198,9 +213,11 @@ class InstructionGroup(object): @staticmethod def append(inst): - assert InstructionGroup._current, "Open an instruction group before defining instructions." + assert InstructionGroup._current, \ + "Open an instruction group before defining instructions." InstructionGroup._current.instructions.append(inst) + class Operand(object): """ An instruction operand. @@ -212,12 +229,12 @@ class Operand(object): concrete type. 2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the - instruction is polymorphic over the possible concrete types that the type - variable can assume. + instruction is polymorphic over the possible concrete types that the + type variable can assume. 3. An :py:class:`ImmediateType` instance indicates an immediate operand - whose value is encoded in the instruction itself rather than being passed - as an SSA value. + whose value is encoded in the instruction itself rather than being + passed as an SSA value. """ def __init__(self, name, typ, doc=''): @@ -231,19 +248,20 @@ class Operand(object): else: return self.typ.__doc__ + class Instruction(object): """ An instruction. The operands to the instruction are specified as two tuples: ``ins`` and ``outs``. Since the Python singleton tuple syntax is a bit awkward, it is - allowed to specify a singleton as just the operand itself, i.e., `ins=x` and - `ins=(x,)` are both allowed and mean the same thing. + allowed to specify a singleton as just the operand itself, i.e., `ins=x` + and `ins=(x,)` are both allowed and mean the same thing. :param name: Instruction mnemonic, also becomes opcode name. :param doc: Documentation string. - :param ins: Tuple of input operands. This can be a mix of SSA value operands - and immediate operands. + :param ins: Tuple of input operands. This can be a mix of SSA value + operands and immediate operands. :param outs: Tuple of output operands. The output operands can't be immediates. """ @@ -258,8 +276,8 @@ class Instruction(object): @staticmethod def _to_operand_tuple(x): - # Allow a single Operand instance instead of the awkward singleton tuple - # syntax. + # Allow a single Operand instance instead of the awkward singleton + # tuple syntax. if isinstance(x, Operand): x = (x,) else: @@ -268,9 +286,10 @@ class Instruction(object): assert isinstance(op, Operand) return x -# + # Defining targets -# + + class Target(object): """ A target instruction set architecture. diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 604687829e..033be2c148 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -1,7 +1,8 @@ """ Cretonne base instruction set. -This module defines the basic Cretonne instruction set that all targets support. +This module defines the basic Cretonne instruction set that all targets +support. """ from . import TypeVar, Operand, Instruction, InstructionGroup from types import i8, f32, f64 @@ -19,42 +20,46 @@ TxN = TypeVar('%Tx%N', 'A SIMD vector type') N = Operand('N', imm64) a = Operand('a', Int, doc='A constant integer scalar or vector value') -iconst = Instruction('iconst', r""" - Integer constant. +iconst = Instruction( + 'iconst', r""" + Integer constant. - Create a scalar integer SSA value with an immediate constant value, or an - integer vector where all the lanes have the same value. - """, - ins=N, outs=a) + Create a scalar integer SSA value with an immediate constant value, or + an integer vector where all the lanes have the same value. + """, + ins=N, outs=a) N = Operand('N', ieee32) a = Operand('a', f32, doc='A constant integer scalar or vector value') -f32const = Instruction('f32const', r""" - Floating point constant. +f32const = Instruction( + 'f32const', r""" + Floating point constant. - Create a :type:`f32` SSA value with an immediate constant value, or a - floating point vector where all the lanes have the same value. - """, - ins=N, outs=a) + Create a :type:`f32` SSA value with an immediate constant value, or a + floating point vector where all the lanes have the same value. + """, + ins=N, outs=a) N = Operand('N', ieee64) a = Operand('a', f64, doc='A constant integer scalar or vector value') -f64const = Instruction('f64const', r""" - Floating point constant. +f64const = Instruction( + 'f64const', r""" + Floating point constant. - Create a :type:`f64` SSA value with an immediate constant value, or a - floating point vector where all the lanes have the same value. - """, - ins=N, outs=a) + Create a :type:`f64` SSA value with an immediate constant value, or a + floating point vector where all the lanes have the same value. + """, + ins=N, outs=a) N = Operand('N', immvector) a = Operand('a', TxN, doc='A constant vector value') -vconst = Instruction('vconst', r""" - Vector constant (floating point or integer). +vconst = Instruction( + 'vconst', r""" + Vector constant (floating point or integer). - Create a SIMD vector value where the lanes don't have to be identical. - """, - ins=N, outs=a) + Create a SIMD vector value where the lanes don't have to be identical. + """, + ins=N, outs=a) # # Integer arithmetic @@ -64,133 +69,148 @@ a = Operand('a', Int) x = Operand('x', Int) y = Operand('y', Int) -iadd = Instruction('iadd', r""" - Wrapping integer addition: :math:`a := x + y \pmod{2^B}`. +iadd = Instruction( + 'iadd', r""" + Wrapping integer addition: :math:`a := x + y \pmod{2^B}`. - This instruction does not depend on the signed/unsigned interpretation of - the operands. - """, - ins=(x,y), outs=a) + This instruction does not depend on the signed/unsigned interpretation + of the operands. + """, + ins=(x, y), outs=a) -isub = Instruction('isub', r""" - Wrapping integer subtraction: :math:`a := x - y \pmod{2^B}`. +isub = Instruction( + 'isub', r""" + Wrapping integer subtraction: :math:`a := x - y \pmod{2^B}`. - This instruction does not depend on the signed/unsigned interpretation of - the operands. - """, - ins=(x,y), outs=a) + This instruction does not depend on the signed/unsigned interpretation + of the operands. + """, + ins=(x, y), outs=a) -imul = Instruction('imul', r""" - Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. +imul = Instruction( + 'imul', r""" + Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. - This instruction does not depend on the signed/unsigned interpretation of - the - operands. + This instruction does not depend on the signed/unsigned interpretation + of the + operands. - Polymorphic over all integer types (vector and scalar). - """, - ins=(x,y), outs=a) + Polymorphic over all integer types (vector and scalar). + """, + ins=(x, y), outs=a) -udiv = Instruction('udiv', r""" - Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. +udiv = Instruction( + 'udiv', r""" + Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. - This operation traps if the divisor is zero. - """, - ins=(x,y), outs=a) + This operation traps if the divisor is zero. + """, + ins=(x, y), outs=a) -sdiv = Instruction('sdiv', r""" - Signed integer division rounded toward zero: :math:`a := sign(xy) \lfloor - {|x| \over |y|}\rfloor`. +sdiv = Instruction( + 'sdiv', r""" + Signed integer division rounded toward zero: :math:`a := sign(xy) + \lfloor {|x| \over |y|}\rfloor`. - This operation traps if the divisor is zero, or if the result is not - representable in :math:`B` bits two's complement. This only happens when - :math:`x = -2^{B-1}, y = -1`. - """, - ins=(x,y), outs=a) + This operation traps if the divisor is zero, or if the result is not + representable in :math:`B` bits two's complement. This only happens + when :math:`x = -2^{B-1}, y = -1`. + """, + ins=(x, y), outs=a) -urem = Instruction('urem', """ - Unsigned integer remainder. +urem = Instruction( + 'urem', """ + Unsigned integer remainder. - This operation traps if the divisor is zero. - """, - ins=(x,y), outs=a) + This operation traps if the divisor is zero. + """, + ins=(x, y), outs=a) -srem = Instruction('srem', """ - Signed integer remainder. +srem = Instruction( + 'srem', """ + Signed integer remainder. - This operation traps if the divisor is zero. + This operation traps if the divisor is zero. - .. todo:: Integer remainder vs modulus. + .. todo:: Integer remainder vs modulus. Clarify whether the result has the sign of the divisor or the dividend. Should we add a ``smod`` instruction for the case where the result has the same sign as the divisor? - """, - ins=(x,y), outs=a) + """, + ins=(x, y), outs=a) a = Operand('a', iB) x = Operand('x', iB) Y = Operand('Y', imm64) -iadd_imm = Instruction('iadd_imm', """ - Add immediate integer. +iadd_imm = Instruction( + 'iadd_imm', """ + Add immediate integer. - Same as :inst:`iadd`, but one operand is an immediate constant. + Same as :inst:`iadd`, but one operand is an immediate constant. - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x,Y), outs=a) + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, Y), outs=a) -imul_imm = Instruction('imul_imm', """ - Integer multiplication by immediate constant. +imul_imm = Instruction( + 'imul_imm', """ + Integer multiplication by immediate constant. - Polymorphic over all scalar integer types. - """, - ins=(x,Y), outs=a) + Polymorphic over all scalar integer types. + """, + ins=(x, Y), outs=a) -udiv_imm = Instruction('udiv_imm', """ - Unsigned integer division by an immediate constant. +udiv_imm = Instruction( + 'udiv_imm', """ + Unsigned integer division by an immediate constant. - This instruction never traps because a divisor of zero is not allowed. - """, - ins=(x,Y), outs=a) + This instruction never traps because a divisor of zero is not allowed. + """, + ins=(x, Y), outs=a) -sdiv_imm = Instruction('sdiv_imm', """ - Signed integer division by an immediate constant. +sdiv_imm = Instruction( + 'sdiv_imm', """ + Signed integer division by an immediate constant. - This instruction never traps because a divisor of -1 or 0 is not allowed. - """, - ins=(x,Y), outs=a) + This instruction never traps because a divisor of -1 or 0 is not + allowed. """, + ins=(x, Y), outs=a) -urem_imm = Instruction('urem_imm', """ - Unsigned integer remainder with immediate divisor. +urem_imm = Instruction( + 'urem_imm', """ + Unsigned integer remainder with immediate divisor. - This instruction never traps because a divisor of zero is not allowed. - """, - ins=(x,Y), outs=a) + This instruction never traps because a divisor of zero is not allowed. + """, + ins=(x, Y), outs=a) -srem_imm = Instruction('srem_imm', """ - Signed integer remainder with immediate divisor. +srem_imm = Instruction( + 'srem_imm', """ + Signed integer remainder with immediate divisor. - This instruction never traps because a divisor of 0 or -1 is not allowed. - """, - ins=(x,Y), outs=a) + This instruction never traps because a divisor of 0 or -1 is not + allowed. """, + ins=(x, Y), outs=a) # Swap x and y for isub_imm. X = Operand('X', imm64) y = Operand('y', iB) -isub_imm = Instruction('isub_imm', """ - Immediate wrapping subtraction: :math:`a := X - y \pmod{2^B}`. +isub_imm = Instruction( + 'isub_imm', """ + Immediate wrapping subtraction: :math:`a := X - y \pmod{2^B}`. - Also works as integer negation when :math:`X = 0`. Use :inst:`iadd_imm` with a - negative immediate operand for the reverse immediate subtraction. + Also works as integer negation when :math:`X = 0`. Use :inst:`iadd_imm` + with a negative immediate operand for the reverse immediate + subtraction. - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(X,y), outs=a) + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(X, y), outs=a) # # Bitwise operations. @@ -202,86 +222,97 @@ x = Operand('x', bits) y = Operand('y', bits) a = Operand('a', bits) -band = Instruction('band', """ - Bitwise and. - """, - ins=(x,y), outs=a) +band = Instruction( + 'band', """ + Bitwise and. + """, + ins=(x, y), outs=a) -bor = Instruction('bor', """ - Bitwise or. - """, - ins=(x,y), outs=a) +bor = Instruction( + 'bor', """ + Bitwise or. + """, + ins=(x, y), outs=a) -bxor = Instruction('bxor', """ - Bitwise xor. - """, - ins=(x,y), outs=a) +bxor = Instruction( + 'bxor', """ + Bitwise xor. + """, + ins=(x, y), outs=a) -bnot = Instruction('bnot', """ - Bitwise not. - """, - ins=x, outs=a) +bnot = Instruction( + 'bnot', """ + Bitwise not. + """, + ins=x, outs=a) # Shift/rotate. x = Operand('x', Int, doc='Scalar or vector value to shift') y = Operand('y', iB, doc='Number of bits to shift') a = Operand('a', Int) -rotl = Instruction('rotl', r""" - Rotate left. +rotl = Instruction( + 'rotl', r""" + Rotate left. - Rotate the bits in ``x`` by ``y`` places. - """, - ins=(x,y), outs=a) + Rotate the bits in ``x`` by ``y`` places. + """, + ins=(x, y), outs=a) -rotr = Instruction('rotr', r""" - Rotate right. +rotr = Instruction( + 'rotr', r""" + Rotate right. - Rotate the bits in ``x`` by ``y`` places. - """, - ins=(x,y), outs=a) + Rotate the bits in ``x`` by ``y`` places. + """, + ins=(x, y), outs=a) -ishl = Instruction('ishl', r""" - Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` - places. Shift in zero bits to the LSB. +ishl = Instruction( + 'ishl', r""" + Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` + places. Shift in zero bits to the LSB. - The shift amount is masked to the size of ``x``. + The shift amount is masked to the size of ``x``. - When shifting a B-bits integer type, this instruction computes: + When shifting a B-bits integer type, this instruction computes: - .. math:: + .. math:: s &:= y \pmod B, \\ a &:= x \cdot 2^s \pmod{2^B}. - .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. - """, - ins=(x,y), outs=a) + .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. + """, + ins=(x, y), outs=a) -ushr = Instruction('ushr', r""" - Unsigned shift right. Shift bits in ``x`` towards the LSB by ``y`` places, - shifting in zero bits to the MSB. Also called a *logical shift*. +ushr = Instruction( + 'ushr', r""" + Unsigned shift right. Shift bits in ``x`` towards the LSB by ``y`` + places, shifting in zero bits to the MSB. Also called a *logical + shift*. - The shift amount is masked to the size of the register. + The shift amount is masked to the size of the register. - When shifting a B-bits integer type, this instruction computes: + When shifting a B-bits integer type, this instruction computes: - .. math:: + .. math:: s &:= y \pmod B, \\ a &:= \lfloor x \cdot 2^{-s} \rfloor. - .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. - """, - ins=(x,y), outs=a) + .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. + """, + ins=(x, y), outs=a) -sshr = Instruction('sshr', r""" - Signed shift right. Shift bits in ``x`` towards the LSB by ``y`` places, - shifting in sign bits to the MSB. Also called an *arithmetic shift*. +sshr = Instruction( + 'sshr', r""" + Signed shift right. Shift bits in ``x`` towards the LSB by ``y`` + places, shifting in sign bits to the MSB. Also called an *arithmetic + shift*. - The shift amount is masked to the size of the register. + The shift amount is masked to the size of the register. - .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. - """, - ins=(x,y), outs=a) + .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. + """, + ins=(x, y), outs=a) # # Bit counting. @@ -290,38 +321,42 @@ sshr = Instruction('sshr', r""" x = Operand('x', iB) a = Operand('a', i8) -clz = Instruction('clz', r""" - Count leading zero bits. +clz = Instruction( + 'clz', r""" + Count leading zero bits. - Starting from the MSB in ``x``, count the number of zero bits before - reaching the first one bit. When ``x`` is zero, returns the size of x in - bits. - """, - ins=x, outs=a) + Starting from the MSB in ``x``, count the number of zero bits before + reaching the first one bit. When ``x`` is zero, returns the size of x + in bits. + """, + ins=x, outs=a) -cls = Instruction('cls', r""" - Count leading sign bits. +cls = Instruction( + 'cls', r""" + Count leading sign bits. - Starting from the MSB after the sign bit in ``x``, count the number of - consecutive bits identical to the sign bit. When ``x`` is 0 or -1, returns - one less than the size of x in bits. - """, - ins=x, outs=a) + Starting from the MSB after the sign bit in ``x``, count the number of + consecutive bits identical to the sign bit. When ``x`` is 0 or -1, + returns one less than the size of x in bits. + """, + ins=x, outs=a) -ctz = Instruction('ctz', r""" - Count trailing zeros. +ctz = Instruction( + 'ctz', r""" + Count trailing zeros. - Starting from the LSB in ``x``, count the number of zero bits before - reaching the first one bit. When ``x`` is zero, returns the size of x in - bits. - """, - ins=x, outs=a) + Starting from the LSB in ``x``, count the number of zero bits before + reaching the first one bit. When ``x`` is zero, returns the size of x + in bits. + """, + ins=x, outs=a) -popcnt = Instruction('popcnt', r""" - Population count +popcnt = Instruction( + 'popcnt', r""" + Population count - Count the number of one bits in ``x``. - """, - ins=x, outs=a) + Count the number of one bits in ``x``. + """, + ins=x, outs=a) instructions.close() diff --git a/meta/cretonne/types.py b/meta/cretonne/types.py index c639d89050..d92e6f9113 100644 --- a/meta/cretonne/types.py +++ b/meta/cretonne/types.py @@ -5,33 +5,34 @@ The cretonne.types module predefines all the Cretonne scalar types. from . import ScalarType, IntType, FloatType, BoolType #: Boolean. -b1 = ScalarType('b1', 0, +b1 = ScalarType( + 'b1', 0, """ A boolean value that is either true or false. """) -b8 = BoolType(8) #: 8-bit bool. -b16 = BoolType(16) #: 16-bit bool. -b32 = BoolType(32) #: 32-bit bool. -b64 = BoolType(64) #: 64-bit bool. +b8 = BoolType(8) #: 8-bit bool. +b16 = BoolType(16) #: 16-bit bool. +b32 = BoolType(32) #: 32-bit bool. +b64 = BoolType(64) #: 64-bit bool. -i8 = IntType(8) #: 8-bit int. -i16 = IntType(16) #: 16-bit int. -i32 = IntType(32) #: 32-bit int. -i64 = IntType(64) #: 64-bit int. +i8 = IntType(8) #: 8-bit int. +i16 = IntType(16) #: 16-bit int. +i32 = IntType(32) #: 32-bit int. +i64 = IntType(64) #: 64-bit int. #: IEEE single precision. -f32 = FloatType(32, - """ - A 32-bit floating point type represented in the IEEE 754-2008 *binary32* - interchange format. This corresponds to the :c:type:`float` type in most - C implementations. +f32 = FloatType( + 32, """ + A 32-bit floating point type represented in the IEEE 754-2008 + *binary32* interchange format. This corresponds to the :c:type:`float` + type in most C implementations. """) #: IEEE double precision. -f64 = FloatType(64, - """ - A 64-bit floating point type represented in the IEEE 754-2008 *binary64* - interchange format. This corresponds to the :c:type:`double` type in - most C implementations. +f64 = FloatType( + 64, """ + A 64-bit floating point type represented in the IEEE 754-2008 + *binary64* interchange format. This corresponds to the :c:type:`double` + type in most C implementations. """) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index eb1b0a361f..93b02bc369 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -5,6 +5,7 @@ Generate sources with instruction info. import srcgen import constant_hash + def collect_instr_groups(targets): seen = set() groups = [] @@ -15,6 +16,7 @@ def collect_instr_groups(targets): seen.add(g) return groups + def gen_opcodes(groups, out_dir): """Generate opcode enumerations.""" fmt = srcgen.Formatter() @@ -46,9 +48,12 @@ def gen_opcodes(groups, out_dir): fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) # Generate an opcode hash table for looking up opcodes by name. - hash_table = constant_hash.compute_quadratic(instrs, + hash_table = constant_hash.compute_quadratic( + instrs, lambda i: constant_hash.simple_hash(i.name)) - with fmt.indented('const OPCODE_HASH_TABLE: [Opcode; {}] = ['.format(len(hash_table)), '];'): + with fmt.indented( + 'const OPCODE_HASH_TABLE: [Opcode; {}] = [' + .format(len(hash_table)), '];'): for i in hash_table: if i is None: fmt.line('Opcode::NotAnOpcode,') @@ -57,6 +62,7 @@ def gen_opcodes(groups, out_dir): fmt.update_file('opcodes.rs', out_dir) + def generate(targets, out_dir): groups = collect_instr_groups(targets) gen_opcodes(groups, out_dir) diff --git a/meta/srcgen.py b/meta/srcgen.py index 125ae0a63a..7895b0ea38 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -9,6 +9,7 @@ source code. import sys import os + class Formatter(object): """ Source code formatter class. @@ -70,7 +71,7 @@ class Formatter(object): self.after = after def __enter__(self): - self.fmt.indent_push(); + self.fmt.indent_push() def __exit__(self, t, v, tb): self.fmt.indent_pop() diff --git a/meta/target/__init__.py b/meta/target/__init__.py index bed730b207..1f9f7a0565 100644 --- a/meta/target/__init__.py +++ b/meta/target/__init__.py @@ -8,6 +8,7 @@ set architecture supported by Cretonne. from . import riscv + def all_targets(): """ Get a list of all the supported targets. Each target is represented as a diff --git a/meta/target/riscv/__init__.py b/meta/target/riscv/__init__.py index e9b9926e31..bdb5b63091 100644 --- a/meta/target/riscv/__init__.py +++ b/meta/target/riscv/__init__.py @@ -2,9 +2,10 @@ RISC-V Target ------------- -`RISC-V `_ is an open instruction set architecture originally -developed at UC Berkeley. It is a RISC-style ISA with either a 32-bit (RV32I) or -64-bit (RV32I) base instruction set and a number of optional extensions: +`RISC-V `_ is an open instruction set architecture +originally developed at UC Berkeley. It is a RISC-style ISA with either a +32-bit (RV32I) or 64-bit (RV32I) base instruction set and a number of optional +extensions: RV32M / RV64M Integer multiplication and division. From 814231245c0f168bb9d47d7c5ccca6381619d265 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 12 May 2016 15:59:40 -0700 Subject: [PATCH 072/968] Add OperandKind to the meta language. We have a two-level type system: OperandKinds and ValueTypes. The value types only apply to value operands, but there are many more kinds of operands: immediate numbers, condition codes, basic block references, etc. --- docs/metaref.rst | 46 ++++++++------- meta/cretonne/__init__.py | 115 ++++++++++++++++++++++++++---------- meta/cretonne/base.py | 9 +-- meta/cretonne/immediates.py | 12 ++-- 4 files changed, 120 insertions(+), 62 deletions(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index c8f8b0e265..fe46e8af4a 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -15,15 +15,15 @@ module :mod:`cretonne.base`. .. module:: cretonne -Types -===== +Value Types +=========== -Concrete value types are represented as instances of :class:`cretonne.Type`. There are +Concrete value types are represented as instances of :class:`cretonne.ValueType`. There are subclasses to represent scalar and vector types. -.. inheritance-diagram:: Type ScalarType VectorType IntType FloatType +.. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType :parts: 1 -.. autoclass:: Type +.. autoclass:: ValueType .. autoclass:: ScalarType :members: .. autoclass:: VectorType @@ -48,33 +48,35 @@ types for their operands. This makes the instructions polymorphic. .. autoclass:: TypeVar -Immediates ----------- - -Immediate instruction operands don't correspond to SSA values, but have values -that are encoded directly in the instruction. Immediate operands don't -have types from the :class:`cretonne.Type` type system; they often have -enumerated values of a specific type. The type of an immediate operand is -indicated with an instance of :class:`ImmediateType`. - -.. autoclass:: ImmediateType - -.. automodule:: cretonne.immediates - :members: - -.. currentmodule:: cretonne - Instructions ============ New instructions are defined as instances of the :class:`cretonne.Instruction` class. -.. autoclass:: Operand .. autoclass:: Instruction +.. autoclass:: Operand +.. autoclass:: OperandKind .. autoclass:: InstructionGroup :members: + +Immediates +---------- + +Immediate instruction operands don't correspond to SSA values, but have values +that are encoded directly in the instruction. Immediate operands don't +have types from the :class:`cretonne.ValueType` type system; they often have +enumerated values of a specific type. The type of an immediate operand is +indicated with an instance of :class:`ImmediateKind`. + +.. autoclass:: ImmediateKind + +.. automodule:: cretonne.immediates + :members: + +.. currentmodule:: cretonne + Targets ======= diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index a4ef2830c0..26a3664c64 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -16,13 +16,75 @@ def camel_case(s): return camel_re.sub(lambda m: m.group(2).upper(), s) -# Concrete types. +# Kinds of operands. # -# Instances (i8, i32, ...) are provided in the cretonne.types module. +# Each instruction has an opcode and a number of operands. The opcode +# determines the instruction format, and the format determines the number of +# operands and the kind of each operand. +class OperandKind(object): + """ + The kind of an operand. + + An instance of the `OperandKind` class corresponds to a kind of operand. + Each operand kind has a corresponding type in the Rust representation of an + instruction. + """ + + def __init__(self, name, doc): + self.name = name + self.__doc__ = doc + # The camel-cased name of an operand kind is also the Rust type used to + # represent it. + self.camel_name = camel_case(name) + + def __str__(self): + return self.name + + def __repr__(self): + return 'OperandKind({})'.format(self.name) -class Type(object): - """A concrete value type.""" +#: An SSA value operand. This is a value defined by another instruction. +value = OperandKind( + 'value', """ + An SSA value defined by another instruction. + + This kind of operand can represent any SSA value type, but the + instruction format may restrict the valid value types for a given + operand. + """) + + +# Instances of immediate operand types are provided in the cretonne.immediates +# module. +class ImmediateKind(OperandKind): + """ + The type of an immediate instruction operand. + """ + + def __init__(self, name, doc): + self.name = name + self.__doc__ = doc + + def __repr__(self): + return 'ImmediateKind({})'.format(self.name) + + def operand_kind(self): + """ + An `ImmediateKind` instance can be used directly as the type of an + `Operand` when defining an instruction. + """ + return self + + +# ValueType instances (i8, i32, ...) are provided in the cretonne.types module. +class ValueType(object): + """ + A concrete SSA value type. + + All SSA values have a type that is described by an instance of `ValueType` + or one of its subclasses. + """ def __init__(self, name, membytes, doc): self.name = name @@ -32,8 +94,15 @@ class Type(object): def __str__(self): return self.name + def operand_kind(self): + """ + When a `ValueType` object is used to describe the type of an `Operand` + in an instruction definition, the kind of that operand is an SSA value. + """ + return value -class ScalarType(Type): + +class ScalarType(ValueType): """ A concrete scalar (not vector) type. @@ -62,7 +131,7 @@ class ScalarType(Type): return v -class VectorType(Type): +class VectorType(ValueType): """ A concrete SIMD vector type. @@ -144,27 +213,12 @@ class TypeVar(object): self.name = name self.__doc__ = doc - -# Immediate operands. -# -# Instances of immediate operand types are provided in the cretonne.immediates -# module. - - -class ImmediateType(object): - """ - The type of an immediate instruction operand. - """ - - def __init__(self, name, doc): - self.name = name - self.__doc__ = doc - - def __str__(self): - return self.name - - def __repr__(self): - return 'ImmediateType({})'.format(self.name) + def operand_kind(self): + """ + When a `TypeVar` object is used to describe the type of an `Operand` + in an instruction definition, the kind of that operand is an SSA value. + """ + return value # Defining instructions. @@ -225,14 +279,14 @@ class Operand(object): An instruction operand can be either an *immediate* or an *SSA value*. The type of the operand is one of: - 1. A :py:class:`Type` instance indicates an SSA value operand with a + 1. A :py:class:`ValueType` instance indicates an SSA value operand with a concrete type. 2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the instruction is polymorphic over the possible concrete types that the type variable can assume. - 3. An :py:class:`ImmediateType` instance indicates an immediate operand + 3. An :py:class:`ImmediateKind` instance indicates an immediate operand whose value is encoded in the instruction itself rather than being passed as an SSA value. @@ -241,6 +295,7 @@ class Operand(object): self.name = name self.typ = typ self.__doc__ = doc + self.kind = typ.operand_kind() def get_doc(self): if self.__doc__: @@ -251,7 +306,7 @@ class Operand(object): class Instruction(object): """ - An instruction. + An instruction description. The operands to the instruction are specified as two tuples: ``ins`` and ``outs``. Since the Python singleton tuple syntax is a bit awkward, it is diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 033be2c148..412e7f3abd 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -277,10 +277,11 @@ ishl = Instruction( When shifting a B-bits integer type, this instruction computes: .. math:: - s &:= y \pmod B, \\ - a &:= x \cdot 2^s \pmod{2^B}. + s &:= y \pmod B, \\ + a &:= x \cdot 2^s \pmod{2^B}. .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. + """, ins=(x, y), outs=a) @@ -295,8 +296,8 @@ ushr = Instruction( When shifting a B-bits integer type, this instruction computes: .. math:: - s &:= y \pmod B, \\ - a &:= \lfloor x \cdot 2^{-s} \rfloor. + s &:= y \pmod B, \\ + a &:= \lfloor x \cdot 2^{-s} \rfloor. .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. """, diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index 2735cd6be1..af7bd72feb 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -1,25 +1,25 @@ """ -The cretonne.immdiates module predefines all the Cretonne immediate operand +The cretonne.immediates module predefines all the Cretonne immediate operand types. """ -from . import ImmediateType +from . import ImmediateKind #: A 64-bit immediate integer operand. #: #: This type of immediate integer can interact with SSA values with any #: :py:class:`cretonne.IntType` type. -imm64 = ImmediateType('imm64', 'A 64-bit immediate integer.') +imm64 = ImmediateKind('imm64', 'A 64-bit immediate integer.') #: A 32-bit immediate floating point operand. #: #: IEEE 754-2008 binary32 interchange format. -ieee32 = ImmediateType('ieee32', 'A 32-bit immediate floating point number.') +ieee32 = ImmediateKind('ieee32', 'A 32-bit immediate floating point number.') #: A 64-bit immediate floating point operand. #: #: IEEE 754-2008 binary64 interchange format. -ieee64 = ImmediateType('ieee64', 'A 64-bit immediate floating point number.') +ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') #: A large SIMD vector constant. -immvector = ImmediateType('immvector', 'An immediate SIMD vector.') +immvector = ImmediateKind('immvector', 'An immediate SIMD vector.') From ef04f4fc40d40a35cbc405bca05ece2ce9b4e655 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 12 May 2016 17:28:01 -0700 Subject: [PATCH 073/968] Add an InstructionFormat class to the meta language. Define all known instruction formats in the cretonne.formats module. --- meta/cretonne/__init__.py | 80 +++++++++++++++++++++++++++++++++++++++ meta/cretonne/formats.py | 25 ++++++++++++ meta/gen_instr.py | 4 +- 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 meta/cretonne/formats.py diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 26a3664c64..36f96ef6f5 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -6,6 +6,7 @@ instructions. """ import re +import importlib camel_re = re.compile('(^|_)([a-z])') @@ -304,6 +305,80 @@ class Operand(object): return self.typ.__doc__ +class InstructionFormat(object): + """ + An instruction format. + + Every instruction opcode has a corresponding instruction format which + determines the number of operands and their kinds. Instruction formats are + identified structurally, i.e., the format of an instruction is derived from + the kinds of operands used in its declaration. + + Most instruction formats produce a single result, or no result at all. If + an instruction can produce more than one result, the `multiple_results` + flag must be set on its format. All results are of the `value` kind, and + the instruction format does not keep track of how many results are + produced. Some instructions, like `call`, may have a variable number of + results. + + All instruction formats must be predefined in the + :py:mod:`cretonne.formats` module. + + :param kinds: List of `OperandKind` objects describing the operands. + :param name: Instruction format name in CamelCase. This is used as a Rust + variant name in both the `InstructionData` and `InstructionFormat` + enums. + :param multiple_results: Set to `True` if this instruction format allows + more than one result to be produced. + """ + + # Map (multiple_results, kind, kind, ...) -> InstructionFormat + _registry = dict() + + def __init__(self, *kinds, **kwargs): + self.name = kwargs.get('name', None) + self.kinds = kinds + self.multiple_results = kwargs.get('multiple_results', False) + # Compute a signature for the global registry. + sig = (self.multiple_results,) + kinds + if sig in InstructionFormat._registry: + raise RuntimeError( + "Format '{}' has the same signature as existing format '{}'" + .format(self.name, InstructionFormat._registry[sig])) + InstructionFormat._registry[sig] = self + + @staticmethod + def lookup(ins, outs): + """ + Find an existing instruction format that matches the given lists of + instruction inputs and outputs. + + The `ins` and `outs` arguments correspond to the + :py:class:`Instruction` arguments of the same name, except they must be + tuples of :py:`Operand` objects. + """ + multiple_results = len(outs) > 1 + sig = (multiple_results,) + tuple(op.kind for op in ins) + if sig not in InstructionFormat._registry: + raise RuntimeError( + "No instruction format matches ins = ({}){}".format( + ", ".join(map(str, sig[1:])), + "[multiple results]" if multiple_results else "")) + return InstructionFormat._registry[sig] + + @staticmethod + def extract_names(globs): + """ + Given a dict mapping name -> object as returned by `globals()`, find + all the InstructionFormat objects and set their name from the dict key. + This is used to name a bunch of global variables in a module. + """ + for name, obj in globs.iteritems(): + if isinstance(obj, InstructionFormat): + assert obj.name is None + obj.name = name + + class Instruction(object): """ An instruction description. @@ -327,6 +402,7 @@ class Instruction(object): self.__doc__ = doc self.ins = self._to_operand_tuple(ins) self.outs = self._to_operand_tuple(outs) + self.format = InstructionFormat.lookup(self.ins, self.outs) InstructionGroup.append(self) @staticmethod @@ -359,3 +435,7 @@ class Target(object): def __init__(self, name, instrution_groups): self.name = name self.instruction_groups = instrution_groups + +# Import the fixed instruction formats now so they can be added to the +# registry. +importlib.import_module('cretonne.formats') diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py new file mode 100644 index 0000000000..6f34214db9 --- /dev/null +++ b/meta/cretonne/formats.py @@ -0,0 +1,25 @@ +""" +The cretonne.formats defines all instruction formats. + +Every instruction format has a corresponding `InstructionData` variant in the +Rust representation of cretonne IL, so all instruction formats must be defined +in this module. +""" + + +from . import InstructionFormat, value +from immediates import imm64, ieee32, ieee64, immvector + +Unary = InstructionFormat(value) +UnaryImm = InstructionFormat(imm64) +UnaryIeee32 = InstructionFormat(ieee32) +UnaryIeee64 = InstructionFormat(ieee64) +UnaryImmVector = InstructionFormat(immvector) + +Binary = InstructionFormat(value, value) +BinaryImm = InstructionFormat(value, imm64) +BinaryImmRev = InstructionFormat(imm64, value) + + +# Finally extract the names of global variables in this module. +InstructionFormat.extract_names(globals()) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 93b02bc369..70ae0ee8e7 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -36,7 +36,9 @@ def gen_opcodes(groups, out_dir): if prefix: prefix = prefix + ' = ' suffix = ', '.join(o.name for o in i.ins) - fmt.doc_comment('`{}{} {}`.'.format(prefix, i.name, suffix)) + fmt.doc_comment( + '`{}{} {}`. ({})' + .format(prefix, i.name, suffix, i.format.name)) # Enum variant itself. fmt.line(i.camel_name + ',') From 3909cdbc2d971594c9eba73d9b3713b160a411e4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 May 2016 10:00:38 -0700 Subject: [PATCH 074/968] Generate recursive meta language dependencies. Cargo doesn't scan a directory for changed dependencies recursively, so do that as part of the build.py script. --- meta/build.py | 2 ++ meta/gen_build_deps.py | 36 ++++++++++++++++++++++++++++++++++++ src/libcretonne/build.rs | 3 --- 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 meta/gen_build_deps.py diff --git a/meta/build.py b/meta/build.py index 76673fa62b..24d9f286ee 100644 --- a/meta/build.py +++ b/meta/build.py @@ -5,6 +5,7 @@ import argparse import target import gen_instr +import gen_build_deps parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') @@ -15,3 +16,4 @@ out_dir = args.out_dir targets = target.all_targets() gen_instr.generate(targets, out_dir) +gen_build_deps.generate() diff --git a/meta/gen_build_deps.py b/meta/gen_build_deps.py new file mode 100644 index 0000000000..4d0c0cb260 --- /dev/null +++ b/meta/gen_build_deps.py @@ -0,0 +1,36 @@ +""" +Generate build dependencies for Cargo. + +The `build.py` script is invoked by cargo when building libcretonne to +generate Rust code from the instruction descriptions. Cargo needs to know when +it is necessary to rerun the build script. + +If the build script outputs lines of the form: + + cargo:rerun-if-changed=/path/to/file + +cargo will rerun the build script when those files have changed since the last +build. +""" + +import os +from os.path import dirname, abspath, join + + +def source_files(top): + """ + Recursively find all interesting source files and directories in the + directory tree starting at top. Yield a path to each file. + """ + for (dirpath, dirnames, filenames) in os.walk(top): + yield dirpath + for f in filenames: + if f.endswith('.py'): + yield join(dirpath, f) + + +def generate(): + print "Dependencies from meta language directory:" + meta = dirname(abspath(__file__)) + for path in source_files(meta): + print "cargo:rerun-if-changed=" + path diff --git a/src/libcretonne/build.rs b/src/libcretonne/build.rs index 523ddb3548..c275e33e53 100644 --- a/src/libcretonne/build.rs +++ b/src/libcretonne/build.rs @@ -32,9 +32,6 @@ fn main() { let meta_dir = top_dir.join("meta"); let build_script = meta_dir.join("build.py"); - // Let Cargo known that this script should be rerun if anything changes in the meta directory. - println!("cargo:rerun-if-changed={}", meta_dir.display()); - // Launch build script with Python. We'll just find python in the path. let status = process::Command::new("python") .current_dir(top_dir) From e3927e205ea3bfe865937028d6cd6d638ce43f86 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 May 2016 11:54:05 -0700 Subject: [PATCH 075/968] Generate an InstructionFormat enum. This is a no-payload enum which will have the same variants as InstructionData. This makes it possible to talk about the format of an instruction without actually creating an InstructionData instance. --- meta/cretonne/__init__.py | 4 ++++ meta/gen_instr.py | 41 ++++++++++++++++++++++++++++++----- meta/srcgen.py | 7 ++++-- src/libcretonne/immediates.rs | 12 ++++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 36f96ef6f5..4b8267d2d1 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -335,6 +335,9 @@ class InstructionFormat(object): # Map (multiple_results, kind, kind, ...) -> InstructionFormat _registry = dict() + # All existing formats. + all_formats = list() + def __init__(self, *kinds, **kwargs): self.name = kwargs.get('name', None) self.kinds = kinds @@ -346,6 +349,7 @@ class InstructionFormat(object): "Format '{}' has the same signature as existing format '{}'" .format(self.name, InstructionFormat._registry[sig])) InstructionFormat._registry[sig] = self + InstructionFormat.all_formats.append(self) @staticmethod def lookup(ins, outs): diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 70ae0ee8e7..8c5ad8a9ce 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -4,6 +4,22 @@ Generate sources with instruction info. import srcgen import constant_hash +import cretonne + + +def gen_formats(fmt): + """Generate an instruction format enumeration""" + + fmt.doc_comment('An instruction format') + fmt.doc_comment('') + fmt.doc_comment('Every opcode has a corresponding instruction format') + fmt.doc_comment('which is represented by both the `InstructionFormat`') + fmt.doc_comment('and the `InstructionData` enums.') + fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') + with fmt.indented('pub enum InstructionFormat {', '}'): + for f in cretonne.InstructionFormat.all_formats: + fmt.line(f.name + ',') + fmt.line() def collect_instr_groups(targets): @@ -17,9 +33,8 @@ def collect_instr_groups(targets): return groups -def gen_opcodes(groups, out_dir): +def gen_opcodes(groups, fmt): """Generate opcode enumerations.""" - fmt = srcgen.Formatter() fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') @@ -41,6 +56,18 @@ def gen_opcodes(groups, out_dir): .format(prefix, i.name, suffix, i.format.name)) # Enum variant itself. fmt.line(i.camel_name + ',') + fmt.line() + + # Generate a private opcode_format table. + with fmt.indented( + 'const OPCODE_FORMAT: [InstructionFormat; {}] = [' + .format(len(instrs)), + '];'): + for i in instrs: + fmt.format( + 'InstructionFormat::{}, // {}', + i.format.name, i.name) + fmt.line() # Generate a private opcode_name function. with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): @@ -48,6 +75,7 @@ def gen_opcodes(groups, out_dir): fmt.line('Opcode::NotAnOpcode => "",') for i in instrs: fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) + fmt.line() # Generate an opcode hash table for looking up opcodes by name. hash_table = constant_hash.compute_quadratic( @@ -61,10 +89,13 @@ def gen_opcodes(groups, out_dir): fmt.line('Opcode::NotAnOpcode,') else: fmt.format('Opcode::{},', i.camel_name) - - fmt.update_file('opcodes.rs', out_dir) + fmt.line() def generate(targets, out_dir): groups = collect_instr_groups(targets) - gen_opcodes(groups, out_dir) + # opcodes.rs + fmt = srcgen.Formatter() + gen_formats(fmt) + gen_opcodes(groups, fmt) + fmt.update_file('opcodes.rs', out_dir) diff --git a/meta/srcgen.py b/meta/srcgen.py index 7895b0ea38..1ebd745286 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -49,9 +49,12 @@ class Formatter(object): assert self.indent != '', 'Already at top level indentation' self.indent = self.indent[0:-self.shiftwidth] - def line(self, s): + def line(self, s=None): """And an indented line.""" - self.lines.append('{}{}\n'.format(self.indent, s)) + if s: + self.lines.append('{}{}\n'.format(self.indent, s)) + else: + self.lines.append('\n') def writelines(self, f=None): """Write all lines to `f`.""" diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 619d790bfe..135d3c6b65 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -23,6 +23,17 @@ impl Display for Opcode { } } +impl Opcode { + /// Get the instruction format for this opcode. + pub fn format(self) -> Option { + if self == Opcode::NotAnOpcode { + None + } else { + Some(OPCODE_FORMAT[self as usize - 1]) + } + } +} + // A primitive hash function for matching opcodes. // Must match `meta/constant_hash.py`. fn simple_hash(s: &str) -> u32 { @@ -491,6 +502,7 @@ mod tests { assert!(x != y); y = Opcode::Iadd; assert_eq!(x, y); + assert_eq!(x.format(), Some(InstructionFormat::Binary)); assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); assert_eq!(Opcode::IaddImm.to_string(), "iadd_imm"); From 9c9be1cb58f3cf4a0d40dd01bc5ca37e8d81f4ad Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 May 2016 13:32:20 -0700 Subject: [PATCH 076/968] Break entity references and instruction info out into new modules. Avoid gathering too much code in repr.rs. The `entities` module contains entity reference types, and the `instructions` module contains instruction opcodes and formats. --- src/libcretonne/entities.rs | 185 ++++++++++++++++++++++ src/libcretonne/immediates.rs | 89 ----------- src/libcretonne/instructions.rs | 229 +++++++++++++++++++++++++++ src/libcretonne/lib.rs | 2 + src/libcretonne/repr.rs | 269 +------------------------------- src/libreader/parser.rs | 3 +- 6 files changed, 424 insertions(+), 353 deletions(-) create mode 100644 src/libcretonne/entities.rs create mode 100644 src/libcretonne/instructions.rs diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs new file mode 100644 index 0000000000..bdc76d8040 --- /dev/null +++ b/src/libcretonne/entities.rs @@ -0,0 +1,185 @@ +//! IL entity references. +//! +//! Instructions in Cretonne IL need to reference other entities in the function. This can be other +//! parts of the function like extended basic blocks or stack slots, or it can be external entities +//! that are declared in the function preamble in the text format. +//! +//! These entity references in instruction operands are not implemented as Rust references both +//! because Rust's ownership and mutability rules make it difficult, and because 64-bit pointers +//! take up a lot of space, and we want a compact in-memory representation. Instead, entity +//! references are structs wrapping a `u32` index into a table in the `Function` main data +//! structure. There is a separate index type for each entity type, so we don't lose type safety. +//! +//! The `entities` module defines public types for the entity references along with constants +//! representing an invalid reference. We prefer to use `Option` whenever possible, but +//! unfortunately that type is twice as large as the 32-bit index type on its own. Thus, compact +//! data structures use the sentinen constant, while function arguments and return values prefer +//! the more Rust-like `Option` variant. +//! +//! The entity references all implement the `Display` trait in a way that matches the textual IL +//! format. + +use std::default::Default; +use std::fmt::{self, Display, Formatter, Write}; +use std::u32; + +/// An opaque reference to an extended basic block in a function. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Ebb(u32); + +impl Ebb { + pub fn new(index: usize) -> Ebb { + assert!(index < (u32::MAX as usize)); + Ebb(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display an `Ebb` reference as "ebb12". +impl Display for Ebb { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "ebb{}", self.0) + } +} + +/// A guaranteed invalid EBB reference. +pub const NO_EBB: Ebb = Ebb(u32::MAX); + +impl Default for Ebb { + fn default() -> Ebb { + NO_EBB + } +} + + +/// An opaque reference to an instruction in a function. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Inst(u32); + +impl Inst { + pub fn new(index: usize) -> Inst { + assert!(index < (u32::MAX as usize)); + Inst(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display an `Inst` reference as "inst7". +impl Display for Inst { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "inst{}", self.0) + } +} + +/// A guaranteed invalid instruction reference. +pub const NO_INST: Inst = Inst(u32::MAX); + +impl Default for Inst { + fn default() -> Inst { + NO_INST + } +} + + +/// An opaque reference to an SSA value. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Value(u32); + +// Value references can either reference an instruction directly, or they can refer to the extended +// value table. +pub enum ExpandedValue { + // This is the first value produced by the referenced instruction. + Direct(Inst), + + // This value is described in the extended value table. + Table(usize), + + // This is NO_VALUE. + None, +} + +impl Value { + pub fn new_direct(i: Inst) -> Value { + let encoding = i.index() * 2; + assert!(encoding < u32::MAX as usize); + Value(encoding as u32) + } + + pub fn new_table(index: usize) -> Value { + let encoding = index * 2 + 1; + assert!(encoding < u32::MAX as usize); + Value(encoding as u32) + } + + // Expand the internal representation into something useful. + pub fn expand(&self) -> ExpandedValue { + use self::ExpandedValue::*; + if *self == NO_VALUE { + return None; + } + let index = (self.0 / 2) as usize; + if self.0 % 2 == 0 { + Direct(Inst::new(index)) + } else { + Table(index) + } + } +} + +/// Display a `Value` reference as "v7" or "v2x". +impl Display for Value { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + use self::ExpandedValue::*; + match self.expand() { + Direct(i) => write!(fmt, "v{}", i.0), + Table(i) => write!(fmt, "vx{}", i), + None => write!(fmt, "NO_VALUE"), + } + } +} + +/// A guaranteed invalid value reference. +pub const NO_VALUE: Value = Value(u32::MAX); + +impl Default for Value { + fn default() -> Value { + NO_VALUE + } +} + +/// An opaque reference to a stack slot. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct StackSlot(u32); + +impl StackSlot { + pub fn new(index: usize) -> StackSlot { + assert!(index < (u32::MAX as usize)); + StackSlot(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display a `StackSlot` reference as "ss12". +impl Display for StackSlot { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "ss{}", self.0) + } +} + +/// A guaranteed invalid stack slot reference. +pub const NO_STACK_SLOT: StackSlot = StackSlot(u32::MAX); + +impl Default for StackSlot { + fn default() -> StackSlot { + NO_STACK_SLOT + } +} diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/immediates.rs index 135d3c6b65..a329d74f70 100644 --- a/src/libcretonne/immediates.rs +++ b/src/libcretonne/immediates.rs @@ -9,73 +9,6 @@ use std::fmt::{self, Display, Formatter}; use std::mem; use std::str::FromStr; -// Include code generated by `meta/gen_instr.py`. This file contains: -// -// - The `pub enum Opcode` definition with all known opcodes, -// - The private `fn opcode_name(Opcode) -> &'static str` function, and -// - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`. -// -include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); - -impl Display for Opcode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", opcode_name(*self)) - } -} - -impl Opcode { - /// Get the instruction format for this opcode. - pub fn format(self) -> Option { - if self == Opcode::NotAnOpcode { - None - } else { - Some(OPCODE_FORMAT[self as usize - 1]) - } - } -} - -// A primitive hash function for matching opcodes. -// Must match `meta/constant_hash.py`. -fn simple_hash(s: &str) -> u32 { - let mut h: u32 = 5381; - for c in s.chars() { - h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); - } - h -} - -impl FromStr for Opcode { - type Err = &'static str; - - /// Parse an Opcode name from a string. - fn from_str(s: &str) -> Result { - let tlen = OPCODE_HASH_TABLE.len(); - assert!(tlen.is_power_of_two()); - let mut idx = simple_hash(s) as usize; - let mut step: usize = 0; - loop { - idx = idx % tlen; - let entry = OPCODE_HASH_TABLE[idx]; - - if entry == Opcode::NotAnOpcode { - return Err("Unknown opcode"); - } - - if *opcode_name(entry) == *s { - return Ok(entry); - } - - // Quadratic probing. - step += 1; - // When `tlen` is a power of two, it can be proven that idx will visit all entries. - // This means that this loop will always terminate if the hash table has even one - // unused entry. - assert!(step < tlen); - idx += step; - } - } -} - /// 64-bit immediate integer operand. /// #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -494,27 +427,6 @@ mod tests { use std::str::FromStr; use std::fmt::Display; - #[test] - fn opcodes() { - let x = Opcode::Iadd; - let mut y = Opcode::Isub; - - assert!(x != y); - y = Opcode::Iadd; - assert_eq!(x, y); - assert_eq!(x.format(), Some(InstructionFormat::Binary)); - - assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); - assert_eq!(Opcode::IaddImm.to_string(), "iadd_imm"); - - // Check the matcher. - assert_eq!("iadd".parse::(), Ok(Opcode::Iadd)); - assert_eq!("iadd_imm".parse::(), Ok(Opcode::IaddImm)); - assert_eq!("iadd\0".parse::(), Err("Unknown opcode")); - assert_eq!("".parse::(), Err("Unknown opcode")); - assert_eq!("\0".parse::(), Err("Unknown opcode")); - } - #[test] fn format_imm64() { assert_eq!(Imm64(0).to_string(), "0"); @@ -791,5 +703,4 @@ mod tests { parse_ok::("sNaN:0x4000000000001", "sNaN:0x4000000000001"); parse_err::("sNaN:0x8000000000001", "Invalid sNaN payload"); } - } diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs new file mode 100644 index 0000000000..025e6ebb23 --- /dev/null +++ b/src/libcretonne/instructions.rs @@ -0,0 +1,229 @@ +//! Instruction formats and opcodes. +//! +//! The `instructions` module contains definitions for instruction formats, opcodes, and the +//! in-memory representation of IL instructions. +//! +//! A large part of this module is auto-generated from the instruction descriptions in the meta +//! directory. + +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; + +use entities::*; +use immediates::*; +use types::Type; + +// Include code generated by `meta/gen_instr.py`. This file contains: +// +// - The `pub enum InstructionFormat` enum with all the instruction formats. +// - The `pub enum Opcode` definition with all known opcodes, +// - The `const OPCODE_FORMAT: [InstructionFormat; N]` table. +// - The private `fn opcode_name(Opcode) -> &'static str` function, and +// - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`. +// +include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); + +impl Display for Opcode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", opcode_name(*self)) + } +} + +impl Opcode { + /// Get the instruction format for this opcode. + pub fn format(self) -> Option { + if self == Opcode::NotAnOpcode { + None + } else { + Some(OPCODE_FORMAT[self as usize - 1]) + } + } +} + +// A primitive hash function for matching opcodes. +// Must match `meta/constant_hash.py`. +fn simple_hash(s: &str) -> u32 { + let mut h: u32 = 5381; + for c in s.chars() { + h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); + } + h +} + +// This trait really belongs in libreader where it is used by the .cton file parser, but since it +// critically depends on the `opcode_name()` function which is needed here anyway, it lives in this +// module. This also saves us from runing the build script twice to generate code for the two +// separate crates. +impl FromStr for Opcode { + type Err = &'static str; + + /// Parse an Opcode name from a string. + fn from_str(s: &str) -> Result { + let tlen = OPCODE_HASH_TABLE.len(); + assert!(tlen.is_power_of_two()); + let mut idx = simple_hash(s) as usize; + let mut step: usize = 0; + loop { + idx = idx % tlen; + let entry = OPCODE_HASH_TABLE[idx]; + + if entry == Opcode::NotAnOpcode { + return Err("Unknown opcode"); + } + + if *opcode_name(entry) == *s { + return Ok(entry); + } + + // Quadratic probing. + step += 1; + // When `tlen` is a power of two, it can be proven that idx will visit all entries. + // This means that this loop will always terminate if the hash table has even one + // unused entry. + assert!(step < tlen); + idx += step; + } + } +} + +/// Contents on an instruction. +/// +/// Every variant must contain `opcode` and `ty` fields. An instruction that doesn't produce a +/// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at +/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a +/// `Box` to store the additional information out of line. +#[derive(Debug)] +pub enum InstructionData { + Nullary { + opcode: Opcode, + ty: Type, + }, + Unary { + opcode: Opcode, + ty: Type, + arg: Value, + }, + UnaryImm { + opcode: Opcode, + ty: Type, + imm: Imm64, + }, + Binary { + opcode: Opcode, + ty: Type, + args: [Value; 2], + }, + BinaryImm { + opcode: Opcode, + ty: Type, + arg: Value, + imm: Imm64, + }, + Call { + opcode: Opcode, + ty: Type, + data: Box, + }, +} + +/// Payload of a call instruction. +#[derive(Debug)] +pub struct CallData { + /// Second result value for a call producing multiple return values. + second_result: Value, + + // Dynamically sized array containing call argument values. + arguments: Vec, +} + + +impl InstructionData { + /// Create data for a call instruction. + pub fn call(opc: Opcode, return_type: Type) -> InstructionData { + InstructionData::Call { + opcode: opc, + ty: return_type, + data: Box::new(CallData { + second_result: NO_VALUE, + arguments: Vec::new(), + }), + } + } + + /// Get the opcode of this instruction. + pub fn opcode(&self) -> Opcode { + use self::InstructionData::*; + match *self { + Nullary { opcode, .. } => opcode, + Unary { opcode, .. } => opcode, + UnaryImm { opcode, .. } => opcode, + Binary { opcode, .. } => opcode, + BinaryImm { opcode, .. } => opcode, + Call { opcode, .. } => opcode, + } + } + + /// Type of the first result. + pub fn first_type(&self) -> Type { + use self::InstructionData::*; + match *self { + Nullary { ty, .. } => ty, + Unary { ty, .. } => ty, + UnaryImm { ty, .. } => ty, + Binary { ty, .. } => ty, + BinaryImm { ty, .. } => ty, + Call { ty, .. } => ty, + } + } + + /// Second result value, if any. + pub fn second_result(&self) -> Option { + use self::InstructionData::*; + match *self { + Nullary { .. } => None, + Unary { .. } => None, + UnaryImm { .. } => None, + Binary { .. } => None, + BinaryImm { .. } => None, + Call { ref data, .. } => Some(data.second_result), + } + } + + pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> { + use self::InstructionData::*; + match *self { + Nullary { .. } => None, + Unary { .. } => None, + UnaryImm { .. } => None, + Binary { .. } => None, + BinaryImm { .. } => None, + Call { ref mut data, .. } => Some(&mut data.second_result), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn opcodes() { + let x = Opcode::Iadd; + let mut y = Opcode::Isub; + + assert!(x != y); + y = Opcode::Iadd; + assert_eq!(x, y); + assert_eq!(x.format(), Some(InstructionFormat::Binary)); + + assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); + assert_eq!(Opcode::IaddImm.to_string(), "iadd_imm"); + + // Check the matcher. + assert_eq!("iadd".parse::(), Ok(Opcode::Iadd)); + assert_eq!("iadd_imm".parse::(), Ok(Opcode::IaddImm)); + assert_eq!("iadd\0".parse::(), Err("Unknown opcode")); + assert_eq!("".parse::(), Err("Unknown opcode")); + assert_eq!("\0".parse::(), Err("Unknown opcode")); + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index f47b049156..248c6bb37f 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -7,5 +7,7 @@ pub mod types; pub mod immediates; +pub mod entities; +pub mod instructions; pub mod repr; pub mod write; diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 534fd1a7cb..5064a7939f 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -2,11 +2,10 @@ //! Representation of Cretonne IL functions. use types::{Type, FunctionName, Signature}; -use immediates::*; -use std::default::Default; -use std::fmt::{self, Display, Formatter, Write}; +use entities::*; +use instructions::*; +use std::fmt::{self, Display, Formatter}; use std::ops::Index; -use std::u32; // ====--------------------------------------------------------------------------------------====// // @@ -14,34 +13,6 @@ use std::u32; // // ====--------------------------------------------------------------------------------------====// -/// An opaque reference to an extended basic block in a function. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Ebb(u32); - -/// A guaranteed invalid EBB reference. -pub const NO_EBB: Ebb = Ebb(u32::MAX); - -/// An opaque reference to an instruction in a function. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Inst(u32); - -/// A guaranteed invalid instruction reference. -pub const NO_INST: Inst = Inst(u32::MAX); - -/// An opaque reference to an SSA value. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Value(u32); - -/// A guaranteed invalid value reference. -pub const NO_VALUE: Value = Value(u32::MAX); - -/// An opaque reference to a stack slot. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct StackSlot(u32); - -/// A guaranteed invalid stack slot reference. -pub const NO_STACK_SLOT: StackSlot = StackSlot(u32::MAX); - /// A function. /// /// The `Function` struct owns all of its instructions and extended basic blocks, and it works as a @@ -98,81 +69,12 @@ pub struct EbbData { last_arg: Value, } -/// Contents on an instruction. -/// -/// Every variant must contain `opcode` and `ty` fields. An instruction that doesn't produce a -/// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at -/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a -/// `Box` to store the additional information out of line. -#[derive(Debug)] -pub enum InstructionData { - Nullary { - opcode: Opcode, - ty: Type, - }, - Unary { - opcode: Opcode, - ty: Type, - arg: Value, - }, - UnaryImm { - opcode: Opcode, - ty: Type, - imm: Imm64, - }, - Binary { - opcode: Opcode, - ty: Type, - args: [Value; 2], - }, - BinaryImm { - opcode: Opcode, - ty: Type, - arg: Value, - imm: Imm64, - }, - Call { - opcode: Opcode, - ty: Type, - data: Box, - }, -} - -/// Payload of a call instruction. -#[derive(Debug)] -pub struct CallData { - /// Second result value for a call producing multiple return values. - second_result: Value, - - // Dynamically sized array containing call argument values. - arguments: Vec, -} - - // ====--------------------------------------------------------------------------------------====// // // Stack slot implementation. // // ====--------------------------------------------------------------------------------------====// -impl StackSlot { - fn new(index: usize) -> StackSlot { - assert!(index < (u32::MAX as usize)); - StackSlot(index as u32) - } - - pub fn index(&self) -> usize { - self.0 as usize - } -} - -/// Display a `StackSlot` reference as "ss12". -impl Display for StackSlot { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "ss{}", self.0) - } -} - impl StackSlotData { /// Create a stack slot with the specified byte size. pub fn new(size: u32) -> StackSlotData { @@ -221,24 +123,6 @@ impl Iterator for StackSlotIter { // // ====--------------------------------------------------------------------------------------====// -impl Ebb { - fn new(index: usize) -> Ebb { - assert!(index < (u32::MAX as usize)); - Ebb(index as u32) - } - - pub fn index(&self) -> usize { - self.0 as usize - } -} - -/// Display an `Ebb` reference as "ebb12". -impl Display for Ebb { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "ebb{}", self.0) - } -} - impl EbbData { fn new() -> EbbData { EbbData { @@ -254,24 +138,6 @@ impl EbbData { // // ====--------------------------------------------------------------------------------------====// -impl Inst { - fn new(index: usize) -> Inst { - assert!(index < (u32::MAX as usize)); - Inst(index as u32) - } - - pub fn index(&self) -> usize { - self.0 as usize - } -} - -/// Display an `Inst` reference as "inst7". -impl Display for Inst { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "inst{}", self.0) - } -} - /// Allow immutable access to instructions via function indexing. impl Index for Function { type Output = InstructionData; @@ -287,65 +153,6 @@ impl Index for Function { // // ====--------------------------------------------------------------------------------------====// -// Value references can either reference an instruction directly, or they can refer to the -// extended value table. -enum ExpandedValue { - // This is the first value produced by the referenced instruction. - Direct(Inst), - - // This value is described in the extended value table. - Table(usize), - - // This is NO_VALUE. - None, -} - -impl Value { - fn new_direct(i: Inst) -> Value { - let encoding = i.index() * 2; - assert!(encoding < u32::MAX as usize); - Value(encoding as u32) - } - - fn new_table(index: usize) -> Value { - let encoding = index * 2 + 1; - assert!(encoding < u32::MAX as usize); - Value(encoding as u32) - } - - // Expand the internal representation into something useful. - fn expand(&self) -> ExpandedValue { - use self::ExpandedValue::*; - if *self == NO_VALUE { - return None; - } - let index = (self.0 / 2) as usize; - if self.0 % 2 == 0 { - Direct(Inst::new(index)) - } else { - Table(index) - } - } -} - -impl Default for Value { - fn default() -> Value { - NO_VALUE - } -} - -/// Display a `Value` reference as "v7" or "v2x". -impl Display for Value { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - use self::ExpandedValue::*; - match self.expand() { - Direct(i) => write!(fmt, "v{}", i.0), - Table(i) => write!(fmt, "vx{}", i), - None => write!(fmt, "NO_VALUE"), - } - } -} - // Most values are simply the first value produced by an instruction. // Other values have an entry in the value table. #[derive(Debug)] @@ -398,71 +205,6 @@ impl<'a> Iterator for Values<'a> { } } -impl InstructionData { - /// Create data for a call instruction. - pub fn call(opc: Opcode, return_type: Type) -> InstructionData { - InstructionData::Call { - opcode: opc, - ty: return_type, - data: Box::new(CallData { - second_result: NO_VALUE, - arguments: Vec::new(), - }), - } - } - - /// Get the opcode of this instruction. - pub fn opcode(&self) -> Opcode { - use self::InstructionData::*; - match *self { - Nullary { opcode, .. } => opcode, - Unary { opcode, .. } => opcode, - UnaryImm { opcode, .. } => opcode, - Binary { opcode, .. } => opcode, - BinaryImm { opcode, .. } => opcode, - Call { opcode, .. } => opcode, - } - } - - /// Type of the first result. - pub fn first_type(&self) -> Type { - use self::InstructionData::*; - match *self { - Nullary { ty, .. } => ty, - Unary { ty, .. } => ty, - UnaryImm { ty, .. } => ty, - Binary { ty, .. } => ty, - BinaryImm { ty, .. } => ty, - Call { ty, .. } => ty, - } - } - - /// Second result value, if any. - fn second_result(&self) -> Option { - use self::InstructionData::*; - match *self { - Nullary { .. } => None, - Unary { .. } => None, - UnaryImm { .. } => None, - Binary { .. } => None, - BinaryImm { .. } => None, - Call { ref data, .. } => Some(data.second_result), - } - } - - fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> { - use self::InstructionData::*; - match *self { - Nullary { .. } => None, - Unary { .. } => None, - UnaryImm { .. } => None, - Binary { .. } => None, - BinaryImm { .. } => None, - Call { ref mut data, .. } => Some(&mut data.second_result), - } - } -} - impl Function { /// Create a function with the given name and signature. pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function { @@ -632,7 +374,7 @@ impl Function { /// Get the type of a value. pub fn value_type(&self, v: Value) -> Type { - use self::ExpandedValue::*; + use entities::ExpandedValue::*; use self::ValueData::*; match v.expand() { Direct(i) => self[i].first_type(), @@ -651,7 +393,8 @@ impl Function { mod tests { use super::*; use types; - use immediates::*; + use entities::*; + use instructions::*; #[test] fn make_inst() { diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index e74ae22c38..119957d7c4 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -12,7 +12,8 @@ use std::u32; use lexer::{self, Lexer, Token}; use cretonne::types::{FunctionName, Signature, ArgumentType, ArgumentExtension}; use cretonne::immediates::Imm64; -use cretonne::repr::{Function, StackSlot, StackSlotData}; +use cretonne::entities::StackSlot; +use cretonne::repr::{Function, StackSlotData}; pub use lexer::Location; From 3670f57c408f889069c41e3316aae0344ae750aa Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 May 2016 14:27:24 -0700 Subject: [PATCH 077/968] Synchronize InstructionFormat and InstructionData. These two enums must have identical variants. One is generated from the instruction formats in meta/cretonne/formats.py, the other defines the contents of an instruction. Emit a conversion from InstructionData to InstructionFormat which also serves to verify the correspondence. Rustc will error is the match is not complete. --- meta/cretonne/__init__.py | 11 ++++++++++ meta/cretonne/formats.py | 5 ++++- meta/gen_instr.py | 14 ++++++++++++ src/libcretonne/instructions.rs | 39 ++++++++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 4b8267d2d1..cbad9185fc 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -55,6 +55,17 @@ value = OperandKind( operand. """) +#: A variable-sizes list of value operands. Use for Ebb and function call +#: arguemnts. +args = OperandKind( + 'args', """ + A variable size list of `value` operands. + + Use this to represent arguemtns passed to a function call, arguments + passed to an extended basic block, or a variable number of results + returned from an instruction. + """) + # Instances of immediate operand types are provided in the cretonne.immediates # module. diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 6f34214db9..cb02d80998 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -7,9 +7,11 @@ in this module. """ -from . import InstructionFormat, value +from . import InstructionFormat, value, args from immediates import imm64, ieee32, ieee64, immvector +Nullary = InstructionFormat() + Unary = InstructionFormat(value) UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) @@ -20,6 +22,7 @@ Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) BinaryImmRev = InstructionFormat(imm64, value) +Call = InstructionFormat(args, multiple_results=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 8c5ad8a9ce..8fe63cb932 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -21,6 +21,20 @@ def gen_formats(fmt): fmt.line(f.name + ',') fmt.line() + # Emit a From which also serves to verify that + # InstructionFormat and InstructionData are in sync. + with fmt.indented( + "impl<'a> From<&'a InstructionData> for InstructionFormat {", '}'): + with fmt.indented( + "fn from(inst: &'a InstructionData) -> InstructionFormat {", + '}'): + with fmt.indented('match *inst {', '}'): + for f in cretonne.InstructionFormat.all_formats: + fmt.line(('InstructionData::{} {{ .. }} => ' + + 'InstructionFormat::{},') + .format(f.name, f.name)) + fmt.line() + def collect_instr_groups(targets): seen = set() diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 025e6ebb23..325033284a 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -108,6 +108,20 @@ pub enum InstructionData { ty: Type, imm: Imm64, }, + UnaryIeee32 { + opcode: Opcode, + ty: Type, + imm: Ieee32, + }, + UnaryIeee64 { + opcode: Opcode, + ty: Type, + imm: Ieee64, + }, + UnaryImmVector { + opcode: Opcode, + ty: Type, // TBD: imm: Box + }, Binary { opcode: Opcode, ty: Type, @@ -117,7 +131,14 @@ pub enum InstructionData { opcode: Opcode, ty: Type, arg: Value, - imm: Imm64, + rhs: Imm64, + }, + // Same as BinaryImm, but the imediate is the lhs operand. + BinaryImmRev { + opcode: Opcode, + ty: Type, + arg: Value, + lhs: Imm64, }, Call { opcode: Opcode, @@ -157,8 +178,12 @@ impl InstructionData { Nullary { opcode, .. } => opcode, Unary { opcode, .. } => opcode, UnaryImm { opcode, .. } => opcode, + UnaryIeee32 { opcode, .. } => opcode, + UnaryIeee64 { opcode, .. } => opcode, + UnaryImmVector { opcode, .. } => opcode, Binary { opcode, .. } => opcode, BinaryImm { opcode, .. } => opcode, + BinaryImmRev { opcode, .. } => opcode, Call { opcode, .. } => opcode, } } @@ -170,8 +195,12 @@ impl InstructionData { Nullary { ty, .. } => ty, Unary { ty, .. } => ty, UnaryImm { ty, .. } => ty, + UnaryIeee32 { ty, .. } => ty, + UnaryIeee64 { ty, .. } => ty, + UnaryImmVector { ty, .. } => ty, Binary { ty, .. } => ty, BinaryImm { ty, .. } => ty, + BinaryImmRev { ty, .. } => ty, Call { ty, .. } => ty, } } @@ -183,8 +212,12 @@ impl InstructionData { Nullary { .. } => None, Unary { .. } => None, UnaryImm { .. } => None, + UnaryIeee32 { .. } => None, + UnaryIeee64 { .. } => None, + UnaryImmVector { .. } => None, Binary { .. } => None, BinaryImm { .. } => None, + BinaryImmRev { .. } => None, Call { ref data, .. } => Some(data.second_result), } } @@ -195,8 +228,12 @@ impl InstructionData { Nullary { .. } => None, Unary { .. } => None, UnaryImm { .. } => None, + UnaryIeee32 { .. } => None, + UnaryIeee64 { .. } => None, + UnaryImmVector { .. } => None, Binary { .. } => None, BinaryImm { .. } => None, + BinaryImmRev { .. } => None, Call { ref mut data, .. } => Some(&mut data.second_result), } } From 5e0e9234642c80fd1b67ac244b890d1ebfd8b1d3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 May 2016 17:45:57 -0700 Subject: [PATCH 078/968] Track instruction order in an EBB. Place instructions in a doubly linked list and point to the first and last instruction in an EBB. Provide an iterator for all the EBBs too. This doesn't reflect the layout order, but simply the order blocks were created. --- src/libcretonne/repr.rs | 483 ++++++++++++++++++++++++++-------------- 1 file changed, 312 insertions(+), 171 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 5064a7939f..a444dd0b3d 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,17 +1,10 @@ - //! Representation of Cretonne IL functions. use types::{Type, FunctionName, Signature}; use entities::*; use instructions::*; use std::fmt::{self, Display, Formatter}; -use std::ops::Index; - -// ====--------------------------------------------------------------------------------------====// -// -// Public data types. -// -// ====--------------------------------------------------------------------------------------====// +use std::ops::{Index, IndexMut}; /// A function. /// @@ -41,168 +34,9 @@ pub struct Function { /// Others index into this table. extended_values: Vec, - /// Return type(s). A function may return zero or more values. - pub return_types: Vec, -} - -/// Contents of a stack slot. -#[derive(Debug)] -pub struct StackSlotData { - /// Size of stack slot in bytes. - pub size: u32, -} - -/// Contents of an extended basic block. -/// -/// Arguments for an extended basic block are values that dominate everything in the EBB. All -/// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must -/// match the function arguments. -#[derive(Debug)] -pub struct EbbData { - /// First argument to this EBB, or `NO_VALUE` if the block has no arguments. - /// - /// The arguments are all ValueData::Argument entries that form a linked list from `first_arg` - /// to `last_arg`. - first_arg: Value, - - /// Last argument to this EBB, or `NO_VALUE` if the block has no arguments. - last_arg: Value, -} - -// ====--------------------------------------------------------------------------------------====// -// -// Stack slot implementation. -// -// ====--------------------------------------------------------------------------------------====// - -impl StackSlotData { - /// Create a stack slot with the specified byte size. - pub fn new(size: u32) -> StackSlotData { - StackSlotData { size: size } - } -} - -impl Display for StackSlotData { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "stack_slot {}", self.size) - } -} - -/// Allow immutable access to stack slots via function indexing. -impl Index for Function { - type Output = StackSlotData; - - fn index<'a>(&'a self, ss: StackSlot) -> &'a StackSlotData { - &self.stack_slots[ss.index()] - } -} - -/// Stack slot iterator visits all stack slots in a function, returning `StackSlot` references. -pub struct StackSlotIter { - cur: usize, - end: usize, -} - -impl Iterator for StackSlotIter { - type Item = StackSlot; - - fn next(&mut self) -> Option { - if self.cur < self.end { - let ss = StackSlot::new(self.cur); - self.cur += 1; - Some(ss) - } else { - None - } - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Extended basic block implementation. -// -// ====--------------------------------------------------------------------------------------====// - -impl EbbData { - fn new() -> EbbData { - EbbData { - first_arg: NO_VALUE, - last_arg: NO_VALUE, - } - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Instruction implementation. -// -// ====--------------------------------------------------------------------------------------====// - -/// Allow immutable access to instructions via function indexing. -impl Index for Function { - type Output = InstructionData; - - fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData { - &self.instructions[inst.index()] - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Value implementation. -// -// ====--------------------------------------------------------------------------------------====// - -// Most values are simply the first value produced by an instruction. -// Other values have an entry in the value table. -#[derive(Debug)] -enum ValueData { - // Value is defined by an instruction, but it is not the first result. - Def { - ty: Type, - def: Inst, - next: Value, // Next result defined by `def`. - }, - - // Value is an EBB argument. - Argument { - ty: Type, - ebb: Ebb, - next: Value, // Next argument to `ebb`. - }, -} - -/// Iterate through a list of related value references, such as: -/// -/// - All results defined by an instruction. -/// - All arguments to an EBB -/// -/// A value iterator borrows a Function reference. -pub struct Values<'a> { - func: &'a Function, - cur: Value, -} - -impl<'a> Iterator for Values<'a> { - type Item = Value; - - fn next(&mut self) -> Option { - let prev = self.cur; - - // Advance self.cur to the next value, or NO_VALUE. - self.cur = match prev.expand() { - ExpandedValue::Direct(inst) => self.func[inst].second_result().unwrap_or_default(), - ExpandedValue::Table(index) => { - match self.func.extended_values[index] { - ValueData::Def { next, .. } => next, - ValueData::Argument { next, .. } => next, - } - } - ExpandedValue::None => return None, - }; - - Some(prev) - } + // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, + // terminated in both ends by NO_INST. + inst_order: Vec, } impl Function { @@ -215,7 +49,7 @@ impl Function { instructions: Vec::new(), extended_basic_blocks: Vec::new(), extended_values: Vec::new(), - return_types: Vec::new(), + inst_order: Vec::new(), } } @@ -255,6 +89,11 @@ impl Function { pub fn make_inst(&mut self, data: InstructionData) -> Inst { let inst = Inst::new(self.instructions.len()); self.instructions.push(data); + self.inst_order.push(InstNode { + prev: NO_INST, + next: NO_INST, + }); + debug_assert_eq!(self.instructions.len(), self.inst_order.len()); inst } @@ -328,6 +167,14 @@ impl Function { &mut self.extended_basic_blocks[ebb.index()] } + /// Iterate over all the EBBs in order of creation. + pub fn ebbs_numerically(&self) -> NumericalEbbs { + NumericalEbbs { + cur: 0, + limit: self.extended_basic_blocks.len(), + } + } + /// Append an argument with type `ty` to `ebb`. pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { let val = self.make_value(ValueData::Argument { @@ -363,6 +210,32 @@ impl Function { } } + /// Append an instruction to a basic block. + pub fn append_inst(&mut self, ebb: Ebb, inst: Inst) { + let old_last = self[ebb].last_inst; + + self.inst_order[inst.index()] = InstNode { + prev: old_last, + next: NO_INST, + }; + + if old_last == NO_INST { + assert!(self[ebb].first_inst == NO_INST); + self[ebb].first_inst = inst; + } else { + self.inst_order[old_last.index()].next = inst; + } + self[ebb].last_inst = inst; + } + + /// Iterate through the instructions in `ebb`. + pub fn ebb_insts<'a>(&'a self, ebb: Ebb) -> EbbInsts<'a> { + EbbInsts { + func: self, + cur: self[ebb].first_inst, + } + } + // Values. /// Allocate an extended value entry. @@ -389,6 +262,238 @@ impl Function { } } +// ====--------------------------------------------------------------------------------------====// +// +// Stack slot implementation. +// +// ====--------------------------------------------------------------------------------------====// + +/// Contents of a stack slot. +#[derive(Debug)] +pub struct StackSlotData { + /// Size of stack slot in bytes. + pub size: u32, +} + +impl StackSlotData { + /// Create a stack slot with the specified byte size. + pub fn new(size: u32) -> StackSlotData { + StackSlotData { size: size } + } +} + +impl Display for StackSlotData { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "stack_slot {}", self.size) + } +} + +/// Allow immutable access to stack slots via function indexing. +impl Index for Function { + type Output = StackSlotData; + + fn index<'a>(&'a self, ss: StackSlot) -> &'a StackSlotData { + &self.stack_slots[ss.index()] + } +} + +/// Stack slot iterator visits all stack slots in a function, returning `StackSlot` references. +pub struct StackSlotIter { + cur: usize, + end: usize, +} + +impl Iterator for StackSlotIter { + type Item = StackSlot; + + fn next(&mut self) -> Option { + if self.cur < self.end { + let ss = StackSlot::new(self.cur); + self.cur += 1; + Some(ss) + } else { + None + } + } +} + +// ====--------------------------------------------------------------------------------------====// +// +// Extended basic block implementation. +// +// ====--------------------------------------------------------------------------------------====// + +/// Contents of an extended basic block. +/// +/// Arguments for an extended basic block are values that dominate everything in the EBB. All +/// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must +/// match the function arguments. +#[derive(Debug)] +pub struct EbbData { + /// First argument to this EBB, or `NO_VALUE` if the block has no arguments. + /// + /// The arguments are all ValueData::Argument entries that form a linked list from `first_arg` + /// to `last_arg`. + first_arg: Value, + + /// Last argument to this EBB, or `NO_VALUE` if the block has no arguments. + last_arg: Value, + + /// First instruction in this block, or `NO_INST`. + first_inst: Inst, + + /// Last instruction in this block, or `NO_INST`. + last_inst: Inst, +} + +impl EbbData { + fn new() -> EbbData { + EbbData { + first_arg: NO_VALUE, + last_arg: NO_VALUE, + first_inst: NO_INST, + last_inst: NO_INST, + } + } +} + +impl Index for Function { + type Output = EbbData; + + fn index<'a>(&'a self, ebb: Ebb) -> &'a EbbData { + &self.extended_basic_blocks[ebb.index()] + } +} + +impl IndexMut for Function { + fn index_mut<'a>(&'a mut self, ebb: Ebb) -> &'a mut EbbData { + &mut self.extended_basic_blocks[ebb.index()] + } +} + +pub struct EbbInsts<'a> { + func: &'a Function, + cur: Inst, +} + +impl<'a> Iterator for EbbInsts<'a> { + type Item = Inst; + + fn next(&mut self) -> Option { + let prev = self.cur; + if prev == NO_INST { + None + } else { + // Advance self.cur to the next inst. + self.cur = self.func.inst_order[prev.index()].next; + Some(prev) + } + } +} + +/// Iterate through all EBBs in a function in numerical order. +/// This order is stable, but has little significance to the semantics of the function. +pub struct NumericalEbbs { + cur: usize, + limit: usize, +} + +impl Iterator for NumericalEbbs { + type Item = Ebb; + + fn next(&mut self) -> Option { + if self.cur < self.limit { + let prev = Ebb::new(self.cur); + self.cur += 1; + Some(prev) + } else { + None + } + } +} + +// ====--------------------------------------------------------------------------------------====// +// +// Instruction implementation. +// +// The InstructionData layout is defined in the `instructions` module. +// +// ====--------------------------------------------------------------------------------------====// + +/// Allow immutable access to instructions via function indexing. +impl Index for Function { + type Output = InstructionData; + + fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData { + &self.instructions[inst.index()] + } +} + +/// A node in a double linked list of instructions is a basic block. +#[derive(Debug)] +struct InstNode { + prev: Inst, + next: Inst, +} + +// ====--------------------------------------------------------------------------------------====// +// +// Value implementation. +// +// ====--------------------------------------------------------------------------------------====// + +// Most values are simply the first value produced by an instruction. +// Other values have an entry in the value table. +#[derive(Debug)] +enum ValueData { + // Value is defined by an instruction, but it is not the first result. + Def { + ty: Type, + def: Inst, + next: Value, // Next result defined by `def`. + }, + + // Value is an EBB argument. + Argument { + ty: Type, + ebb: Ebb, + next: Value, // Next argument to `ebb`. + }, +} + +/// Iterate through a list of related value references, such as: +/// +/// - All results defined by an instruction. +/// - All arguments to an EBB +/// +/// A value iterator borrows a Function reference. +pub struct Values<'a> { + func: &'a Function, + cur: Value, +} + +impl<'a> Iterator for Values<'a> { + type Item = Value; + + fn next(&mut self) -> Option { + let prev = self.cur; + + // Advance self.cur to the next value, or NO_VALUE. + self.cur = match prev.expand() { + ExpandedValue::Direct(inst) => self.func[inst].second_result().unwrap_or_default(), + ExpandedValue::Table(index) => { + match self.func.extended_values[index] { + ValueData::Def { next, .. } => next, + ValueData::Argument { next, .. } => next, + } + } + ExpandedValue::None => return None, + }; + + Some(prev) + } +} + #[cfg(test)] mod tests { use super::*; @@ -445,6 +550,8 @@ mod tests { fn ebb() { let mut func = Function::new(); + assert_eq!(func.ebbs_numerically().next(), None); + let ebb = func.make_ebb(); assert_eq!(ebb.to_string(), "ebb0"); assert_eq!(func.ebb_args(ebb).next(), None); @@ -464,5 +571,39 @@ mod tests { assert_eq!(args2.next(), Some(arg2)); assert_eq!(args2.next(), None); } + + // The numerical ebb iterator doesn't capture the function. + let mut ebbs = func.ebbs_numerically(); + assert_eq!(ebbs.next(), Some(ebb)); + assert_eq!(ebbs.next(), None); + + assert_eq!(func.ebb_insts(ebb).next(), None); + + let inst = func.make_inst(InstructionData::Nullary { + opcode: Opcode::Iconst, + ty: types::I32, + }); + func.append_inst(ebb, inst); + { + let mut ii = func.ebb_insts(ebb); + assert_eq!(ii.next(), Some(inst)); + assert_eq!(ii.next(), None); + } + assert_eq!(func[ebb].first_inst, inst); + assert_eq!(func[ebb].last_inst, inst); + + let inst2 = func.make_inst(InstructionData::Nullary { + opcode: Opcode::Iconst, + ty: types::I32, + }); + func.append_inst(ebb, inst2); + { + let mut ii = func.ebb_insts(ebb); + assert_eq!(ii.next(), Some(inst)); + assert_eq!(ii.next(), Some(inst2)); + assert_eq!(ii.next(), None); + } + assert_eq!(func[ebb].first_inst, inst); + assert_eq!(func[ebb].last_inst, inst2); } } From 9838a4040e7a04e0e8d4f8d61263ed8559e23eab Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 May 2016 15:31:37 -0700 Subject: [PATCH 079/968] Implement write_instruction and write_ebb. Use the new iterators to write out the contents of a function. --- src/libcretonne/instructions.rs | 4 +- src/libcretonne/write.rs | 114 +++++++++++++++++++++++++++++--- 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 325033284a..39202b000f 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -130,14 +130,14 @@ pub enum InstructionData { BinaryImm { opcode: Opcode, ty: Type, - arg: Value, + lhs: Value, rhs: Imm64, }, // Same as BinaryImm, but the imediate is the lhs operand. BinaryImmRev { opcode: Opcode, ty: Type, - arg: Value, + rhs: Value, lhs: Imm64, }, Call { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index f245d977c2..9f5c28c0b6 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -6,6 +6,7 @@ use std::io::{self, Write}; use repr::Function; +use entities::{Inst, Ebb, Value}; pub type Result = io::Result<()>; @@ -13,7 +14,14 @@ pub type Result = io::Result<()>; pub fn write_function(w: &mut Write, func: &Function) -> Result { try!(write_spec(w, func)); try!(writeln!(w, " {{")); - try!(write_preamble(w, func)); + let mut any = try!(write_preamble(w, func)); + for ebb in func.ebbs_numerically() { + if !any { + try!(writeln!(w, "")); + } + try!(write_ebb(w, func, ebb)); + any = true; + } writeln!(w, "}}") } @@ -58,7 +66,7 @@ fn write_spec(w: &mut Write, func: &Function) -> Result { } } -fn write_preamble(w: &mut Write, func: &Function) -> Result { +fn write_preamble(w: &mut Write, func: &Function) -> io::Result { let mut any = false; for ss in func.stack_slot_iter() { @@ -66,11 +74,88 @@ fn write_preamble(w: &mut Write, func: &Function) -> Result { try!(writeln!(w, " {} = {}", ss, func[ss])); } - // Put a blank line after the preamble unless it was empty. - if any { - writeln!(w, "") - } else { - Ok(()) + Ok(any) +} + +// ====--------------------------------------------------------------------------------------====// +// +// Basic blocks +// +// ====--------------------------------------------------------------------------------------====// + +pub fn write_arg(w: &mut Write, func: &Function, arg: Value) -> Result { + write!(w, "{}: {}", arg, func.value_type(arg)) +} + +pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { + // Write out the basic block header, outdented: + // + // ebb1: + // ebb1(vx1: i32): + // ebb10(vx4: f64, vx5: b1): + // + + let mut args = func.ebb_args(ebb); + match args.next() { + None => return writeln!(w, "{}:", ebb), + Some(arg) => { + try!(write!(w, "{}(", ebb)); + try!(write_arg(w, func, arg)); + } + } + // Remaining args. + for arg in args { + try!(write!(w, ", ")); + try!(write_arg(w, func, arg)); + } + writeln!(w, "):") +} + +pub fn write_ebb(w: &mut Write, func: &Function, ebb: Ebb) -> Result { + try!(write_ebb_header(w, func, ebb)); + for inst in func.ebb_insts(ebb) { + try!(write_instruction(w, func, inst)); + } + Ok(()) +} + + +// ====--------------------------------------------------------------------------------------====// +// +// Instructions +// +// ====--------------------------------------------------------------------------------------====// + +pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { + try!(write!(w, " ")); + + // First write out the result values, if any. + let mut has_results = false; + for r in func.inst_results(inst) { + if !has_results { + has_results = true; + try!(write!(w, "{}", r)); + } else { + try!(write!(w, ", {}", r)); + } + } + if has_results { + try!(write!(w, " = ")); + } + + // Then the opcode and operands, depending on format. + use instructions::InstructionData::*; + match func[inst] { + Nullary { opcode, .. } => writeln!(w, "{}", opcode), + Unary { opcode, arg, .. } => writeln!(w, "{} {}", opcode, arg), + UnaryImm { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), + UnaryIeee32 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), + UnaryIeee64 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), + UnaryImmVector { opcode, .. } => writeln!(w, "{} [...]", opcode), + Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), + BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), + BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), + Call { opcode, .. } => writeln!(w, "{} [...]", opcode), } } @@ -79,6 +164,7 @@ mod tests { use super::*; use super::{needs_quotes, escaped}; use repr::{Function, StackSlotData}; + use types; #[test] fn quoting() { @@ -108,6 +194,18 @@ mod tests { f.make_stack_slot(StackSlotData::new(4)); assert_eq!(function_to_string(&f), - "function foo() {\n ss0 = stack_slot 4\n\n}\n"); + "function foo() {\n ss0 = stack_slot 4\n}\n"); + + let ebb = f.make_ebb(); + assert_eq!(function_to_string(&f), + "function foo() {\n ss0 = stack_slot 4\nebb0:\n}\n"); + + f.append_ebb_arg(ebb, types::I8); + assert_eq!(function_to_string(&f), + "function foo() {\n ss0 = stack_slot 4\nebb0(vx0: i8):\n}\n"); + + f.append_ebb_arg(ebb, types::F32.by(4).unwrap()); + assert_eq!(function_to_string(&f), + "function foo() {\n ss0 = stack_slot 4\nebb0(vx0: i8, vx1: f32x4):\n}\n"); } } From bd221af412cc89213881894abe2af02889993278 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 18 May 2016 11:31:47 -0700 Subject: [PATCH 080/968] Reorganize meta language reference. Separate instruction descriptions from instruction formats which deal with the Rust representation. Add type class restrictions to type variables. --- docs/metaref.rst | 159 +++++++++++++++++++++++++++----------- meta/cretonne/__init__.py | 39 ++++++---- meta/cretonne/base.py | 13 +++- 3 files changed, 145 insertions(+), 66 deletions(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index fe46e8af4a..2e47535492 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -4,65 +4,79 @@ Cretonne Meta Language Reference .. default-domain:: py .. highlight:: python - -The Cretonne meta language is used to define instructions for Cretonne. It is a -domain specific language embedded in Python. - -An instruction set is described by a Python module under the :file:`meta` -directory that has a global variable called ``instructions``. The basic -Cretonne instruction set described in :doc:`langref` is defined by the Python -module :mod:`cretonne.base`. - .. module:: cretonne -Value Types -=========== +The Cretonne meta language is used to define instructions for Cretonne. It is a +domain specific language embedded in Python. This document describes the Python +modules that form the embedded DSL. -Concrete value types are represented as instances of :class:`cretonne.ValueType`. There are -subclasses to represent scalar and vector types. +The meta language descriptions are Python modules under the :file:`meta` +top-level directory. The descriptions are processed in two steps: -.. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType - :parts: 1 -.. autoclass:: ValueType -.. autoclass:: ScalarType - :members: -.. autoclass:: VectorType - :members: -.. autoclass:: IntType - :members: -.. autoclass:: FloatType - :members: +1. The Python modules are imported. This has the effect of building static data + structures in global variables in the modules. These static data structures + use the classes in the :mod:`cretonne` module to describe instruction sets + and other properties. -Predefined types ----------------- -.. automodule:: cretonne.types - :members: +2. The static data structures are processed to produce Rust source code and + constant dables tables. -.. currentmodule:: cretonne +The main driver for this source code generation process is the +:file:`meta/build.py` script which is invoked as part of the build process if +anything in the :file:`meta` directory has changed since the last build. -Parametric polymorphism ------------------------ +Instruction descriptions +======================== -Instruction operands can be defined with *type variables* instead of concrete -types for their operands. This makes the instructions polymorphic. +New instructions are defined as instances of the :class:`Instruction` +class. As instruction instances are created, they are added to the currently +open :class:`InstructionGroup`. -.. autoclass:: TypeVar - -Instructions -============ - -New instructions are defined as instances of the :class:`cretonne.Instruction` -class. - -.. autoclass:: Instruction -.. autoclass:: Operand -.. autoclass:: OperandKind .. autoclass:: InstructionGroup :members: +The basic Cretonne instruction set described in :doc:`langref` is defined by the +Python module :mod:`cretonne.base`. This module has a global variable +:data:`cretonne.base.instructions` which is an :class:`InstructionGroup` +instance containing all the base instructions. -Immediates ----------- +.. autoclass:: Instruction + +An instruction is defined with a set of distinct input and output operands which +must be instances of the :class:`Operand` class. + +.. autoclass:: Operand + +Cretonne uses two separate type systems for immediate operands and SSA values. + +Type variables +-------------- + +Instruction descriptions can be made polymorphic by using :class:`Operand` +instances that refer to a *type variable* instead of a concrete value type. +Polymorphism only works for SSA value operands. Immediate operands have a fixed +operand kind. + +.. autoclass:: TypeVar + +If multiple operands refer to the same type variable they will be required to +have the same concrete type. For example, this defines an integer addition +instruction:: + + Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) + a = Operand('a', Int) + x = Operand('x', Int) + y = Operand('y', Int) + + iadd = Instruction('iadd', 'Integer addition', ins=(x, y), outs=a) + +The type variable `Int` is allowed to vary over all scalar and vector integer +value types, but in a given instance of the `iadd` instruction, the two +operands must have the same type, and the result will be the same type as the +inputs. + +Immediate operands +------------------ Immediate instruction operands don't correspond to SSA values, but have values that are encoded directly in the instruction. Immediate operands don't @@ -77,6 +91,59 @@ indicated with an instance of :class:`ImmediateKind`. .. currentmodule:: cretonne + +Value types +----------- + +Concrete value types are represented as instances of :class:`cretonne.ValueType`. There are +subclasses to represent scalar and vector types. + +.. autoclass:: ValueType +.. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType + :parts: 1 +.. autoclass:: ScalarType + :members: +.. autoclass:: VectorType + :members: +.. autoclass:: IntType + :members: +.. autoclass:: FloatType + :members: + +.. automodule:: cretonne.types + :members: + +.. currentmodule:: cretonne + +There are no predefined vector types, but they can be created as needed with +the :func:`ScalarType.by` function. + + +Instruction representation +========================== + +The Rust in-memory representation of instructions is derived from the +instruction descriptions. Part of the representation is generated, and part is +written as Rust code in the `cretonne.instructions` module. The instruction +representation depends on the input operand kinds and whether the instruction +can produce multiple results. + +.. autoclass:: OperandKind + +Since all SSA value operands are represented as a `Value` in Rust code, value +types don't affect the representation. Two special operand kinds are used to +represent SSA values: + +.. autodata:: value +.. autodata:: args + +When an instruction description is created, it is automatically assigned a +predefined instruction format which is an instance of +:class:`InstructionFormat`: + +.. autoclass:: InstructionFormat + + Targets ======= diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index cbad9185fc..7e6f61d6bf 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -24,8 +24,6 @@ def camel_case(s): # operands and the kind of each operand. class OperandKind(object): """ - The kind of an operand. - An instance of the `OperandKind` class corresponds to a kind of operand. Each operand kind has a corresponding type in the Rust representation of an instruction. @@ -55,8 +53,8 @@ value = OperandKind( operand. """) -#: A variable-sizes list of value operands. Use for Ebb and function call -#: arguemnts. +#: A variable-sized list of value operands. Use for Ebb and function call +#: arguments. args = OperandKind( 'args', """ A variable size list of `value` operands. @@ -71,7 +69,7 @@ args = OperandKind( # module. class ImmediateKind(OperandKind): """ - The type of an immediate instruction operand. + The kind of an immediate instruction operand. """ def __init__(self, name, doc): @@ -215,13 +213,30 @@ class BoolType(ScalarType): class TypeVar(object): """ - A Type Variable. - Type variables can be used in place of concrete types when defining instructions. This makes the instructions *polymorphic*. + + A type variable is restricted to vary over a subset of the value types. + This subset is specified by a set of flags that control the permitted base + types and whether the type variable can assume scalar or vector types, or + both. + + :param name: Short name of type variable used in instruction descriptions. + :param doc: Documentation string. + :param base: Single base type or list of base types. Use this to specify an + exact set of base types if the general categories below are not good + enough. + :param ints: Allow all integer base types. + :param floats: Allow all floating point base types. + :param bools: Allow all boolean base types. + :param scalars: Allow type variable to assume scalar types. + :param simd: Allow type variable to assume vector types. """ - def __init__(self, name, doc): + def __init__( + self, name, doc, base=None, + ints=False, floats=False, bools=False, + scalars=True, simd=False): self.name = name self.__doc__ = doc @@ -238,8 +253,6 @@ class TypeVar(object): class InstructionGroup(object): """ - An instruction group. - Every instruction must belong to exactly one instruction group. A given target architecture can support instructions from multiple groups, and it does not necessarily support all instructions in a group. @@ -286,8 +299,6 @@ class InstructionGroup(object): class Operand(object): """ - An instruction operand. - An instruction operand can be either an *immediate* or an *SSA value*. The type of the operand is one of: @@ -318,8 +329,6 @@ class Operand(object): class InstructionFormat(object): """ - An instruction format. - Every instruction opcode has a corresponding instruction format which determines the number of operands and their kinds. Instruction formats are identified structurally, i.e., the format of an instruction is derived from @@ -396,8 +405,6 @@ class InstructionFormat(object): class Instruction(object): """ - An instruction description. - The operands to the instruction are specified as two tuples: ``ins`` and ``outs``. Since the Python singleton tuple syntax is a bit awkward, it is allowed to specify a singleton as just the operand itself, i.e., `ins=x` diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 412e7f3abd..3fd8554cf3 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -10,9 +10,11 @@ from immediates import imm64, ieee32, ieee64, immvector instructions = InstructionGroup("base", "Shared base instruction set") -Int = TypeVar('Int', 'A scalar or vector integer type') -iB = TypeVar('iB', 'A scalar integer type') -TxN = TypeVar('%Tx%N', 'A SIMD vector type') +Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) +iB = TypeVar('iB', 'A scalar integer type', ints=True) +TxN = TypeVar( + '%Tx%N', 'A SIMD vector type', + ints=True, floats=True, bools=True, scalars=False, simd=True) # # Materializing constants. @@ -217,7 +219,10 @@ isub_imm = Instruction( # # TODO: Which types should permit boolean operations? Any reason to restrict? -bits = TypeVar('bits', 'Any integer, float, or boolean scalar or vector type') +bits = TypeVar( + 'bits', 'Any integer, float, or boolean scalar or vector type', + ints=True, floats=True, bools=True, scalars=True, simd=True) + x = Operand('x', bits) y = Operand('y', bits) a = Operand('a', bits) From 2dc15b78aed67ec19e5f3106baca1f408c28cf44 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 18 May 2016 12:24:14 -0700 Subject: [PATCH 081/968] Add restrictions on polymorphism. Also introduce the concept of a derived type variable, and provide two methods for deriving type vars: lane() and as_bool(). --- docs/metaref.rst | 39 +++++++++++++++++++++++++++++++++++++++ meta/cretonne/__init__.py | 24 ++++++++++++++++++++---- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index 2e47535492..5858ef512e 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -58,6 +58,7 @@ Polymorphism only works for SSA value operands. Immediate operands have a fixed operand kind. .. autoclass:: TypeVar + :members: If multiple operands refer to the same type variable they will be required to have the same concrete type. For example, this defines an integer addition @@ -75,6 +76,9 @@ value types, but in a given instance of the `iadd` instruction, the two operands must have the same type, and the result will be the same type as the inputs. +There are some practical restrictions on the use of type variables, see +:ref:`restricted-polymorphism`. + Immediate operands ------------------ @@ -144,6 +148,41 @@ predefined instruction format which is an instance of .. autoclass:: InstructionFormat +.. _restricted-polymorphism: + +Restricted polymorphism +----------------------- + +The instruction format strictly controls the kinds of operands on an +instruction, but it does not constrain value types at all. A given instruction +description typically does constrain the allowed value types for its value +operands. The type variables give a lot of freedom in describing the value type +constraints, in practice more freedom than what is needed for normal instruction +set architectures. In order to simplify the Rust representation of value type +constraints, some restrictions are imposed on the use of type variables. + +A polymorphic instruction has a single *controlling type variable*. The value +types of instruction results must be one of the following: + +1. A concrete value type. +2. The controlling type variable. +3. A type variable derived from the controlling type variable. + +This means that all result types can be computed from the controlling type +variable. + +Input values to the instruction are allowed a bit more freedom. Input value +types must be one of: + +1. A concrete value type. +2. The controlling type variable. +3. A type variable derived from the controlling type variable. +4. A free type variable that is not used by any other operands. + +This means that the type of an input operand can either be computed from the +controlling type variable, or it can vary independently of the other operands. + + Targets ======= diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 7e6f61d6bf..8ce84394da 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -240,11 +240,27 @@ class TypeVar(object): self.name = name self.__doc__ = doc + def lane(self): + """ + Return a derived type variable that is the scalar lane type of this + type variable. + + When this type variable assumes a scalar type, the derived type will be + the same scalar type. + """ + return TypeVar("Lane type of " + self.name, '', base=self, simd=False) + + def as_bool(self): + """ + Return a derived type variable that has the same vector geometry as + this type variable, but with boolean lanes. Scalar types map to `b1`. + """ + return TypeVar(self.name + " as boolean", '', base=self, bools=True) + def operand_kind(self): - """ - When a `TypeVar` object is used to describe the type of an `Operand` - in an instruction definition, the kind of that operand is an SSA value. - """ + # When a `TypeVar` object is used to describe the type of an `Operand` + # in an instruction definition, the kind of that operand is an SSA + # value. return value From 1dcac579fb8e49e8bfe0495c86226d53b6c83acb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 17 May 2016 11:54:46 -0700 Subject: [PATCH 082/968] Parse basic blocks and instructions. Create map entries for ebbs and values as they are defined, but leave ebb and value operands unresolved on instructions as they are parsed. Instruction operands can refer to ebbs and values that may not have been defined yet. Don't infer or check result types yet. --- src/libcretonne/entities.rs | 6 + src/libcretonne/repr.rs | 4 + src/libreader/lexer.rs | 6 +- src/libreader/parser.rs | 371 ++++++++++++++++++++++++++++++++++-- 4 files changed, 371 insertions(+), 16 deletions(-) diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index bdc76d8040..d4e1cb849b 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -105,6 +105,12 @@ pub enum ExpandedValue { } impl Value { + pub fn direct_from_number(n: u32) -> Value { + let encoding = n * 2; + assert!(encoding < u32::MAX); + Value(encoding) + } + pub fn new_direct(i: Inst) -> Value { let encoding = i.index() * 2; assert!(encoding < u32::MAX as usize); diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index a444dd0b3d..6e7e6ea80f 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -19,6 +19,9 @@ pub struct Function { /// Signature of this function. signature: Signature, + /// The entry block. + pub entry_block: Ebb, + /// Stack slots allocated in this function. stack_slots: Vec, @@ -45,6 +48,7 @@ impl Function { Function { name: name, signature: sig, + entry_block: NO_EBB, stack_slots: Vec::new(), instructions: Vec::new(), extended_basic_blocks: Vec::new(), diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 855dd34056..3611a70f69 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -36,7 +36,7 @@ pub enum Token<'a> { Integer(&'a str), // Integer immediate Type(types::Type), // i32, f32, b32x4, ... ValueDirect(u32), // v12 - ValueExtended(u32), // vx7 + ValueTable(u32), // vx7 Ebb(u32), // ebb3 StackSlot(u32), // ss3 Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) @@ -290,7 +290,7 @@ impl<'a> Lexer<'a> { match prefix { "v" => Some(Token::ValueDirect(value)), - "vx" => Some(Token::ValueExtended(value)), + "vx" => Some(Token::ValueTable(value)), "ebb" => Some(Token::Ebb(value)), "ss" => Some(Token::StackSlot(value)), _ => None, @@ -452,7 +452,7 @@ mod tests { assert_eq!(lex.next(), token(Token::Identifier("ebb5234567890"), 1)); assert_eq!(lex.next(), token(Token::Entry, 1)); assert_eq!(lex.next(), token(Token::Identifier("v1x"), 1)); - assert_eq!(lex.next(), token(Token::ValueExtended(1), 1)); + assert_eq!(lex.next(), token(Token::ValueTable(1), 1)); assert_eq!(lex.next(), token(Token::Identifier("vxvx4"), 1)); assert_eq!(lex.next(), token(Token::Identifier("function0"), 1)); assert_eq!(lex.next(), token(Token::Function, 1)); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 119957d7c4..f390f0dcfa 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -10,9 +10,10 @@ use std::result; use std::fmt::{self, Display, Formatter, Write}; use std::u32; use lexer::{self, Lexer, Token}; -use cretonne::types::{FunctionName, Signature, ArgumentType, ArgumentExtension}; -use cretonne::immediates::Imm64; -use cretonne::entities::StackSlot; +use cretonne::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; +use cretonne::immediates::{Imm64, Ieee32, Ieee64}; +use cretonne::entities::*; +use cretonne::instructions::{Opcode, InstructionFormat, InstructionData}; use cretonne::repr::{Function, StackSlotData}; pub use lexer::Location; @@ -50,7 +51,10 @@ pub struct Parser<'a> { // file by number. We need to map these numbers to real references. struct Context { function: Function, - stack_slots: HashMap, + stack_slots: HashMap, // ssNN + ebbs: HashMap, // ebbNN + value_directs: HashMap, // vNN + value_tables: HashMap, // vxNN } impl Context { @@ -58,10 +62,14 @@ impl Context { Context { function: f, stack_slots: HashMap::new(), + ebbs: HashMap::new(), + value_directs: HashMap::new(), + value_tables: HashMap::new(), } } - fn add(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { + // Allocate a new stack slot and add a mapping number -> StackSlot. + fn add_ss(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { if self.stack_slots.insert(number, self.function.make_stack_slot(data)).is_some() { Err(Error { location: loc.clone(), @@ -71,6 +79,43 @@ impl Context { Ok(()) } } + + // Allocate a new EBB and add a mapping number -> Ebb. + fn add_ebb(&mut self, number: u32, loc: &Location) -> Result { + let ebb = self.function.make_ebb(); + if self.ebbs.insert(number, ebb).is_some() { + Err(Error { + location: loc.clone(), + message: format!("duplicate EBB: ebb{}", number), + }) + } else { + Ok(ebb) + } + } + + // Add a value mapping number -> data for a direct value (vNN). + fn add_v(&mut self, number: u32, data: Value, loc: &Location) -> Result<()> { + if self.value_directs.insert(number, data).is_some() { + Err(Error { + location: loc.clone(), + message: format!("duplicate value: v{}", number), + }) + } else { + Ok(()) + } + } + + // Add a value mapping number -> data for a table value (vxNN). + fn add_vx(&mut self, number: u32, data: Value, loc: &Location) -> Result<()> { + if self.value_tables.insert(number, data).is_some() { + Err(Error { + location: loc.clone(), + message: format!("duplicate value: vx{}", number), + }) + } else { + Ok(()) + } + } } impl<'a> Parser<'a> { @@ -154,6 +199,16 @@ impl<'a> Parser<'a> { } } + // Match and consume a type. + fn match_type(&mut self, err_msg: &str) -> Result { + if let Some(Token::Type(t)) = self.token() { + self.consume(); + Ok(t) + } else { + Err(self.error(err_msg)) + } + } + // Match and consume a stack slot reference. fn match_ss(&mut self, err_msg: &str) -> Result { if let Some(Token::StackSlot(ss)) = self.token() { @@ -164,6 +219,38 @@ impl<'a> Parser<'a> { } } + // Match and consume an ebb reference. + fn match_ebb(&mut self, err_msg: &str) -> Result { + if let Some(Token::Ebb(ebb)) = self.token() { + self.consume(); + Ok(ebb) + } else { + Err(self.error(err_msg)) + } + } + + // Match and consume a vx reference. + fn match_vx(&mut self, err_msg: &str) -> Result { + if let Some(Token::ValueTable(vx)) = self.token() { + self.consume(); + Ok(vx) + } else { + Err(self.error(err_msg)) + } + } + + // Match and consume a value reference, direct or vtable. + // This does not convert from the source value numbering to our in-memory value numbering. + fn match_value(&mut self, err_msg: &str) -> Result { + let val = match self.token() { + Some(Token::ValueDirect(v)) => Value::direct_from_number(v), + Some(Token::ValueTable(vx)) => Value::new_table(vx as usize), + _ => return Err(self.error(err_msg)), + }; + self.consume(); + Ok(val) + } + // Match and consume an Imm64 immediate. fn match_imm64(&mut self, err_msg: &str) -> Result { if let Some(Token::Integer(text)) = self.token() { @@ -176,6 +263,30 @@ impl<'a> Parser<'a> { } } + // Match and consume an Ieee32 immediate. + fn match_ieee32(&mut self, err_msg: &str) -> Result { + if let Some(Token::Float(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like a float. + // Parse it as an Ieee32 to check for the right number of digits and other issues. + text.parse().map_err(|e| self.error(e)) + } else { + Err(self.error(err_msg)) + } + } + + // Match and consume an Ieee64 immediate. + fn match_ieee64(&mut self, err_msg: &str) -> Result { + if let Some(Token::Float(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like a float. + // Parse it as an Ieee64 to check for the right number of digits and other issues. + text.parse().map_err(|e| self.error(e)) + } else { + Err(self.error(err_msg)) + } + } + /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. @@ -199,6 +310,8 @@ impl<'a> Parser<'a> { try!(self.match_token(Token::LBrace, "expected '{' before function body")); // function ::= function-spec "{" * preample function-body "}" try!(self.parse_preamble(&mut ctx)); + // function ::= function-spec "{" preample * function-body "}" + try!(self.parse_function_body(&mut ctx)); // function ::= function-spec "{" preample function-body * "}" try!(self.match_token(Token::RBrace, "expected '}' after function body")); @@ -279,12 +392,7 @@ impl<'a> Parser<'a> { // Parse a single argument type with flags. fn parse_argument_type(&mut self) -> Result { // arg ::= * type { flag } - let mut arg = if let Some(Token::Type(t)) = self.token() { - ArgumentType::new(t) - } else { - return Err(self.error("expected argument type")); - }; - self.consume(); + let mut arg = ArgumentType::new(try!(self.match_type("expected argument type"))); // arg ::= type * { flag } while let Some(Token::Identifier(s)) = self.token() { @@ -313,7 +421,7 @@ impl<'a> Parser<'a> { try!(match self.token() { Some(Token::StackSlot(..)) => { self.parse_stack_slot_decl() - .and_then(|(num, dat)| ctx.add(num, dat, &self.location)) + .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.location)) } // More to come.. _ => return Ok(()), @@ -339,6 +447,221 @@ impl<'a> Parser<'a> { // TBD: stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" Bytes * {"," stack-slot-flag} Ok((number, data)) } + + // Parse a function body, add contents to `ctx`. + // + // function-body ::= * { extended-basic-block } + // + fn parse_function_body(&mut self, ctx: &mut Context) -> Result<()> { + while self.token() != Some(Token::RBrace) { + try!(self.parse_extended_basic_block(ctx)); + } + Ok(()) + } + + // Parse an extended basic block, add contents to `ctx`. + // + // extended-basic-block ::= * ebb-header { instruction } + // ebb-header ::= ["entry"] Ebb(ebb) [ebb-args] ":" + // + fn parse_extended_basic_block(&mut self, ctx: &mut Context) -> Result<()> { + let is_entry = self.optional(Token::Entry); + let ebb_num = try!(self.match_ebb("expected EBB header")); + let ebb = try!(ctx.add_ebb(ebb_num, &self.location)); + + if is_entry { + if ctx.function.entry_block != NO_EBB { + return Err(self.error("multiple entry blocks in function")); + } + ctx.function.entry_block = ebb; + } + + if !self.optional(Token::Colon) { + // ebb-header ::= ["entry"] Ebb(ebb) [ * ebb-args ] ":" + try!(self.parse_ebb_args(ctx, ebb)); + try!(self.match_token(Token::Colon, "expected ':' after EBB arguments")); + } + + // extended-basic-block ::= ebb-header * { instruction } + while match self.token() { + Some(Token::ValueDirect(_)) => true, + Some(Token::Identifier(_)) => true, + _ => false, + } { + try!(self.parse_instruction(ctx, ebb)); + } + + Ok(()) + } + + // Parse parenthesized list of EBB arguments. Returns a vector of (u32, Type) pairs with the + // source vx numbers of the defined values and the defined types. + // + // ebb-args ::= * "(" ebb-arg { "," ebb-arg } ")" + fn parse_ebb_args(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { + // ebb-args ::= * "(" ebb-arg { "," ebb-arg } ")" + try!(self.match_token(Token::LPar, "expected '(' before EBB arguments")); + + // ebb-args ::= "(" * ebb-arg { "," ebb-arg } ")" + try!(self.parse_ebb_arg(ctx, ebb)); + + // ebb-args ::= "(" ebb-arg * { "," ebb-arg } ")" + while self.optional(Token::Comma) { + // ebb-args ::= "(" ebb-arg { "," * ebb-arg } ")" + try!(self.parse_ebb_arg(ctx, ebb)); + } + + // ebb-args ::= "(" ebb-arg { "," ebb-arg } * ")" + try!(self.match_token(Token::RPar, "expected ')' after EBB arguments")); + + Ok(()) + } + + // Parse a single EBB argument declaration, and append it to `ebb`. + // + // ebb-arg ::= * ValueTable(vx) ":" Type(t) + // + fn parse_ebb_arg(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { + // ebb-arg ::= * ValueTable(vx) ":" Type(t) + let vx = try!(self.match_vx("EBB argument must be a vx value")); + let vx_location = self.location; + // ebb-arg ::= ValueTable(vx) * ":" Type(t) + try!(self.match_token(Token::Colon, "expected ':' after EBB argument")); + // ebb-arg ::= ValueTable(vx) ":" * Type(t) + let t = try!(self.match_type("expected EBB argument type")); + // Allocate the EBB argument and add the mapping. + let value = ctx.function.append_ebb_arg(ebb, t); + ctx.add_vx(vx, value, &vx_location) + } + + // Parse an instruction, append it to `ebb`. + // + // instruction ::= [inst-results "="] Opcode(opc) ... + // inst-results ::= ValueDirect(v) { "," ValueTable(vx) } + // + fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { + // Result value numbers. First is a ValueDirect, remaining are ValueTable. + let mut results = Vec::new(); + + // instruction ::= * [inst-results "="] Opcode(opc) ... + if let Some(Token::ValueDirect(v)) = self.token() { + self.consume(); + results.push(v); + + // inst-results ::= ValueDirect(v) * { "," ValueTable(vx) } + while self.optional(Token::Comma) { + // inst-results ::= ValueDirect(v) { "," * ValueTable(vx) } + results.push(try!(self.match_vx("expected vx result value"))); + } + + try!(self.match_token(Token::Equal, "expected '=' before opcode")); + } + + // instruction ::= [inst-results "="] * Opcode(opc) ... + let opcode = if let Some(Token::Identifier(text)) = self.token() { + match text.parse() { + Ok(opc) => opc, + Err(msg) => return Err(self.error(msg)), + } + } else { + return Err(self.error("expected instruction opcode")); + }; + + // instruction ::= [inst-results "="] Opcode(opc) * ... + let inst_data = try!(self.parse_inst_operands(opcode)); + let inst = ctx.function.make_inst(inst_data); + + // TODO: Check that results.len() matches the opcode. + // TODO: Multiple results. + if !results.is_empty() { + assert!(results.len() == 1, "Multiple results not implemented"); + let result = ctx.function.first_result(inst); + try!(ctx.add_v(results[0], result, &self.location)); + } + + ctx.function.append_inst(ebb, inst); + + Ok(()) + } + + // Parse the operands following the instruction opcode. + // This depends on the format of the opcode. + fn parse_inst_operands(&mut self, opcode: Opcode) -> Result { + Ok(match opcode.format().unwrap() { + InstructionFormat::Nullary => { + InstructionData::Nullary { + opcode: opcode, + ty: VOID, + } + } + InstructionFormat::Unary => { + InstructionData::Unary { + opcode: opcode, + ty: VOID, + arg: try!(self.match_value("expected SSA value operand")), + } + } + InstructionFormat::UnaryImm => { + InstructionData::UnaryImm { + opcode: opcode, + ty: VOID, + imm: try!(self.match_imm64("expected immediate integer operand")), + } + } + InstructionFormat::UnaryIeee32 => { + InstructionData::UnaryIeee32 { + opcode: opcode, + ty: VOID, + imm: try!(self.match_ieee32("expected immediate 32-bit float operand")), + } + } + InstructionFormat::UnaryIeee64 => { + InstructionData::UnaryIeee64 { + opcode: opcode, + ty: VOID, + imm: try!(self.match_ieee64("expected immediate 64-bit float operand")), + } + } + InstructionFormat::UnaryImmVector => { + unimplemented!(); + } + InstructionFormat::Binary => { + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value second operand")); + InstructionData::Binary { + opcode: opcode, + ty: VOID, + args: [lhs, rhs], + } + } + InstructionFormat::BinaryImm => { + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_imm64("expected immediate integer second operand")); + InstructionData::BinaryImm { + opcode: opcode, + ty: VOID, + lhs: lhs, + rhs: rhs, + } + } + InstructionFormat::BinaryImmRev => { + let lhs = try!(self.match_imm64("expected immediate integer first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value second operand")); + InstructionData::BinaryImmRev { + opcode: opcode, + ty: VOID, + lhs: lhs, + rhs: rhs, + } + } + InstructionFormat::Call => { + unimplemented!(); + } + }) + } } #[cfg(test)] @@ -400,7 +723,7 @@ mod tests { assert_eq!(func[ss1].size, 1); assert_eq!(iter.next(), None); - // Catch suplicate definitions. + // Catch duplicate definitions. assert_eq!(Parser::new("function bar() { ss1 = stack_slot 13 ss1 = stack_slot 1 @@ -410,4 +733,26 @@ mod tests { .to_string(), "3: duplicate stack slot: ss1"); } + + #[test] + fn ebb_header() { + let func = Parser::new("function ebbs() { + ebb0: + ebb4(vx3: i32): + }") + .parse_function() + .unwrap(); + assert_eq!(func.name, "ebbs"); + + let mut ebbs = func.ebbs_numerically(); + + let ebb0 = ebbs.next().unwrap(); + assert_eq!(func.ebb_args(ebb0).next(), None); + + let ebb4 = ebbs.next().unwrap(); + let mut ebb4_args = func.ebb_args(ebb4); + let arg0 = ebb4_args.next().unwrap(); + assert_eq!(func.value_type(arg0), types::I32); + assert_eq!(ebb4_args.next(), None); + } } From d85fda0346e008c39f91dc173bd9be0990b0bb37 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 18 May 2016 15:30:16 -0700 Subject: [PATCH 083/968] Add entity references as a new operand kind. Define known entities in the cretonne.entities module. --- docs/langref.rst | 30 +++++++++++------------ docs/metaref.rst | 19 ++++++++++++--- meta/cretonne/__init__.py | 48 +++++++++++++++++++++++++------------ meta/cretonne/entities.py | 23 ++++++++++++++++++ meta/cretonne/formats.py | 5 ++-- meta/cretonne/immediates.py | 2 +- src/libreader/parser.rs | 10 ++++---- 7 files changed, 96 insertions(+), 41 deletions(-) create mode 100644 meta/cretonne/entities.py diff --git a/docs/langref.rst b/docs/langref.rst index 0851ea8fcd..1d987fb3c7 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -36,11 +36,11 @@ Here is the same function compiled into Cretonne IL: The first line of a function definition provides the function *name* and the :term:`function signature` which declares the argument and return types. -Then follows the :term:`function preample` which declares a number of entities -that can be referenced inside the function. In the example above, the preample +Then follows the :term:`function preamble` which declares a number of entities +that can be referenced inside the function. In the example above, the preamble declares a single local variable, ``ss1``. -After the preample follows the :term:`function body` which consists of +After the preamble follows the :term:`function body` which consists of :term:`extended basic block`\s, one of which is marked as the :term:`entry block`. Every EBB ends with a :term:`terminator instruction`, so execution can never fall through to the next EBB without an explicit branch. @@ -49,7 +49,7 @@ A ``.cton`` file consists of a sequence of independent function definitions: .. productionlist:: function-list : { function } - function : function-spec "{" preample function-body "}" + function : function-spec "{" preamble function-body "}" function-spec : "function" function-name signature preamble : { preamble-decl } function-body : { extended-basic-block } @@ -361,12 +361,12 @@ instruction in the EBB. blocks. Split critical edges as needed to work around this. :arg iN x: Integer index into jump table. - :arg JT: Jump table which was declared in the preample. + :arg JT: Jump table which was declared in the preamble. :result: None. .. inst:: JT = jump_table EBB0, EBB1, ..., EBBn - Declare a jump table in the :term:`function preample`. + Declare a jump table in the :term:`function preamble`. This declares a jump table for use by the :inst:`br_table` indirect branch instruction. Entries in the table are either EBB names, or ``0`` which @@ -433,7 +433,7 @@ dependent. They make it possible to call native functions on the target platform. When calling other Cretonne functions, the flags are not necessary. Functions that are called directly must be declared in the :term:`function -preample`: +preamble`: .. inst:: F = function NAME signature @@ -476,7 +476,7 @@ This simple example illustrates direct function calls and signatures:: return v1 } -Indirect function calls use a signature declared in the preample. +Indirect function calls use a signature declared in the preamble. .. inst:: SIG = signature signature @@ -556,12 +556,12 @@ Local variables One set of restricted memory operations access the current function's stack frame. The stack frame is divided into fixed-size stack slots that are -allocated in the :term:`function preample`. Stack slots are not typed, they +allocated in the :term:`function preamble`. Stack slots are not typed, they simply represent a contiguous sequence of bytes in the stack frame. .. inst:: SS = stack_slot Bytes, Flags... - Allocate a stack slot in the preample. + Allocate a stack slot in the preamble. If no alignment is specified, Cretonne will pick an appropriate alignment for the stack slot based on its size and access patterns. @@ -633,14 +633,14 @@ all process memory. Instead, it is given a small set of memory areas to work in, and all accesses are bounds checked. Cretonne models this through the concept of *heaps*. -A heap is declared in the function preample and can be accessed with restricted +A heap is declared in the function preamble and can be accessed with restricted instructions that trap on out-of-bounds accesses. Heap addresses can be smaller than the native pointer size, for example unsigned :type:`i32` offsets on a 64-bit architecture. .. inst:: H = heap Name - Declare a heap in the function preample. + Declare a heap in the function preamble. This doesn't allocate memory, it just retrieves a handle to a sandbox from the runtime environment. @@ -1034,9 +1034,9 @@ Glossary is not necessary to know when calling it, so it is just an attribute, and not part of the signature. - function preample + function preamble A list of declarations of entities that are used by the function body. - Some of the entities that can be declared in the preample are: + Some of the entities that can be declared in the preamble are: - Local variables. - Functions that are called directly. @@ -1045,7 +1045,7 @@ Glossary function body The extended basic blocks which contain all the executable code in a - function. The function body follows the function preample. + function. The function body follows the function preamble. basic block A maximal sequence of instructions that can only be entered from the diff --git a/docs/metaref.rst b/docs/metaref.rst index 5858ef512e..c0b75f4101 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -47,14 +47,14 @@ must be instances of the :class:`Operand` class. .. autoclass:: Operand -Cretonne uses two separate type systems for immediate operands and SSA values. +Cretonne uses two separate type systems for operand kinds and SSA values. Type variables -------------- Instruction descriptions can be made polymorphic by using :class:`Operand` instances that refer to a *type variable* instead of a concrete value type. -Polymorphism only works for SSA value operands. Immediate operands have a fixed +Polymorphism only works for SSA value operands. Other operands have a fixed operand kind. .. autoclass:: TypeVar @@ -95,6 +95,18 @@ indicated with an instance of :class:`ImmediateKind`. .. currentmodule:: cretonne +Entity references +----------------- + +Instruction operands can also refer to other entties in the same function. This +can be extended basic blocks, or entities declared in the function preamble. + +.. autoclass:: EntityRefKind + +.. automodule:: cretonne.entities + :members: + +.. currentmodule:: cretonne Value types ----------- @@ -133,13 +145,14 @@ representation depends on the input operand kinds and whether the instruction can produce multiple results. .. autoclass:: OperandKind +.. inheritance-diagram:: OperandKind ImmediateKind EntityRefkind Since all SSA value operands are represented as a `Value` in Rust code, value types don't affect the representation. Two special operand kinds are used to represent SSA values: .. autodata:: value -.. autodata:: args +.. autodata:: variable_args When an instruction description is created, it is automatically assigned a predefined instruction format which is an instance of diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 8ce84394da..0447cdfcc0 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -42,6 +42,12 @@ class OperandKind(object): def __repr__(self): return 'OperandKind({})'.format(self.name) + def operand_kind(self): + """ + An `OperandKind` instance can be used directly as the type of an + `Operand` when defining an instruction. + """ + return self #: An SSA value operand. This is a value defined by another instruction. value = OperandKind( @@ -55,8 +61,8 @@ value = OperandKind( #: A variable-sized list of value operands. Use for Ebb and function call #: arguments. -args = OperandKind( - 'args', """ +variable_args = OperandKind( + 'variable_args', """ A variable size list of `value` operands. Use this to represent arguemtns passed to a function call, arguments @@ -65,8 +71,8 @@ args = OperandKind( """) -# Instances of immediate operand types are provided in the cretonne.immediates -# module. +# Instances of immediate operand types are provided in the +# `cretonne.immediates` module. class ImmediateKind(OperandKind): """ The kind of an immediate instruction operand. @@ -79,12 +85,20 @@ class ImmediateKind(OperandKind): def __repr__(self): return 'ImmediateKind({})'.format(self.name) - def operand_kind(self): - """ - An `ImmediateKind` instance can be used directly as the type of an - `Operand` when defining an instruction. - """ - return self + +# Instances of entity reference operand types are provided in the +# `cretonne.entities` module. +class EntityRefKind(OperandKind): + """ + The kind of an entity reference instruction operand. + """ + + def __init__(self, name, doc): + self.name = name + self.__doc__ = doc + + def __repr__(self): + return 'EntityRefKind({})'.format(self.name) # ValueType instances (i8, i32, ...) are provided in the cretonne.types module. @@ -315,8 +329,8 @@ class InstructionGroup(object): class Operand(object): """ - An instruction operand can be either an *immediate* or an *SSA value*. The - type of the operand is one of: + An instruction operand can be an *immediate*, an *SSA value*, or an *entity + reference*. The type of the operand is one of: 1. A :py:class:`ValueType` instance indicates an SSA value operand with a concrete type. @@ -329,6 +343,10 @@ class Operand(object): whose value is encoded in the instruction itself rather than being passed as an SSA value. + 4. An :py:class:`EntityRefKind` instance indicates an operand that + references another entity in the function, typically something declared + in the function preamble. + """ def __init__(self, name, typ, doc=''): self.name = name @@ -429,9 +447,9 @@ class Instruction(object): :param name: Instruction mnemonic, also becomes opcode name. :param doc: Documentation string. :param ins: Tuple of input operands. This can be a mix of SSA value - operands and immediate operands. - :param outs: Tuple of output operands. The output operands can't be - immediates. + operands and other operand kinds. + :param outs: Tuple of output operands. The output operands must be SSA + values. """ def __init__(self, name, doc, ins=(), outs=(), **kwargs): diff --git a/meta/cretonne/entities.py b/meta/cretonne/entities.py new file mode 100644 index 0000000000..67265ee844 --- /dev/null +++ b/meta/cretonne/entities.py @@ -0,0 +1,23 @@ +""" +The `cretonne.entities` module predefines all the Cretonne entity reference +operand types. Thee are corresponding definitions in the `cretonne.entities` +Rust module. +""" + +from . import EntityRefKind + + +#: A reference to an extended basic block in the same function. +#: This is primarliy used in control flow instructions. +ebb = EntityRefKind('ebb', 'An extended basic block in the same function.') + +#: A reference to a stack slot declared in the function preamble. +stack_slot = EntityRefKind('stack_slot', 'A stack slot.') + +#: A reference to a function sugnature declared in the function preamble. +#: Tbis is used to provide the call signature in an indirect call instruction. +signature = EntityRefKind('signature', 'A function signature.') + +#: A reference to an external function declared in the function preamble. +#: This is used to provide the callee and signature in a call instruction. +function = EntityRefKind('function', 'An external function.') diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index cb02d80998..9ea555a257 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -7,8 +7,9 @@ in this module. """ -from . import InstructionFormat, value, args +from . import InstructionFormat, value, variable_args from immediates import imm64, ieee32, ieee64, immvector +from entities import function Nullary = InstructionFormat() @@ -22,7 +23,7 @@ Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) BinaryImmRev = InstructionFormat(imm64, value) -Call = InstructionFormat(args, multiple_results=True) +Call = InstructionFormat(function, variable_args, multiple_results=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index af7bd72feb..e8e581d64d 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -1,5 +1,5 @@ """ -The cretonne.immediates module predefines all the Cretonne immediate operand +The `cretonne.immediates` module predefines all the Cretonne immediate operand types. """ diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index f390f0dcfa..ab37e0896a 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -300,19 +300,19 @@ impl<'a> Parser<'a> { // Parse a whole function definition. // - // function ::= * function-spec "{" preample function-body "}" + // function ::= * function-spec "{" preamble function-body "}" // fn parse_function(&mut self) -> Result { let (name, sig) = try!(self.parse_function_spec()); let mut ctx = Context::new(Function::with_name_signature(name, sig)); - // function ::= function-spec * "{" preample function-body "}" + // function ::= function-spec * "{" preamble function-body "}" try!(self.match_token(Token::LBrace, "expected '{' before function body")); - // function ::= function-spec "{" * preample function-body "}" + // function ::= function-spec "{" * preamble function-body "}" try!(self.parse_preamble(&mut ctx)); - // function ::= function-spec "{" preample * function-body "}" + // function ::= function-spec "{" preamble * function-body "}" try!(self.parse_function_body(&mut ctx)); - // function ::= function-spec "{" preample function-body * "}" + // function ::= function-spec "{" preamble function-body * "}" try!(self.match_token(Token::RBrace, "expected '}' after function body")); Ok(ctx.function) From ebe224a91259b13afaf23765df7e9ff18fe09e3b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 18 May 2016 16:28:21 -0700 Subject: [PATCH 084/968] Define control flow instructions. Rename 'br' to 'jump'. We'll use jump/br to mean unconditional/conditional control transfer respectively. --- docs/cton_domain.py | 7 ++- docs/langref.rst | 80 +++-------------------- meta/cretonne/__init__.py | 2 + meta/cretonne/base.py | 78 ++++++++++++++++++++++- meta/cretonne/entities.py | 3 + meta/cretonne/formats.py | 6 +- src/libcretonne/entities.rs | 31 +++++++++ src/libcretonne/instructions.rs | 108 +++++++++++++++++++++++++++++++- src/libcretonne/write.rs | 5 +- src/libreader/parser.rs | 3 + 10 files changed, 244 insertions(+), 79 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 6f20cf8d32..9c5c38e17a 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -262,7 +262,12 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): if len(inst.outs) > 0: sig = ', '.join([op.name for op in inst.outs]) + ' = ' + sig if len(inst.ins) > 0: - sig = sig + ' ' + ', '.join([op.name for op in inst.ins]) + sig = sig + ' ' + inst.ins[0].name + for op in inst.ins[1:]: + if op.typ.operand_kind().name == 'variable_args': + sig += '({}...)'.format(op.name) + else: + sig += ', ' + op.name return sig def add_directive_header(self, sig): diff --git a/docs/langref.rst b/docs/langref.rst index 1d987fb3c7..ca58a9b273 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -313,56 +313,10 @@ arguments, if it has any. Conditional branches only take the branch if their condition is satisfied, otherwise execution continues at the following instruction in the EBB. -.. inst:: br EBB(args...) - - Branch. - - Unconditionally branch to an extended basic block, passing the specified - EBB arguments. The number and types of arguments must match the destination - EBB. - - :arg EBB: Destination extended basic block. - :arg args...: Zero or more arguments passed to EBB. - :result: None. This is a terminator instruction. - -.. inst:: brz x, EBB(args...) - - Branch when zero. - - If ``x`` is a :type:`b1` value, take the branch when ``x`` is false. If - ``x`` is an integer value, take the branch when ``x = 0``. - - :arg Testable x: Value to test. - :arg EBB: Destination extended basic block. - :arg args...: Arguments passed to EBB. - :result: None. - -.. inst:: brnz x, EBB(args...) - - Branch when non-zero. - - If ``x`` is a :type:`b1` value, take the branch when ``x`` is true. If - ``x`` is an integer value, take the branch when ``x != 0``. - - :arg Testable x: Value to test. - :arg EBB: Destination extended basic block. - :arg args...: Zero or more arguments passed to EBB. - :result: None. - -.. inst:: br_table x, JT - - Jump table branch. - - Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table - entry is found, branch to the corresponding EBB. If no entry was found fall - through to the next instruction. - - Note that this branch instruction can't pass arguments to the targeted - blocks. Split critical edges as needed to work around this. - - :arg iN x: Integer index into jump table. - :arg JT: Jump table which was declared in the preamble. - :result: None. +.. autoinst:: jump +.. autoinst:: brz +.. autoinst:: brnz +.. autoinst:: br_table .. inst:: JT = jump_table EBB0, EBB1, ..., EBBn @@ -386,29 +340,9 @@ explicit trap instructions defined below, but some instructions may also cause traps for certain input value. For example, :inst:`udiv` traps when the divisor is zero. -.. inst:: trap - - Terminate execution unconditionally. - - :result: None. This is a terminator instruction. - -.. inst:: trapz x - - Trap when zero. - - if ``x`` is non-zero, execution continues at the following instruction. - - :arg Testable x: Value to test. - :result: None. - -.. inst:: trapnz x - - Trap when non-zero. - - if ``x`` is zero, execution continues at the following instruction. - - :arg Testable x: Value to test. - :result: None. +.. autoinst:: trap +.. autoinst:: trapz +.. autoinst:: trapnz Function calls diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 0447cdfcc0..961a3a500c 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -450,6 +450,8 @@ class Instruction(object): operands and other operand kinds. :param outs: Tuple of output operands. The output operands must be SSA values. + :param is_terminator: This is a terminator instruction. + :param is_branch: This is a branch instruction. """ def __init__(self, name, doc, ins=(), outs=(), **kwargs): diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 3fd8554cf3..9f77fbf860 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -4,18 +4,94 @@ Cretonne base instruction set. This module defines the basic Cretonne instruction set that all targets support. """ -from . import TypeVar, Operand, Instruction, InstructionGroup +from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args from types import i8, f32, f64 from immediates import imm64, ieee32, ieee64, immvector +import entities instructions = InstructionGroup("base", "Shared base instruction set") Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) iB = TypeVar('iB', 'A scalar integer type', ints=True) +Testable = TypeVar( + 'Testable', 'A scalar boolean or integer type', + ints=True, bools=True) TxN = TypeVar( '%Tx%N', 'A SIMD vector type', ints=True, floats=True, bools=True, scalars=False, simd=True) +# +# Control flow +# +c = Operand('c', Testable, doc='Controlling value to test') +EBB = Operand('EBB', entities.ebb, doc='Destination extended basic block') +args = Operand('args', variable_args, doc='EBB arguments') + +jump = Instruction( + 'jump', r""" + Jump. + + Unconditionally jump to an extended basic block, passing the specified + EBB arguments. The number and types of arguments must match the + destination EBB. + """, + ins=(EBB, args), is_terminator=True) + +brz = Instruction( + 'brz', r""" + Branch when zero. + + If ``c`` is a :type:`b1` value, take the branch when ``c`` is false. If + ``c`` is an integer value, take the branch when ``c = 0``. + """, + ins=(c, EBB, args), is_branch=True) + +brnz = Instruction( + 'brnz', r""" + Branch when non-zero. + + If ``c`` is a :type:`b1` value, take the branch when ``c`` is true. If + ``c`` is an integer value, take the branch when ``c != 0``. + """, + ins=(c, EBB, args), is_branch=True) + +x = Operand('x', iB, doc='index into jump table') +JT = Operand('JT', entities.jump_table) +br_table = Instruction( + 'br_table', r""" + Indirect branch via jump table. + + Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table + entry is found, branch to the corresponding EBB. If no entry was found fall + through to the next instruction. + + Note that this branch instruction can't pass arguments to the targeted + blocks. Split critical edges as needed to work around this. + """, + ins=(x, JT), is_branch=True) + +trap = Instruction( + 'trap', r""" + Terminate execution unconditionally. + """, + is_terminator=True) + +trapz = Instruction( + 'trapz', r""" + Trap when zero. + + if ``c`` is non-zero, execution continues at the following instruction. + """, + ins=c) + +trapnz = Instruction( + 'trapnz', r""" + Trap when non-zero. + + if ``c`` is zero, execution continues at the following instruction. + """, + ins=c) + # # Materializing constants. # diff --git a/meta/cretonne/entities.py b/meta/cretonne/entities.py index 67265ee844..14587d402f 100644 --- a/meta/cretonne/entities.py +++ b/meta/cretonne/entities.py @@ -21,3 +21,6 @@ signature = EntityRefKind('signature', 'A function signature.') #: A reference to an external function declared in the function preamble. #: This is used to provide the callee and signature in a call instruction. function = EntityRefKind('function', 'An external function.') + +#: A reference to a jump table declared in the function preamble. +jump_table = EntityRefKind('jump_table', 'A jump table.') diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 9ea555a257..8b32936d7c 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -9,7 +9,7 @@ in this module. from . import InstructionFormat, value, variable_args from immediates import imm64, ieee32, ieee64, immvector -from entities import function +from entities import ebb, function, jump_table Nullary = InstructionFormat() @@ -23,6 +23,10 @@ Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) BinaryImmRev = InstructionFormat(imm64, value) +Jump = InstructionFormat(ebb, variable_args) +Branch = InstructionFormat(value, ebb, variable_args) +BranchTable = InstructionFormat(value, jump_table) + Call = InstructionFormat(function, variable_args, multiple_results=True) # Finally extract the names of global variables in this module. diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index d4e1cb849b..8513447ab2 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -189,3 +189,34 @@ impl Default for StackSlot { NO_STACK_SLOT } } + +/// An opaque reference to a jump table. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct JumpTable(u32); + +impl JumpTable { + pub fn new(index: usize) -> JumpTable { + assert!(index < (u32::MAX as usize)); + JumpTable(index as u32) + } + + pub fn index(&self) -> usize { + self.0 as usize + } +} + +/// Display a `JumpTable` reference as "jt12". +impl Display for JumpTable { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "jt{}", self.0) + } +} + +/// A guaranteed invalid jump table reference. +pub const NO_JUMP_TABLE: JumpTable = JumpTable(u32::MAX); + +impl Default for JumpTable { + fn default() -> JumpTable { + NO_JUMP_TABLE + } +} diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 39202b000f..3cb1ba5378 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -140,6 +140,22 @@ pub enum InstructionData { rhs: Value, lhs: Imm64, }, + Jump { + opcode: Opcode, + ty: Type, + data: Box, + }, + Branch { + opcode: Opcode, + ty: Type, + data: Box, + }, + BranchTable { + opcode: Opcode, + ty: Type, + arg: Value, + table: JumpTable, + }, Call { opcode: Opcode, ty: Type, @@ -147,6 +163,66 @@ pub enum InstructionData { }, } +/// A variable list of `Value` operands used for function call arguments and passing arguments to +/// basic blocks. +#[derive(Debug)] +pub struct VariableArgs(Vec); + +impl VariableArgs { + pub fn new() -> VariableArgs { + VariableArgs(Vec::new()) + } +} + +impl Display for VariableArgs { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + try!(write!(fmt, "(")); + for (i, val) in self.0.iter().enumerate() { + if i == 0 { + try!(write!(fmt, "{}", val)); + } else { + try!(write!(fmt, ", {}", val)); + } + } + write!(fmt, ")") + } +} + +impl Default for VariableArgs { + fn default() -> VariableArgs { + VariableArgs::new() + } +} + +/// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit +/// in the allowed InstructionData size. +#[derive(Debug)] +pub struct JumpData { + destination: Ebb, + arguments: VariableArgs, +} + +impl Display for JumpData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}{}", self.destination, self.arguments) + } +} + +/// Payload data for branch instructions. These need to carry lists of EBB arguments that won't fit +/// in the allowed InstructionData size. +#[derive(Debug)] +pub struct BranchData { + arg: Value, + destination: Ebb, + arguments: VariableArgs, +} + +impl Display for BranchData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}, {}{}", self.arg, self.destination, self.arguments) + } +} + /// Payload of a call instruction. #[derive(Debug)] pub struct CallData { @@ -154,9 +230,14 @@ pub struct CallData { second_result: Value, // Dynamically sized array containing call argument values. - arguments: Vec, + arguments: VariableArgs, } +impl Display for CallData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "TBD{}", self.arguments) + } +} impl InstructionData { /// Create data for a call instruction. @@ -166,7 +247,7 @@ impl InstructionData { ty: return_type, data: Box::new(CallData { second_result: NO_VALUE, - arguments: Vec::new(), + arguments: VariableArgs::new(), }), } } @@ -184,6 +265,9 @@ impl InstructionData { Binary { opcode, .. } => opcode, BinaryImm { opcode, .. } => opcode, BinaryImmRev { opcode, .. } => opcode, + Jump { opcode, .. } => opcode, + Branch { opcode, .. } => opcode, + BranchTable { opcode, .. } => opcode, Call { opcode, .. } => opcode, } } @@ -201,6 +285,9 @@ impl InstructionData { Binary { ty, .. } => ty, BinaryImm { ty, .. } => ty, BinaryImmRev { ty, .. } => ty, + Jump { ty, .. } => ty, + Branch { ty, .. } => ty, + BranchTable { ty, .. } => ty, Call { ty, .. } => ty, } } @@ -218,6 +305,9 @@ impl InstructionData { Binary { .. } => None, BinaryImm { .. } => None, BinaryImmRev { .. } => None, + Jump { .. } => None, + Branch { .. } => None, + BranchTable { .. } => None, Call { ref data, .. } => Some(data.second_result), } } @@ -234,6 +324,9 @@ impl InstructionData { Binary { .. } => None, BinaryImm { .. } => None, BinaryImmRev { .. } => None, + Jump { .. } => None, + Branch { .. } => None, + BranchTable { .. } => None, Call { ref mut data, .. } => Some(&mut data.second_result), } } @@ -263,4 +356,15 @@ mod tests { assert_eq!("".parse::(), Err("Unknown opcode")); assert_eq!("\0".parse::(), Err("Unknown opcode")); } + + #[test] + fn instruction_data() { + use std::mem; + // The size of the InstructionData enum is important for performance. It should not exceed + // 16 bytes. Use `Box` out-of-line payloads for instruction formats that require + // more space than that. + // It would be fine with a data structure smaller than 16 bytes, but what are the odds of + // that? + assert_eq!(mem::size_of::(), 16); + } } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 9f5c28c0b6..74ece4790e 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -155,7 +155,10 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), - Call { opcode, .. } => writeln!(w, "{} [...]", opcode), + Jump { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), + Branch { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), + BranchTable { opcode, arg, table, .. } => writeln!(w, "{} {}, {}", opcode, arg, table), + Call { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index ab37e0896a..713fa7e829 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -657,6 +657,9 @@ impl<'a> Parser<'a> { rhs: rhs, } } + InstructionFormat::Jump | + InstructionFormat::Branch | + InstructionFormat::BranchTable | InstructionFormat::Call => { unimplemented!(); } From 2ce5f05bed49aadb157ed804a3d82334be7ac7db Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 May 2016 10:16:40 -0700 Subject: [PATCH 085/968] Auto-generate boilerplate for 'impl InstructionData'. Accessors for shared fields and multiple results can be generated automatically. Add a 'boxed_storage' flag to the instruction format definitions to enable generated code to access 'data'. --- meta/cretonne/__init__.py | 4 ++ meta/cretonne/formats.py | 7 +-- meta/gen_instr.py | 85 +++++++++++++++++++++++++++++++++ src/libcretonne/instructions.rs | 79 ------------------------------ 4 files changed, 93 insertions(+), 82 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 961a3a500c..5df6931aec 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -384,6 +384,9 @@ class InstructionFormat(object): enums. :param multiple_results: Set to `True` if this instruction format allows more than one result to be produced. + :param boxed_storage: Set to `True` is this instruction format requires a + `data: Box<...>` pointer to additional storage in its `InstructionData` + variant. """ # Map (multiple_results, kind, kind, ...) -> InstructionFormat @@ -396,6 +399,7 @@ class InstructionFormat(object): self.name = kwargs.get('name', None) self.kinds = kinds self.multiple_results = kwargs.get('multiple_results', False) + self.boxed_storage = kwargs.get('boxed_storage', False) # Compute a signature for the global registry. sig = (self.multiple_results,) + kinds if sig in InstructionFormat._registry: diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 8b32936d7c..319dc6fb69 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -23,11 +23,12 @@ Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) BinaryImmRev = InstructionFormat(imm64, value) -Jump = InstructionFormat(ebb, variable_args) -Branch = InstructionFormat(value, ebb, variable_args) +Jump = InstructionFormat(ebb, variable_args, boxed_storage=True) +Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) BranchTable = InstructionFormat(value, jump_table) -Call = InstructionFormat(function, variable_args, multiple_results=True) +Call = InstructionFormat( + function, variable_args, multiple_results=True, boxed_storage=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 8fe63cb932..c055e87ffa 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -36,6 +36,89 @@ def gen_formats(fmt): fmt.line() +def gen_instruction_data_impl(fmt): + """ + Generate the boring parts of the InstructionData implementation. + + These methods in `impl InstructionData` can be generated automatically from + the instruction formats: + + - `pub fn opcode(&self) -> Opcode` + - `pub fn first_type(&self) -> Type` + - `pub fn second_result(&self) -> Option` + - `pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value>` + """ + + # The `opcode` and `first_type` methods simply read the `opcode` and `ty` + # members. This is really a workaround for Rust's enum types missing shared + # members. + with fmt.indented('impl InstructionData {', '}'): + fmt.doc_comment('Get the opcode of this instruction.') + with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + fmt.line( + 'InstructionData::{} {{ opcode, .. }} => opcode,' + .format(f.name)) + + fmt.doc_comment('Type of the first result, or `VOID`.') + with fmt.indented('pub fn first_type(&self) -> Type {', '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + fmt.line( + 'InstructionData::{} {{ ty, .. }} => ty,' + .format(f.name)) + + # Generate shared and mutable accessors for `second_result` which only + # applies to instruction formats that can produce multiple results. + # Everything else returns `None`. + fmt.doc_comment('Second result value, if any.') + with fmt.indented( + 'pub fn second_result(&self) -> Option {', '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + if not f.multiple_results: + # Single or no results. + fmt.line( + 'InstructionData::{} {{ .. }} => None,' + .format(f.name)) + elif f.boxed_storage: + # Multiple results, boxed storage. + fmt.line( + 'InstructionData::' + f.name + + ' { ref data, .. }' + + ' => Some(data.second_result),') + else: + # Multiple results, inline storage. + fmt.line( + 'InstructionData::' + f.name + + ' { second_result, .. }' + + ' => Some(second_result),') + + fmt.doc_comment('Mutable reference to second result value, if any.') + with fmt.indented( + "pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> {", '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + if not f.multiple_results: + # Single or no results. + fmt.line( + 'InstructionData::{} {{ .. }} => None,' + .format(f.name)) + elif f.boxed_storage: + # Multiple results, boxed storage. + fmt.line( + 'InstructionData::' + f.name + + ' { ref mut data, .. }' + + ' => Some(&mut data.second_result),') + else: + # Multiple results, inline storage. + fmt.line( + 'InstructionData::' + f.name + + ' { ref mut second_result, .. }' + + ' => Some(second_result),') + + def collect_instr_groups(targets): seen = set() groups = [] @@ -108,8 +191,10 @@ def gen_opcodes(groups, fmt): def generate(targets, out_dir): groups = collect_instr_groups(targets) + # opcodes.rs fmt = srcgen.Formatter() gen_formats(fmt) + gen_instruction_data_impl(fmt) gen_opcodes(groups, fmt) fmt.update_file('opcodes.rs', out_dir) diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 3cb1ba5378..50cd4d8aad 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -251,85 +251,6 @@ impl InstructionData { }), } } - - /// Get the opcode of this instruction. - pub fn opcode(&self) -> Opcode { - use self::InstructionData::*; - match *self { - Nullary { opcode, .. } => opcode, - Unary { opcode, .. } => opcode, - UnaryImm { opcode, .. } => opcode, - UnaryIeee32 { opcode, .. } => opcode, - UnaryIeee64 { opcode, .. } => opcode, - UnaryImmVector { opcode, .. } => opcode, - Binary { opcode, .. } => opcode, - BinaryImm { opcode, .. } => opcode, - BinaryImmRev { opcode, .. } => opcode, - Jump { opcode, .. } => opcode, - Branch { opcode, .. } => opcode, - BranchTable { opcode, .. } => opcode, - Call { opcode, .. } => opcode, - } - } - - /// Type of the first result. - pub fn first_type(&self) -> Type { - use self::InstructionData::*; - match *self { - Nullary { ty, .. } => ty, - Unary { ty, .. } => ty, - UnaryImm { ty, .. } => ty, - UnaryIeee32 { ty, .. } => ty, - UnaryIeee64 { ty, .. } => ty, - UnaryImmVector { ty, .. } => ty, - Binary { ty, .. } => ty, - BinaryImm { ty, .. } => ty, - BinaryImmRev { ty, .. } => ty, - Jump { ty, .. } => ty, - Branch { ty, .. } => ty, - BranchTable { ty, .. } => ty, - Call { ty, .. } => ty, - } - } - - /// Second result value, if any. - pub fn second_result(&self) -> Option { - use self::InstructionData::*; - match *self { - Nullary { .. } => None, - Unary { .. } => None, - UnaryImm { .. } => None, - UnaryIeee32 { .. } => None, - UnaryIeee64 { .. } => None, - UnaryImmVector { .. } => None, - Binary { .. } => None, - BinaryImm { .. } => None, - BinaryImmRev { .. } => None, - Jump { .. } => None, - Branch { .. } => None, - BranchTable { .. } => None, - Call { ref data, .. } => Some(data.second_result), - } - } - - pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> { - use self::InstructionData::*; - match *self { - Nullary { .. } => None, - Unary { .. } => None, - UnaryImm { .. } => None, - UnaryIeee32 { .. } => None, - UnaryIeee64 { .. } => None, - UnaryImmVector { .. } => None, - Binary { .. } => None, - BinaryImm { .. } => None, - BinaryImmRev { .. } => None, - Jump { .. } => None, - Branch { .. } => None, - BranchTable { .. } => None, - Call { ref mut data, .. } => Some(&mut data.second_result), - } - } } #[cfg(test)] From 210139c4bd0e1795443013d50aef4c1f068f5fa6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 May 2016 10:43:14 -0700 Subject: [PATCH 086/968] Add a BinaryOverflow instruction format. This will eventualy be used for add-with-carry and add-with-overflow type instructions. For now it only serves as a representative of instruction formats that have multiple_results=True and boxed_storage=False at the same time. --- meta/cretonne/formats.py | 3 +++ src/libcretonne/instructions.rs | 8 +++++++- src/libcretonne/write.rs | 1 + src/libreader/parser.rs | 11 +++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 319dc6fb69..261b400321 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -23,6 +23,9 @@ Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) BinaryImmRev = InstructionFormat(imm64, value) +# Generate result + overflow flag. +BinaryOverflow = InstructionFormat(value, value, multiple_results=True) + Jump = InstructionFormat(ebb, variable_args, boxed_storage=True) Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) BranchTable = InstructionFormat(value, jump_table) diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 50cd4d8aad..cbc6b053dd 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -133,13 +133,19 @@ pub enum InstructionData { lhs: Value, rhs: Imm64, }, - // Same as BinaryImm, but the imediate is the lhs operand. + // Same as BinaryImm, but the immediate is the lhs operand. BinaryImmRev { opcode: Opcode, ty: Type, rhs: Value, lhs: Imm64, }, + BinaryOverflow { + opcode: Opcode, + ty: Type, + second_result: Value, + args: [Value; 2], + }, Jump { opcode: Opcode, ty: Type, diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 74ece4790e..d3a676293d 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -155,6 +155,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), + BinaryOverflow { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), Jump { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), Branch { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), BranchTable { opcode, arg, table, .. } => writeln!(w, "{} {}, {}", opcode, arg, table), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 713fa7e829..2247259530 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -657,6 +657,17 @@ impl<'a> Parser<'a> { rhs: rhs, } } + InstructionFormat::BinaryOverflow => { + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value second operand")); + InstructionData::BinaryOverflow { + opcode: opcode, + ty: VOID, + second_result: NO_VALUE, + args: [lhs, rhs], + } + } InstructionFormat::Jump | InstructionFormat::Branch | InstructionFormat::BranchTable | From 1e631fdbd6dac53fe52a8277c79cf7d8e4b1535a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 May 2016 11:21:36 -0700 Subject: [PATCH 087/968] Verify restrictions on polymorphism. Add a typevar_operand argument to the InstructionFormat constructor which determines the operand used for inferring the controlling type variable. Identify polymorphic instructions when they are created, determine if the controlling type variable can be inferred from the typevar_operand, and verify the use of type variables in the other operands. Generate type variable summary in the documentation, including how the controlling type variable is inferred. --- docs/cton_domain.py | 60 +++++++++++++----- docs/metaref.rst | 11 +++- meta/cretonne/__init__.py | 129 +++++++++++++++++++++++++++++++++++++- meta/gen_instr.py | 6 ++ 4 files changed, 187 insertions(+), 19 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 9c5c38e17a..bd207e994d 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -18,13 +18,14 @@ from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType -from sphinx.locale import l_, _ +from sphinx.locale import l_ from sphinx.roles import XRefRole from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.nodes import make_refnode import sphinx.ext.autodoc + class CtonObject(ObjectDescription): """ Any kind of Cretonne IL object. @@ -68,10 +69,11 @@ class CtonObject(ObjectDescription): # Type variables are indicated as %T. typevar = re.compile('(\%[A-Z])') + def parse_type(name, signode): """ Parse a type with embedded type vars and append to signode. - + Return a a string that can be compiled into a regular expression matching the type. """ @@ -92,6 +94,7 @@ def parse_type(name, signode): re_str += re.escape(part) return re_str + class CtonType(CtonObject): """A Cretonne IL type description.""" @@ -103,7 +106,7 @@ class CtonType(CtonObject): """ name = sig.strip() - re_str = parse_type(name, signode) + parse_type(name, signode) return name def get_index_text(self, name): @@ -112,12 +115,14 @@ class CtonType(CtonObject): sep_equal = re.compile('\s*=\s*') sep_comma = re.compile('\s*,\s*') + def parse_params(s, signode): - for i,p in enumerate(sep_comma.split(s)): + for i, p in enumerate(sep_comma.split(s)): if i != 0: signode += nodes.Text(', ') signode += nodes.emphasis(p, p) + class CtonInst(CtonObject): """A Cretonne IL instruction.""" @@ -128,6 +133,7 @@ class CtonInst(CtonObject): TypedField('result', label=l_('Results'), names=('out', 'result'), typerolename='type', typenames=('type',)), + GroupedField('typevar', names=('typevar',), label=l_('Type Variables')), GroupedField('flag', names=('flag',), label=l_('Flags')), Field('resulttype', label=l_('Result type'), has_arg=False, names=('rtype',)), @@ -164,24 +170,25 @@ class CtonInst(CtonObject): def get_index_text(self, name): return name + class CretonneDomain(Domain): """Cretonne domain for intermediate language objects.""" name = 'cton' label = 'Cretonne' object_types = { - 'type' : ObjType(l_('type'), 'type'), - 'inst' : ObjType(l_('instruction'), 'inst') + 'type': ObjType(l_('type'), 'type'), + 'inst': ObjType(l_('instruction'), 'inst') } directives = { - 'type' : CtonType, - 'inst' : CtonInst, + 'type': CtonType, + 'inst': CtonInst, } roles = { - 'type' : XRefRole(), - 'inst' : XRefRole(), + 'type': XRefRole(), + 'inst': XRefRole(), } initial_data = { @@ -230,7 +237,7 @@ class TypeDocumenter(sphinx.ext.autodoc.Documenter): return False def resolve_name(self, modname, parents, path, base): - return 'cretonne.types', [ base ] + return 'cretonne.types', [base] def add_content(self, more_content, no_docstring=False): super(TypeDocumenter, self).add_content(more_content, no_docstring) @@ -254,7 +261,7 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): return False def resolve_name(self, modname, parents, path, base): - return 'cretonne.base', [ base ] + return 'cretonne.base', [base] def format_signature(self): inst = self.object @@ -285,9 +292,32 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): # Add inputs and outputs. for op in self.object.ins: - self.add_line(u':in {} {}: {}'.format(op.typ.name, op.name, op.get_doc()), sourcename) + self.add_line(u':in {} {}: {}'.format( + op.typ.name, op.name, op.get_doc()), sourcename) for op in self.object.outs: - self.add_line(u':out {} {}: {}'.format(op.typ.name, op.name, op.get_doc()), sourcename) + self.add_line(u':out {} {}: {}'.format( + op.typ.name, op.name, op.get_doc()), sourcename) + + # Document type inference for polymorphic instructions. + if self.object.is_polymorphic: + if self.object.ctrl_typevar is not None: + if self.object.use_typevar_operand: + self.add_line( + u':typevar {}: inferred from {}' + .format( + self.object.ctrl_typevar.name, + self.object.ins[ + self.object.format.typevar_operand]), + sourcename) + else: + self.add_line( + u':typevar {}: explicitly provided' + .format(self.object.ctrl_typevar.name), + sourcename) + for tv in self.object.other_typevars: + self.add_line( + u':typevar {}: from input operand'.format(tv.name), + sourcename) def setup(app): @@ -295,4 +325,4 @@ def setup(app): app.add_autodocumenter(TypeDocumenter) app.add_autodocumenter(InstDocumenter) - return { 'version' : '0.1' } + return {'version': '0.1'} diff --git a/docs/metaref.rst b/docs/metaref.rst index c0b75f4101..65b8f88654 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -145,7 +145,7 @@ representation depends on the input operand kinds and whether the instruction can produce multiple results. .. autoclass:: OperandKind -.. inheritance-diagram:: OperandKind ImmediateKind EntityRefkind +.. inheritance-diagram:: OperandKind ImmediateKind EntityRefKind Since all SSA value operands are represented as a `Value` in Rust code, value types don't affect the representation. Two special operand kinds are used to @@ -174,8 +174,13 @@ constraints, in practice more freedom than what is needed for normal instruction set architectures. In order to simplify the Rust representation of value type constraints, some restrictions are imposed on the use of type variables. -A polymorphic instruction has a single *controlling type variable*. The value -types of instruction results must be one of the following: +A polymorphic instruction has a single *controlling type variable*. For a given +opcode, this type variable must be the type of the first result or the type of +the input value operand designated by the `typevar_operand` argument to the +:py:class:`InstructionFormat` constructor. By default, this is the first value +operand, which works most of the time. + +The value types of instruction results must be one of the following: 1. A concrete value type. 2. The controlling type variable. diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 5df6931aec..924ce8a109 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -49,6 +49,10 @@ class OperandKind(object): """ return self + def free_typevar(self): + # Return the free typevariable controlling the type of this operand. + return None + #: An SSA value operand. This is a value defined by another instruction. value = OperandKind( 'value', """ @@ -125,6 +129,9 @@ class ValueType(object): """ return value + def free_typevar(self): + return None + class ScalarType(ValueType): """ @@ -253,6 +260,10 @@ class TypeVar(object): scalars=True, simd=False): self.name = name self.__doc__ = doc + self.base = base + + def __str__(self): + return "`{}`".format(self.name) def lane(self): """ @@ -277,6 +288,11 @@ class TypeVar(object): # value. return value + def free_typevar(self): + if isinstance(self.base, TypeVar): + return self.base + else: + return self # Defining instructions. @@ -360,6 +376,9 @@ class Operand(object): else: return self.typ.__doc__ + def __str__(self): + return "`{}`".format(self.name) + class InstructionFormat(object): """ @@ -387,6 +406,9 @@ class InstructionFormat(object): :param boxed_storage: Set to `True` is this instruction format requires a `data: Box<...>` pointer to additional storage in its `InstructionData` variant. + :param typevar_operand: Index of the input operand that is used to infer + the controlling type variable. By default, this is the first `value` + operand. """ # Map (multiple_results, kind, kind, ...) -> InstructionFormat @@ -400,6 +422,20 @@ class InstructionFormat(object): self.kinds = kinds self.multiple_results = kwargs.get('multiple_results', False) self.boxed_storage = kwargs.get('boxed_storage', False) + + # Which of self.kinds are `value`? + self.value_operands = tuple( + i for i, k in enumerate(self.kinds) if k is value) + + # The typevar_operand argument must point to a 'value' operand. + self.typevar_operand = kwargs.get('typevar_operand', None) + if self.typevar_operand is not None: + assert self.kinds[self.typevar_operand] is value, \ + "typevar_operand must indicate a 'value' operand" + elif len(self.value_operands) > 0: + # Default to the first 'value' operand, if there is one. + self.typevar_operand = self.value_operands[0] + # Compute a signature for the global registry. sig = (self.multiple_results,) + kinds if sig in InstructionFormat._registry: @@ -453,7 +489,7 @@ class Instruction(object): :param ins: Tuple of input operands. This can be a mix of SSA value operands and other operand kinds. :param outs: Tuple of output operands. The output operands must be SSA - values. + values or `variable_args`. :param is_terminator: This is a terminator instruction. :param is_branch: This is a branch instruction. """ @@ -465,8 +501,99 @@ class Instruction(object): self.ins = self._to_operand_tuple(ins) self.outs = self._to_operand_tuple(outs) self.format = InstructionFormat.lookup(self.ins, self.outs) + # Indexes into outs for value results. Others are `variable_args`. + self.value_results = tuple( + i for i, o in enumerate(self.outs) if o.kind is value) + self._verify_polymorphic() InstructionGroup.append(self) + def _verify_polymorphic(self): + """ + Check if this instruction is polymorphic, and verify its use of type + variables. + """ + poly_ins = [ + i for i in self.format.value_operands + if self.ins[i].typ.free_typevar()] + poly_outs = [ + i for i, o in enumerate(self.outs) + if o.typ.free_typevar()] + self.is_polymorphic = len(poly_ins) > 0 or len(poly_outs) > 0 + if not self.is_polymorphic: + return + + # Prefer to use the typevar_operand to infer the controlling typevar. + self.use_typevar_operand = False + typevar_error = None + if self.format.typevar_operand is not None: + try: + tv = self.ins[self.format.typevar_operand].typ + if tv is tv.free_typevar(): + self.other_typevars = self._verify_ctrl_typevar(tv) + self.ctrl_typevar = tv + self.use_typevar_operand = True + except RuntimeError as e: + typevar_error = e + + if not self.use_typevar_operand: + # The typevar_operand argument doesn't work. Can we infer from the + # first result instead? + if len(self.outs) == 0: + if typevar_error: + raise typevar_error + else: + raise RuntimeError( + "typevar_operand must be a free type variable") + tv = self.outs[0].typ + if tv is not tv.free_typevar(): + raise RuntimeError("first result must be a free type variable") + self.other_typevars = self._verify_ctrl_typevar(tv) + self.ctrl_typevar = tv + + def _verify_ctrl_typevar(self, ctrl_typevar): + """ + Verify that the use of TypeVars is consistent with `ctrl_typevar` as + the controlling type variable. + + All polymorhic inputs must either be derived from `ctrl_typevar` or be + independent free type variables only used once. + + All polymorphic results must be derived from `ctrl_typevar`. + + Return list of other type variables used, or raise an error. + """ + other_tvs = [] + # Check value inputs. + for opidx in self.format.value_operands: + typ = self.ins[opidx].typ + tv = typ.free_typevar() + # Non-polymorphic or derived form ctrl_typevar is OK. + if tv is None or tv is ctrl_typevar: + continue + # No other derived typevars allowed. + if typ is not tv: + raise RuntimeError( + "type variable {} must not be derived from {}" + .format(typ.name, tv.name)) + # Other free type variables can only be used once each. + if tv in other_tvs: + raise RuntimeError( + "type variable {} can't be used more than once" + .format(tv.name)) + other_tvs.append(tv) + + # Check outputs. + for result in self.outs: + typ = result.typ + tv = typ.free_typevar() + # Non-polymorphic or derived from ctrl_typevar is OK. + if tv is None or tv is ctrl_typevar: + continue + raise RuntimeError( + "type variable in output not derived from ctrl_typevar") + + return other_tvs + @staticmethod def _to_operand_tuple(x): # Allow a single Operand instance instead of the awkward singleton diff --git a/meta/gen_instr.py b/meta/gen_instr.py index c055e87ffa..08cd81f1b0 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -151,6 +151,12 @@ def gen_opcodes(groups, fmt): fmt.doc_comment( '`{}{} {}`. ({})' .format(prefix, i.name, suffix, i.format.name)) + # Document polymorphism. + if i.is_polymorphic: + if i.use_typevar_operand: + fmt.doc_comment( + 'Type inferred from {}.' + .format(i.ins[i.format.typevar_operand])) # Enum variant itself. fmt.line(i.camel_name + ',') fmt.line() From 692a85d72030941626757b92f218299e24bcee7d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 20 May 2016 10:45:02 -0700 Subject: [PATCH 088/968] Generate value type constraints. Add an Opcode::constraints() method which returns an OpcodeConstraints object. This object provides information on instruction polymorphism and how many results is produced. Generate a list of TypeSet objects for checking free type variables. The type sets are parametrized rather than being represented as fully general sets. Add UniqueTable and UniqueSeqTable classes to the meta code generator. Use for compressing tabular data by removing duplicates. --- meta/cretonne/__init__.py | 40 +++++++- meta/cretonne/base.py | 6 +- meta/gen_instr.py | 132 +++++++++++++++++++++++++- meta/unique_table.py | 68 +++++++++++++ src/libcretonne/instructions.rs | 163 +++++++++++++++++++++++++++++++- src/libcretonne/types.rs | 14 +++ 6 files changed, 413 insertions(+), 10 deletions(-) create mode 100644 meta/unique_table.py diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 924ce8a109..2c737c2e7e 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -7,6 +7,7 @@ instructions. import re import importlib +from collections import namedtuple camel_re = re.compile('(^|_)([a-z])') @@ -148,6 +149,9 @@ class ScalarType(ValueType): def __repr__(self): return 'ScalarType({})'.format(self.name) + def rust_name(self): + return 'types::' + self.name.upper() + def by(self, lanes): """ Get a vector type with this type as the lane type. @@ -232,6 +236,19 @@ class BoolType(ScalarType): # Parametric polymorphism. +#: A `TypeSet` represents a set of types. We don't allow arbitrary subsets of +#: types, but use a parametrized approach instead. +#: This is represented as a named tuple so it can be used as a dictionary key. +TypeSet = namedtuple( + 'TypeSet', [ + 'allow_scalars', + 'allow_simd', + 'base', + 'all_ints', + 'all_floats', + 'all_bools']) + + class TypeVar(object): """ Type variables can be used in place of concrete types when defining @@ -257,10 +274,23 @@ class TypeVar(object): def __init__( self, name, doc, base=None, ints=False, floats=False, bools=False, - scalars=True, simd=False): + scalars=True, simd=False, + derived_func=None): self.name = name self.__doc__ = doc self.base = base + self.is_derived = isinstance(base, TypeVar) + if self.is_derived: + assert derived_func + self.derived_func = derived_func + else: + self.type_set = TypeSet( + allow_scalars=scalars, + allow_simd=simd, + base=base, + all_ints=ints, + all_floats=floats, + all_bools=bools) def __str__(self): return "`{}`".format(self.name) @@ -273,14 +303,18 @@ class TypeVar(object): When this type variable assumes a scalar type, the derived type will be the same scalar type. """ - return TypeVar("Lane type of " + self.name, '', base=self, simd=False) + return TypeVar( + "Lane type of " + self.name, '', + base=self, derived_func='Lane') def as_bool(self): """ Return a derived type variable that has the same vector geometry as this type variable, but with boolean lanes. Scalar types map to `b1`. """ - return TypeVar(self.name + " as boolean", '', base=self, bools=True) + return TypeVar( + self.name + " as boolean", '', + base=self, derived_func='AsBool') def operand_kind(self): # When a `TypeVar` object is used to describe the type of an `Operand` diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 9f77fbf860..9e13377ed6 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -61,9 +61,9 @@ br_table = Instruction( 'br_table', r""" Indirect branch via jump table. - Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table - entry is found, branch to the corresponding EBB. If no entry was found fall - through to the next instruction. + Use ``x`` as an unsigned index into the jump table ``JT``. If a jump + table entry is found, branch to the corresponding EBB. If no entry was + found fall through to the next instruction. Note that this branch instruction can't pass arguments to the targeted blocks. Split critical edges as needed to work around this. diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 08cd81f1b0..1cd0ea8b4f 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -4,6 +4,7 @@ Generate sources with instruction info. import srcgen import constant_hash +from unique_table import UniqueTable, UniqueSeqTable import cretonne @@ -97,7 +98,8 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Mutable reference to second result value, if any.') with fmt.indented( - "pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> {", '}'): + "pub fn second_result_mut<'a>(&'a mut self)" + + " -> Option<&'a mut Value> {", '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: if not f.multiple_results: @@ -131,7 +133,11 @@ def collect_instr_groups(targets): def gen_opcodes(groups, fmt): - """Generate opcode enumerations.""" + """ + Generate opcode enumerations. + + Return a list of all instructions. + """ fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') @@ -193,6 +199,125 @@ def gen_opcodes(groups, fmt): else: fmt.format('Opcode::{},', i.camel_name) fmt.line() + return instrs + + +def get_constraint(op, ctrl_typevar, type_sets): + """ + Get the value type constraint for an SSA value operand, where + `ctrl_typevar` is the controlling type variable. + + Each operand constraint is represented as a string, one of: + + - `Concrete(vt)`, where `vt` is a value type name. + - `Free(idx)` where `idx` is an index into `type_sets`. + - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. + """ + t = op.typ + assert t.operand_kind() is cretonne.value + + # A concrete value type. + if isinstance(t, cretonne.ValueType): + return 'Concrete({})'.format(t.rust_name()) + + if t.free_typevar() is not ctrl_typevar: + assert not t.is_derived + return 'Free({})'.format(type_sets.add(t.type_set)) + + if t.is_derived: + assert t.base is ctrl_typevar, "Not derived directly from ctrl_typevar" + return t.derived_func + + assert t is ctrl_typevar + return 'Same' + + +def gen_type_constraints(fmt, instrs): + """ + Generate value type constraints for all instructions. + + - Emit a compact constant table of ValueTypeSet objects. + - Emit a compact constant table of OperandConstraint objects. + - Emit an opcode-indexed table of instruction constraints. + + """ + + # Table of TypeSet instances. + type_sets = UniqueTable() + + # Table of operand constraint sequences (as tuples). Each operand + # constraint is represented as a string, one of: + # - `Concrete(vt)`, where `vt` is a value type name. + # - `Free(idx)` where `idx` isan index into `type_sets`. + # - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. + operand_seqs = UniqueSeqTable() + + # Preload table with constraints for typical binops. + operand_seqs.add(['Same'] * 3) + + # TypeSet indexes are encoded in 3 bits, with `111` reserved. + typeset_limit = 7 + + fmt.comment('Table of opcode constraints.') + with fmt.indented( + 'const OPCODE_CONSTRAINTS : [OpcodeConstraints; {}] = [' + .format(len(instrs)), '];'): + for i in instrs: + # Collect constraints for the value results, not including + # `variable_args` results which are always special cased. + constraints = list() + ctrl_typevar = None + ctrl_typeset = typeset_limit + if i.is_polymorphic: + ctrl_typevar = i.ctrl_typevar + ctrl_typeset = type_sets.add(ctrl_typevar.type_set) + for idx in i.value_results: + constraints.append( + get_constraint(i.outs[idx], ctrl_typevar, type_sets)) + for idx in i.format.value_operands: + constraints.append( + get_constraint(i.ins[idx], ctrl_typevar, type_sets)) + offset = operand_seqs.add(constraints) + fixed_results = len(i.value_results) + use_typevar_operand = i.is_polymorphic and i.use_typevar_operand + fmt.comment( + '{}: fixed_results={}, use_typevar_operand={}' + .format(i.camel_name, fixed_results, use_typevar_operand)) + fmt.comment('Constraints={}'.format(constraints)) + if i.is_polymorphic: + fmt.comment( + 'Polymorphic over {}'.format(ctrl_typevar.type_set)) + # Compute the bit field encoding, c.f. instructions.rs. + assert fixed_results < 8, "Bit field encoding too tight" + bits = (offset << 8) | (ctrl_typeset << 4) | fixed_results + if use_typevar_operand: + bits |= 8 + assert bits < 0x10000, "Constraint table too large for bit field" + fmt.line('OpcodeConstraints({:#06x}),'.format(bits)) + + fmt.comment('Table of value type sets.') + assert len(type_sets.table) <= typeset_limit, "Too many type sets" + with fmt.indented( + 'const TYPE_SETS : [ValueTypeSet; {}] = [' + .format(len(type_sets.table)), '];'): + for ts in type_sets.table: + with fmt.indented('ValueTypeSet {', '},'): + if ts.base: + fmt.line('base: {},'.format(ts.base.rust_name())) + else: + fmt.line('base: types::VOID,') + for field in ts._fields: + if field == 'base': + continue + fmt.line('{}: {},'.format( + field, str(getattr(ts, field)).lower())) + + fmt.comment('Table of operand constraint sequences.') + with fmt.indented( + 'const OPERAND_CONSTRAINTS : [OperandConstraint; {}] = [' + .format(len(operand_seqs.table)), '];'): + for c in operand_seqs.table: + fmt.line('OperandConstraint::{},'.format(c)) def generate(targets, out_dir): @@ -202,5 +327,6 @@ def generate(targets, out_dir): fmt = srcgen.Formatter() gen_formats(fmt) gen_instruction_data_impl(fmt) - gen_opcodes(groups, fmt) + instrs = gen_opcodes(groups, fmt) + gen_type_constraints(fmt, instrs) fmt.update_file('opcodes.rs', out_dir) diff --git a/meta/unique_table.py b/meta/unique_table.py new file mode 100644 index 0000000000..cbc45af200 --- /dev/null +++ b/meta/unique_table.py @@ -0,0 +1,68 @@ +""" +Generate a table of unique items. + +The `UniqueTable` class collects items into an array, removing duplicates. Each +item is mapped to its offset in the final array. + +This is a compression technique for compile-time generated tables. +""" + + +class UniqueTable: + """ + Collect items into the `table` list, removing duplicates. + """ + def __init__(self): + # List of items added in order. + self.table = list() + # Map item -> index. + self.index = dict() + + def add(self, item): + """ + Add a single item to the table if it isn't already there. + + Return the offset into `self.table` of the item. + """ + if item in self.index: + return self.index[item] + + idx = len(self.table) + self.index[item] = idx + self.table.append(item) + return idx + + +class UniqueSeqTable: + """ + Collect sequences into the `table` list, removing duplicates. + + Sequences don't have to be of the same length. + """ + def __init__(self): + self.table = list() + # Map seq -> index. + self.index = dict() + + def add(self, seq): + """ + Add a sequence of items to the table. If the table already contains the + items in `seq` in the same order, use those instead. + + Return the offset into `self.table` of the beginning of `seq`. + """ + if len(seq) == 0: + return 0 + seq = tuple(seq) + if seq in self.index: + return self.index[seq] + + idx = len(self.table) + self.table.extend(seq) + + # Add seq and all sub-sequences to `index`. + for length in range(1, len(seq) + 1): + for offset in range(len(seq) - length + 1): + self.index[seq[offset:offset+length]] = idx + offset + + return idx diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index cbc6b053dd..c6b353ae9c 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use entities::*; use immediates::*; -use types::Type; +use types::{self, Type}; // Include code generated by `meta/gen_instr.py`. This file contains: // @@ -21,6 +21,12 @@ use types::Type; // - The private `fn opcode_name(Opcode) -> &'static str` function, and // - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`. // +// For value type constraints: +// +// - The `const OPCODE_CONSTRAINTS : [OpcodeConstraints; N]` table. +// - The `const TYPE_SETS : [ValueTypeSet; N]` table. +// - The `const OPERAND_CONSTRAINTS : [OperandConstraint; N]` table. +// include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); impl Display for Opcode { @@ -38,6 +44,12 @@ impl Opcode { Some(OPCODE_FORMAT[self as usize - 1]) } } + + /// Get the constraint descriptor for this opcode. + /// Panic if this is called on `NotAnOpcode`. + pub fn constraints(self) -> OpcodeConstraints { + OPCODE_CONSTRAINTS[self as usize - 1] + } } // A primitive hash function for matching opcodes. @@ -259,6 +271,155 @@ impl InstructionData { } } + +/// Value type constraints for a given opcode. +/// +/// The `InstructionFormat` determines the constraints on most operands, but `Value` operands and +/// results are not determined by the format. Every `Opcode` has an associated +/// `OpcodeConstraints` object that provides the missing details. +/// +/// Since there can be a lot of opcodes, the `OpcodeConstraints` object is encoded as a bit field +/// by the `meta/gen_instr.py` script. +/// +/// The bit field bits are: +/// +/// Bits 0-2: +/// Number of fixed result values. This does not include `variable_args` results as are +/// produced by call instructions. +/// +/// Bit 3: +/// This opcode is polymorphic and the controlling type variable can be inferred from the +/// designated input operand. This is the `typevar_operand` index given to the +/// `InstructionFormat` meta language object. When bit 0 is not set, the controlling type +/// variable must be the first output value instead. +/// +/// Bits 4-7: +/// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. +/// +/// Bits 8-15: +/// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first +/// `fixed_results()` entries describe the result constraints, then follows constraints for the +/// fixed `Value` input operands. The number of `Value` inputs isdetermined by the instruction +/// format. +/// +#[derive(Clone, Copy)] +pub struct OpcodeConstraints(u16); + +impl OpcodeConstraints { + /// Can the controlling type variable for this opcode be inferred from the designated value + /// input operand? + /// This also implies that this opcode is polymorphic. + pub fn use_typevar_operand(self) -> bool { + (self.0 & 0x8) != 0 + } + + /// Get the number of *fixed* result values produced by this opcode. + /// This does not include `variable_args` produced by calls. + pub fn fixed_results(self) -> usize { + (self.0 & 0x7) as usize + } + + /// Get the offset into `TYPE_SETS` for the controlling type variable. + /// Returns `None` if the instruction is not polymorphic. + fn typeset_offset(self) -> Option { + let offset = ((self.0 & 0xff) >> 4) as usize; + if offset < TYPE_SETS.len() { + Some(offset) + } else { + None + } + } + + /// Get the offset into OPERAND_CONSTRAINTS where the descriptors for this opcode begin. + fn constraint_offset(self) -> usize { + (self.0 >> 8) as usize + } + + /// Get the value type of result number `n`, having resolved the controlling type variable to + /// `ctrl_type`. + pub fn result_type(self, n: usize, ctrl_type: Type) -> Type { + assert!(n < self.fixed_results(), "Invalid result index"); + OPERAND_CONSTRAINTS[self.constraint_offset() + n] + .resolve(ctrl_type) + .expect("Result constraints can't be free") + } + + /// Get the typeset of allowed types for the controlling type variable in a polymorphic + /// instruction. + pub fn ctrl_typeset(self) -> Option { + self.typeset_offset().map(|offset| TYPE_SETS[offset]) + } +} + +/// A value type set describes the permitted set of types for a type variable. +#[derive(Clone, Copy)] +pub struct ValueTypeSet { + allow_scalars: bool, + allow_simd: bool, + + base: Type, + all_ints: bool, + all_floats: bool, + all_bools: bool, +} + +impl ValueTypeSet { + /// Is `scalar` part of the base type set? + /// + /// Note that the base type set does not have to be included in the type set proper. + fn is_base_type(&self, scalar: Type) -> bool { + scalar == self.base || (self.all_ints && scalar.is_int()) || + (self.all_floats && scalar.is_float()) || (self.all_bools && scalar.is_bool()) + } + + /// Does `typ` belong to this set? + pub fn contains(&self, typ: Type) -> bool { + let allowed = if typ.is_scalar() { + self.allow_scalars + } else { + self.allow_simd + }; + allowed && self.is_base_type(typ.lane_type()) + } +} + +/// Operand constraints. This describes the value type constraints on a single `Value` operand. +enum OperandConstraint { + /// This operand has a concrete value type. + Concrete(Type), + + /// This operand can vary freely within the given type set. + /// The type set is identified by its index into the TYPE_SETS constant table. + Free(u8), + + /// This operand is the same type as the controlling type variable. + Same, + + /// This operand is `ctrlType.lane_type()`. + Lane, + + /// This operand is `ctrlType.as_bool()`. + AsBool, +} + +impl OperandConstraint { + /// Resolve this operand constraint into a concrete value type, given the value of the + /// controlling type variable. + /// Returns `None` if this is a free operand which is independent of the controlling type + /// variable. + pub fn resolve(&self, ctrl_type: Type) -> Option { + use self::OperandConstraint::*; + match *self { + Concrete(t) => Some(t), + Free(_) => None, + Same => Some(ctrl_type), + Lane => Some(ctrl_type.lane_type()), + AsBool => Some(ctrl_type.as_bool()), + } + } +} + + #[cfg(test)] mod tests { use super::*; diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 967879c14d..a4ff443541 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -83,6 +83,20 @@ impl Type { } } + /// Get a type with the same number of lanes as this type, but with the lanes replaces by + /// booleans of the same size. + pub fn as_bool(self) -> Type { + // Replace the low 4 bits with the boolean version, preserve the high 4 bits. + let lane = match self.lane_type() { + B8 | I8 => B8, + B16 | I16 => B16, + B32 | I32 | F32 => B32, + B64 | I64 | F64 => B64, + _ => B1, + }; + Type(lane.0 | (self.0 & 0xf0)) + } + /// Is this the VOID type? pub fn is_void(self) -> bool { self == VOID From b44d6c6541ff7afc9bba77223866e92391e6fd0e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 20 May 2016 15:10:31 -0700 Subject: [PATCH 089/968] Implement select and vselect instructions. This gives us the opportunity to use the AsBool derived type variables and a Select instruction format with a non-default typevar_operand setting. --- docs/langref.rst | 27 ++--------------------- meta/cretonne/__init__.py | 13 +++++------ meta/cretonne/base.py | 38 +++++++++++++++++++++++++++++++++ meta/cretonne/formats.py | 4 ++++ src/libcretonne/instructions.rs | 9 ++++++-- src/libcretonne/write.rs | 3 +++ src/libreader/parser.rs | 1 + 7 files changed, 60 insertions(+), 35 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index ca58a9b273..05ebfa9e05 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -656,35 +656,12 @@ load a constant into an SSA value. .. autoinst:: f32const .. autoinst:: f64const .. autoinst:: vconst - -.. inst:: a = select c, x, y - - Conditional select. - - :arg b1 c: Controlling flag. - :arg T x: Value to return when ``c`` is true. - :arg T y: Value to return when ``c`` is false. Must be same type as ``x``. - :result T a: Same type as ``x`` and ``y``. - - This instruction selects whole values. Use :inst:`vselect` for lane-wise - selection. +.. autoinst:: select Vector operations ----------------- -.. inst:: a = vselect c, x, y - - Vector lane select. - - Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean - vector ``c``. - - :arg b1xN c: Controlling flag vector. - :arg TxN x: Vector with lanes selected by the true lanes of ``c``. - Must be a vector type with the same number of lanes as ``c``. - :arg TxN y: Vector with lanes selected by the false lanes of ``c``. - Must be same type as ``x``. - :result TxN a: Same type as ``x`` and ``y``. +.. autoinst:: vselect .. inst:: a = vbuild x, y, z, ... diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 2c737c2e7e..c0eec9e970 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -283,6 +283,7 @@ class TypeVar(object): if self.is_derived: assert derived_func self.derived_func = derived_func + self.name = '{}({})'.format(derived_func, base.name) else: self.type_set = TypeSet( allow_scalars=scalars, @@ -303,18 +304,14 @@ class TypeVar(object): When this type variable assumes a scalar type, the derived type will be the same scalar type. """ - return TypeVar( - "Lane type of " + self.name, '', - base=self, derived_func='Lane') + return TypeVar(None, None, base=self, derived_func='Lane') def as_bool(self): """ Return a derived type variable that has the same vector geometry as this type variable, but with boolean lanes. Scalar types map to `b1`. """ - return TypeVar( - self.name + " as boolean", '', - base=self, derived_func='AsBool') + return TypeVar(None, None, base=self, derived_func='AsBool') def operand_kind(self): # When a `TypeVar` object is used to describe the type of an `Operand` @@ -607,8 +604,8 @@ class Instruction(object): # No other derived typevars allowed. if typ is not tv: raise RuntimeError( - "type variable {} must not be derived from {}" - .format(typ.name, tv.name)) + "{}: type variable {} must be derived from {}" + .format(self.ins[opidx], typ.name, ctrl_typevar)) # Other free type variables can only be used once each. if tv in other_tvs: raise RuntimeError( diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 9e13377ed6..c28c3724a2 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -19,6 +19,9 @@ Testable = TypeVar( TxN = TypeVar( '%Tx%N', 'A SIMD vector type', ints=True, floats=True, bools=True, scalars=False, simd=True) +Any = TypeVar( + 'Any', 'Any integer, float, or boolean scalar or vector type', + ints=True, floats=True, bools=True, scalars=True, simd=True) # # Control flow @@ -140,6 +143,41 @@ vconst = Instruction( ins=N, outs=a) # +# Generics. +# + +c = Operand('c', Testable, doc='Controlling value to test') +x = Operand('x', Any, doc='Value to use when `c` is true') +y = Operand('y', Any, doc='Value to use when `c` is false') +a = Operand('a', Any) + +select = Instruction( + 'select', r""" + Conditional select. + + This instruction selects whole values. Use :inst:`vselect` for + lane-wise selection. + """, + ins=(c, x, y), outs=a) + +# +# Vector operations +# + +c = Operand('c', TxN.as_bool(), doc='Controlling vector') +x = Operand('x', TxN, doc='Value to use where `c` is true') +y = Operand('y', TxN, doc='Value to use where `c` is false') +a = Operand('a', TxN) + +vselect = Instruction( + 'vselect', r""" + Vector lane select. + + Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean + vector ``c``. + """, + ins=(c, x, y), outs=a) +# # Integer arithmetic # diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 261b400321..4843befea9 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -26,6 +26,10 @@ BinaryImmRev = InstructionFormat(imm64, value) # Generate result + overflow flag. BinaryOverflow = InstructionFormat(value, value, multiple_results=True) +# The select instructions are controlled by the second value operand. +# The first value operand is the controlling flag whisch has a derived type. +Select = InstructionFormat(value, value, value, typevar_operand=1) + Jump = InstructionFormat(ebb, variable_args, boxed_storage=True) Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) BranchTable = InstructionFormat(value, jump_table) diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index c6b353ae9c..d1861fb669 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -158,6 +158,11 @@ pub enum InstructionData { second_result: Value, args: [Value; 2], }, + Select { + opcode: Opcode, + ty: Type, + args: [Value; 3], + }, Jump { opcode: Opcode, ty: Type, @@ -295,13 +300,13 @@ impl InstructionData { /// /// Bits 4-7: /// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. -/// +/// /// Bits 8-15: /// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first /// `fixed_results()` entries describe the result constraints, then follows constraints for the /// fixed `Value` input operands. The number of `Value` inputs isdetermined by the instruction /// format. -/// +/// #[derive(Clone, Copy)] pub struct OpcodeConstraints(u16); diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index d3a676293d..e6eb3a477f 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -156,6 +156,9 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), BinaryOverflow { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), + Select { opcode, args, .. } => { + writeln!(w, "{} {}, {}, {}", opcode, args[0], args[1], args[2]) + } Jump { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), Branch { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), BranchTable { opcode, arg, table, .. } => writeln!(w, "{} {}, {}", opcode, arg, table), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 2247259530..6aab089330 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -668,6 +668,7 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } + InstructionFormat::Select | InstructionFormat::Jump | InstructionFormat::Branch | InstructionFormat::BranchTable | From b1dd4ad37394e65f4936df90094aed4c1de9047c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 20 May 2016 15:36:03 -0700 Subject: [PATCH 090/968] Add vector instructions. Use derived type variables with the 'LaneOf' function. Add u8 immediates to be used for lane indexes and bit shifts. --- docs/langref.rst | 34 +++------------------------- meta/cretonne/__init__.py | 4 ++-- meta/cretonne/base.py | 40 +++++++++++++++++++++++++++++++-- meta/cretonne/formats.py | 5 ++++- meta/cretonne/immediates.py | 6 +++++ src/libcretonne/instructions.rs | 16 +++++++++++-- src/libcretonne/write.rs | 4 ++++ src/libreader/parser.rs | 2 ++ 8 files changed, 73 insertions(+), 38 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 05ebfa9e05..8bf890b080 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -669,37 +669,9 @@ Vector operations Build a vector value from the provided lanes. -.. inst:: a = splat x - - Vector splat. - - Return a vector whose lanes are all ``x``. - - :arg T x: Scalar value to be replicated. - :result TxN a: Vector with identical lanes. - -.. inst:: a = insertlane x, Idx, y - - Insert ``y`` as lane ``Idx`` in x. - - The lane index, ``Idx``, is an immediate value, not an SSA value. It must - indicate a valid lane index for the type of ``x``. - - :arg TxN x: Vector to modify. - :arg Idx: Lane index smaller than N. - :arg T y: New lane value. - :result TxN y: Updated vector. - -.. inst:: a = extractlane x, Idx - - Extract lane ``Idx`` from ``x``. - - The lane index, ``Idx``, is an immediate value, not an SSA value. It must - indicate a valid lane index for the type of ``x``. - - :arg TxN x: Source vector - :arg Idx: Lane index - :result T a: Lane value. +.. autoinst:: splat +.. autoinst:: insertlane +.. autoinst:: extractlane Integer operations ------------------ diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index c0eec9e970..9660e9e602 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -296,7 +296,7 @@ class TypeVar(object): def __str__(self): return "`{}`".format(self.name) - def lane(self): + def lane_of(self): """ Return a derived type variable that is the scalar lane type of this type variable. @@ -304,7 +304,7 @@ class TypeVar(object): When this type variable assumes a scalar type, the derived type will be the same scalar type. """ - return TypeVar(None, None, base=self, derived_func='Lane') + return TypeVar(None, None, base=self, derived_func='LaneOf') def as_bool(self): """ diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index c28c3724a2..b41225ae00 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -6,7 +6,7 @@ support. """ from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args from types import i8, f32, f64 -from immediates import imm64, ieee32, ieee64, immvector +from immediates import imm64, uimm8, ieee32, ieee64, immvector import entities instructions = InstructionGroup("base", "Shared base instruction set") @@ -17,7 +17,7 @@ Testable = TypeVar( 'Testable', 'A scalar boolean or integer type', ints=True, bools=True) TxN = TypeVar( - '%Tx%N', 'A SIMD vector type', + 'TxN', 'A SIMD vector type', ints=True, floats=True, bools=True, scalars=False, simd=True) Any = TypeVar( 'Any', 'Any integer, float, or boolean scalar or vector type', @@ -177,6 +177,42 @@ vselect = Instruction( vector ``c``. """, ins=(c, x, y), outs=a) + +x = Operand('x', TxN.lane_of()) + +splat = Instruction( + 'splat', r""" + Vector splat. + + Return a vector whose lanes are all ``x``. + """, + ins=x, outs=a) + +x = Operand('x', TxN, doc='SIMD vector to modify') +y = Operand('y', TxN.lane_of(), doc='New lane value') +Idx = Operand('Idx', uimm8, doc='Lane index') + +insertlane = Instruction( + 'insertlane', r""" + Insert ``y`` as lane ``Idx`` in x. + + The lane index, ``Idx``, is an immediate value, not an SSA value. It + must indicate a valid lane index for the type of ``x``. + """, + ins=(x, Idx, y), outs=a) + +x = Operand('x', TxN) +a = Operand('a', TxN.lane_of()) + +extractlane = Instruction( + 'extractlane', r""" + Extract lane ``Idx`` from ``x``. + + The lane index, ``Idx``, is an immediate value, not an SSA value. It + must indicate a valid lane index for the type of ``x``. + """, + ins=(x, Idx), outs=a) + # # Integer arithmetic # diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 4843befea9..6f09c4b5af 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -8,7 +8,7 @@ in this module. from . import InstructionFormat, value, variable_args -from immediates import imm64, ieee32, ieee64, immvector +from immediates import imm64, uimm8, ieee32, ieee64, immvector from entities import ebb, function, jump_table Nullary = InstructionFormat() @@ -30,6 +30,9 @@ BinaryOverflow = InstructionFormat(value, value, multiple_results=True) # The first value operand is the controlling flag whisch has a derived type. Select = InstructionFormat(value, value, value, typevar_operand=1) +InsertLane = InstructionFormat(value, uimm8, value) +ExtractLane = InstructionFormat(value, uimm8) + Jump = InstructionFormat(ebb, variable_args, boxed_storage=True) Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) BranchTable = InstructionFormat(value, jump_table) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index e8e581d64d..299b340bce 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -11,6 +11,12 @@ from . import ImmediateKind #: :py:class:`cretonne.IntType` type. imm64 = ImmediateKind('imm64', 'A 64-bit immediate integer.') +#: An unsigned 8-bit immediate integer operand. +#: +#: This small operand is used to indicate lane indexes in SIMD vectors and +#: immediate bit counts on shift instructions. +uimm8 = ImmediateKind('uimm8', 'An 8-bit immediate unsigned integer.') + #: A 32-bit immediate floating point operand. #: #: IEEE 754-2008 binary32 interchange format. diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index d1861fb669..338bf3f28b 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -163,6 +163,18 @@ pub enum InstructionData { ty: Type, args: [Value; 3], }, + InsertLane { + opcode: Opcode, + ty: Type, + lane: u8, + args: [Value; 2], + }, + ExtractLane { + opcode: Opcode, + ty: Type, + lane: u8, + arg: Value, + }, Jump { opcode: Opcode, ty: Type, @@ -401,7 +413,7 @@ enum OperandConstraint { Same, /// This operand is `ctrlType.lane_type()`. - Lane, + LaneOf, /// This operand is `ctrlType.as_bool()`. AsBool, @@ -418,7 +430,7 @@ impl OperandConstraint { Concrete(t) => Some(t), Free(_) => None, Same => Some(ctrl_type), - Lane => Some(ctrl_type.lane_type()), + LaneOf => Some(ctrl_type.lane_type()), AsBool => Some(ctrl_type.as_bool()), } } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index e6eb3a477f..406296c2a3 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -159,6 +159,10 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { Select { opcode, args, .. } => { writeln!(w, "{} {}, {}, {}", opcode, args[0], args[1], args[2]) } + InsertLane { opcode, lane, args, .. } => { + writeln!(w, "{} {}, {}, {}", opcode, args[0], lane, args[1]) + } + ExtractLane { opcode, lane, arg, .. } => writeln!(w, "{} {}, {}", opcode, arg, lane), Jump { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), Branch { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), BranchTable { opcode, arg, table, .. } => writeln!(w, "{} {}, {}", opcode, arg, table), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 6aab089330..0344b6238f 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -669,6 +669,8 @@ impl<'a> Parser<'a> { } } InstructionFormat::Select | + InstructionFormat::InsertLane | + InstructionFormat::ExtractLane | InstructionFormat::Jump | InstructionFormat::Branch | InstructionFormat::BranchTable | From f0fc9c9477450d39f6cc9741b77fbdf602222808 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 27 May 2016 12:03:09 -0700 Subject: [PATCH 091/968] Generate Value and Ebb references in lexer. During parsing, it is possible to see instruction operands that reference values or EBBs that have not been created yet. These references have to be resolved by a second pass following parsing once all EBBs and values have been created. To prepare for this second pass, start creating Ebb and Value references that use the numbering from the source file rather than the in-memory real references. Maintain Value -> Value and Ebb -> Ebb mappings. This makes it possible to store source-numbered Ebb and Value references in instructions. All other entities are created in the preamble, so they should have been created before they are referenced. --- src/libcretonne/entities.rs | 73 +++++++++++++++++++++++++----- src/libreader/lexer.rs | 22 +++++---- src/libreader/parser.rs | 89 +++++++++++++------------------------ 3 files changed, 108 insertions(+), 76 deletions(-) diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index 8513447ab2..dcdff9e822 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -24,7 +24,7 @@ use std::fmt::{self, Display, Formatter, Write}; use std::u32; /// An opaque reference to an extended basic block in a function. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Ebb(u32); impl Ebb { @@ -33,6 +33,15 @@ impl Ebb { Ebb(index as u32) } + /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. + pub fn with_number(n: u32) -> Option { + if n < u32::MAX { + Some(Ebb(n)) + } else { + None + } + } + pub fn index(&self) -> usize { self.0 as usize } @@ -54,9 +63,8 @@ impl Default for Ebb { } } - /// An opaque reference to an instruction in a function. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Inst(u32); impl Inst { @@ -88,7 +96,7 @@ impl Default for Inst { /// An opaque reference to an SSA value. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Value(u32); // Value references can either reference an instruction directly, or they can refer to the extended @@ -105,12 +113,29 @@ pub enum ExpandedValue { } impl Value { - pub fn direct_from_number(n: u32) -> Value { - let encoding = n * 2; - assert!(encoding < u32::MAX); - Value(encoding) + /// Create a `Direct` value from its number representation. + /// This is the number in the vNN notation. + pub fn direct_with_number(n: u32) -> Option { + if n < u32::MAX / 2 { + let encoding = n * 2; + assert!(encoding < u32::MAX); + Some(Value(encoding)) + } else { + None + } } + /// Create a `Table` value from its number representation. + /// This is the number in the vxNN notation. + pub fn table_with_number(n: u32) -> Option { + if n < u32::MAX / 2 { + let encoding = n * 2 + 1; + assert!(encoding < u32::MAX); + Some(Value(encoding)) + } else { + None + } + } pub fn new_direct(i: Inst) -> Value { let encoding = i.index() * 2; assert!(encoding < u32::MAX as usize); @@ -160,7 +185,7 @@ impl Default for Value { } /// An opaque reference to a stack slot. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct StackSlot(u32); impl StackSlot { @@ -191,7 +216,7 @@ impl Default for StackSlot { } /// An opaque reference to a jump table. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct JumpTable(u32); impl JumpTable { @@ -220,3 +245,31 @@ impl Default for JumpTable { NO_JUMP_TABLE } } + +#[cfg(test)] +mod tests { + use super::*; + use std::u32; + + #[test] + fn value_with_number() { + assert_eq!(Value::direct_with_number(0).unwrap().to_string(), "v0"); + assert_eq!(Value::direct_with_number(1).unwrap().to_string(), "v1"); + assert_eq!(Value::table_with_number(0).unwrap().to_string(), "vx0"); + assert_eq!(Value::table_with_number(1).unwrap().to_string(), "vx1"); + + assert_eq!(Value::direct_with_number(u32::MAX / 2), None); + assert_eq!(match Value::direct_with_number(u32::MAX / 2 - 1).unwrap().expand() { + ExpandedValue::Direct(i) => i.index() as u32, + _ => u32::MAX, + }, + u32::MAX / 2 - 1); + + assert_eq!(Value::table_with_number(u32::MAX / 2), None); + assert_eq!(match Value::table_with_number(u32::MAX / 2 - 1).unwrap().expand() { + ExpandedValue::Table(i) => i as u32, + _ => u32::MAX, + }, + u32::MAX / 2 - 1); + } +} diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 3611a70f69..4d3dfdf7c1 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -7,6 +7,7 @@ use std::str::CharIndices; use cretonne::types; +use cretonne::entities::{Value, Ebb}; /// The location of a `Token` or `Error`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -35,9 +36,8 @@ pub enum Token<'a> { Float(&'a str), // Floating point immediate Integer(&'a str), // Integer immediate Type(types::Type), // i32, f32, b32x4, ... - ValueDirect(u32), // v12 - ValueTable(u32), // vx7 - Ebb(u32), // ebb3 + Value(Value), // v12, vx7 + Ebb(Ebb), // ebb3 StackSlot(u32), // ss3 Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) } @@ -289,9 +289,9 @@ impl<'a> Lexer<'a> { }; match prefix { - "v" => Some(Token::ValueDirect(value)), - "vx" => Some(Token::ValueTable(value)), - "ebb" => Some(Token::Ebb(value)), + "v" => Value::direct_with_number(value).map(|v| Token::Value(v)), + "vx" => Value::table_with_number(value).map(|v| Token::Value(v)), + "ebb" => Ebb::with_number(value).map(|ebb| Token::Ebb(ebb)), "ss" => Some(Token::StackSlot(value)), _ => None, } @@ -374,6 +374,7 @@ impl<'a> Lexer<'a> { mod tests { use super::*; use cretonne::types; + use cretonne::entities::{Value, Ebb}; fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) @@ -445,14 +446,17 @@ mod tests { fn lex_identifiers() { let mut lex = Lexer::new("v0 v00 vx01 ebb1234567890 ebb5234567890 entry v1x vx1 vxvx4 \ function0 function b1 i32x4 f32x5"); - assert_eq!(lex.next(), token(Token::ValueDirect(0), 1)); + assert_eq!(lex.next(), + token(Token::Value(Value::direct_with_number(0).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("v00"), 1)); assert_eq!(lex.next(), token(Token::Identifier("vx01"), 1)); - assert_eq!(lex.next(), token(Token::Ebb(1234567890), 1)); + assert_eq!(lex.next(), + token(Token::Ebb(Ebb::with_number(1234567890).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("ebb5234567890"), 1)); assert_eq!(lex.next(), token(Token::Entry, 1)); assert_eq!(lex.next(), token(Token::Identifier("v1x"), 1)); - assert_eq!(lex.next(), token(Token::ValueTable(1), 1)); + assert_eq!(lex.next(), + token(Token::Value(Value::table_with_number(1).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("vxvx4"), 1)); assert_eq!(lex.next(), token(Token::Identifier("function0"), 1)); assert_eq!(lex.next(), token(Token::Function, 1)); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 0344b6238f..ff52940941 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -52,9 +52,8 @@ pub struct Parser<'a> { struct Context { function: Function, stack_slots: HashMap, // ssNN - ebbs: HashMap, // ebbNN - value_directs: HashMap, // vNN - value_tables: HashMap, // vxNN + ebbs: HashMap, // ebbNN + values: HashMap, // vNN, vxNN } impl Context { @@ -63,8 +62,7 @@ impl Context { function: f, stack_slots: HashMap::new(), ebbs: HashMap::new(), - value_directs: HashMap::new(), - value_tables: HashMap::new(), + values: HashMap::new(), } } @@ -80,37 +78,25 @@ impl Context { } } - // Allocate a new EBB and add a mapping number -> Ebb. - fn add_ebb(&mut self, number: u32, loc: &Location) -> Result { + // Allocate a new EBB and add a mapping src_ebb -> Ebb. + fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { let ebb = self.function.make_ebb(); - if self.ebbs.insert(number, ebb).is_some() { + if self.ebbs.insert(src_ebb, ebb).is_some() { Err(Error { location: loc.clone(), - message: format!("duplicate EBB: ebb{}", number), + message: format!("duplicate EBB: {}", src_ebb), }) } else { Ok(ebb) } } - // Add a value mapping number -> data for a direct value (vNN). - fn add_v(&mut self, number: u32, data: Value, loc: &Location) -> Result<()> { - if self.value_directs.insert(number, data).is_some() { + // Add a value mapping src_val -> data. + fn add_value(&mut self, src_val: Value, data: Value, loc: &Location) -> Result<()> { + if self.values.insert(src_val, data).is_some() { Err(Error { location: loc.clone(), - message: format!("duplicate value: v{}", number), - }) - } else { - Ok(()) - } - } - - // Add a value mapping number -> data for a table value (vxNN). - fn add_vx(&mut self, number: u32, data: Value, loc: &Location) -> Result<()> { - if self.value_tables.insert(number, data).is_some() { - Err(Error { - location: loc.clone(), - message: format!("duplicate value: vx{}", number), + message: format!("duplicate value: {}", src_val), }) } else { Ok(()) @@ -220,7 +206,7 @@ impl<'a> Parser<'a> { } // Match and consume an ebb reference. - fn match_ebb(&mut self, err_msg: &str) -> Result { + fn match_ebb(&mut self, err_msg: &str) -> Result { if let Some(Token::Ebb(ebb)) = self.token() { self.consume(); Ok(ebb) @@ -229,26 +215,15 @@ impl<'a> Parser<'a> { } } - // Match and consume a vx reference. - fn match_vx(&mut self, err_msg: &str) -> Result { - if let Some(Token::ValueTable(vx)) = self.token() { - self.consume(); - Ok(vx) - } else { - Err(self.error(err_msg)) - } - } - // Match and consume a value reference, direct or vtable. // This does not convert from the source value numbering to our in-memory value numbering. fn match_value(&mut self, err_msg: &str) -> Result { - let val = match self.token() { - Some(Token::ValueDirect(v)) => Value::direct_from_number(v), - Some(Token::ValueTable(vx)) => Value::new_table(vx as usize), - _ => return Err(self.error(err_msg)), - }; - self.consume(); - Ok(val) + if let Some(Token::Value(v)) = self.token() { + self.consume(); + Ok(v) + } else { + Err(self.error(err_msg)) + } } // Match and consume an Imm64 immediate. @@ -484,7 +459,7 @@ impl<'a> Parser<'a> { // extended-basic-block ::= ebb-header * { instruction } while match self.token() { - Some(Token::ValueDirect(_)) => true, + Some(Token::Value(_)) => true, Some(Token::Identifier(_)) => true, _ => false, } { @@ -519,39 +494,39 @@ impl<'a> Parser<'a> { // Parse a single EBB argument declaration, and append it to `ebb`. // - // ebb-arg ::= * ValueTable(vx) ":" Type(t) + // ebb-arg ::= * Value(vx) ":" Type(t) // fn parse_ebb_arg(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { - // ebb-arg ::= * ValueTable(vx) ":" Type(t) - let vx = try!(self.match_vx("EBB argument must be a vx value")); + // ebb-arg ::= * Value(vx) ":" Type(t) + let vx = try!(self.match_value("EBB argument must be a value")); let vx_location = self.location; - // ebb-arg ::= ValueTable(vx) * ":" Type(t) + // ebb-arg ::= Value(vx) * ":" Type(t) try!(self.match_token(Token::Colon, "expected ':' after EBB argument")); - // ebb-arg ::= ValueTable(vx) ":" * Type(t) + // ebb-arg ::= Value(vx) ":" * Type(t) let t = try!(self.match_type("expected EBB argument type")); // Allocate the EBB argument and add the mapping. let value = ctx.function.append_ebb_arg(ebb, t); - ctx.add_vx(vx, value, &vx_location) + ctx.add_value(vx, value, &vx_location) } // Parse an instruction, append it to `ebb`. // // instruction ::= [inst-results "="] Opcode(opc) ... - // inst-results ::= ValueDirect(v) { "," ValueTable(vx) } + // inst-results ::= Value(v) { "," Value(vx) } // fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { - // Result value numbers. First is a ValueDirect, remaining are ValueTable. + // Result value numbers. let mut results = Vec::new(); // instruction ::= * [inst-results "="] Opcode(opc) ... - if let Some(Token::ValueDirect(v)) = self.token() { + if let Some(Token::Value(v)) = self.token() { self.consume(); results.push(v); - // inst-results ::= ValueDirect(v) * { "," ValueTable(vx) } + // inst-results ::= Value(v) * { "," Value(vx) } while self.optional(Token::Comma) { - // inst-results ::= ValueDirect(v) { "," * ValueTable(vx) } - results.push(try!(self.match_vx("expected vx result value"))); + // inst-results ::= Value(v) { "," * Value(vx) } + results.push(try!(self.match_value("expected result value"))); } try!(self.match_token(Token::Equal, "expected '=' before opcode")); @@ -576,7 +551,7 @@ impl<'a> Parser<'a> { if !results.is_empty() { assert!(results.len() == 1, "Multiple results not implemented"); let result = ctx.function.first_result(inst); - try!(ctx.add_v(results[0], result, &self.location)); + try!(ctx.add_value(results[0], result, &self.location)); } ctx.function.append_inst(ebb, inst); From fc8d2f92fd85edbd01861978fcf165bfbdd4bd00 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 1 Jun 2016 09:14:01 -0700 Subject: [PATCH 092/968] Clean up unused-import warnings. --- src/libcretonne/repr.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 6e7e6ea80f..716887211d 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,7 +1,7 @@ //! Representation of Cretonne IL functions. use types::{Type, FunctionName, Signature}; -use entities::*; +use entities::{Ebb, NO_EBB, Inst, NO_INST, Value, NO_VALUE, ExpandedValue, StackSlot}; use instructions::*; use std::fmt::{self, Display, Formatter}; use std::ops::{Index, IndexMut}; @@ -502,7 +502,6 @@ impl<'a> Iterator for Values<'a> { mod tests { use super::*; use types; - use entities::*; use instructions::*; #[test] From ecd8287eb0c257288b9c214490b0f1a0c3729a76 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 27 May 2016 11:04:55 -0700 Subject: [PATCH 093/968] Parse controlling type variable. Do basic type inference. Replace the make_multi_inst() function with a make_inst_results() which uses the constraint system to create the result values. A typevar argument ensures that this function does not infer anything from the instruction data arguments. These arguments may not be valid during parsing. Implement basic type inference in the parser. If the designated value operand on a polymorphic instruction refers to a known value, use that to infer the controlling type variable. This simple method of type inference requires the operand value to be defined above the use in the text. Since reordering the EBBs could place a dominating EBB below the current one, this is a bit fragile. One possibility would be to require the value is defined in the same EBB. In all other cases, the controlling typevar should be explicit. --- meta/gen_instr.py | 40 ++++++++++ src/libcretonne/instructions.rs | 13 +++- src/libcretonne/repr.rs | 90 +++++++++++----------- src/libcretonne/types.rs | 7 ++ src/libcretonne/write.rs | 4 +- src/libreader/parser.rs | 129 +++++++++++++++++++++++++++----- 6 files changed, 215 insertions(+), 68 deletions(-) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 1cd0ea8b4f..590dd13659 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -70,6 +70,14 @@ def gen_instruction_data_impl(fmt): 'InstructionData::{} {{ ty, .. }} => ty,' .format(f.name)) + fmt.doc_comment('Mutable reference to the type of the first result.') + with fmt.indented('pub fn first_type_mut(&mut self) -> &mut Type {', '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + fmt.line( + 'InstructionData::{} {{ ref mut ty, .. }} => ty,' + .format(f.name)) + # Generate shared and mutable accessors for `second_result` which only # applies to instruction formats that can produce multiple results. # Everything else returns `None`. @@ -120,6 +128,38 @@ def gen_instruction_data_impl(fmt): ' { ref mut second_result, .. }' + ' => Some(second_result),') + fmt.doc_comment('Get the controlling type variable operand.') + with fmt.indented( + 'pub fn typevar_operand(&self) -> Option {', '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.typevar_operand is None: + fmt.line(n + ' { .. } => None,') + elif len(f.value_operands) == 1: + # We have a single value operand called 'arg'. + if f.boxed_storage: + fmt.line( + n + ' { ref data, .. } => Some(data.arg),') + else: + fmt.line(n + ' { arg, .. } => Some(arg),') + else: + # We have multiple value operands and an array `args`. + # Which `args` index to use? + # Map from index into f.kinds into f.value_operands + # index. + i = f.value_operands.index(f.typevar_operand) + if f.boxed_storage: + fmt.line( + n + + ' {{ ref data, .. }} => Some(data.args[{}]),' + .format(i)) + else: + fmt.line( + n + + ' {{ ref args, .. }} => Some(args[{}]),' + .format(i)) + def collect_instr_groups(targets): seen = set() diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 338bf3f28b..9ac3ac7220 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -142,15 +142,15 @@ pub enum InstructionData { BinaryImm { opcode: Opcode, ty: Type, - lhs: Value, - rhs: Imm64, + arg: Value, + imm: Imm64, }, // Same as BinaryImm, but the immediate is the lhs operand. BinaryImmRev { opcode: Opcode, ty: Type, - rhs: Value, - lhs: Imm64, + arg: Value, + imm: Imm64, }, BinaryOverflow { opcode: Opcode, @@ -366,6 +366,11 @@ impl OpcodeConstraints { pub fn ctrl_typeset(self) -> Option { self.typeset_offset().map(|offset| TYPE_SETS[offset]) } + + /// Is this instruction polymorphic? + pub fn is_polymorphic(self) -> bool { + self.ctrl_typeset().is_some() + } } /// A value type set describes the permitted set of types for a type variable. diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 716887211d..9c44f524ca 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -88,8 +88,8 @@ impl Function { /// Create a new instruction. /// - /// The instruction is allowed to produce at most one result as indicated by `data.ty`. Use - /// `make_multi_inst()` to create instructions with multiple results. + /// The type of the first result is indicated by `data.ty`. If the instruction produces + /// multiple results, also call `make_inst_results` to allocate value table entries. pub fn make_inst(&mut self, data: InstructionData) -> Inst { let inst = Inst::new(self.instructions.len()); self.instructions.push(data); @@ -101,40 +101,59 @@ impl Function { inst } - /// Make an instruction that may produce multiple results. - /// - /// The type of the first result is `data.ty`. If the instruction generates more than one - /// result, additional result types are in `extra_result_types`. - /// - /// Not all instruction formats can represent multiple result values. This function will panic - /// if the format of `data` is insufficient. - pub fn make_multi_inst(&mut self, data: InstructionData, extra_result_types: &[Type]) -> Inst { - let inst = self.make_inst(data); + fn inst_mut(&mut self, inst: Inst) -> &mut InstructionData { + &mut self.instructions[inst.index()] + } - if !extra_result_types.is_empty() { - // Additional values form a linked list starting from the second result value. Generate - // the list backwards so we don't have to modify value table entries in place. (This - // causes additional result values to be numbered backwards which is not the aestetic - // choice, but since it is only visible in extremely rare instructions with 3+ results, - // we don't care). - let mut head = NO_VALUE; - for ty in extra_result_types.into_iter().rev() { + /// Create result values for an instruction that produces multiple results. + /// + /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If + /// the instruction may produce more than 1 result, call `make_inst_results` to allocate + /// `Value` table entries for the additional results. + /// + /// The result value types are determined from the instruction's value type constraints and the + /// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic + /// instructions, `ctrl_typevar` is ignored, and `VOID` can be used. + /// + /// The type of the first result value is also set, even if it was already set in the + /// `InstructionData` passed to `make_inst`. If this function is called with a single-result + /// instruction, that is the only effect. + /// + /// Returns the number of results produced by the instruction. + pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { + let constraints = self[inst].opcode().constraints(); + let fixed_results = constraints.fixed_results(); + + // Additional values form a linked list starting from the second result value. Generate + // the list backwards so we don't have to modify value table entries in place. (This + // causes additional result values to be numbered backwards which is not the aestetic + // choice, but since it is only visible in extremely rare instructions with 3+ results, + // we don't care). + let mut head = NO_VALUE; + let mut first_type = Type::default(); + + // TBD: Function call return values for direct and indirect function calls. + + if fixed_results > 0 { + for res_idx in (1..fixed_results).rev() { head = self.make_value(ValueData::Def { - ty: *ty, + ty: constraints.result_type(res_idx, ctrl_typevar), def: inst, next: head, }); } - - // Update the second_result pointer in `inst`. - if let Some(second_result_ref) = self.instructions[inst.index()].second_result_mut() { - *second_result_ref = head; - } else { - panic!("Instruction format doesn't allow multiple results."); - } + first_type = constraints.result_type(0, ctrl_typevar); } - inst + // Update the second_result pointer in `inst`. + if head != NO_VALUE { + *self.inst_mut(inst) + .second_result_mut() + .expect("instruction format doesn't allow multiple results") = head; + } + *self.inst_mut(inst).first_type_mut() = first_type; + + fixed_results } /// Get the first result of an instruction. @@ -521,21 +540,6 @@ mod tests { assert_eq!(ins.first_type(), types::I32); } - #[test] - fn multiple_results() { - use types::*; - let mut func = Function::new(); - - let idata = InstructionData::call(Opcode::Vconst, I64); - let inst = func.make_multi_inst(idata, &[I8, F64]); - assert_eq!(inst.to_string(), "inst0"); - let results: Vec = func.inst_results(inst).collect(); - assert_eq!(results.len(), 3); - assert_eq!(func.value_type(results[0]), I64); - assert_eq!(func.value_type(results[1]), I8); - assert_eq!(func.value_type(results[2]), F64); - } - #[test] fn stack_slot() { let mut func = Function::new(); diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index a4ff443541..bc4cecca6b 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -1,6 +1,7 @@ //! Common types for the Cretonne code generator. +use std::default::Default; use std::fmt::{self, Display, Formatter, Write}; // ====--------------------------------------------------------------------------------------====// @@ -202,6 +203,12 @@ impl Display for Type { } } +impl Default for Type { + fn default() -> Type { + VOID + } +} + // ====--------------------------------------------------------------------------------------====// // // Function signatures diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 406296c2a3..0d615c545b 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -153,8 +153,8 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { UnaryIeee64 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), UnaryImmVector { opcode, .. } => writeln!(w, "{} [...]", opcode), Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), - BinaryImm { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), - BinaryImmRev { opcode, lhs, rhs, .. } => writeln!(w, "{} {}, {}", opcode, lhs, rhs), + BinaryImm { opcode, arg, imm, .. } => writeln!(w, "{} {}, {}", opcode, arg, imm), + BinaryImmRev { opcode, imm, arg, .. } => writeln!(w, "{} {}, {}", opcode, imm, arg), BinaryOverflow { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), Select { opcode, args, .. } => { writeln!(w, "{} {}, {}, {}", opcode, args[0], args[1], args[2]) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index ff52940941..f9a66a5235 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -144,18 +144,22 @@ impl<'a> Parser<'a> { } // Generate an error. - fn error(&self, message: &str) -> Error { + fn error_string(&self, message: String) -> Error { Error { location: self.location, message: // If we have a lexer error latched, report that. match self.lex_error { Some(lexer::Error::InvalidChar) => "invalid character".to_string(), - None => message.to_string(), + None => message, } } } + fn error(&self, message: &str) -> Error { + self.error_string(message.to_string()) + } + // Match and consume a token without payload. fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result> { if self.token() == Some(want) { @@ -511,14 +515,15 @@ impl<'a> Parser<'a> { // Parse an instruction, append it to `ebb`. // - // instruction ::= [inst-results "="] Opcode(opc) ... + // instruction ::= [inst-results "="] Opcode(opc) ["." Type] ... // inst-results ::= Value(v) { "," Value(vx) } // fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { // Result value numbers. let mut results = Vec::new(); - // instruction ::= * [inst-results "="] Opcode(opc) ... + // instruction ::= * [inst-results "="] Opcode(opc) ["." Type] ... + // inst-results ::= * Value(v) { "," Value(vx) } if let Some(Token::Value(v)) = self.token() { self.consume(); results.push(v); @@ -532,7 +537,7 @@ impl<'a> Parser<'a> { try!(self.match_token(Token::Equal, "expected '=' before opcode")); } - // instruction ::= [inst-results "="] * Opcode(opc) ... + // instruction ::= [inst-results "="] * Opcode(opc) ["." Type] ... let opcode = if let Some(Token::Identifier(text)) = self.token() { match text.parse() { Ok(opc) => opc, @@ -541,24 +546,110 @@ impl<'a> Parser<'a> { } else { return Err(self.error("expected instruction opcode")); }; + self.consume(); - // instruction ::= [inst-results "="] Opcode(opc) * ... + // Look for a controlling type variable annotation. + // instruction ::= [inst-results "="] Opcode(opc) * ["." Type] ... + let explicit_ctrl_type = if self.optional(Token::Dot) { + Some(try!(self.match_type("expected type after 'opcode.'"))) + } else { + None + }; + + // instruction ::= [inst-results "="] Opcode(opc) ["." Type] * ... let inst_data = try!(self.parse_inst_operands(opcode)); + + // We're done parsing the instruction now. + // + // We still need to check that the number of result values in the source matches the opcode + // or function call signature. We also need to create values with the right type for all + // the instruction results. + let ctrl_typevar = try!(self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data)); let inst = ctx.function.make_inst(inst_data); - - // TODO: Check that results.len() matches the opcode. - // TODO: Multiple results. - if !results.is_empty() { - assert!(results.len() == 1, "Multiple results not implemented"); - let result = ctx.function.first_result(inst); - try!(ctx.add_value(results[0], result, &self.location)); - } - + let num_results = ctx.function.make_inst_results(inst, ctrl_typevar); ctx.function.append_inst(ebb, inst); + if results.len() != num_results { + let m = format!("instruction produces {} result values, {} given", + num_results, + results.len()); + return Err(self.error_string(m)); + } + + // Now map the source result values to the just created instruction results. + // We need to copy the list of result values to avoid fighting the borrow checker. + let new_results: Vec = ctx.function.inst_results(inst).collect(); + for (src, val) in results.iter().zip(new_results) { + try!(ctx.add_value(*src, val, &self.location)); + } + Ok(()) } + // Type inference for polymorphic instructions. + // + // The controlling type variable can be specified explicitly as 'splat.i32x4 v5', or it can be + // inferred from `inst_data.typevar_operand` for some opcodes. + // + // The value operands in `inst_data` are expected to use source numbering. + // + // Returns the controlling typevar for a polymorphic opcode, or `VOID` for a non-polymorphic + // opcode. + fn infer_typevar(&self, + ctx: &Context, + opcode: Opcode, + explicit_ctrl_type: Option, + inst_data: &InstructionData) + -> Result { + let constraints = opcode.constraints(); + let ctrl_type = match explicit_ctrl_type { + Some(t) => t, + None => { + if constraints.use_typevar_operand() { + // This is an opcode that supports type inference, AND there was no explicit + // type specified. Look up `ctrl_value` to see if it was defined already. + // TBD: If it is defined in another block, the type should have been specified + // explicitly. It is unfortunate that the correctness of IL depends on the + // layout of the blocks. + let ctrl_src_value = inst_data.typevar_operand() + .expect("Constraints <-> Format inconsistency"); + ctx.function.value_type(match ctx.values.get(&ctrl_src_value) { + Some(&v) => v, + None => { + let m = format!("cannot determine type of operand {}", ctrl_src_value); + return Err(self.error_string(m)); + } + }) + } else if constraints.is_polymorphic() { + // This opcode does not support type inference, so the explicit type variable + // is required. + return Err(self.error("type variable required for polymorphic opcode")); + } else { + // This is a non-polymorphic opcode. No typevar needed. + VOID + } + } + }; + + // Verify that `ctrl_type` is valid for the controlling type variable. We don't want to + // attempt deriving types from an incorrect basis. + // This is not a complete type check. The verifier does that. + if let Some(typeset) = constraints.ctrl_typeset() { + // This is a polymorphic opcode. + if !typeset.contains(ctrl_type) { + let m = format!("{} is not a valid typevar for {}", ctrl_type, opcode); + return Err(self.error_string(m)); + } + } else { + // Treat it as a syntax error to speficy a typevar on a non-polymorphic opcode. + if ctrl_type != VOID { + return Err(self.error_string(format!("{} does not take a typevar", opcode))); + } + } + + Ok(ctrl_type) + } + // Parse the operands following the instruction opcode. // This depends on the format of the opcode. fn parse_inst_operands(&mut self, opcode: Opcode) -> Result { @@ -617,8 +708,8 @@ impl<'a> Parser<'a> { InstructionData::BinaryImm { opcode: opcode, ty: VOID, - lhs: lhs, - rhs: rhs, + arg: lhs, + imm: rhs, } } InstructionFormat::BinaryImmRev => { @@ -628,8 +719,8 @@ impl<'a> Parser<'a> { InstructionData::BinaryImmRev { opcode: opcode, ty: VOID, - lhs: lhs, - rhs: rhs, + imm: lhs, + arg: rhs, } } InstructionFormat::BinaryOverflow => { From 4eb327d0273946dda4ea79ab99c008942556fc4f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 1 Jun 2016 20:45:58 -0700 Subject: [PATCH 094/968] Avoid allocating a temporary Vec in the parser. Wrangle the borrow checker into allowing us to iterate over function result values while mutating the ctx.values table. --- src/libreader/parser.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index f9a66a5235..600632a51a 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -577,13 +577,11 @@ impl<'a> Parser<'a> { } // Now map the source result values to the just created instruction results. - // We need to copy the list of result values to avoid fighting the borrow checker. - let new_results: Vec = ctx.function.inst_results(inst).collect(); - for (src, val) in results.iter().zip(new_results) { - try!(ctx.add_value(*src, val, &self.location)); - } - - Ok(()) + // Pass a reference to `ctx.values` instead of `ctx` itself since the `Values` iterator + // holds a reference to `ctx.function`. + self.add_values(&mut ctx.values, + results.into_iter(), + ctx.function.inst_results(inst)) } // Type inference for polymorphic instructions. @@ -650,6 +648,24 @@ impl<'a> Parser<'a> { Ok(ctrl_type) } + // Add mappings for a list of source values to their corresponding new values. + fn add_values(&self, + values: &mut HashMap, + results: S, + new_results: V) + -> Result<()> + where S: Iterator, + V: Iterator + { + for (src, val) in results.zip(new_results) { + if values.insert(src, val).is_some() { + return Err(self.error_string(format!("duplicate result value: {}", src))); + } + } + Ok(()) + } + + // Parse the operands following the instruction opcode. // This depends on the format of the opcode. fn parse_inst_operands(&mut self, opcode: Opcode) -> Result { From 8fac050bb5c9650f3f476f6a3c771e1a99784457 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 2 Jun 2016 08:40:47 -0700 Subject: [PATCH 095/968] Use an err! macro to build parser errors with format! arguments. --- src/libreader/parser.rs | 123 ++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 600632a51a..7d25fc096b 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -33,6 +33,23 @@ impl Display for Error { pub type Result = result::Result; +// Create an `Err` variant of `Result` from a location and `format!` args. +macro_rules! err { + ( $loc:expr, $msg:expr ) => { + Err(Error { + location: $loc.clone(), + message: String::from($msg), + }) + }; + + ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => { + Err(Error { + location: $loc.clone(), + message: format!( $fmt, $( $arg ),+ ), + }) + }; +} + pub struct Parser<'a> { lex: Lexer<'a>, @@ -42,7 +59,7 @@ pub struct Parser<'a> { lookahead: Option>, // Location of lookahead. - location: Location, + loc: Location, } // Context for resolving references when parsing a single function. @@ -69,10 +86,7 @@ impl Context { // Allocate a new stack slot and add a mapping number -> StackSlot. fn add_ss(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { if self.stack_slots.insert(number, self.function.make_stack_slot(data)).is_some() { - Err(Error { - location: loc.clone(), - message: format!("duplicate stack slot: ss{}", number), - }) + err!(loc, "duplicate stack slot: ss{}", number) } else { Ok(()) } @@ -82,10 +96,7 @@ impl Context { fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { let ebb = self.function.make_ebb(); if self.ebbs.insert(src_ebb, ebb).is_some() { - Err(Error { - location: loc.clone(), - message: format!("duplicate EBB: {}", src_ebb), - }) + err!(loc, "duplicate EBB: {}", src_ebb) } else { Ok(ebb) } @@ -94,10 +105,7 @@ impl Context { // Add a value mapping src_val -> data. fn add_value(&mut self, src_val: Value, data: Value, loc: &Location) -> Result<()> { if self.values.insert(src_val, data).is_some() { - Err(Error { - location: loc.clone(), - message: format!("duplicate value: {}", src_val), - }) + err!(loc, "duplicate value: {}", src_val) } else { Ok(()) } @@ -111,7 +119,7 @@ impl<'a> Parser<'a> { lex: Lexer::new(text), lex_error: None, lookahead: None, - location: Location { line_number: 0 }, + loc: Location { line_number: 0 }, } } @@ -131,11 +139,11 @@ impl<'a> Parser<'a> { match self.lex.next() { Some(Ok(lexer::LocatedToken { token, location })) => { self.lookahead = Some(token); - self.location = location; + self.loc = location; } Some(Err(lexer::LocatedError { error, location })) => { self.lex_error = Some(error); - self.location = location; + self.loc = location; } None => {} } @@ -143,29 +151,12 @@ impl<'a> Parser<'a> { return self.lookahead; } - // Generate an error. - fn error_string(&self, message: String) -> Error { - Error { - location: self.location, - message: - // If we have a lexer error latched, report that. - match self.lex_error { - Some(lexer::Error::InvalidChar) => "invalid character".to_string(), - None => message, - } - } - } - - fn error(&self, message: &str) -> Error { - self.error_string(message.to_string()) - } - // Match and consume a token without payload. fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result> { if self.token() == Some(want) { Ok(self.consume()) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -185,7 +176,7 @@ impl<'a> Parser<'a> { if self.token() == Some(Token::Identifier(want)) { Ok(self.consume()) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -195,7 +186,7 @@ impl<'a> Parser<'a> { self.consume(); Ok(t) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -205,7 +196,7 @@ impl<'a> Parser<'a> { self.consume(); Ok(ss) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -215,7 +206,7 @@ impl<'a> Parser<'a> { self.consume(); Ok(ebb) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -226,7 +217,14 @@ impl<'a> Parser<'a> { self.consume(); Ok(v) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) + } + } + + fn error(&self, message: &str) -> Error { + Error { + location: self.loc.clone(), + message: message.to_string(), } } @@ -238,7 +236,7 @@ impl<'a> Parser<'a> { // Parse it as an Imm64 to check for overflow and other issues. text.parse().map_err(|e| self.error(e)) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -250,7 +248,7 @@ impl<'a> Parser<'a> { // Parse it as an Ieee32 to check for the right number of digits and other issues. text.parse().map_err(|e| self.error(e)) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -262,7 +260,7 @@ impl<'a> Parser<'a> { // Parse it as an Ieee64 to check for the right number of digits and other issues. text.parse().map_err(|e| self.error(e)) } else { - Err(self.error(err_msg)) + err!(self.loc, err_msg) } } @@ -323,7 +321,7 @@ impl<'a> Parser<'a> { self.consume(); Ok(s.to_string()) } - _ => Err(self.error("expected function name")), + _ => err!(self.loc, "expected function name"), } } @@ -400,7 +398,7 @@ impl<'a> Parser<'a> { try!(match self.token() { Some(Token::StackSlot(..)) => { self.parse_stack_slot_decl() - .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.location)) + .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) } // More to come.. _ => return Ok(()), @@ -419,7 +417,7 @@ impl<'a> Parser<'a> { // stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag} let bytes = try!(self.match_imm64("expected byte-size in stack_slot decl")).to_bits(); if bytes > u32::MAX as u64 { - return Err(self.error("stack slot too large")); + return err!(self.loc, "stack slot too large"); } let data = StackSlotData::new(bytes as u32); @@ -446,11 +444,11 @@ impl<'a> Parser<'a> { fn parse_extended_basic_block(&mut self, ctx: &mut Context) -> Result<()> { let is_entry = self.optional(Token::Entry); let ebb_num = try!(self.match_ebb("expected EBB header")); - let ebb = try!(ctx.add_ebb(ebb_num, &self.location)); + let ebb = try!(ctx.add_ebb(ebb_num, &self.loc)); if is_entry { if ctx.function.entry_block != NO_EBB { - return Err(self.error("multiple entry blocks in function")); + return err!(self.loc, "multiple entry blocks in function"); } ctx.function.entry_block = ebb; } @@ -503,7 +501,7 @@ impl<'a> Parser<'a> { fn parse_ebb_arg(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { // ebb-arg ::= * Value(vx) ":" Type(t) let vx = try!(self.match_value("EBB argument must be a value")); - let vx_location = self.location; + let vx_location = self.loc; // ebb-arg ::= Value(vx) * ":" Type(t) try!(self.match_token(Token::Colon, "expected ':' after EBB argument")); // ebb-arg ::= Value(vx) ":" * Type(t) @@ -541,10 +539,10 @@ impl<'a> Parser<'a> { let opcode = if let Some(Token::Identifier(text)) = self.token() { match text.parse() { Ok(opc) => opc, - Err(msg) => return Err(self.error(msg)), + Err(msg) => return err!(self.loc, msg), } } else { - return Err(self.error("expected instruction opcode")); + return err!(self.loc, "expected instruction opcode"); }; self.consume(); @@ -570,10 +568,10 @@ impl<'a> Parser<'a> { ctx.function.append_inst(ebb, inst); if results.len() != num_results { - let m = format!("instruction produces {} result values, {} given", - num_results, - results.len()); - return Err(self.error_string(m)); + return err!(self.loc, + "instruction produces {} result values, {} given", + num_results, + results.len()); } // Now map the source result values to the just created instruction results. @@ -614,14 +612,15 @@ impl<'a> Parser<'a> { ctx.function.value_type(match ctx.values.get(&ctrl_src_value) { Some(&v) => v, None => { - let m = format!("cannot determine type of operand {}", ctrl_src_value); - return Err(self.error_string(m)); + return err!(self.loc, + "cannot determine type of operand {}", + ctrl_src_value); } }) } else if constraints.is_polymorphic() { // This opcode does not support type inference, so the explicit type variable // is required. - return Err(self.error("type variable required for polymorphic opcode")); + return err!(self.loc, "type variable required for polymorphic opcode"); } else { // This is a non-polymorphic opcode. No typevar needed. VOID @@ -635,13 +634,15 @@ impl<'a> Parser<'a> { if let Some(typeset) = constraints.ctrl_typeset() { // This is a polymorphic opcode. if !typeset.contains(ctrl_type) { - let m = format!("{} is not a valid typevar for {}", ctrl_type, opcode); - return Err(self.error_string(m)); + return err!(self.loc, + "{} is not a valid typevar for {}", + ctrl_type, + opcode); } } else { // Treat it as a syntax error to speficy a typevar on a non-polymorphic opcode. if ctrl_type != VOID { - return Err(self.error_string(format!("{} does not take a typevar", opcode))); + return err!(self.loc, "{} does not take a typevar", opcode); } } @@ -659,7 +660,7 @@ impl<'a> Parser<'a> { { for (src, val) in results.zip(new_results) { if values.insert(src, val).is_some() { - return Err(self.error_string(format!("duplicate result value: {}", src))); + return err!(self.loc, "duplicate result value: {}", src); } } Ok(()) From 96e88893be9bccf55393dde01152508ec69b969b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Jun 2016 14:56:25 -0700 Subject: [PATCH 096/968] Begin a basic command line interface. Add an external dependency to the docopt package and use it for a scaffold command line interface for the cton-util command. I am not too happy about taking external dependencies, and docopt pulls in 13 other packages. However, I really don't want to be writing command line parsers, and as long as the external dependencies are confined to the tools crate, we should be fine. The core cretonne crate should stay free of external dependencies to avoid trouble with embedding it. Implement a basic 'cat' subcommand which currently behaves like unix 'cat'. It will gain parser powers soon. --- src/libcretonne/lib.rs | 2 + src/tools/Cargo.lock | 101 +++++++++++++++++++++++++++++++++++++++++ src/tools/Cargo.toml | 2 + src/tools/cat.rs | 28 ++++++++++++ src/tools/main.rs | 62 ++++++++++++++++++++++++- 5 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/tools/cat.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 248c6bb37f..8b92e5dbed 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -5,6 +5,8 @@ // // ====------------------------------------------------------------------------------------==== // +pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + pub mod types; pub mod immediates; pub mod entities; diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index 91e9a5fae4..0b85735d6c 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -4,6 +4,16 @@ version = "0.0.0" dependencies = [ "cretonne 0.0.0", "cretonne-reader 0.0.0", + "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -17,3 +27,94 @@ dependencies = [ "cretonne 0.0.0", ] +[[package]] +name = "docopt" +version = "0.6.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.1.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index 702bd635e6..8059002f40 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -12,3 +12,5 @@ path = "main.rs" [dependencies] cretonne = { path = "../libcretonne" } cretonne-reader = { path = "../libreader" } +docopt = "0.6.80" +rustc-serialize = "0.3.19" diff --git a/src/tools/cat.rs b/src/tools/cat.rs new file mode 100644 index 0000000000..c8f30d0a99 --- /dev/null +++ b/src/tools/cat.rs @@ -0,0 +1,28 @@ +//! The `cat` sub-command. +//! +//! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of +//! normalizing formatting and removing comments. + +use CommandResult; +use std::fs::File; +use std::io::Read; + +pub fn run(files: Vec) -> CommandResult { + for (i, f) in files.into_iter().enumerate() { + if i != 0 { + println!(""); + } + try!(cat_one(f)) + } + Ok(()) +} + +fn cat_one(filename: String) -> CommandResult { + let mut file = try!(File::open(&filename).map_err(|e| format!("{}: {}", filename, e))); + let mut buffer = String::new(); + try!(file.read_to_string(&mut buffer) + .map_err(|e| format!("Couldn't read {}: {}", filename, e))); + + print!("{}", buffer); + Ok(()) +} diff --git a/src/tools/main.rs b/src/tools/main.rs index 97b261d22d..1ea40c201c 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -1,5 +1,65 @@ extern crate cretonne; extern crate cton_reader; +extern crate docopt; +extern crate rustc_serialize; -fn main() {} +use cretonne::VERSION; +use docopt::Docopt; +use std::io::{self, Write}; +use std::process; + + +mod cat; + +const USAGE: &'static str = " +Cretonne code generator utility + +Usage: + cton-util cat ... + cton-util --help | --version + +Options: + -h, --help print this help message + --version print the Cretonne version + +"; + +#[derive(RustcDecodable, Debug)] +struct Args { + cmd_cat: bool, + arg_file: Vec, +} + +/// A command either succeeds or fails with an error message. +pub type CommandResult = Result<(), String>; + +/// Parse the command line arguments and run the requested command. +fn cton_util() -> CommandResult { + // Parse comand line arguments. + let args: Args = Docopt::new(USAGE) + .and_then(|d| { + d.help(true) + .version(Some(format!("Cretonne {}", VERSION))) + .decode() + }) + .unwrap_or_else(|e| e.exit()); + + // Find the sub-command to execute. + if args.cmd_cat { + cat::run(args.arg_file) + } else { + // Debugging / shouldn't happen with proper command line handling above. + Err(format!("Unhandled args: {:?}", args)) + } +} + +fn main() { + if let Err(mut msg) = cton_util() { + if !msg.ends_with('\n') { + msg.push('\n'); + } + io::stderr().write(msg.as_bytes()).unwrap(); + process::exit(1); + } +} From 7519475f917ae21fcc117119a8d74378355f0201 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 1 Jul 2016 14:09:34 -0700 Subject: [PATCH 097/968] Parse and write IR in the 'cat' subcommand. The 'cton-util cat' command parses the given files and writes them out again to stdout. This has the effect of reformatting and stripping comments. Fix a writer bug that inverted the blank line before the first EBB. --- src/libcretonne/write.rs | 8 ++++---- src/tools/cat.rs | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 0d615c545b..38800bb48a 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -16,7 +16,7 @@ pub fn write_function(w: &mut Write, func: &Function) -> Result { try!(writeln!(w, " {{")); let mut any = try!(write_preamble(w, func)); for ebb in func.ebbs_numerically() { - if !any { + if any { try!(writeln!(w, "")); } try!(write_ebb(w, func, ebb)); @@ -209,14 +209,14 @@ mod tests { let ebb = f.make_ebb(); assert_eq!(function_to_string(&f), - "function foo() {\n ss0 = stack_slot 4\nebb0:\n}\n"); + "function foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); f.append_ebb_arg(ebb, types::I8); assert_eq!(function_to_string(&f), - "function foo() {\n ss0 = stack_slot 4\nebb0(vx0: i8):\n}\n"); + "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8):\n}\n"); f.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(function_to_string(&f), - "function foo() {\n ss0 = stack_slot 4\nebb0(vx0: i8, vx1: f32x4):\n}\n"); + "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8, vx1: f32x4):\n}\n"); } } diff --git a/src/tools/cat.rs b/src/tools/cat.rs index c8f30d0a99..6d740da701 100644 --- a/src/tools/cat.rs +++ b/src/tools/cat.rs @@ -3,9 +3,13 @@ //! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of //! normalizing formatting and removing comments. -use CommandResult; use std::fs::File; -use std::io::Read; +use std::io::{self, Read}; + +use CommandResult; + +use cton_reader::parser::Parser; +use cretonne::write::write_function; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { @@ -22,7 +26,16 @@ fn cat_one(filename: String) -> CommandResult { let mut buffer = String::new(); try!(file.read_to_string(&mut buffer) .map_err(|e| format!("Couldn't read {}: {}", filename, e))); + let items = try!(Parser::parse(&buffer).map_err(|e| format!("{}: {}", filename, e))); + + for (idx, func) in items.into_iter().enumerate() { + if idx != 0 { + println!(""); + } + let stdout = io::stdout(); + let mut handle = stdout.lock(); + try!(write_function(&mut handle, &func).map_err(|e| format!("{}: {}", filename, e))); + } - print!("{}", buffer); Ok(()) } From cb4e9fbae02a933bd63ea3178c9a3461eec8f42c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 1 Jul 2016 14:23:54 -0700 Subject: [PATCH 098/968] Give a better error message for unknown opcodes. Include the name of the unrecognized opcode along with the line number. --- src/libreader/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 7d25fc096b..1fe3368241 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -539,7 +539,7 @@ impl<'a> Parser<'a> { let opcode = if let Some(Token::Identifier(text)) = self.token() { match text.parse() { Ok(opc) => opc, - Err(msg) => return err!(self.loc, msg), + Err(msg) => return err!(self.loc, "{}: '{}'", msg, text), } } else { return err!(self.loc, "expected instruction opcode"); From a981fc5605892a119b8f0e3d8cd56a4d851679be Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 1 Jul 2016 14:26:24 -0700 Subject: [PATCH 099/968] rustfmt v0.5.0 --- src/libcretonne/repr.rs | 4 ++-- src/libcretonne/types.rs | 2 +- src/libreader/parser.rs | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 9c44f524ca..a42b9de20f 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -148,8 +148,8 @@ impl Function { // Update the second_result pointer in `inst`. if head != NO_VALUE { *self.inst_mut(inst) - .second_result_mut() - .expect("instruction format doesn't allow multiple results") = head; + .second_result_mut() + .expect("instruction format doesn't allow multiple results") = head; } *self.inst_mut(inst).first_type_mut() = first_type; diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index bc4cecca6b..1c44736111 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -66,7 +66,7 @@ pub const B64: Type = Type(11); impl Type { /// Get the lane type of this SIMD vector type. - /// + /// /// A scalar type is the same as a SIMD vector type with one lane, so it returns itself. pub fn lane_type(self) -> Type { Type(self.0 & 0x0f) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 1fe3368241..94e94cbdcd 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -608,7 +608,7 @@ impl<'a> Parser<'a> { // explicitly. It is unfortunate that the correctness of IL depends on the // layout of the blocks. let ctrl_src_value = inst_data.typevar_operand() - .expect("Constraints <-> Format inconsistency"); + .expect("Constraints <-> Format inconsistency"); ctx.function.value_type(match ctx.values.get(&ctrl_src_value) { Some(&v) => v, None => { @@ -791,8 +791,8 @@ mod tests { assert_eq!(sig.return_types.len(), 0); let sig2 = Parser::new("(i8 inreg uext, f32, f64) -> i32 sext, f64") - .parse_signature() - .unwrap(); + .parse_signature() + .unwrap(); assert_eq!(sig2.to_string(), "(i8 uext inreg, f32, f64) -> i32 sext, f64"); @@ -811,8 +811,8 @@ mod tests { ss3 = stack_slot 13 ss1 = stack_slot 1 }") - .parse_function() - .unwrap(); + .parse_function() + .unwrap(); assert_eq!(func.name, "foo"); let mut iter = func.stack_slot_iter(); let ss0 = iter.next().unwrap(); @@ -840,8 +840,8 @@ mod tests { ebb0: ebb4(vx3: i32): }") - .parse_function() - .unwrap(); + .parse_function() + .unwrap(); assert_eq!(func.name, "ebbs"); let mut ebbs = func.ebbs_numerically(); From 9b5760d54425c9f1f4723ed3100e1175930178d7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 1 Jul 2016 14:32:04 -0700 Subject: [PATCH 100/968] Add a script for reformatting all sources. This is mostly useful when updating to a new version of rustfmt with different behavior. --- src/format-all.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 src/format-all.sh diff --git a/src/format-all.sh b/src/format-all.sh new file mode 100755 index 0000000000..3569c460be --- /dev/null +++ b/src/format-all.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Format all sources using rustfmt. + +# Exit immediately on errors. +set -e + +cd $(dirname "$0") +src=$(pwd) + +for crate in $(find "$src" -name Cargo.toml); do + cd $(dirname "$crate") + cargo fmt +done From a985bc18bcd7dbfc242082782124b925cdaf7df0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Jul 2016 12:33:19 -0700 Subject: [PATCH 101/968] Don't return any values from inst_results() for VOID instructions. Instructions that don't produce any result values are marked with first_type() = VOID. The inst_results() iterator should not return any values for such instructions. --- src/libcretonne/repr.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index a42b9de20f..ff8d5d4dee 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,6 +1,6 @@ //! Representation of Cretonne IL functions. -use types::{Type, FunctionName, Signature}; +use types::{Type, FunctionName, Signature, VOID}; use entities::{Ebb, NO_EBB, Inst, NO_INST, Value, NO_VALUE, ExpandedValue, StackSlot}; use instructions::*; use std::fmt::{self, Display, Formatter}; @@ -167,7 +167,11 @@ impl Function { pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> { Values { func: self, - cur: Value::new_direct(inst), + cur: if self[inst].first_type() == VOID { + NO_VALUE + } else { + Value::new_direct(inst) + }, } } @@ -538,6 +542,26 @@ mod tests { let ins = &func[inst]; assert_eq!(ins.opcode(), Opcode::Iconst); assert_eq!(ins.first_type(), types::I32); + + // Result iterator. + let mut res = func.inst_results(inst); + assert!(res.next().is_some()); + assert!(res.next().is_none()); + } + + #[test] + fn no_results() { + let mut func = Function::new(); + + let idata = InstructionData::Nullary { + opcode: Opcode::Trap, + ty: types::VOID, + }; + let inst = func.make_inst(idata); + + // Result iterator should be empty. + let mut res = func.inst_results(inst); + assert_eq!(res.next(), None); } #[test] From 74038d153c12468ffc33b016c07c03647e194435 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Jul 2016 12:49:34 -0700 Subject: [PATCH 102/968] Ignore comments in .cton files. The lexer still recognizes comments and generates tokens for them. They may be useful for test annotations at some point. --- src/libreader/parser.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 94e94cbdcd..ec584cdcaa 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -135,17 +135,23 @@ impl<'a> Parser<'a> { // Get the current lookahead token, after making sure there is one. fn token(&mut self) -> Option> { - if self.lookahead == None { + while self.lookahead == None { match self.lex.next() { Some(Ok(lexer::LocatedToken { token, location })) => { - self.lookahead = Some(token); + match token { + Token::Comment(_) => { + // Ignore comments. + } + _ => self.lookahead = Some(token), + } self.loc = location; } Some(Err(lexer::LocatedError { error, location })) => { self.lex_error = Some(error); self.loc = location; + break; } - None => {} + None => break, } } return self.lookahead; From 954fd015e0aacc5b30bf02db7ecf929e6f4fc1c6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Jul 2016 11:40:42 -0700 Subject: [PATCH 103/968] Add very basic test framework for parser tests. Start with a shell script that runs .cton files through 'cton-util cat' and compares the output to a reference. This can get fancy later. --- tests/parser/README.rst | 9 +++++++++ tests/parser/run.sh | 40 ++++++++++++++++++++++++++++++++++++++ tests/parser/tiny.cton | 5 +++++ tests/parser/tiny.cton.ref | 4 ++++ 4 files changed, 58 insertions(+) create mode 100644 tests/parser/README.rst create mode 100755 tests/parser/run.sh create mode 100644 tests/parser/tiny.cton create mode 100644 tests/parser/tiny.cton.ref diff --git a/tests/parser/README.rst b/tests/parser/README.rst new file mode 100644 index 0000000000..9ac4658d13 --- /dev/null +++ b/tests/parser/README.rst @@ -0,0 +1,9 @@ +Parser tests +============ + +This directory contains test cases for the Cretonne IL parser. + +Each test case consists of a `foo.cton` input file and a `foo.ref` reference +output file. Each input file is run through the `cton-util cat` command, and the +output is compared against the reference file. If the two are identical, the +test passes. diff --git a/tests/parser/run.sh b/tests/parser/run.sh new file mode 100755 index 0000000000..e5795df1da --- /dev/null +++ b/tests/parser/run.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Go to tests directory. +cd $(dirname "$0")/.. + +# The path to cton-util should be in $CTONUTIL. +if [ -z "$CTONUTIL" ]; then + CTONUTIL=../src/tools/target/debug/cton-util +fi + +if [ ! -x "$CTONUTIL" ]; then + echo "Can't fund executable cton-util: $CTONUTIL" 1>&2 + exit 1 +fi + +declare -a fails + +for testcase in $(find parser -name '*.cton'); do + ref="${testcase}.ref" + if [ ! -r "$ref" ]; then + fails=(${fails[@]} "$testcase") + echo MISSING: $ref + elif diff -u "$ref" <("$CTONUTIL" cat "$testcase"); then + echo OK $testcase + else + fails=(${fails[@]} "$testcase") + echo FAIL $testcase + fi +done + +if [ ${#fails[@]} -ne 0 ]; then + echo + echo Failures: + for f in "${fails[@]}"; do + echo " $f" + done + exit 1 +else + echo "All passed" +fi diff --git a/tests/parser/tiny.cton b/tests/parser/tiny.cton new file mode 100644 index 0000000000..acc8ea7384 --- /dev/null +++ b/tests/parser/tiny.cton @@ -0,0 +1,5 @@ +; The smallest possible function. +function minimal() { +ebb0: + trap +} diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref new file mode 100644 index 0000000000..474c30527e --- /dev/null +++ b/tests/parser/tiny.cton.ref @@ -0,0 +1,4 @@ +function minimal() { +ebb0: + trap +} From a82554192aabbcdad310b2e17030fa9503bd603c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Jul 2016 13:45:15 -0700 Subject: [PATCH 104/968] Print a type suffix on some polymorphic instructions. --- src/libcretonne/write.rs | 78 +++++++++++++++++++++++++++----------- tests/parser/tiny.cton | 9 +++++ tests/parser/tiny.cton.ref | 7 ++++ 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 38800bb48a..e0d19aa5a8 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -7,6 +7,7 @@ use std::io::{self, Write}; use repr::Function; use entities::{Inst, Ebb, Value}; +use types::Type; pub type Result = io::Result<()>; @@ -126,6 +127,35 @@ pub fn write_ebb(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // // ====--------------------------------------------------------------------------------------====// +// Should `inst` be printed with a type suffix? +// +// Polymorphic instructions may need a suffix indicating the value of the controlling type variable +// if it can't be trivially inferred. +// +fn type_suffix(func: &Function, inst: Inst) -> Option { + let constraints = func[inst].opcode().constraints(); + + if !constraints.is_polymorphic() { + return None; + } + + // If the controlling type variable can be inferred from the type of the designated value input + // operand, we don't need the type suffix. + // TODO: Should we include the suffix when the input value is defined in another block? The + // parser needs to know the type of the value, so it must be defined in a block that lexically + // comes before this one. + if constraints.use_typevar_operand() { + return None; + } + + // This polymorphic instruction doesn't support basic type inference. + // The controlling type variable is required to be the type of the first result. + let rtype = func.value_type(func.first_result(inst)); + assert!(!rtype.is_void(), + "Polymorphic instruction must produce a result"); + Some(rtype) +} + pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { try!(write!(w, " ")); @@ -143,30 +173,34 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { try!(write!(w, " = ")); } - // Then the opcode and operands, depending on format. + // Then the opcode, possibly with a '.type' suffix. + let opcode = func[inst].opcode(); + + match type_suffix(func, inst) { + Some(suf) => try!(write!(w, "{}.{}", opcode, suf)), + None => try!(write!(w, "{}", opcode)), + } + + // Then the operands, depending on format. use instructions::InstructionData::*; match func[inst] { - Nullary { opcode, .. } => writeln!(w, "{}", opcode), - Unary { opcode, arg, .. } => writeln!(w, "{} {}", opcode, arg), - UnaryImm { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), - UnaryIeee32 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), - UnaryIeee64 { opcode, imm, .. } => writeln!(w, "{} {}", opcode, imm), - UnaryImmVector { opcode, .. } => writeln!(w, "{} [...]", opcode), - Binary { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), - BinaryImm { opcode, arg, imm, .. } => writeln!(w, "{} {}, {}", opcode, arg, imm), - BinaryImmRev { opcode, imm, arg, .. } => writeln!(w, "{} {}, {}", opcode, imm, arg), - BinaryOverflow { opcode, args, .. } => writeln!(w, "{} {}, {}", opcode, args[0], args[1]), - Select { opcode, args, .. } => { - writeln!(w, "{} {}, {}, {}", opcode, args[0], args[1], args[2]) - } - InsertLane { opcode, lane, args, .. } => { - writeln!(w, "{} {}, {}, {}", opcode, args[0], lane, args[1]) - } - ExtractLane { opcode, lane, arg, .. } => writeln!(w, "{} {}, {}", opcode, arg, lane), - Jump { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), - Branch { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), - BranchTable { opcode, arg, table, .. } => writeln!(w, "{} {}, {}", opcode, arg, table), - Call { opcode, ref data, .. } => writeln!(w, "{} {}", opcode, data), + Nullary { .. } => writeln!(w, ""), + Unary { arg, .. } => writeln!(w, " {}", arg), + UnaryImm { imm, .. } => writeln!(w, " {}", imm), + UnaryIeee32 { imm, .. } => writeln!(w, " {}", imm), + UnaryIeee64 { imm, .. } => writeln!(w, " {}", imm), + UnaryImmVector { .. } => writeln!(w, " [...]"), + Binary { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), + BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), + BinaryImmRev { imm, arg, .. } => writeln!(w, " {}, {}", imm, arg), + BinaryOverflow { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), + Select { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), + InsertLane { lane, args, .. } => writeln!(w, " {}, {}, {}", args[0], lane, args[1]), + ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), + Jump { ref data, .. } => writeln!(w, " {}", data), + Branch { ref data, .. } => writeln!(w, " {}", data), + BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), + Call { ref data, .. } => writeln!(w, " {}", data), } } diff --git a/tests/parser/tiny.cton b/tests/parser/tiny.cton index acc8ea7384..9619147f6f 100644 --- a/tests/parser/tiny.cton +++ b/tests/parser/tiny.cton @@ -3,3 +3,12 @@ function minimal() { ebb0: trap } + +; Create and use values. +; Polymorphic instructions with type suffix. +function ivalues() { +ebb0: + v0 = iconst.i32 2 + v1 = iconst.i8 6 + v2 = ishl v0, v1 +} diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref index 474c30527e..c8c28dd419 100644 --- a/tests/parser/tiny.cton.ref +++ b/tests/parser/tiny.cton.ref @@ -2,3 +2,10 @@ function minimal() { ebb0: trap } + +function ivalues() { +ebb0: + v0 = iconst.i32 2 + v1 = iconst.i8 6 + v2 = ishl v0, v1 +} From cdc2638f9687169cca0858f1612bf4674bb0e43b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Jul 2016 14:44:21 -0700 Subject: [PATCH 105/968] Parse branch and jump instructions. These instruction formats take EBB references with lists of argument values. For EBBs with no arguments, the argument value list may be omitted. --- src/libcretonne/instructions.rs | 30 +++++++++++---- src/libreader/parser.rs | 66 +++++++++++++++++++++++++++++++-- tests/parser/branch.cton | 45 ++++++++++++++++++++++ tests/parser/branch.cton.ref | 39 +++++++++++++++++++ 4 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 tests/parser/branch.cton create mode 100644 tests/parser/branch.cton.ref diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 9ac3ac7220..d8880c28ed 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -207,6 +207,14 @@ impl VariableArgs { pub fn new() -> VariableArgs { VariableArgs(Vec::new()) } + + pub fn push(&mut self, v: Value) { + self.0.push(v) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } impl Display for VariableArgs { @@ -233,13 +241,17 @@ impl Default for VariableArgs { /// in the allowed InstructionData size. #[derive(Debug)] pub struct JumpData { - destination: Ebb, - arguments: VariableArgs, + pub destination: Ebb, + pub arguments: VariableArgs, } impl Display for JumpData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}{}", self.destination, self.arguments) + if self.arguments.is_empty() { + write!(f, "{}", self.destination) + } else { + write!(f, "{}{}", self.destination, self.arguments) + } } } @@ -247,14 +259,18 @@ impl Display for JumpData { /// in the allowed InstructionData size. #[derive(Debug)] pub struct BranchData { - arg: Value, - destination: Ebb, - arguments: VariableArgs, + pub arg: Value, + pub destination: Ebb, + pub arguments: VariableArgs, } impl Display for BranchData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}, {}{}", self.arg, self.destination, self.arguments) + try!(write!(f, "{}, {}", self.arg, self.destination)); + if !self.arguments.is_empty() { + try!(write!(f, "{}", self.arguments)); + } + Ok(()) } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index ec584cdcaa..eb57504a6f 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -13,7 +13,8 @@ use lexer::{self, Lexer, Token}; use cretonne::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; use cretonne::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::entities::*; -use cretonne::instructions::{Opcode, InstructionFormat, InstructionData}; +use cretonne::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, JumpData, + BranchData}; use cretonne::repr::{Function, StackSlotData}; pub use lexer::Location; @@ -672,6 +673,39 @@ impl<'a> Parser<'a> { Ok(()) } + // Parse comma-separated value list into a VariableArgs struct. + // + // value_list ::= [ value { "," value } ] + // + fn parse_value_list(&mut self) -> Result { + let mut args = VariableArgs::new(); + + if let Some(Token::Value(v)) = self.token() { + args.push(v); + self.consume(); + } else { + return Ok(args); + } + + while self.optional(Token::Comma) { + args.push(try!(self.match_value("expected value in argument list"))); + } + + Ok(args) + } + + // Parse an optional value list enclosed in parantheses. + fn parse_opt_value_list(&mut self) -> Result { + if !self.optional(Token::LPar) { + return Ok(VariableArgs::new()); + } + + let args = try!(self.parse_value_list()); + + try!(self.match_token(Token::RPar, "expected ')' after arguments")); + + Ok(args) + } // Parse the operands following the instruction opcode. // This depends on the format of the opcode. @@ -757,11 +791,37 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } + InstructionFormat::Jump => { + // Parse the destination EBB number. Don't translate source to local numbers yet. + let ebb_num = try!(self.match_ebb("expected jump destination EBB")); + let args = try!(self.parse_opt_value_list()); + InstructionData::Jump { + opcode: opcode, + ty: VOID, + data: Box::new(JumpData { + destination: ebb_num, + arguments: args, + }), + } + } + InstructionFormat::Branch => { + let ctrl_arg = try!(self.match_value("expected SSA value control operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let ebb_num = try!(self.match_ebb("expected branch destination EBB")); + let args = try!(self.parse_opt_value_list()); + InstructionData::Branch { + opcode: opcode, + ty: VOID, + data: Box::new(BranchData { + arg: ctrl_arg, + destination: ebb_num, + arguments: args, + }), + } + } InstructionFormat::Select | InstructionFormat::InsertLane | InstructionFormat::ExtractLane | - InstructionFormat::Jump | - InstructionFormat::Branch | InstructionFormat::BranchTable | InstructionFormat::Call => { unimplemented!(); diff --git a/tests/parser/branch.cton b/tests/parser/branch.cton new file mode 100644 index 0000000000..bf49ba8a9f --- /dev/null +++ b/tests/parser/branch.cton @@ -0,0 +1,45 @@ +; Parsing branches and jumps. + +; Jumps with no arguments. The '()' empty argument list is optional. +function minimal() { +ebb0: + jump ebb1 + +ebb1: + jump ebb0() +} + +; Jumps with 1 arg. +function onearg(i32) { +ebb0(vx0: i32): + jump ebb1(vx0) + +ebb1(vx1: i32): + jump ebb0(vx1) +} + +; Jumps with 2 args. +function twoargs(i32, f32) { +ebb0(vx0: i32, vx1: f32): + jump ebb1(vx0, vx1) + +ebb1(vx2: i32, vx3: f32): + jump ebb0(vx2, vx3) +} + +; Branches with no arguments. The '()' empty argument list is optional. +function minimal(i32) { +ebb0(vx0: i32): + brz vx0, ebb1 + +ebb1: + brnz vx0, ebb1() +} + +function twoargs(i32, f32) { +ebb0(vx0: i32, vx1: f32): + brz vx0, ebb1(vx0, vx1) + +ebb1(vx2: i32, vx3: f32): + brnz vx0, ebb0(vx2, vx3) +} diff --git a/tests/parser/branch.cton.ref b/tests/parser/branch.cton.ref new file mode 100644 index 0000000000..02444326b1 --- /dev/null +++ b/tests/parser/branch.cton.ref @@ -0,0 +1,39 @@ +function minimal() { +ebb0: + jump ebb1 + +ebb1: + jump ebb0 +} + +function onearg(i32) { +ebb0(vx0: i32): + jump ebb1(vx0) + +ebb1(vx1: i32): + jump ebb0(vx1) +} + +function twoargs(i32, f32) { +ebb0(vx0: i32, vx1: f32): + jump ebb1(vx0, vx1) + +ebb1(vx2: i32, vx3: f32): + jump ebb0(vx2, vx3) +} + +function minimal(i32) { +ebb0(vx0: i32): + brz vx0, ebb1 + +ebb1: + brnz vx0, ebb1 +} + +function twoargs(i32, f32) { +ebb0(vx0: i32, vx1: f32): + brz vx0, ebb1(vx0, vx1) + +ebb1(vx2: i32, vx3: f32): + brnz vx0, ebb0(vx2, vx3) +} From e5feb864c7e7924d9046b2e0420753086c96e7ec Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 5 Jul 2016 16:51:26 -0700 Subject: [PATCH 106/968] Parse select instructions. --- src/libreader/parser.rs | 13 ++++++++++++- tests/parser/tiny.cton | 6 ++++++ tests/parser/tiny.cton.ref | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index eb57504a6f..82e17aea5f 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -791,6 +791,18 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } + InstructionFormat::Select => { + let ctrl_arg = try!(self.match_value("expected SSA value control operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let true_arg = try!(self.match_value("expected SSA value true operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let false_arg = try!(self.match_value("expected SSA value false operand")); + InstructionData::Select { + opcode: opcode, + ty: VOID, + args: [ctrl_arg, true_arg, false_arg], + } + } InstructionFormat::Jump => { // Parse the destination EBB number. Don't translate source to local numbers yet. let ebb_num = try!(self.match_ebb("expected jump destination EBB")); @@ -819,7 +831,6 @@ impl<'a> Parser<'a> { }), } } - InstructionFormat::Select | InstructionFormat::InsertLane | InstructionFormat::ExtractLane | InstructionFormat::BranchTable | diff --git a/tests/parser/tiny.cton b/tests/parser/tiny.cton index 9619147f6f..5e38d5efbb 100644 --- a/tests/parser/tiny.cton +++ b/tests/parser/tiny.cton @@ -12,3 +12,9 @@ ebb0: v1 = iconst.i8 6 v2 = ishl v0, v1 } + +; Polymorphic istruction controlled by second operand. +function select() { +ebb0(vx0: i32, vx1:i32, vx2: b1): + v0 = select vx2, vx0, vx1 +} diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref index c8c28dd419..29b604f29d 100644 --- a/tests/parser/tiny.cton.ref +++ b/tests/parser/tiny.cton.ref @@ -9,3 +9,8 @@ ebb0: v1 = iconst.i8 6 v2 = ishl v0, v1 } + +function select() { +ebb0(vx0: i32, vx1: i32, vx2: b1): + v0 = select vx2, vx0, vx1 +} From a6c136297579603eb35880b6052659e2d2924c8a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 6 Jul 2016 13:36:15 -0700 Subject: [PATCH 107/968] Parse insertlane and extractlane instruction formats. These instruction formats take immediate lane index operands. We store these as u8 fields and require them to be in decimal format in the source. No hexadecimal lane indexes are supported. --- src/libreader/parser.rs | 39 ++++++++++++++++++++++++++++++++++++-- tests/parser/tiny.cton | 10 +++++++++- tests/parser/tiny.cton.ref | 7 +++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 82e17aea5f..ca8f56232b 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -247,6 +247,19 @@ impl<'a> Parser<'a> { } } + // Match and consume a u8 immediate. + // This is used for lane numbers in SIMD vectors. + fn match_u8(&mut self, err_msg: &str) -> Result { + if let Some(Token::Integer(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like an integer. + // Parse it as a u8 to check for overflow and other issues. + text.parse().map_err(|_| self.error("expected u8 decimal immediate")) + } else { + err!(self.loc, err_msg) + } + } + // Match and consume an Ieee32 immediate. fn match_ieee32(&mut self, err_msg: &str) -> Result { if let Some(Token::Float(text)) = self.token() { @@ -831,8 +844,30 @@ impl<'a> Parser<'a> { }), } } - InstructionFormat::InsertLane | - InstructionFormat::ExtractLane | + InstructionFormat::InsertLane => { + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let lane = try!(self.match_u8("expected lane number")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value last operand")); + InstructionData::InsertLane { + opcode: opcode, + ty: VOID, + lane: lane, + args: [lhs, rhs], + } + } + InstructionFormat::ExtractLane => { + let arg = try!(self.match_value("expected SSA value last operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let lane = try!(self.match_u8("expected lane number")); + InstructionData::ExtractLane { + opcode: opcode, + ty: VOID, + lane: lane, + arg: arg, + } + } InstructionFormat::BranchTable | InstructionFormat::Call => { unimplemented!(); diff --git a/tests/parser/tiny.cton b/tests/parser/tiny.cton index 5e38d5efbb..6be261e273 100644 --- a/tests/parser/tiny.cton +++ b/tests/parser/tiny.cton @@ -15,6 +15,14 @@ ebb0: ; Polymorphic istruction controlled by second operand. function select() { -ebb0(vx0: i32, vx1:i32, vx2: b1): +ebb0(vx0: i32, vx1: i32, vx2: b1): v0 = select vx2, vx0, vx1 } + +; Lane indexes. +function lanes() { +ebb0: + v0 = iconst.i32x4 2 + v1 = extractlane v0, 3 + v2 = insertlane v0, 1, v1 +} diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref index 29b604f29d..1fc1745a34 100644 --- a/tests/parser/tiny.cton.ref +++ b/tests/parser/tiny.cton.ref @@ -14,3 +14,10 @@ function select() { ebb0(vx0: i32, vx1: i32, vx2: b1): v0 = select vx2, vx0, vx1 } + +function lanes() { +ebb0: + v0 = iconst.i32x4 2 + v1 = extractlane v0, 3 + v2 = insertlane v0, 1, v1 +} From 90bb2fd27d793b6dd221a7340f372d2d3c0d2642 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 6 Jul 2016 15:02:12 -0700 Subject: [PATCH 108/968] Add enums for condition codes. The icmp and fmp instructions use different kinds of condition codes because integers and floating point values behave differently. Add a CondCode trait implementing shared behavior. --- src/libcretonne/condcodes.rs | 325 +++++++++++++++++++++++++++++++++++ src/libcretonne/lib.rs | 1 + 2 files changed, 326 insertions(+) create mode 100644 src/libcretonne/condcodes.rs diff --git a/src/libcretonne/condcodes.rs b/src/libcretonne/condcodes.rs new file mode 100644 index 0000000000..692ecda96f --- /dev/null +++ b/src/libcretonne/condcodes.rs @@ -0,0 +1,325 @@ +//! Condition codes for the Cretonne code generator. +//! +//! A condition code here is an enumerated type that determined how to compare two numbers. There +//! are different rules for comparing integers and floating point numbers, so they use different +//! condition codes. + +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; + +/// Common traits of condition codes. +pub trait CondCode: Copy { + /// Get the inverse condition code of `self`. + /// + /// The inverse condition code produces the opposite result for all comparisons. + /// That is, `cmp CC, x, y` is true if and only if `cmp CC.inverse(), x, y` is false. + fn inverse(self) -> Self; + + /// Get the reversed condition code for `self`. + /// + /// The reversed condition code produces the same result as swapping `x` and `y` in the + /// comparison. That is, `cmp CC, x, y` is the same as `cmp CC.reverse(), y, x`. + fn reverse(self) -> Self; +} + +/// Condition code for comparing integers. +/// +/// This condition code is used by the `icmp` instruction to compare integer values. There are +/// separate codes for comparing the integers as signed or unsigned numbers where it makes a +/// difference. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntCC { + Equal, + NotEqual, + SignedLessThan, + SignedGreaterThanOrEqual, + SignedGreaterThan, + SignedLessThanOrEqual, + UnsignedLessThan, + UnsignedGreaterThanOrEqual, + UnsignedGreaterThan, + UnsignedLessThanOrEqual, +} + +impl CondCode for IntCC { + fn inverse(self) -> Self { + use self::IntCC::*; + match self { + Equal => NotEqual, + NotEqual => Equal, + SignedLessThan => SignedGreaterThanOrEqual, + SignedGreaterThanOrEqual => SignedLessThan, + SignedGreaterThan => SignedLessThanOrEqual, + SignedLessThanOrEqual => SignedGreaterThan, + UnsignedLessThan => UnsignedGreaterThanOrEqual, + UnsignedGreaterThanOrEqual => UnsignedLessThan, + UnsignedGreaterThan => UnsignedLessThanOrEqual, + UnsignedLessThanOrEqual => UnsignedGreaterThan, + } + } + + fn reverse(self) -> Self { + use self::IntCC::*; + match self { + Equal => Equal, + NotEqual => NotEqual, + SignedGreaterThan => SignedLessThan, + SignedGreaterThanOrEqual => SignedLessThanOrEqual, + SignedLessThan => SignedGreaterThan, + SignedLessThanOrEqual => SignedGreaterThanOrEqual, + UnsignedGreaterThan => UnsignedLessThan, + UnsignedGreaterThanOrEqual => UnsignedLessThanOrEqual, + UnsignedLessThan => UnsignedGreaterThan, + UnsignedLessThanOrEqual => UnsignedGreaterThanOrEqual, + } + } +} + +impl Display for IntCC { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use self::IntCC::*; + f.write_str(match self { + &Equal => "eq", + &NotEqual => "ne", + &SignedGreaterThan => "sgt", + &SignedGreaterThanOrEqual => "sge", + &SignedLessThan => "slt", + &SignedLessThanOrEqual => "sle", + &UnsignedGreaterThan => "ugt", + &UnsignedGreaterThanOrEqual => "uge", + &UnsignedLessThan => "ult", + &UnsignedLessThanOrEqual => "ule", + }) + } +} + +impl FromStr for IntCC { + type Err = (); + + fn from_str(s: &str) -> Result { + use self::IntCC::*; + match s { + "eq" => Ok(Equal), + "ne" => Ok(NotEqual), + "sge" => Ok(SignedGreaterThanOrEqual), + "sgt" => Ok(SignedGreaterThan), + "sle" => Ok(SignedLessThanOrEqual), + "slt" => Ok(SignedLessThan), + "uge" => Ok(UnsignedGreaterThanOrEqual), + "ugt" => Ok(UnsignedGreaterThan), + "ule" => Ok(UnsignedLessThanOrEqual), + "ult" => Ok(UnsignedLessThan), + _ => Err(()), + } + } +} + +/// Condition code for comparing floating point numbers. +/// +/// This condition code is used by the `fcmp` instruction to compare floating point values. Two +/// IEEE floating point values relate in exactly one of four ways: +/// +/// 1. `UN` - unordered when either value is NaN. +/// 2. `EQ` - equal numerical value. +/// 3. `LT` - `x` is less than `y`. +/// 4. `GT` - `x` is greater than `y`. +/// +/// Note that `0.0` and `-0.0` relate as `EQ` because they both represent the number 0. +/// +/// The condition codes described here are used to produce a single boolean value from the +/// comparison. The 14 condition codes here cover every possible combination of the relation above +/// except the impossible `!UN & !EQ & !LT & !GT` and the always true `UN | EQ | LT | GT`. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatCC { + Ordered, // EQ | LT | GT + Unordered, // UN + + Equal, // EQ + // The C '!=' operator is the inverse of '==': NotEqual. + NotEqual, // UN | LT | GT + OrderedNotEqual, // LT | GT + UnorderedOrEqual, // UN | EQ + + LessThan, // LT + LessThanOrEqual, // LT | EQ + GreaterThan, // GT + GreaterThanOrEqual, // GT | EQ + + UnorderedOrLessThan, // UN | LT + UnorderedOrLessThanOrEqual, // UN | LT | EQ + UnorderedOrGreaterThan, // UN | GT + UnorderedOrGreaterThanOrEqual, // UN | GT | EQ +} + +impl CondCode for FloatCC { + fn inverse(self) -> Self { + use self::FloatCC::*; + match self { + Ordered => Unordered, + Unordered => Ordered, + Equal => NotEqual, + NotEqual => Equal, + OrderedNotEqual => UnorderedOrEqual, + UnorderedOrEqual => OrderedNotEqual, + LessThan => UnorderedOrGreaterThanOrEqual, + LessThanOrEqual => UnorderedOrGreaterThan, + GreaterThan => UnorderedOrLessThanOrEqual, + GreaterThanOrEqual => UnorderedOrLessThan, + UnorderedOrLessThan => GreaterThanOrEqual, + UnorderedOrLessThanOrEqual => GreaterThan, + UnorderedOrGreaterThan => LessThanOrEqual, + UnorderedOrGreaterThanOrEqual => LessThan, + } + } + fn reverse(self) -> Self { + use self::FloatCC::*; + match self { + Ordered => Ordered, + Unordered => Unordered, + Equal => Equal, + NotEqual => NotEqual, + OrderedNotEqual => OrderedNotEqual, + UnorderedOrEqual => UnorderedOrEqual, + LessThan => GreaterThan, + LessThanOrEqual => GreaterThanOrEqual, + GreaterThan => LessThan, + GreaterThanOrEqual => LessThanOrEqual, + UnorderedOrLessThan => UnorderedOrGreaterThan, + UnorderedOrLessThanOrEqual => UnorderedOrGreaterThanOrEqual, + UnorderedOrGreaterThan => UnorderedOrLessThan, + UnorderedOrGreaterThanOrEqual => UnorderedOrLessThanOrEqual, + } + } +} + +impl Display for FloatCC { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use self::FloatCC::*; + f.write_str(match self { + &Ordered => "ord", + &Unordered => "uno", + &Equal => "eq", + &NotEqual => "ne", + &OrderedNotEqual => "one", + &UnorderedOrEqual => "ueq", + &LessThan => "lt", + &LessThanOrEqual => "le", + &GreaterThan => "gt", + &GreaterThanOrEqual => "ge", + &UnorderedOrLessThan => "ult", + &UnorderedOrLessThanOrEqual => "ule", + &UnorderedOrGreaterThan => "ugt", + &UnorderedOrGreaterThanOrEqual => "uge", + }) + } +} + +impl FromStr for FloatCC { + type Err = (); + + fn from_str(s: &str) -> Result { + use self::FloatCC::*; + match s { + "ord" => Ok(Ordered), + "uno" => Ok(Unordered), + "eq" => Ok(Equal), + "ne" => Ok(NotEqual), + "one" => Ok(OrderedNotEqual), + "ueq" => Ok(UnorderedOrEqual), + "lt" => Ok(LessThan), + "le" => Ok(LessThanOrEqual), + "gt" => Ok(GreaterThan), + "ge" => Ok(GreaterThanOrEqual), + "ult" => Ok(UnorderedOrLessThan), + "ule" => Ok(UnorderedOrLessThanOrEqual), + "ugt" => Ok(UnorderedOrGreaterThan), + "uge" => Ok(UnorderedOrGreaterThanOrEqual), + _ => Err(()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + static INT_ALL: [IntCC; 10] = [IntCC::Equal, + IntCC::NotEqual, + IntCC::SignedLessThan, + IntCC::SignedGreaterThanOrEqual, + IntCC::SignedGreaterThan, + IntCC::SignedLessThanOrEqual, + IntCC::UnsignedLessThan, + IntCC::UnsignedGreaterThanOrEqual, + IntCC::UnsignedGreaterThan, + IntCC::UnsignedLessThanOrEqual]; + + #[test] + fn int_inverse() { + for r in &INT_ALL { + let cc = *r; + let inv = cc.inverse(); + assert!(cc != inv); + assert_eq!(inv.inverse(), cc); + } + } + + #[test] + fn int_reverse() { + for r in &INT_ALL { + let cc = *r; + let rev = cc.reverse(); + assert_eq!(rev.reverse(), cc); + } + } + + #[test] + fn int_display() { + for r in &INT_ALL { + let cc = *r; + assert_eq!(cc.to_string().parse(), Ok(cc)); + } + } + + static FLOAT_ALL: [FloatCC; 14] = [FloatCC::Ordered, + FloatCC::Unordered, + FloatCC::Equal, + FloatCC::NotEqual, + FloatCC::OrderedNotEqual, + FloatCC::UnorderedOrEqual, + FloatCC::LessThan, + FloatCC::LessThanOrEqual, + FloatCC::GreaterThan, + FloatCC::GreaterThanOrEqual, + FloatCC::UnorderedOrLessThan, + FloatCC::UnorderedOrLessThanOrEqual, + FloatCC::UnorderedOrGreaterThan, + FloatCC::UnorderedOrGreaterThanOrEqual]; + + #[test] + fn float_inverse() { + for r in &FLOAT_ALL { + let cc = *r; + let inv = cc.inverse(); + assert!(cc != inv); + assert_eq!(inv.inverse(), cc); + } + } + + #[test] + fn float_reverse() { + for r in &FLOAT_ALL { + let cc = *r; + let rev = cc.reverse(); + assert_eq!(rev.reverse(), cc); + } + } + + #[test] + fn float_display() { + for r in &FLOAT_ALL { + let cc = *r; + assert_eq!(cc.to_string().parse(), Ok(cc)); + } + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 8b92e5dbed..26fc74f7ab 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -8,6 +8,7 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod types; +pub mod condcodes; pub mod immediates; pub mod entities; pub mod instructions; From 86688053a65b6455ffc1bce5743fccabe99ea231 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 11:20:56 -0700 Subject: [PATCH 109/968] Define icmp and fcmp comparison instructions. Add new intcc and floatcc operand types for the immediate condition codes on these instructions. Add new IntCompare and FloatCompare instruction formats. Add a generic match_enum() parser function that can match any identifier-like enumerated operand kind that implements FromStr. Define the icmp and fcmp instructions in case.py. Include documentation for the condition codes with these two instructions. --- docs/langref.rst | 57 +++-------------- meta/cretonne/base.py | 107 +++++++++++++++++++++++++++++++- meta/cretonne/formats.py | 5 +- meta/cretonne/immediates.py | 13 ++++ src/libcretonne/instructions.rs | 13 ++++ src/libcretonne/write.rs | 2 + src/libreader/parser.rs | 43 ++++++++++++- tests/parser/tiny.cton | 16 +++++ tests/parser/tiny.cton.ref | 14 +++++ 9 files changed, 218 insertions(+), 52 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 8bf890b080..7df404e02c 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -270,6 +270,14 @@ indicate the different kinds of immediate operands on an instruction. bits of the operand are interpreted as if the SIMD vector was loaded from memory containing the immediate. +.. type:: intcc + + An integer condition code. See the :inst:`icmp` instruction for details. + +.. type:: floatcc + + A floating point condition code. See the :inst:`fcmp` instruction for details. + The two IEEE floating point immediate types :type:`ieee32` and :type:`ieee64` are displayed as hexadecimal floating point literals in the textual IL format. Decimal floating point literals are not allowed because some computer systems @@ -676,29 +684,7 @@ Vector operations Integer operations ------------------ -.. inst:: a = icmp Cond, x, y - - Integer comparison. - - :arg Cond: Condition code determining how ``x`` and ``y`` are compared. - :arg Int x: First value to compare. - :arg Int y: Second value to compare. - :result Logic a: With the same number of lanes as ``x`` and ``y``. - - The condition code determines if the operands are interpreted as signed or - unsigned integers. - - ====== ======== ========= - Signed Unsigned Condition - ====== ======== ========= - eq eq Equal - ne ne Not equal - slt ult Less than - sge uge Greater than or equal - sgt ugt Greater than - sle ule Less than or equal - ====== ======== ========= - +.. autoinst:: icmp .. autoinst:: iadd .. autoinst:: iadd_imm .. autoinst:: isub @@ -784,30 +770,7 @@ Floating point operations These operations generally follow IEEE 754-2008 semantics. -.. inst:: a = fcmp Cond, x, y - - Floating point comparison. - - :arg Cond: Condition code determining how ``x`` and ``y`` are compared. - :arg x,y: Floating point scalar or vector values of the same type. - :rtype: :type:`b1` or :type:`b1xN` with the same number of lanes as - ``x`` and ``y``. - - An 'ordered' condition code yields ``false`` if either operand is Nan. - - An 'unordered' condition code yields ``true`` if either operand is Nan. - - ======= ========= ========= - Ordered Unordered Condition - ======= ========= ========= - ord uno None (ord = no NaNs, uno = some NaNs) - oeq ueq Equal - one une Not equal - olt ult Less than - oge uge Greater than or equal - ogt ugt Greater than - ole ule Less than or equal - ======= ========= ========= +.. autoinst:: fcmp .. inst:: fadd x,y diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index b41225ae00..e6d008585b 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -6,7 +6,7 @@ support. """ from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args from types import i8, f32, f64 -from immediates import imm64, uimm8, ieee32, ieee64, immvector +from immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc import entities instructions = InstructionGroup("base", "Shared base instruction set") @@ -217,6 +217,34 @@ extractlane = Instruction( # Integer arithmetic # +a = Operand('a', Int.as_bool()) +Cond = Operand('Cond', intcc) +x = Operand('x', Int) +y = Operand('y', Int) + +icmp = Instruction( + 'icmp', r""" + Integer comparison. + + The condition code determines if the operands are interpreted as signed + or unsigned integers. + + ====== ======== ========= + Signed Unsigned Condition + ====== ======== ========= + eq eq Equal + ne ne Not equal + slt ult Less than + sge uge Greater than or equal + sgt ugt Greater than + sle ule Less than or equal + ====== ======== ========= + + When this instruction compares integer vectors, it returns a boolean + vector of lane-wise comparisons. + """, + ins=(Cond, x, y), outs=a) + a = Operand('a', Int) x = Operand('x', Int) y = Operand('y', Int) @@ -515,4 +543,81 @@ popcnt = Instruction( """, ins=x, outs=a) +# +# Floating point. +# + +Float = TypeVar( + 'Float', 'A scalar or vector floating point type type', + floats=True, simd=True) + +Cond = Operand('Cond', floatcc) +x = Operand('x', Float) +y = Operand('y', Float) +a = Operand('a', Float.as_bool()) + +fcmp = Instruction( + 'fcmp', r""" + Floating point comparison. + + Two IEEE 754-2008 floating point numbers, `x` and `y`, relate to each + other in exactly one of four ways: + + == ========================================== + UN Unordered when one or both numbers is NaN. + EQ When :math:`x = y`. (And :math:`0.0 = -0.0`). + LT When :math:`x < y`. + GT When :math:`x > y`. + == ========================================== + + The 14 :type:`floatcc` condition codes each correspond to a subset of + the four relations, except for the empty set which would always be + false, and the full set which would always be true. + + The condition codes are divided into 7 'ordered' conditions which don't + include UN, and 7 unordered conditions which all include UN. + + +-------+------------+---------+------------+-------------------------+ + |Ordered |Unordered |Condition | + +=======+============+=========+============+=========================+ + |ord |EQ | LT | GT|uno |UN |NaNs absent / present. | + +-------+------------+---------+------------+-------------------------+ + |eq |EQ |ueq |UN | EQ |Equal | + +-------+------------+---------+------------+-------------------------+ + |one |LT | GT |ne |UN | LT | GT|Not equal | + +-------+------------+---------+------------+-------------------------+ + |lt |LT |ult |UN | LT |Less than | + +-------+------------+---------+------------+-------------------------+ + |le |LT | EQ |ule |UN | LT | EQ|Less than or equal | + +-------+------------+---------+------------+-------------------------+ + |gt |GT |ugt |UN | GT |Greater than | + +-------+------------+---------+------------+-------------------------+ + |ge |GT | EQ |uge |UN | GT | EQ|Greater than or equal | + +-------+------------+---------+------------+-------------------------+ + + The standard C comparison operators, `<, <=, >, >=`, are all ordered, + so they are false if either operand is NaN. The C equality operator, + `==`, is ordered, and since inequality is defined as the logical + inverse it is *unordered*. They map to the :type:`floatcc` condition + codes as follows: + + ==== ====== ============ + C `Cond` Subset + ==== ====== ============ + `==` eq EQ + `!=` ne UN | LT | GT + `<` lt LT + `<=` le LT | EQ + `>` gt GT + `>=` ge GT | EQ + ==== ====== ============ + + This subset of condition codes also corresponds to the WebAssembly + floating point comparisons of the same name. + + When this instruction compares floating point vectors, it returns a + boolean vector with the results of lane-wise comparisons. + """, + ins=(Cond, x, y), outs=a) + instructions.close() diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 6f09c4b5af..62c678f9b9 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -8,7 +8,7 @@ in this module. from . import InstructionFormat, value, variable_args -from immediates import imm64, uimm8, ieee32, ieee64, immvector +from immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from entities import ebb, function, jump_table Nullary = InstructionFormat() @@ -33,6 +33,9 @@ Select = InstructionFormat(value, value, value, typevar_operand=1) InsertLane = InstructionFormat(value, uimm8, value) ExtractLane = InstructionFormat(value, uimm8) +IntCompare = InstructionFormat(intcc, value, value) +FloatCompare = InstructionFormat(floatcc, value, value) + Jump = InstructionFormat(ebb, variable_args, boxed_storage=True) Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) BranchTable = InstructionFormat(value, jump_table) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index 299b340bce..f20a6142d3 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -29,3 +29,16 @@ ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') #: A large SIMD vector constant. immvector = ImmediateKind('immvector', 'An immediate SIMD vector.') + +#: A condition code for comparing integer values. +#: +#: This enumerated operand kind is used for the :cton:inst:`icmp` instruction +#: and corresponds to the `condcodes::IntCC` Rust type. +intcc = ImmediateKind('intcc', 'An integer comparison condition code.') + +#: A condition code for comparing floating point values. +#: +#: This enumerated operand kind is used for the :cton:inst:`fcmp` instruction +#: and corresponds to the `condcodes::FloatCC` Rust type. +floatcc = ImmediateKind( + 'floatcc', 'A floating point comparison condition code.') diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index d8880c28ed..106f506e47 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -11,6 +11,7 @@ use std::str::FromStr; use entities::*; use immediates::*; +use condcodes::*; use types::{self, Type}; // Include code generated by `meta/gen_instr.py`. This file contains: @@ -175,6 +176,18 @@ pub enum InstructionData { lane: u8, arg: Value, }, + IntCompare { + opcode: Opcode, + ty: Type, + cond: IntCC, + args: [Value; 2], + }, + FloatCompare { + opcode: Opcode, + ty: Type, + cond: FloatCC, + args: [Value; 2], + }, Jump { opcode: Opcode, ty: Type, diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index e0d19aa5a8..217d670afc 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -197,6 +197,8 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { Select { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), InsertLane { lane, args, .. } => writeln!(w, " {}, {}, {}", args[0], lane, args[1]), ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), + IntCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), + FloatCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), Jump { ref data, .. } => writeln!(w, " {}", data), Branch { ref data, .. } => writeln!(w, " {}", data), BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index ca8f56232b..a17449008e 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::result; use std::fmt::{self, Display, Formatter, Write}; +use std::str::FromStr; use std::u32; use lexer::{self, Lexer, Token}; use cretonne::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; @@ -249,7 +250,7 @@ impl<'a> Parser<'a> { // Match and consume a u8 immediate. // This is used for lane numbers in SIMD vectors. - fn match_u8(&mut self, err_msg: &str) -> Result { + fn match_uimm8(&mut self, err_msg: &str) -> Result { if let Some(Token::Integer(text)) = self.token() { self.consume(); // Lexer just gives us raw text that looks like an integer. @@ -284,6 +285,16 @@ impl<'a> Parser<'a> { } } + // Match and consume an enumerated immediate, like one of the condition codes. + fn match_enum(&mut self, err_msg: &str) -> Result { + if let Some(Token::Identifier(text)) = self.token() { + self.consume(); + text.parse().map_err(|_| self.error(err_msg)) + } else { + err!(self.loc, err_msg) + } + } + /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. @@ -847,7 +858,7 @@ impl<'a> Parser<'a> { InstructionFormat::InsertLane => { let lhs = try!(self.match_value("expected SSA value first operand")); try!(self.match_token(Token::Comma, "expected ',' between operands")); - let lane = try!(self.match_u8("expected lane number")); + let lane = try!(self.match_uimm8("expected lane number")); try!(self.match_token(Token::Comma, "expected ',' between operands")); let rhs = try!(self.match_value("expected SSA value last operand")); InstructionData::InsertLane { @@ -860,7 +871,7 @@ impl<'a> Parser<'a> { InstructionFormat::ExtractLane => { let arg = try!(self.match_value("expected SSA value last operand")); try!(self.match_token(Token::Comma, "expected ',' between operands")); - let lane = try!(self.match_u8("expected lane number")); + let lane = try!(self.match_uimm8("expected lane number")); InstructionData::ExtractLane { opcode: opcode, ty: VOID, @@ -868,6 +879,32 @@ impl<'a> Parser<'a> { arg: arg, } } + InstructionFormat::IntCompare => { + let cond = try!(self.match_enum("expected intcc condition code")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value second operand")); + InstructionData::IntCompare { + opcode: opcode, + ty: VOID, + cond: cond, + args: [lhs, rhs], + } + } + InstructionFormat::FloatCompare => { + let cond = try!(self.match_enum("expected floatcc condition code")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value second operand")); + InstructionData::FloatCompare { + opcode: opcode, + ty: VOID, + cond: cond, + args: [lhs, rhs], + } + } InstructionFormat::BranchTable | InstructionFormat::Call => { unimplemented!(); diff --git a/tests/parser/tiny.cton b/tests/parser/tiny.cton index 6be261e273..e404985bf8 100644 --- a/tests/parser/tiny.cton +++ b/tests/parser/tiny.cton @@ -26,3 +26,19 @@ ebb0: v1 = extractlane v0, 3 v2 = insertlane v0, 1, v1 } + +; Integer condition codes. +function icmp(i32, i32) { +ebb0(vx0: i32, vx1: i32): + v0 = icmp eq, vx0, vx1 + v1 = icmp ult, vx0, vx1 + v2 = icmp sge, vx0, vx1 +} + +; Floating condition codes. +function fcmp(f32, f32) { +ebb0(vx0: f32, vx1: f32): + v0 = fcmp eq, vx0, vx1 + v1 = fcmp uno, vx0, vx1 + v2 = fcmp lt, vx0, vx1 +} diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref index 1fc1745a34..31f5767335 100644 --- a/tests/parser/tiny.cton.ref +++ b/tests/parser/tiny.cton.ref @@ -21,3 +21,17 @@ ebb0: v1 = extractlane v0, 3 v2 = insertlane v0, 1, v1 } + +function icmp(i32, i32) { +ebb0(vx0: i32, vx1: i32): + v0 = icmp eq, vx0, vx1 + v1 = icmp ult, vx0, vx1 + v2 = icmp sge, vx0, vx1 +} + +function fcmp(f32, f32) { +ebb0(vx0: f32, vx1: f32): + v0 = fcmp eq, vx0, vx1 + v1 = fcmp uno, vx0, vx1 + v2 = fcmp lt, vx0, vx1 +} From 2bfb4ca5b7830a67022f9ce4e3e95df102acb6b5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 13:16:24 -0700 Subject: [PATCH 110/968] Add meta definitions for floating point operations. Rename the Select instruction format to Ternary since it is also used by the fma instruction. --- docs/langref.rst | 89 +++++++------------- meta/cretonne/base.py | 143 +++++++++++++++++++++++++++++++- meta/cretonne/formats.py | 5 +- src/libcretonne/instructions.rs | 2 +- src/libcretonne/write.rs | 2 +- src/libreader/parser.rs | 6 +- 6 files changed, 183 insertions(+), 64 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 7df404e02c..61c534ad69 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -771,73 +771,48 @@ Floating point operations These operations generally follow IEEE 754-2008 semantics. .. autoinst:: fcmp +.. autoinst:: fadd +.. autoinst:: fsub +.. autoinst:: fmul +.. autoinst:: fdiv +.. autoinst:: sqrt +.. autoinst:: fma -.. inst:: fadd x,y +Sign bit manipulations +~~~~~~~~~~~~~~~~~~~~~~ - Floating point addition. +The sign manipulating instructions work as bitwise operations, so they don't +have special behavior for signaling NaN operands. The exponent and trailing +significand bits are always preserved. -.. inst:: fsub x,y +.. autoinst:: fneg +.. autoinst:: fabs +.. autoinst:: fcopysign - Floating point subtraction. +Minimum and maximum +~~~~~~~~~~~~~~~~~~~ -.. inst:: fneg x +These instructions return the larger or smaller of their operands. They differ +in their handling of quiet NaN inputs. Note that signaling NaN operands always +cause a NaN result. - Floating point negation. +When comparing zeroes, these instructions behave as if :math:`-0.0 < 0.0`. - :result: ``x`` with its sign bit inverted. +.. autoinst:: fmin +.. autoinst:: fminnum +.. autoinst:: fmax +.. autoinst:: fmaxnum - Note that this is a pure bitwise operation. +Rounding +~~~~~~~~ -.. inst:: fabs x +These instructions round their argument to a nearby integral value, still +represented as a floating point number. - Floating point absolute value. - - :result: ``x`` with its sign bit cleared. - - Note that this is a pure bitwise operation. - -.. inst:: a = fcopysign x, y - - Floating point copy sign. - - :result: ``x`` with its sign changed to that of ``y``. - - Note that this is a pure bitwise operation. The sign bit from ``y`` is - copied to the sign bit of ``x``. - -.. inst:: a = fmul x, y -.. inst:: a = fdiv x, y -.. inst:: a = fmin x, y -.. inst:: a = fminnum x, y -.. inst:: a = fmax x, y -.. inst:: a = fmaxnum x, y - -.. inst:: a = ceil x - - Round floating point round to integral, towards positive infinity. - -.. inst:: floor x - - Round floating point round to integral, towards negative infinity. - -.. inst:: trunc x - - Round floating point round to integral, towards zero. - -.. inst:: nearest x - - Round floating point round to integral, towards nearest with ties to even. - -.. inst:: sqrt x - - Floating point square root. - -.. inst:: a = fma x, y, z - - Floating point fused multiply-and-add. - - Computes :math:`a := xy+z` wihtout any intermediate rounding of the - product. +.. autoinst:: ceil +.. autoinst:: floor +.. autoinst:: trunc +.. autoinst:: nearest Conversion operations --------------------- diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index e6d008585b..fd3e4fdcb5 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -548,7 +548,7 @@ popcnt = Instruction( # Float = TypeVar( - 'Float', 'A scalar or vector floating point type type', + 'Float', 'A scalar or vector floating point number', floats=True, simd=True) Cond = Operand('Cond', floatcc) @@ -620,4 +620,145 @@ fcmp = Instruction( """, ins=(Cond, x, y), outs=a) +x = Operand('x', Float) +y = Operand('y', Float) +z = Operand('z', Float) +a = Operand('a', Float, 'Result of applying operator to each lane') + +fadd = Instruction( + 'fadd', r""" + Floating point addition. + """, + ins=(x, y), outs=a) + +fsub = Instruction( + 'fsub', r""" + Floating point subtraction. + """, + ins=(x, y), outs=a) + +fmul = Instruction( + 'fmul', r""" + Floating point multiplication. + """, + ins=(x, y), outs=a) + +fdiv = Instruction( + 'fdiv', r""" + Floating point division. + + Unlike the integer division instructions :cton:inst:`sdiv` and + :cton:inst:`udiv`, this can't trap. Division by zero is infinity or + NaN, depending on the dividend. + """, + ins=(x, y), outs=a) + +sqrt = Instruction( + 'sqrt', r""" + Floating point square root. + """, + ins=x, outs=a) + +fma = Instruction( + 'fma', r""" + Floating point fused multiply-and-add. + + Computes :math:`a := xy+z` wihtout any intermediate rounding of the + product. + """, + ins=(x, y, z), outs=a) + +a = Operand('a', Float, '``x`` with its sign bit inverted') +fneg = Instruction( + 'fneg', r""" + Floating point negation. + + Note that this is a pure bitwise operation. + """, + ins=x, outs=a) + +a = Operand('a', Float, '``x`` with its sign bit cleared') +fabs = Instruction( + 'fabs', r""" + Floating point absolute value. + + Note that this is a pure bitwise operation. + """, + ins=x, outs=a) + +a = Operand('a', Float, '``x`` with its sign bit changed to that of ``y``') +fcopysign = Instruction( + 'fcopysign', r""" + Floating point copy sign. + + Note that this is a pure bitwise operation. The sign bit from ``y`` is + copied to the sign bit of ``x``. + """, + ins=(x, y), outs=a) + +a = Operand('a', Float, 'The smaller of ``x`` and ``y``') + +fmin = Instruction( + 'fmin', r""" + Floating point minimum, propagating NaNs. + + If either operand is NaN, this returns a NaN. + """, + ins=(x, y), outs=a) + +fminnum = Instruction( + 'fminnum', r""" + Floating point minimum, suppressing quiet NaNs. + + If either operand is a quiet NaN, the other operand is returned. If + either operand is a signaling NaN, NaN is returned. + """, + ins=(x, y), outs=a) + +a = Operand('a', Float, 'The larger of ``x`` and ``y``') + +fmax = Instruction( + 'fmax', r""" + Floating point maximum, propagating NaNs. + + If either operand is NaN, this returns a NaN. + """, + ins=(x, y), outs=a) + +fmaxnum = Instruction( + 'fmaxnum', r""" + Floating point maximum, suppressing quiet NaNs. + + If either operand is a quiet NaN, the other operand is returned. If + either operand is a signaling NaN, NaN is returned. + """, + ins=(x, y), outs=a) + +a = Operand('a', Float, '``x`` rounded to integral value') + +ceil = Instruction( + 'ceil', r""" + Round floating point round to integral, towards positive infinity. + """, + ins=x, outs=a) + +floor = Instruction( + 'floor', r""" + Round floating point round to integral, towards negative infinity. + """, + ins=x, outs=a) + +trunc = Instruction( + 'trunc', r""" + Round floating point round to integral, towards zero. + """, + ins=x, outs=a) + +nearest = Instruction( + 'nearest', r""" + Round floating point round to integral, towards nearest with ties to + even. + """, + ins=x, outs=a) + instructions.close() diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 62c678f9b9..5fc6cd7a2d 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -27,8 +27,9 @@ BinaryImmRev = InstructionFormat(imm64, value) BinaryOverflow = InstructionFormat(value, value, multiple_results=True) # The select instructions are controlled by the second value operand. -# The first value operand is the controlling flag whisch has a derived type. -Select = InstructionFormat(value, value, value, typevar_operand=1) +# The first value operand is the controlling flag which has a derived type. +# The fma instruction has the same constraint on all inputs. +Ternary = InstructionFormat(value, value, value, typevar_operand=1) InsertLane = InstructionFormat(value, uimm8, value) ExtractLane = InstructionFormat(value, uimm8) diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 106f506e47..21138e2ce7 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -159,7 +159,7 @@ pub enum InstructionData { second_result: Value, args: [Value; 2], }, - Select { + Ternary { opcode: Opcode, ty: Type, args: [Value; 3], diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 217d670afc..6a14497f81 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -194,7 +194,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), BinaryImmRev { imm, arg, .. } => writeln!(w, " {}, {}", imm, arg), BinaryOverflow { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), - Select { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), + Ternary { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), InsertLane { lane, args, .. } => writeln!(w, " {}, {}, {}", args[0], lane, args[1]), ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), IntCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index a17449008e..7977ccbf54 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -815,13 +815,15 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } - InstructionFormat::Select => { + InstructionFormat::Ternary => { + // Names here refer to the `select` instruction. + // This format is also use by `fma`. let ctrl_arg = try!(self.match_value("expected SSA value control operand")); try!(self.match_token(Token::Comma, "expected ',' between operands")); let true_arg = try!(self.match_value("expected SSA value true operand")); try!(self.match_token(Token::Comma, "expected ',' between operands")); let false_arg = try!(self.match_value("expected SSA value false operand")); - InstructionData::Select { + InstructionData::Ternary { opcode: opcode, ty: VOID, args: [ctrl_arg, true_arg, false_arg], From 4a929f5e41e3d08c4248baa664db674f9b7baee5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 13:40:16 -0700 Subject: [PATCH 111/968] Add meta definition for bitcast. This instruction uses two type variables: input and output. Make sure that our parser can handle it. The output type variable annotation is mandatory. Add a ValueTypeSet::example() method which is used to provide better diagnostics for a missing type variable. --- docs/langref.rst | 9 +---- meta/cretonne/base.py | 25 ++++++++++++ src/libcretonne/instructions.rs | 69 +++++++++++++++++++++++++++++++++ src/libreader/parser.rs | 5 ++- tests/parser/tiny.cton | 8 ++++ tests/parser/tiny.cton.ref | 6 +++ 6 files changed, 113 insertions(+), 9 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 61c534ad69..0f49b8f6ed 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -817,14 +817,7 @@ represented as a floating point number. Conversion operations --------------------- -.. inst:: a = bitcast x - - Reinterpret the bits in ``x`` as a different type. - - The input and output types must be storable to memory and of the same size. - A bitcast is equivalent to storing one type and loading the other type from - the same address. - +.. autoinst:: bitcast .. inst:: a = itrunc x .. inst:: a = uext x .. inst:: a = sext x diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index fd3e4fdcb5..15f23b1abc 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -761,4 +761,29 @@ nearest = Instruction( """, ins=x, outs=a) + +# +# Conversions +# + +Mem = TypeVar( + 'Mem', 'Any type that can be stored in memory', + ints=True, floats=True, simd=True) +MemTo = TypeVar( + 'MemTo', 'Any type that can be stored in memory', + ints=True, floats=True, simd=True) + +x = Operand('x', Mem) +a = Operand('a', MemTo, 'Bits of `x` reinterpreted') + +bitcast = Instruction( + 'bitcast', r""" + Reinterpret the bits in `x` as a different type. + + The input and output types must be storable to memory and of the same + size. A bitcast is equivalent to storing one type and loading the other + type from the same address. + """, + ins=x, outs=a) + instructions.close() diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 21138e2ce7..f6ca187511 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -432,6 +432,30 @@ impl ValueTypeSet { }; allowed && self.is_base_type(typ.lane_type()) } + + /// Get an example member of this type set. + /// + /// This is used for error messages to avoid suggesting invalid types. + pub fn example(&self) -> Type { + if self.base != types::VOID { + return self.base; + } + let t = if self.all_ints { + types::I32 + } else if self.all_floats { + types::F32 + } else if self.allow_scalars { + types::B1 + } else { + types::B32 + }; + + if self.allow_scalars { + t + } else { + t.by(4).unwrap() + } + } } /// Operand constraints. This describes the value type constraints on a single `Value` operand. @@ -506,4 +530,49 @@ mod tests { // that? assert_eq!(mem::size_of::(), 16); } + + #[test] + fn value_set() { + use types::*; + + let vts = ValueTypeSet { + allow_scalars: true, + allow_simd: true, + base: VOID, + all_ints: true, + all_floats: false, + all_bools: true, + }; + assert_eq!(vts.example().to_string(), "i32"); + + let vts = ValueTypeSet { + allow_scalars: true, + allow_simd: true, + base: VOID, + all_ints: false, + all_floats: true, + all_bools: true, + }; + assert_eq!(vts.example().to_string(), "f32"); + + let vts = ValueTypeSet { + allow_scalars: false, + allow_simd: true, + base: VOID, + all_ints: false, + all_floats: true, + all_bools: true, + }; + assert_eq!(vts.example().to_string(), "f32x4"); + + let vts = ValueTypeSet { + allow_scalars: false, + allow_simd: true, + base: VOID, + all_ints: false, + all_floats: false, + all_bools: true, + }; + assert_eq!(vts.example().to_string(), "b32x4"); + } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 7977ccbf54..cf0efb1ffd 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -651,7 +651,10 @@ impl<'a> Parser<'a> { } else if constraints.is_polymorphic() { // This opcode does not support type inference, so the explicit type variable // is required. - return err!(self.loc, "type variable required for polymorphic opcode"); + return err!(self.loc, + "type variable required for polymorphic opcode, e.g. '{}.{}'", + opcode, + constraints.ctrl_typeset().unwrap().example()); } else { // This is a non-polymorphic opcode. No typevar needed. VOID diff --git a/tests/parser/tiny.cton b/tests/parser/tiny.cton index e404985bf8..ae3466b618 100644 --- a/tests/parser/tiny.cton +++ b/tests/parser/tiny.cton @@ -42,3 +42,11 @@ ebb0(vx0: f32, vx1: f32): v1 = fcmp uno, vx0, vx1 v2 = fcmp lt, vx0, vx1 } + +; The bitcast instruction has two type variables: The controlling type variable +; controls the outout type, and the input type is a free variable. +function bitcast(i32, f32) { +ebb0(vx0: i32, vx1: f32): + v0 = bitcast.i8x4 vx0 + v1 = bitcast.i32 vx1 +} diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref index 31f5767335..f2404bbf94 100644 --- a/tests/parser/tiny.cton.ref +++ b/tests/parser/tiny.cton.ref @@ -35,3 +35,9 @@ ebb0(vx0: f32, vx1: f32): v1 = fcmp uno, vx0, vx1 v2 = fcmp lt, vx0, vx1 } + +function bitcast(i32, f32) { +ebb0(vx0: i32, vx1: f32): + v0 = bitcast.i8x4 vx0 + v1 = bitcast.i32 vx1 +} From a8c7ca6c757379c2f382d47d3cc3363a1518b392 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 17:17:30 -0700 Subject: [PATCH 112/968] Include parser tests in the test-all.sh script. Move test-all.sh to the top level directory, and also run the parser tests from this script. Use a release build of cton-util to run the parser tests. As we accumulate many tests in the tests directory tree, this will mean they can still be run quickly. Point Travis config to the new test script. --- .travis.yml | 2 +- src/test-all.sh | 12 ------------ test-all.sh | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 13 deletions(-) delete mode 100755 src/test-all.sh create mode 100755 test-all.sh diff --git a/.travis.yml b/.travis.yml index 041073c206..1e6f291c4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,4 @@ rust: - stable - beta - nightly -script: src/test-all.sh +script: test-all.sh diff --git a/src/test-all.sh b/src/test-all.sh deleted file mode 100755 index c0d9977ed1..0000000000 --- a/src/test-all.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Exit immediately on errors. -set -e - -# Run from the src/tools directory which includes all our crates. -cd $(dirname "$0")/tools - -PKGS="-p cretonne -p cretonne-reader -p cretonne-tools" -cargo build $PKGS -cargo doc $PKGS -cargo test $PKGS diff --git a/test-all.sh b/test-all.sh new file mode 100755 index 0000000000..502966e55d --- /dev/null +++ b/test-all.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# This is the top-level test script: +# +# - Build documentation for Rust code in 'src/tools/target/doc'. +# - Run unit tests for all Rust crates. +# - Make a debug build of all crates. +# - Make a release build of cton-util. +# - Run file-level tests with the release build of cton-util. +# +# All tests run by this script should be passing at all times. + +# Exit immediately on errors. +set -e + +# Repository top-level directory. +cd $(dirname "$0") +topdir=$(pwd) + +# Run cargo from the src/tools directory which includes all our crates for +# building cton-util. +cd "$topdir/src/tools" +PKGS="-p cretonne -p cretonne-reader -p cretonne-tools" +echo ====== Rust unit tests and debug build ====== +cargo test $PKGS +cargo build $PKGS +cargo doc + +echo ====== Rust release build ====== +cargo build --release + +export CTONUTIL="$topdir/src/tools/target/release/cton-util" + +# Run the parser tests. +echo ====== Parser tests ====== +cd "$topdir/tests" +parser/run.sh + +echo ====== OK ====== From ce7524d68f3ab11fff637612fbb03d490b653eca Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 17:27:51 -0700 Subject: [PATCH 113/968] Fix Travis script path. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1e6f291c4f..567d824194 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,4 @@ rust: - stable - beta - nightly -script: test-all.sh +script: ./test-all.sh From 6321fd1f5f39917c21fa2ef0c696855c07fa7cdd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 18:12:01 -0700 Subject: [PATCH 114/968] Metadefs for integer reduce and extend operations. Naming is interesting here. Since 'truncate' refers to removing the least significant digits, use 'ireduce' instead. The 'extend' use is fairly established. Don't abbreviate, avoid unfortunate modern vernacular. --- docs/langref.rst | 6 +++--- meta/cretonne/base.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 0f49b8f6ed..881976a701 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -818,9 +818,9 @@ Conversion operations --------------------- .. autoinst:: bitcast -.. inst:: a = itrunc x -.. inst:: a = uext x -.. inst:: a = sext x +.. autoinst:: ireduce +.. autoinst:: uextend +.. autoinst:: sextend .. inst:: a = ftrunc x .. inst:: a = fext x .. inst:: a = cvt_ftou x diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 15f23b1abc..b560b9bb7a 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -786,4 +786,51 @@ bitcast = Instruction( """, ins=x, outs=a) +Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) +IntTo = TypeVar('IntTo', 'A smaller integer type with the same number of lanes', ints=True, simd=True) + +x = Operand('x', Int) +a = Operand('a', IntTo) + +ireduce = Instruction( + 'ireduce', r""" + Convert `x` to a smaller integer type by dropping high bits. + + Each lane in `x` is converted to a smaller integer type by discarding + the most significant bits. This is the same as reducing modulo + :math:`2^n`. + + The result type must have the same number of vector lanes as the input, + and each lane must not have more bits that the input lanes. If the input + and output types are the same, this is a no-op. + """, + ins=x, outs=a) + +uextend = Instruction( + 'uextend', r""" + Convert `x` to a larger integer type by zero-extending. + + Each lane in `x` is converted to a larger integer type by adding zeroes. + The result has the same numerical value as `x` when both are interpreted + as unsigned integers. + + The result type must have the same number of vector lanes as the input, + and each lane must not have fewer bits that the input lanes. If the input + and output types are the same, this is a no-op. + """, + ins=x, outs=a) + +sextend = Instruction( + 'sextend', r""" + Convert `x` to a larger integer type by sign-extending. + + Each lane in `x` is converted to a larger integer type by replicating + the sign bit. The result has the same numerical value as `x` when both + are interpreted as signed integers. + + The result type must have the same number of vector lanes as the input, + and each lane must not have fewer bits that the input lanes. If the input + and output types are the same, this is a no-op. + """, + ins=x, outs=a) instructions.close() From 84abe288377684a255f6f68cdf51990cecccb471 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 18:24:22 -0700 Subject: [PATCH 115/968] Fix rustc warning about unused Write trait. --- src/libcretonne/entities.rs | 2 +- src/libcretonne/types.rs | 2 +- src/libreader/parser.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index dcdff9e822..6294a8e8c7 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -20,7 +20,7 @@ //! format. use std::default::Default; -use std::fmt::{self, Display, Formatter, Write}; +use std::fmt::{self, Display, Formatter}; use std::u32; /// An opaque reference to an extended basic block in a function. diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 1c44736111..1bc0d0a0b4 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -2,7 +2,7 @@ //! Common types for the Cretonne code generator. use std::default::Default; -use std::fmt::{self, Display, Formatter, Write}; +use std::fmt::{self, Display, Formatter}; // ====--------------------------------------------------------------------------------------====// // diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index cf0efb1ffd..24d46b884d 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use std::result; -use std::fmt::{self, Display, Formatter, Write}; +use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; use lexer::{self, Lexer, Token}; From 6971ae4e407fc7f2ccb569a3324a7bc145233888 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 7 Jul 2016 19:24:08 -0700 Subject: [PATCH 116/968] Fix the recommended Sphinx version. The latest Sphinx 1.4.4 produces lots of warnings about four-column indices. We'll wait for Read the Docs to upgrade their systems before moving to the newer Sphinx version. --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0a26eae385..a80a23a283 100644 --- a/README.rst +++ b/README.rst @@ -42,8 +42,11 @@ Building the documentation To build the Cretonne documentation, you need the `Sphinx documentation generator `_:: - $ pip install sphinx + $ pip install sphinx==1.3.5 sphinx-autobuild $ cd cretonne/docs $ make html $ open _build/html/index.html +The specific Sphinx version is currently used by Read the Docs. Sphinx 1.4 has +been released, but produces lots of warnings about four-column indices. We'll +upgrade when Read the Docs does. From fd9f08c30f54d89d20b571b1654a2cc284bc957f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Jul 2016 11:20:19 -0700 Subject: [PATCH 117/968] Define floating point conversion instructions. --- docs/langref.rst | 13 +++-- meta/cretonne/base.py | 119 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 17 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 881976a701..9181f10e26 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -821,13 +821,12 @@ Conversion operations .. autoinst:: ireduce .. autoinst:: uextend .. autoinst:: sextend -.. inst:: a = ftrunc x -.. inst:: a = fext x -.. inst:: a = cvt_ftou x -.. inst:: a = cvt_ftos x -.. inst:: a = cvt_utof x -.. inst:: a = cvt_stof x - +.. autoinst:: fpromote +.. autoinst:: fdemote +.. autoinst:: fcvt_to_uint +.. autoinst:: fcvt_to_sint +.. autoinst:: fcvt_from_uint +.. autoinst:: fcvt_from_sint Glossary ======== diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index b560b9bb7a..894ac2f505 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -787,7 +787,9 @@ bitcast = Instruction( ins=x, outs=a) Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) -IntTo = TypeVar('IntTo', 'A smaller integer type with the same number of lanes', ints=True, simd=True) +IntTo = TypeVar( + 'IntTo', 'A smaller integer type with the same number of lanes', + ints=True, simd=True) x = Operand('x', Int) a = Operand('a', IntTo) @@ -801,22 +803,30 @@ ireduce = Instruction( :math:`2^n`. The result type must have the same number of vector lanes as the input, - and each lane must not have more bits that the input lanes. If the input - and output types are the same, this is a no-op. + and each lane must not have more bits that the input lanes. If the + input and output types are the same, this is a no-op. """, ins=x, outs=a) + +IntTo = TypeVar( + 'IntTo', 'A larger integer type with the same number of lanes', + ints=True, simd=True) + +x = Operand('x', Int) +a = Operand('a', IntTo) + uextend = Instruction( 'uextend', r""" Convert `x` to a larger integer type by zero-extending. - Each lane in `x` is converted to a larger integer type by adding zeroes. - The result has the same numerical value as `x` when both are interpreted - as unsigned integers. + Each lane in `x` is converted to a larger integer type by adding + zeroes. The result has the same numerical value as `x` when both are + interpreted as unsigned integers. The result type must have the same number of vector lanes as the input, - and each lane must not have fewer bits that the input lanes. If the input - and output types are the same, this is a no-op. + and each lane must not have fewer bits that the input lanes. If the + input and output types are the same, this is a no-op. """, ins=x, outs=a) @@ -829,8 +839,97 @@ sextend = Instruction( are interpreted as signed integers. The result type must have the same number of vector lanes as the input, - and each lane must not have fewer bits that the input lanes. If the input - and output types are the same, this is a no-op. + and each lane must not have fewer bits that the input lanes. If the + input and output types are the same, this is a no-op. """, ins=x, outs=a) + +FloatTo = TypeVar( + 'FloatTo', 'A scalar or vector floating point number', + floats=True, simd=True) + +x = Operand('x', Float) +a = Operand('a', FloatTo) + +fpromote = Instruction( + 'fcvt_ftof', r""" + Convert `x` to a larger floating point format. + + Each lane in `x` is converted to the destination floating point format. + This is an exact operation. + + Since Cretonne currently only supports two floating point formats, this + instruction always converts :type:`f32` to :type:`f64`. This may change + in the future. + + The result type must have the same number of vector lanes as the input, + and the result lanes must be larger than the input lanes. + """, + ins=x, outs=a) + +fdemote = Instruction( + 'fdemote', r""" + Convert `x` to a smaller floating point format. + + Each lane in `x` is converted to the destination floating point format + by rounding to nearest, ties to even. + + Since Cretonne currently only supports two floating point formats, this + instruction always converts :type:`f64` to :type:`f32`. This may change + in the future. + + The result type must have the same number of vector lanes as the input, + and the result lanes must be smaller than the input lanes. + """, + ins=x, outs=a) + +x = Operand('x', Float) +a = Operand('a', Int) + +fcvt_to_uint = Instruction( + 'fcvt_to_uint', r""" + Convert floating point to unsigned integer. + + Each lane in `x` is converted to an unsigned integer by rounding + towards zero. If `x` is NaN or if the unsigned integral value cannot be + represented in the result type, this instruction traps. + + The result type must have the same number of vector lanes as the input. + """, + ins=x, outs=a) + +fcvt_to_sint = Instruction( + 'fcvt_to_sint', r""" + Convert floating point to signed integer. + + Each lane in `x` is converted to a signed integer by rounding towards + zero. If `x` is NaN or if the signed integral value cannot be + represented in the result type, this instruction traps. + + The result type must have the same number of vector lanes as the input. + """, + ins=x, outs=a) + +fcvt_from_uint = Instruction( + 'fcvt_from_uint', r""" + Convert unsigned integer to floating point. + + Each lane in `x` is interpreted as an unsigned integer and converted to + floating point using round to nearest, ties to even. + + The result type must have the same number of vector lanes as the input. + """, + ins=x, outs=a) + +fcvt_from_sint = Instruction( + 'fcvt_from_sint', r""" + Convert signed integer to floating point. + + Each lane in `x` is interpreted as a signed integer and converted to + floating point using round to nearest, ties to even. + + The result type must have the same number of vector lanes as the input. + """, + ins=x, outs=a) + instructions.close() From 02861df78c167343f827411efa275941ebdaf4dc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Jul 2016 11:44:20 -0700 Subject: [PATCH 118/968] Don't print a space after quoted function names. --- src/libcretonne/write.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 6a14497f81..22d87ba103 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -63,7 +63,7 @@ fn write_spec(w: &mut Write, func: &Function) -> Result { if !needs_quotes(&func.name) { write!(w, "function {}{}", func.name, sig) } else { - write!(w, "function \"{}\" {}", escaped(&func.name), sig) + write!(w, "function \"{}\"{}", escaped(&func.name), sig) } } @@ -234,7 +234,7 @@ mod tests { #[test] fn basic() { let mut f = Function::new(); - assert_eq!(function_to_string(&f), "function \"\" () {\n}\n"); + assert_eq!(function_to_string(&f), "function \"\"() {\n}\n"); f.name.push_str("foo"); assert_eq!(function_to_string(&f), "function foo() {\n}\n"); From a39e418d320e7d99f2193f8d20b5cb510920a9dd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Jul 2016 15:15:53 -0700 Subject: [PATCH 119/968] Rewrite EBB and value references after parsing. We llow forward references to values and EBBs, so it is not possible to rewrite these from the source domain to the in-memory domain during parsing. Instead go through all the instructions after parsing everything and rewrite the value and EBB references when everything has been created and mapped. --- src/libcretonne/instructions.rs | 18 +++++- src/libcretonne/repr.rs | 15 +++-- src/libreader/parser.rs | 105 ++++++++++++++++++++++++++++++++ tests/parser/rewrite.cton | 24 ++++++++ tests/parser/rewrite.cton.ref | 13 ++++ 5 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 tests/parser/rewrite.cton create mode 100644 tests/parser/rewrite.cton.ref diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index f6ca187511..9a87e85d71 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -8,6 +8,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; +use std::ops::{Deref, DerefMut}; use entities::*; use immediates::*; @@ -230,6 +231,21 @@ impl VariableArgs { } } +// Coerce VariableArgs into a &[Value] slice. +impl Deref for VariableArgs { + type Target = [Value]; + + fn deref<'a>(&'a self) -> &'a [Value] { + &self.0 + } +} + +impl DerefMut for VariableArgs { + fn deref_mut<'a>(&'a mut self) -> &'a mut [Value] { + &mut self.0 + } +} + impl Display for VariableArgs { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { try!(write!(fmt, "(")); @@ -294,7 +310,7 @@ pub struct CallData { second_result: Value, // Dynamically sized array containing call argument values. - arguments: VariableArgs, + pub arguments: VariableArgs, } impl Display for CallData { diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index ff8d5d4dee..5a36115fbd 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -101,10 +101,6 @@ impl Function { inst } - fn inst_mut(&mut self, inst: Inst) -> &mut InstructionData { - &mut self.instructions[inst.index()] - } - /// Create result values for an instruction that produces multiple results. /// /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If @@ -147,11 +143,11 @@ impl Function { // Update the second_result pointer in `inst`. if head != NO_VALUE { - *self.inst_mut(inst) + *self[inst] .second_result_mut() .expect("instruction format doesn't allow multiple results") = head; } - *self.inst_mut(inst).first_type_mut() = first_type; + *self[inst].first_type_mut() = first_type; fixed_results } @@ -456,6 +452,13 @@ impl Index for Function { } } +/// Allow mutable access to instructions via function indexing. +impl IndexMut for Function { + fn index_mut<'a>(&'a mut self, inst: Inst) -> &'a mut InstructionData { + &mut self.instructions[inst.index()] + } +} + /// A node in a double linked list of instructions is a basic block. #[derive(Debug)] struct InstNode { diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 24d46b884d..e55a277d18 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -73,6 +73,9 @@ struct Context { stack_slots: HashMap, // ssNN ebbs: HashMap, // ebbNN values: HashMap, // vNN, vxNN + + // Remember the location of every instruction. + inst_locs: Vec<(Inst, Location)>, } impl Context { @@ -82,6 +85,7 @@ impl Context { stack_slots: HashMap::new(), ebbs: HashMap::new(), values: HashMap::new(), + inst_locs: Vec::new(), } } @@ -112,6 +116,101 @@ impl Context { Ok(()) } } + + // Record the location of an instuction. + fn add_inst_loc(&mut self, inst: Inst, loc: &Location) { + self.inst_locs.push((inst, *loc)); + } + + // The parser creates all instructions with Ebb and Value references using the source file + // numbering. These references need to be rewritten after parsing is complete since forward + // references are allowed. + + // Rewrite an Ebb reference. + fn rewrite_ebb(map: &HashMap, ebb: &mut Ebb, loc: &Location) -> Result<()> { + match map.get(ebb) { + Some(&new) => { + *ebb = new; + Ok(()) + } + None => err!(loc, "undefined reference: {}", ebb), + } + } + + // Rewrite a value reference. + fn rewrite_value(map: &HashMap, val: &mut Value, loc: &Location) -> Result<()> { + match map.get(val) { + Some(&new) => { + *val = new; + Ok(()) + } + None => err!(loc, "undefined reference: {}", val), + } + } + + // Rewrite a slice of value references. + fn rewrite_values(map: &HashMap, + vals: &mut [Value], + loc: &Location) + -> Result<()> { + for val in vals { + try!(Self::rewrite_value(map, val, loc)); + } + Ok(()) + } + + // Rewrite all EBB and value references in the function. + fn rewrite_references(&mut self) -> Result<()> { + for &(inst, loc) in &self.inst_locs { + match self.function[inst] { + InstructionData::Nullary { .. } | + InstructionData::UnaryImm { .. } | + InstructionData::UnaryIeee32 { .. } | + InstructionData::UnaryIeee64 { .. } | + InstructionData::UnaryImmVector { .. } => {} + + InstructionData::Unary { ref mut arg, .. } | + InstructionData::BinaryImm { ref mut arg, .. } | + InstructionData::BinaryImmRev { ref mut arg, .. } | + InstructionData::ExtractLane { ref mut arg, .. } | + InstructionData::BranchTable { ref mut arg, .. } => { + try!(Self::rewrite_value(&self.values, arg, &loc)); + } + + InstructionData::Binary { ref mut args, .. } | + InstructionData::BinaryOverflow { ref mut args, .. } | + InstructionData::InsertLane { ref mut args, .. } | + InstructionData::IntCompare { ref mut args, .. } | + InstructionData::FloatCompare { ref mut args, .. } => { + try!(Self::rewrite_values(&self.values, args, &loc)); + } + + InstructionData::Ternary { ref mut args, .. } => { + try!(Self::rewrite_values(&self.values, args, &loc)); + } + + InstructionData::Jump { ref mut data, .. } => { + try!(Self::rewrite_ebb(&self.ebbs, &mut data.destination, &loc)); + try!(Self::rewrite_values(&self.values, &mut data.arguments, &loc)); + } + + InstructionData::Branch { ref mut data, .. } => { + try!(Self::rewrite_value(&self.values, &mut data.arg, &loc)); + try!(Self::rewrite_ebb(&self.ebbs, &mut data.destination, &loc)); + try!(Self::rewrite_values(&self.values, &mut data.arguments, &loc)); + } + + InstructionData::Call { ref mut data, .. } => { + try!(Self::rewrite_values(&self.values, &mut data.arguments, &loc)); + } + } + } + + // TODO: Rewrite EBB references in jump tables. (Once jump table data structures are + // defined). + + Ok(()) + } } impl<'a> Parser<'a> { @@ -323,6 +422,10 @@ impl<'a> Parser<'a> { // function ::= function-spec "{" preamble function-body * "}" try!(self.match_token(Token::RBrace, "expected '}' after function body")); + // Rewrite references to values and EBBs after parsing everuthing to allow forward + // references. + try!(ctx.rewrite_references()); + Ok(ctx.function) } @@ -575,6 +678,7 @@ impl<'a> Parser<'a> { } else { return err!(self.loc, "expected instruction opcode"); }; + let opcode_loc = self.loc; self.consume(); // Look for a controlling type variable annotation. @@ -597,6 +701,7 @@ impl<'a> Parser<'a> { let inst = ctx.function.make_inst(inst_data); let num_results = ctx.function.make_inst_results(inst, ctrl_typevar); ctx.function.append_inst(ebb, inst); + ctx.add_inst_loc(inst, &opcode_loc); if results.len() != num_results { return err!(self.loc, diff --git a/tests/parser/rewrite.cton b/tests/parser/rewrite.cton new file mode 100644 index 0000000000..33e5277db6 --- /dev/null +++ b/tests/parser/rewrite.cton @@ -0,0 +1,24 @@ +; The .cton parser can't preserve the actual entity numbers in the input file +; since entities are numbered as they are created. For entities declared in the +; preamble, this is no problem, but for EBB and value references, mapping +; source numbers to real numbers can be a problem. +; +; It is possible to refer to instructions and EBBs that have not yet been +; defined in the lexical order, so the parser needs to rewrite these references +; after the fact. + +; Check that defining numbers are rewritten. +function defs() { +ebb100(v20: i32): + v1000 = iconst.i32x8 5 + vx200 = f64const 0x4.0p0 + trap +} + +; Using values. +function use_value() { +ebb100(v20: i32): + v1000 = iadd_imm v20, 5 + vx200 = iadd v20, v1000 + jump ebb100(v1000) +} diff --git a/tests/parser/rewrite.cton.ref b/tests/parser/rewrite.cton.ref new file mode 100644 index 0000000000..8f379abd64 --- /dev/null +++ b/tests/parser/rewrite.cton.ref @@ -0,0 +1,13 @@ +function defs() { +ebb0(vx0: i32): + v0 = iconst.i32x8 5 + v1 = f64const 0x1.0000000000000p2 + trap +} + +function "use_value"() { +ebb0(vx0: i32): + v0 = iadd_imm vx0, 5 + v1 = iadd vx0, v0 + jump ebb0(v0) +} From 520a438c42332c8c47a08a5cbad9ccdb6c4c4ca1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Jul 2016 16:19:26 -0700 Subject: [PATCH 120/968] Define a return instruction. It is possible to return multiple values from a function, so ReturnData contains a VariableArgs instance. We don't want return instructions to appear as 'return (v1)', so tweak the printing of VariableArgs so the parantheses are added externally. --- docs/cton_domain.py | 9 +++++++-- docs/langref.rst | 11 +---------- meta/cretonne/base.py | 12 ++++++++++++ meta/cretonne/formats.py | 2 ++ src/libcretonne/instructions.rs | 25 ++++++++++++++++++------- src/libcretonne/write.rs | 7 +++++++ src/libreader/parser.rs | 16 ++++++++++++++-- 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index bd207e994d..aafb7724cf 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -265,12 +265,17 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): def format_signature(self): inst = self.object - sig = self.format_name() + sig = inst.name if len(inst.outs) > 0: sig = ', '.join([op.name for op in inst.outs]) + ' = ' + sig if len(inst.ins) > 0: - sig = sig + ' ' + inst.ins[0].name + op = inst.ins[0] + sig += ' ' + op.name + # If the first input is variable-args, this is 'return'. No parens. + if op.typ.operand_kind().name == 'variable_args': + sig += '...'.format(op.name) for op in inst.ins[1:]: + # This is a call or branch with args in (...). if op.typ.operand_kind().name == 'variable_args': sig += '({}...)'.format(op.name) else: diff --git a/docs/langref.rst b/docs/langref.rst index 9181f10e26..e323dd617d 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -393,16 +393,7 @@ preamble`: :arg args...: Function arguments matching the signature of F. :result a,b,...: Return values matching the signature of F. -.. inst:: return args... - - Return from function. - - Unconditionally transfer control to the calling function, passing the - provided return values. - - :arg args: Return values. The list of return values must match the list of - return value types in the function signature. - :result: None. This is a terminator instruction. +.. autoinst:: x_return This simple example illustrates direct function calls and signatures:: diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 894ac2f505..1f17bab749 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -95,6 +95,18 @@ trapnz = Instruction( """, ins=c) +rvals = Operand('rvals', variable_args, doc='return values') + +x_return = Instruction( + 'return', r""" + Return from the function. + + Unconditionally transfer control to the calling function, passing the + provided return values. The list of return values must match the + function signature's return types. + """, + ins=rvals) + # # Materializing constants. # diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 5fc6cd7a2d..4798b9ed25 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -43,6 +43,8 @@ BranchTable = InstructionFormat(value, jump_table) Call = InstructionFormat( function, variable_args, multiple_results=True, boxed_storage=True) +Return = InstructionFormat(variable_args, boxed_storage=True) + # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index 9a87e85d71..8ebd63d411 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -210,6 +210,11 @@ pub enum InstructionData { ty: Type, data: Box, }, + Return { + opcode: Opcode, + ty: Type, + data: Box, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to @@ -248,7 +253,6 @@ impl DerefMut for VariableArgs { impl Display for VariableArgs { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - try!(write!(fmt, "(")); for (i, val) in self.0.iter().enumerate() { if i == 0 { try!(write!(fmt, "{}", val)); @@ -256,7 +260,7 @@ impl Display for VariableArgs { try!(write!(fmt, ", {}", val)); } } - write!(fmt, ")") + Ok(()) } } @@ -279,7 +283,7 @@ impl Display for JumpData { if self.arguments.is_empty() { write!(f, "{}", self.destination) } else { - write!(f, "{}{}", self.destination, self.arguments) + write!(f, "{}({})", self.destination, self.arguments) } } } @@ -297,7 +301,7 @@ impl Display for BranchData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { try!(write!(f, "{}, {}", self.arg, self.destination)); if !self.arguments.is_empty() { - try!(write!(f, "{}", self.arguments)); + try!(write!(f, "({})", self.arguments)); } Ok(()) } @@ -310,15 +314,22 @@ pub struct CallData { second_result: Value, // Dynamically sized array containing call argument values. - pub arguments: VariableArgs, + pub args: VariableArgs, } impl Display for CallData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "TBD{}", self.arguments) + write!(f, "TBD({})", self.args) } } +/// Payload of a return instruction. +#[derive(Debug)] +pub struct ReturnData { + // Dynamically sized array containing return values. + pub args: VariableArgs, +} + impl InstructionData { /// Create data for a call instruction. pub fn call(opc: Opcode, return_type: Type) -> InstructionData { @@ -327,7 +338,7 @@ impl InstructionData { ty: return_type, data: Box::new(CallData { second_result: NO_VALUE, - arguments: VariableArgs::new(), + args: VariableArgs::new(), }), } } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 22d87ba103..e49956970a 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -203,6 +203,13 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { Branch { ref data, .. } => writeln!(w, " {}", data), BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), Call { ref data, .. } => writeln!(w, " {}", data), + Return { ref data, .. } => { + if data.args.is_empty() { + writeln!(w, "") + } else { + writeln!(w, " {}", data.args) + } + } } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index e55a277d18..34dd15f735 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -15,7 +15,7 @@ use cretonne::types::{Type, VOID, FunctionName, Signature, ArgumentType, Argumen use cretonne::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::entities::*; use cretonne::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, JumpData, - BranchData}; + BranchData, ReturnData}; use cretonne::repr::{Function, StackSlotData}; pub use lexer::Location; @@ -201,7 +201,11 @@ impl Context { } InstructionData::Call { ref mut data, .. } => { - try!(Self::rewrite_values(&self.values, &mut data.arguments, &loc)); + try!(Self::rewrite_values(&self.values, &mut data.args, &loc)); + } + + InstructionData::Return { ref mut data, .. } => { + try!(Self::rewrite_values(&self.values, &mut data.args, &loc)); } } } @@ -1015,6 +1019,14 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } + InstructionFormat::Return => { + let args = try!(self.parse_value_list()); + InstructionData::Return { + opcode: opcode, + ty: VOID, + data: Box::new(ReturnData { args: args }), + } + } InstructionFormat::BranchTable | InstructionFormat::Call => { unimplemented!(); From e7adcf9af942aefa3f68d66828a8ab6796105851 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Jul 2016 16:26:11 -0700 Subject: [PATCH 121/968] Delete the concept of 'local SSA form'. This was supposed to make verification fast, but WebAssembly is no longer in this form since it's blocks can produce values. Also, computing a flow graph and dominator tree is really fast anyway. --- docs/langref.rst | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index e323dd617d..c6e7b3ced6 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -83,11 +83,6 @@ instructions which behave more like variable accesses in a typical programming language. Cretonne can perform the necessary dataflow analysis to convert stack slots to SSA form. -If all values are only used in the same EBB where they are defined, the -function is said to be in :term:`local SSA form`. It is much faster for -Cretonne to verify the correctness of a function in local SSA form since no -complicated control flow analysis is required. - Value types =========== @@ -890,16 +885,3 @@ Glossary stack slot A fixed size memory allocation in the current function's activation frame. Also called a local variable. - - local SSA form - A restricted version of SSA form where all values are defined and used - in the same EBB. A function is in local SSA form iff it is in SSA form - and: - - - No branches pass arguments to their target EBB. - - Only the entry EBB may have arguments. - - This also implies that there are no branches to the entry EBB. - - Local SSA form is easy to generate and fast to verify. It passes data - between EBBs by using stack slots. From ad5e6b14cff7318cb6256f5da9f49a98d6b3adec Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Jul 2016 16:28:19 -0700 Subject: [PATCH 122/968] Add tests for parsing call and return. --- tests/parser/call.cton | 13 +++++++++++++ tests/parser/call.cton.ref | 11 +++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/parser/call.cton create mode 100644 tests/parser/call.cton.ref diff --git a/tests/parser/call.cton b/tests/parser/call.cton new file mode 100644 index 0000000000..12221c3ff1 --- /dev/null +++ b/tests/parser/call.cton @@ -0,0 +1,13 @@ +; Parser tests for call and return syntax. + +function mini() { +ebb1: + return +} + +function r1() -> i32, f32 { +ebb1: + v1 = iconst.i32 3 + v2 = f32const 0.0 + return v1, v2 +} diff --git a/tests/parser/call.cton.ref b/tests/parser/call.cton.ref new file mode 100644 index 0000000000..a6d51166f4 --- /dev/null +++ b/tests/parser/call.cton.ref @@ -0,0 +1,11 @@ +function mini() { +ebb0: + return +} + +function r1() -> i32, f32 { +ebb0: + v0 = iconst.i32 3 + v1 = f32const 0.0 + return v0, v1 +} From 84357c340298bf487ee1e4d2d8380f1ab61abcdf Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 12 Jul 2016 13:59:27 -0700 Subject: [PATCH 123/968] Ignore cargo-fmt and vim related files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 0d20b6487c..002a368e2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ *.pyc +*.bk +*.swp +*.swo +tags From c6b1388fdcd8eaaa6a49273049bcff0b8aebfe2d Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 12 Jul 2016 14:37:37 -0700 Subject: [PATCH 124/968] Add a Control Flow Graph representation. The CFG must be instantiated against an existing function but may be modified after creation --- src/libcretonne/cfg.rs | 270 ++++++++++++++++++++++++++++++++++++ src/libcretonne/entities.rs | 4 +- src/libcretonne/lib.rs | 1 + 3 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 src/libcretonne/cfg.rs diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs new file mode 100644 index 0000000000..a8640d3459 --- /dev/null +++ b/src/libcretonne/cfg.rs @@ -0,0 +1,270 @@ +//! A control flow graph represented as mappings of extended basic blocks to their predecessors. +//! Predecessors are denoted by tuples of EBB and branch/jump instructions. Each predecessor +//! tuple corresponds to the end of a basic block. +//! +//!```c +//! Ebb0: +//! ... ; beginning of basic block +//! +//! ... +//! +//! brz vx, Ebb1 ; end of basic block +//! +//! ... ; beginning of basic block +//! +//! ... +//! +//! jmp Ebb2 ; end of basic block +//!``` +//! +//! Here Ebb1 and Ebb2 would each have a single predecessor denoted as (Ebb0, `brz vx, Ebb1`) +//! and (Ebb0, `jmp Ebb2`) respectively. + +use repr::Function; +use entities::{Inst, Ebb}; +use instructions::InstructionData; +use std::collections::{BTreeSet, BTreeMap, btree_map}; + +/// A basic block denoted by its enclosing Ebb and last instruction. +pub type Predecessor = (Ebb, Inst); + +/// Storing predecessors in a BTreeSet ensures that their ordering is +/// stable with no duplicates. +pub type PredecessorSet = BTreeSet; + +/// The Control Flow Graph maintains a mapping of ebbs to their predecessors +/// where predecessors are basic blocks. +#[derive(Debug)] +pub struct ControlFlowGraph { + data: BTreeMap, +} + +impl ControlFlowGraph { + + /// During initialization mappings will be generated for any existing + /// blocks within the CFG's associated function. Basic sanity checks will + /// also be performed to ensure that the blocks are well formed. + pub fn new(func: &Function) -> Result { + let mut cfg = ControlFlowGraph{data: BTreeMap::new()}; + + // Even ebbs without predecessors should show up in the CFG, albeit + // with no entires. + for ebb in func.ebbs_numerically() { + try!(cfg.init_ebb(ebb)); + } + + for ebb in func.ebbs_numerically() { + // Flips to true when a terminating instruction is seen. So that if additional + // instructions occur an error may be returned. + let mut terminated = false; + for inst in func.ebb_insts(ebb) { + if terminated { + return Err(format!("{} contains unreachable instructions.", ebb)); + } + + match func[inst] { + InstructionData::Branch { ty: _, opcode: _, ref data } => { + try!(cfg.add_predecessor(data.destination, (ebb, inst))); + } + InstructionData::Jump { ty: _, opcode: _, ref data } => { + try!(cfg.add_predecessor(data.destination, (ebb, inst))); + terminated = true; + } + InstructionData::Return { ty: _, opcode: _, data: _ } => { + terminated = true; + } + InstructionData::Nullary { ty: _, opcode: _ } => { + terminated = true; + } + _ => () + } + } + } + Ok(cfg) + } + + /// Initializes a predecessor set for some ebb. If an ebb already has an + /// entry it will be clobbered. + pub fn init_ebb(&mut self, ebb: Ebb) -> Result<&mut PredecessorSet, &'static str> { + self.data.insert(ebb, BTreeSet::new()); + match self.data.get_mut(&ebb) { + Some(predecessors) => Ok(predecessors), + None => Err("Ebb initialization failed.") + } + } + + /// Attempts to add a predecessor for some ebb, attempting to initialize + /// any ebb which has no entry. + pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: Predecessor) -> Result<(), &'static str> { + let success = match self.data.get_mut(&ebb) { + Some(predecessors) => predecessors.insert(predecessor), + None => false + }; + + if success { + Ok(()) + } else { + let mut predecessors = try!(self.init_ebb(ebb)); + if predecessors.insert(predecessor) { + return Ok(()); + } + Err("Predecessor insertion failed.") + } + + } + + /// Returns all of the predecessors for some ebb, if it has an entry. + pub fn get_predecessors(&self, ebb: Ebb) -> Option<&PredecessorSet> { + self.data.get(&ebb) + } + + /// An iterator over all of the ebb to predecessor mappings in the CFG. + pub fn iter<'a>(&'a self) -> btree_map::Iter<'a, Ebb, PredecessorSet> { + self.data.iter() + } +} + +#[cfg(test)] +mod tests { + use instructions::*; + use entities::{Ebb, Inst, NO_VALUE}; + use repr::Function; + use super::*; + use types; + + // Some instructions will be re-used in several tests. + + fn nullary(func: &mut Function) -> Inst { + func.make_inst(InstructionData::Nullary { + opcode: Opcode::Iconst, + ty: types::I32, + }) + } + + fn jump(func: &mut Function, dest: Ebb) -> Inst { + func.make_inst(InstructionData::Jump { + opcode: Opcode::Jump, + ty: types::VOID, + data: Box::new(JumpData { + destination: dest, + arguments: VariableArgs::new(), + }), + }) + } + + fn branch(func: &mut Function, dest: Ebb) -> Inst { + func.make_inst(InstructionData::Branch { + opcode: Opcode::Brz, + ty: types::VOID, + data: Box::new(BranchData { + arg: NO_VALUE, + destination: dest, + arguments: VariableArgs::new(), + }), + }) + } + + #[test] + fn empty() { + let func = Function::new(); + let cfg = ControlFlowGraph::new(&func).unwrap(); + assert_eq!(None, cfg.iter().next()); + } + + #[test] + fn no_predecessors() { + let mut func = Function::new(); + func.make_ebb(); + func.make_ebb(); + func.make_ebb(); + let cfg = ControlFlowGraph::new(&func).unwrap(); + let nodes = cfg.iter().collect::>(); + assert_eq!(nodes.len(), 3); + + let mut fun_ebbs = func.ebbs_numerically(); + for (ebb, predecessors) in nodes { + assert_eq!(ebb.index(), fun_ebbs.next().unwrap().index()); + assert_eq!(predecessors.len(), 0); + } + } + + #[test] + #[should_panic(expected = "instructions")] + fn nullable_before_branch() { + // Ensure that branching after a nullary, within an ebb, triggers an error. + let mut func = Function::new(); + let ebb0 = func.make_ebb(); + let ebb1_malformed = func.make_ebb(); + let ebb2 = func.make_ebb(); + + let nullary_inst = nullary(&mut func); + func.append_inst(ebb1_malformed, nullary_inst); + + // This jump should not be recorded since a nullary takes place + // before it appears. + let jmp_ebb1_ebb0 = jump(&mut func, ebb0); + func.append_inst(ebb1_malformed, jmp_ebb1_ebb0); + + let jmp_ebb0_ebb2 = jump(&mut func, ebb2); + func.append_inst(ebb0, jmp_ebb0_ebb2); + + ControlFlowGraph::new(&func).unwrap(); + } + + #[test] + #[should_panic(expected = "instructions")] + fn jump_before_branch() { + // Ensure that branching after a jump, within an ebb, triggers an error. + let mut func = Function::new(); + let ebb0 = func.make_ebb(); + let ebb1_malformed = func.make_ebb(); + let ebb2 = func.make_ebb(); + + let jmp_ebb0_ebb1 = jump(&mut func, ebb2); + func.append_inst(ebb0, jmp_ebb0_ebb1); + + let jmp_ebb1_ebb2 = jump(&mut func, ebb2); + func.append_inst(ebb1_malformed, jmp_ebb1_ebb2); + + // This branch should not be recorded since a jump takes place + // before it appears. + let br_ebb1_ebb0 = branch(&mut func, ebb0); + func.append_inst(ebb1_malformed, br_ebb1_ebb0); + + ControlFlowGraph::new(&func).unwrap(); + } + + #[test] + fn branches_and_jumps() { + let mut func = Function::new(); + let ebb0 = func.make_ebb(); + let ebb1 = func.make_ebb(); + let ebb2 = func.make_ebb(); + + let br_ebb0_ebb2 = branch(&mut func, ebb2); + func.append_inst(ebb0, br_ebb0_ebb2); + + let jmp_ebb0_ebb1 = jump(&mut func, ebb1); + func.append_inst(ebb0, jmp_ebb0_ebb1); + + let br_ebb1_ebb1 = branch(&mut func, ebb1); + func.append_inst(ebb1, br_ebb1_ebb1); + + let jmp_ebb1_ebb2 = jump(&mut func, ebb2); + func.append_inst(ebb1, jmp_ebb1_ebb2); + + let cfg = ControlFlowGraph::new(&func).unwrap(); + let ebb0_predecessors = cfg.get_predecessors(ebb0).unwrap(); + let ebb1_predecessors = cfg.get_predecessors(ebb1).unwrap(); + let ebb2_predecessors = cfg.get_predecessors(ebb2).unwrap(); + assert_eq!(ebb0_predecessors.len(), 0); + assert_eq!(ebb1_predecessors.len(), 2); + assert_eq!(ebb2_predecessors.len(), 2); + + assert_eq!(ebb1_predecessors.contains(&(ebb0, jmp_ebb0_ebb1)), true); + assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true); + assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), true); + assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true); + } + +} diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index 6294a8e8c7..c87ed6a818 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -24,7 +24,7 @@ use std::fmt::{self, Display, Formatter}; use std::u32; /// An opaque reference to an extended basic block in a function. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Ebb(u32); impl Ebb { @@ -64,7 +64,7 @@ impl Default for Ebb { } /// An opaque reference to an instruction in a function. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Inst(u32); impl Inst { diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 26fc74f7ab..f0bf55af39 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -14,3 +14,4 @@ pub mod entities; pub mod instructions; pub mod repr; pub mod write; +pub mod cfg; From 180eae3bb5c85dbd1a3a12728692489fe91840dc Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 12 Jul 2016 14:42:49 -0700 Subject: [PATCH 125/968] Cargo-fmt fixes --- src/libcretonne/cfg.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index a8640d3459..cf69d0f184 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -2,7 +2,7 @@ //! Predecessors are denoted by tuples of EBB and branch/jump instructions. Each predecessor //! tuple corresponds to the end of a basic block. //! -//!```c +//! ```c //! Ebb0: //! ... ; beginning of basic block //! @@ -15,7 +15,7 @@ //! ... //! //! jmp Ebb2 ; end of basic block -//!``` +//! ``` //! //! Here Ebb1 and Ebb2 would each have a single predecessor denoted as (Ebb0, `brz vx, Ebb1`) //! and (Ebb0, `jmp Ebb2`) respectively. @@ -40,12 +40,11 @@ pub struct ControlFlowGraph { } impl ControlFlowGraph { - /// During initialization mappings will be generated for any existing /// blocks within the CFG's associated function. Basic sanity checks will /// also be performed to ensure that the blocks are well formed. pub fn new(func: &Function) -> Result { - let mut cfg = ControlFlowGraph{data: BTreeMap::new()}; + let mut cfg = ControlFlowGraph { data: BTreeMap::new() }; // Even ebbs without predecessors should show up in the CFG, albeit // with no entires. @@ -76,7 +75,7 @@ impl ControlFlowGraph { InstructionData::Nullary { ty: _, opcode: _ } => { terminated = true; } - _ => () + _ => (), } } } @@ -89,16 +88,19 @@ impl ControlFlowGraph { self.data.insert(ebb, BTreeSet::new()); match self.data.get_mut(&ebb) { Some(predecessors) => Ok(predecessors), - None => Err("Ebb initialization failed.") + None => Err("Ebb initialization failed."), } } /// Attempts to add a predecessor for some ebb, attempting to initialize /// any ebb which has no entry. - pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: Predecessor) -> Result<(), &'static str> { + pub fn add_predecessor(&mut self, + ebb: Ebb, + predecessor: Predecessor) + -> Result<(), &'static str> { let success = match self.data.get_mut(&ebb) { Some(predecessors) => predecessors.insert(predecessor), - None => false + None => false, }; if success { @@ -120,7 +122,7 @@ impl ControlFlowGraph { /// An iterator over all of the ebb to predecessor mappings in the CFG. pub fn iter<'a>(&'a self) -> btree_map::Iter<'a, Ebb, PredecessorSet> { - self.data.iter() + self.data.iter() } } From 79c7ae6233d2db63e5f0a85868251c3445bebe1c Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 12 Jul 2016 14:46:24 -0700 Subject: [PATCH 126/968] Remove misleading test comments --- src/libcretonne/cfg.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index cf69d0f184..14f0467fcb 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -202,8 +202,6 @@ mod tests { let nullary_inst = nullary(&mut func); func.append_inst(ebb1_malformed, nullary_inst); - // This jump should not be recorded since a nullary takes place - // before it appears. let jmp_ebb1_ebb0 = jump(&mut func, ebb0); func.append_inst(ebb1_malformed, jmp_ebb1_ebb0); @@ -228,8 +226,6 @@ mod tests { let jmp_ebb1_ebb2 = jump(&mut func, ebb2); func.append_inst(ebb1_malformed, jmp_ebb1_ebb2); - // This branch should not be recorded since a jump takes place - // before it appears. let br_ebb1_ebb0 = branch(&mut func, ebb0); func.append_inst(ebb1_malformed, br_ebb1_ebb0); From 295a4eb03fa93f3ba80654f5bdab7c1c5003478b Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Wed, 13 Jul 2016 11:04:39 -0700 Subject: [PATCH 127/968] Replace Results with assertions in invariant cases. It seems reasonable that certain non-recoverable errors during the building of the CFG should crash. --- src/libcretonne/cfg.rs | 107 +++++------------------------------------ 1 file changed, 12 insertions(+), 95 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 14f0467fcb..4fc2f1b292 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -43,76 +43,42 @@ impl ControlFlowGraph { /// During initialization mappings will be generated for any existing /// blocks within the CFG's associated function. Basic sanity checks will /// also be performed to ensure that the blocks are well formed. - pub fn new(func: &Function) -> Result { + pub fn new(func: &Function) -> ControlFlowGraph { let mut cfg = ControlFlowGraph { data: BTreeMap::new() }; // Even ebbs without predecessors should show up in the CFG, albeit // with no entires. for ebb in func.ebbs_numerically() { - try!(cfg.init_ebb(ebb)); + cfg.init_ebb(ebb); } for ebb in func.ebbs_numerically() { // Flips to true when a terminating instruction is seen. So that if additional // instructions occur an error may be returned. - let mut terminated = false; for inst in func.ebb_insts(ebb) { - if terminated { - return Err(format!("{} contains unreachable instructions.", ebb)); - } - match func[inst] { InstructionData::Branch { ty: _, opcode: _, ref data } => { - try!(cfg.add_predecessor(data.destination, (ebb, inst))); + cfg.add_predecessor(data.destination, (ebb, inst)); } InstructionData::Jump { ty: _, opcode: _, ref data } => { - try!(cfg.add_predecessor(data.destination, (ebb, inst))); - terminated = true; - } - InstructionData::Return { ty: _, opcode: _, data: _ } => { - terminated = true; - } - InstructionData::Nullary { ty: _, opcode: _ } => { - terminated = true; + cfg.add_predecessor(data.destination, (ebb, inst)); } _ => (), } } } - Ok(cfg) + cfg } /// Initializes a predecessor set for some ebb. If an ebb already has an /// entry it will be clobbered. - pub fn init_ebb(&mut self, ebb: Ebb) -> Result<&mut PredecessorSet, &'static str> { + pub fn init_ebb(&mut self, ebb: Ebb) -> &mut PredecessorSet { self.data.insert(ebb, BTreeSet::new()); - match self.data.get_mut(&ebb) { - Some(predecessors) => Ok(predecessors), - None => Err("Ebb initialization failed."), - } + self.data.get_mut(&ebb).unwrap() } - /// Attempts to add a predecessor for some ebb, attempting to initialize - /// any ebb which has no entry. - pub fn add_predecessor(&mut self, - ebb: Ebb, - predecessor: Predecessor) - -> Result<(), &'static str> { - let success = match self.data.get_mut(&ebb) { - Some(predecessors) => predecessors.insert(predecessor), - None => false, - }; - - if success { - Ok(()) - } else { - let mut predecessors = try!(self.init_ebb(ebb)); - if predecessors.insert(predecessor) { - return Ok(()); - } - Err("Predecessor insertion failed.") - } - + pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: Predecessor) { + self.data.get_mut(&ebb).unwrap().insert(predecessor); } /// Returns all of the predecessors for some ebb, if it has an entry. @@ -136,13 +102,6 @@ mod tests { // Some instructions will be re-used in several tests. - fn nullary(func: &mut Function) -> Inst { - func.make_inst(InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }) - } - fn jump(func: &mut Function, dest: Ebb) -> Inst { func.make_inst(InstructionData::Jump { opcode: Opcode::Jump, @@ -169,7 +128,7 @@ mod tests { #[test] fn empty() { let func = Function::new(); - let cfg = ControlFlowGraph::new(&func).unwrap(); + let cfg = ControlFlowGraph::new(&func); assert_eq!(None, cfg.iter().next()); } @@ -179,7 +138,7 @@ mod tests { func.make_ebb(); func.make_ebb(); func.make_ebb(); - let cfg = ControlFlowGraph::new(&func).unwrap(); + let cfg = ControlFlowGraph::new(&func); let nodes = cfg.iter().collect::>(); assert_eq!(nodes.len(), 3); @@ -190,48 +149,6 @@ mod tests { } } - #[test] - #[should_panic(expected = "instructions")] - fn nullable_before_branch() { - // Ensure that branching after a nullary, within an ebb, triggers an error. - let mut func = Function::new(); - let ebb0 = func.make_ebb(); - let ebb1_malformed = func.make_ebb(); - let ebb2 = func.make_ebb(); - - let nullary_inst = nullary(&mut func); - func.append_inst(ebb1_malformed, nullary_inst); - - let jmp_ebb1_ebb0 = jump(&mut func, ebb0); - func.append_inst(ebb1_malformed, jmp_ebb1_ebb0); - - let jmp_ebb0_ebb2 = jump(&mut func, ebb2); - func.append_inst(ebb0, jmp_ebb0_ebb2); - - ControlFlowGraph::new(&func).unwrap(); - } - - #[test] - #[should_panic(expected = "instructions")] - fn jump_before_branch() { - // Ensure that branching after a jump, within an ebb, triggers an error. - let mut func = Function::new(); - let ebb0 = func.make_ebb(); - let ebb1_malformed = func.make_ebb(); - let ebb2 = func.make_ebb(); - - let jmp_ebb0_ebb1 = jump(&mut func, ebb2); - func.append_inst(ebb0, jmp_ebb0_ebb1); - - let jmp_ebb1_ebb2 = jump(&mut func, ebb2); - func.append_inst(ebb1_malformed, jmp_ebb1_ebb2); - - let br_ebb1_ebb0 = branch(&mut func, ebb0); - func.append_inst(ebb1_malformed, br_ebb1_ebb0); - - ControlFlowGraph::new(&func).unwrap(); - } - #[test] fn branches_and_jumps() { let mut func = Function::new(); @@ -251,7 +168,7 @@ mod tests { let jmp_ebb1_ebb2 = jump(&mut func, ebb2); func.append_inst(ebb1, jmp_ebb1_ebb2); - let cfg = ControlFlowGraph::new(&func).unwrap(); + let cfg = ControlFlowGraph::new(&func); let ebb0_predecessors = cfg.get_predecessors(ebb0).unwrap(); let ebb1_predecessors = cfg.get_predecessors(ebb1).unwrap(); let ebb2_predecessors = cfg.get_predecessors(ebb2).unwrap(); From 4a6e53f90d032aa2fa93a36163927bf2c7cf196e Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Wed, 13 Jul 2016 14:31:05 -0700 Subject: [PATCH 128/968] Add a print-cfg subcommand. The command returns parses a .cton file, builds a CFG, and prints it to stdout in graphviz format. --- src/tools/main.rs | 5 ++ src/tools/print_cfg.rs | 169 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 src/tools/print_cfg.rs diff --git a/src/tools/main.rs b/src/tools/main.rs index 1ea40c201c..abad701af6 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -11,12 +11,14 @@ use std::process; mod cat; +mod print_cfg; const USAGE: &'static str = " Cretonne code generator utility Usage: cton-util cat ... + cton-util print-cfg ... cton-util --help | --version Options: @@ -28,6 +30,7 @@ Options: #[derive(RustcDecodable, Debug)] struct Args { cmd_cat: bool, + cmd_print_cfg: bool, arg_file: Vec, } @@ -48,6 +51,8 @@ fn cton_util() -> CommandResult { // Find the sub-command to execute. if args.cmd_cat { cat::run(args.arg_file) + } else if args.cmd_print_cfg { + print_cfg::run(args.arg_file) } else { // Debugging / shouldn't happen with proper command line handling above. Err(format!("Unhandled args: {:?}", args)) diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs new file mode 100644 index 0000000000..e55033832a --- /dev/null +++ b/src/tools/print_cfg.rs @@ -0,0 +1,169 @@ +//! The `print-cfg` sub-command. +//! +//! Read a series of Cretonne IL files and print their control flow graphs +//! in graphviz format. +use std::fs::File; +use std::io::{Read, Write, stdout}; + +use CommandResult; +use cretonne::repr::Function; +use cretonne::cfg::ControlFlowGraph; +use cretonne::instructions::InstructionData; +use cton_reader::parser::Parser; + +pub fn run(files: Vec) -> CommandResult { + for (i, f) in files.into_iter().enumerate() { + if i != 0 { + println!(""); + } + try!(print_cfg(f)) + } + Ok(()) +} + + +struct CFGPrinter { + level: usize, + writer: T, + buffer: String, +} + +impl CFGPrinter { + pub fn new(writer: T) -> CFGPrinter { + CFGPrinter{level: 0, writer: writer, buffer: String::new()} + } + + pub fn print(&mut self, func: &Function) -> Result<(), String> { + self.level = 0; + self.header(); + self.push_indent(); + self.ebb_subgraphs(func); + let cfg = ControlFlowGraph::new(&func); + self.cfg_connections(&cfg); + self.pop_indent(); + self.footer(); + self.write() + } + + fn write(&mut self) -> Result<(), String> { + match self.writer.write(self.buffer.as_bytes()) { + Err(_) => return Err("Write failed!".to_string()), + _ => (), + }; + match self.writer.flush() { + Err(_) => return Err("Flush failed!".to_string()), + _ => (), + }; + Ok(()) + } + + fn append(&mut self, s: &str) { + let mut indent = String::new(); + for _ in 0 .. self.level { + indent = indent + " "; + } + self.buffer.push_str(&(indent + s)); + } + + fn push_indent(&mut self) { + self.level += 1; + } + + fn pop_indent(&mut self) { + if self.level > 0 { + self.level -= 1; + } + } + + fn open_paren(&mut self) { + self.append("{"); + } + + fn close_paren(&mut self) { + self.append("}"); + } + + fn newline(&mut self) { + self.append("\n"); + } + + fn header(&mut self) { + self.append("digraph "); + self.open_paren(); + self.newline(); + self.push_indent(); + self.append("{rank=min; ebb0}"); + self.pop_indent(); + self.newline(); + } + + fn footer(&mut self) { + self.close_paren(); + self.newline(); + } + + fn ebb_subgraphs(&mut self, func: &Function) { + for ebb in func.ebbs_numerically() { + let inst_data = func.ebb_insts(ebb) + .filter(|inst| { + match func[*inst] { + InstructionData::Branch{ ty: _, opcode: _, data: _ } => true, + InstructionData::Jump{ ty: _, opcode: _, data: _ } => true, + _ => false + } + }) + .map(|inst| { + let op = match func[inst] { + InstructionData::Branch{ ty: _, opcode, ref data } => { + Some((opcode, data.destination)) + }, + InstructionData::Jump{ ty: _, opcode, ref data } => { + Some((opcode, data.destination)) + }, + _ => None + }; + (inst, op) + }) + .collect::>(); + + let mut insts = vec![format!("{}", ebb)]; + for (inst, data) in inst_data { + let (op, dest) = data.unwrap(); + insts.push(format!("<{}>{} {}", inst, op, dest)); + } + + self.append(&format!("{} [shape=record, label=\"{}{}{}\"]", + ebb, "{", insts.join(" | "), "}")); + self.newline(); + } + } + + fn cfg_connections(&mut self, cfg: &ControlFlowGraph) { + for (ref ebb, ref predecessors) in cfg.iter() { + for &(parent, inst) in *predecessors { + self.append(&format!("{}:{} -> {}", parent, inst, ebb)); + self.newline(); + } + } + } + +} + +fn print_cfg(filename: String) -> CommandResult { + let mut file = try!(File::open(&filename).map_err(|e| format!("{}: {}", filename, e))); + let mut buffer = String::new(); + try!(file.read_to_string(&mut buffer) + .map_err(|e| format!("Couldn't read {}: {}", filename, e))); + let items = try!(Parser::parse(&buffer).map_err(|e| format!("{}: {}", filename, e))); + + let mut cfg_printer = CFGPrinter::new(stdout()); + for (idx, func) in items.into_iter().enumerate() { + if idx != 0 { + println!(""); + } + + try!(cfg_printer.print(&func)); + } + + Ok(()) +} From 0cdcf29308b5eea353bacae09127e15a70a516d5 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 14 Jul 2016 12:22:08 -0700 Subject: [PATCH 129/968] Id CFG graphs by function name --- src/tools/print_cfg.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index e55033832a..fc85cbebec 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -35,7 +35,7 @@ impl CFGPrinter { pub fn print(&mut self, func: &Function) -> Result<(), String> { self.level = 0; - self.header(); + self.header(func); self.push_indent(); self.ebb_subgraphs(func); let cfg = ControlFlowGraph::new(&func); @@ -87,8 +87,8 @@ impl CFGPrinter { self.append("\n"); } - fn header(&mut self) { - self.append("digraph "); + fn header(&mut self, func: &Function) { + self.append(&format!("digraph {} ", func.name)); self.open_paren(); self.newline(); self.push_indent(); From 5bcce51bd94568f0401abebd7aa46435a0368c72 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 14 Jul 2016 13:43:11 -0700 Subject: [PATCH 130/968] Add print-cfg tests --- test-all.sh | 3 ++- tests/cfg/README.rst | 14 ++++++++++++++ tests/cfg/loop.cton | 30 ++++++++++++++++++++++++++++++ tests/cfg/run.sh | 38 ++++++++++++++++++++++++++++++++++++++ tests/cfg/traps_early.cton | 18 ++++++++++++++++++ tests/cfg/unused_node.cton | 16 ++++++++++++++++ 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/cfg/README.rst create mode 100644 tests/cfg/loop.cton create mode 100755 tests/cfg/run.sh create mode 100644 tests/cfg/traps_early.cton create mode 100644 tests/cfg/unused_node.cton diff --git a/test-all.sh b/test-all.sh index 502966e55d..31638a6db3 100755 --- a/test-all.sh +++ b/test-all.sh @@ -7,7 +7,7 @@ # - Make a debug build of all crates. # - Make a release build of cton-util. # - Run file-level tests with the release build of cton-util. -# +# # All tests run by this script should be passing at all times. # Exit immediately on errors. @@ -35,5 +35,6 @@ export CTONUTIL="$topdir/src/tools/target/release/cton-util" echo ====== Parser tests ====== cd "$topdir/tests" parser/run.sh +cfg/run.sh echo ====== OK ====== diff --git a/tests/cfg/README.rst b/tests/cfg/README.rst new file mode 100644 index 0000000000..c1ee4b1fa3 --- /dev/null +++ b/tests/cfg/README.rst @@ -0,0 +1,14 @@ +CFG tests +============ + +This directory contains test cases for the Cretonne cfg printer. + +Each test case consists of a `foo.cton` input file annotated with its expected connections. +Annotations are comments of the form: `ebbx:insty -> ebbz` where ebbx is connected to ebbz via +a branch or jump instruction at line y. Instructions are labeled by line number starting from zero: `inst0` .. `instn`. + + +Each input file is run through the `cton-util print-cfg` command and the +output is compared against the specially formatted comments to ensure that +expected connections exist. This scheme allows for changes to graph style +without the need to update tests. diff --git a/tests/cfg/loop.cton b/tests/cfg/loop.cton new file mode 100644 index 0000000000..73ad1c4b6a --- /dev/null +++ b/tests/cfg/loop.cton @@ -0,0 +1,30 @@ +; For testing cfg generation. This code is nonsense. + +function nonsense(i32, i32) -> f32 { + +ebb0(v1: i32, v2: i32): + v3 = f64const 0x0.0 + brz v2, ebb2 ;;;; ebb0:inst1 -> ebb2 + v4 = iconst.i32 0 + jump ebb1(v4) ;;;; ebb0:inst3 -> ebb1 + +ebb1(v5: i32): + v6 = imul_imm v5, 4 + v7 = iadd v1, v6 + v8 = f32const 0.0 + v9 = f32const 0.0 + v10 = f32const 0.0 + v11 = fadd v9, v10 + v12 = iadd_imm v5, 1 + v13 = icmp ult, v12, v2 + brnz v13, ebb1(v12) ;;;; ebb1:inst12 -> ebb1 + v14 = f64const 0.0 + v15 = f64const 0.0 + v16 = fdiv v14, v15 + v17 = f32const 0.0 + return v17 + +ebb2: + v100 = f32const 0.0 + return v100 +} diff --git a/tests/cfg/run.sh b/tests/cfg/run.sh new file mode 100755 index 0000000000..2230d4c1ca --- /dev/null +++ b/tests/cfg/run.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Go to tests directory. +cd $(dirname "$0")/.. + +# The path to cton-util should be in $CTONUTIL. +if [ -z "$CTONUTIL" ]; then + CTONUTIL=../src/tools/target/debug/cton-util +fi + +if [ ! -x "$CTONUTIL" ]; then + echo "Can't fund executable cton-util: $CTONUTIL" 1>&2 + exit 1 +fi + +declare -a fails + +for testcase in $(find cfg -name '*.cton'); do + annotations=$(cat $testcase | awk /';;;;'/ | awk -F ";;;;" '{print $2}' | sort) + connections=$("${CTONUTIL}" print-cfg "$testcase" | awk /"->"/ | sort) + if diff -u <(echo $annotations) <(echo $connections); then + echo OK $testcase + else + fails=(${fails[@]} "$testcase") + echo FAIL $testcase + fi +done + +if [ ${#fails[@]} -ne 0 ]; then + echo + echo Failures: + for f in "${fails[@]}"; do + echo " $f" + done + exit 1 +else + echo "All passed" +fi diff --git a/tests/cfg/traps_early.cton b/tests/cfg/traps_early.cton new file mode 100644 index 0000000000..6993cc128e --- /dev/null +++ b/tests/cfg/traps_early.cton @@ -0,0 +1,18 @@ +; For testing cfg generation. This code explores the implications of encountering +; a terminating instruction before any connections have been made. + +function nonsense(i32) { + +ebb0(v1: i32): + trap + brnz v1, ebb2 ;;;; ebb0:inst1 -> ebb2 + jump ebb1 ;;;; ebb0:inst2 -> ebb1 + +ebb1: + v2 = iconst.i32 0 + v3 = iadd v1, v3 + jump ebb0(v3) ;;;; ebb1:inst5 -> ebb0 + +ebb2: + return v1 +} diff --git a/tests/cfg/unused_node.cton b/tests/cfg/unused_node.cton new file mode 100644 index 0000000000..3120ffbe68 --- /dev/null +++ b/tests/cfg/unused_node.cton @@ -0,0 +1,16 @@ +; For testing cfg generation where some block is never reached. + +function not_reached(i32) -> i32 { + +ebb0(v0: i32): + brnz v0, ebb2 ;;;; ebb0:inst0 -> ebb2 + trap + +ebb1: + v1 = iconst.i32 1 + v2 = iadd v0, v1 + jump ebb0(v2) ;;;; ebb1:inst4 -> ebb0 + +ebb2: + return v0 +} From 191c607bf9f9d842accdead8fe45decc4d1ea66b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 15 Jul 2016 15:14:16 -0700 Subject: [PATCH 131/968] Add an entity_map module. This supports the pattern of creating structs wrapping a u32 and using them as indexes into a vector of entities. These entity references should implement the EntityRef trait. The EntityMap is a generic map from an EntityRef to some value type. It expects densely indexed entities and uses a Vec to represent the mapping compactly. --- src/libcretonne/entity_map.rs | 130 ++++++++++++++++++++++++++++++++++ src/libcretonne/lib.rs | 2 + 2 files changed, 132 insertions(+) create mode 100644 src/libcretonne/entity_map.rs diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs new file mode 100644 index 0000000000..3e1f16a595 --- /dev/null +++ b/src/libcretonne/entity_map.rs @@ -0,0 +1,130 @@ +//! Densely numbered entity references as mapping keys. +//! +//! This module defines an `EntityRef` trait that should be implemented by reference types wrapping +//! a small integer index. The `EntityMap` data structure uses the dense index space to implement a +//! map with a vector. + +use std::vec::Vec; +use std::default::Default; +use std::marker::PhantomData; +use std::ops::{Index, IndexMut}; + +/// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key +/// of an `EntityMap`. +pub trait EntityRef: Copy { + /// Create a new entity reference from a small integer. + /// This should crash if the requested index is not representable. + fn new(usize) -> Self; + + /// Get the index that was used to create this entity reference. + fn index(self) -> usize; +} + +/// A mapping `K -> V` for densely indexed entity references. +pub struct EntityMap + where K: EntityRef, + V: Clone + Default +{ + elems: Vec, + unused: PhantomData, +} + +impl EntityMap + where K: EntityRef, + V: Clone + Default +{ + /// Create a new empty map. + pub fn new() -> Self { + EntityMap { + elems: Vec::new(), + unused: PhantomData, + } + } + + /// Ensure that `k` is a valid key but adding default entries if necesssary. + pub fn ensure(&mut self, k: K) { + let idx = k.index(); + if idx >= self.elems.len() { + self.elems.resize(idx + 1, V::default()) + } + } + + /// Append `v` to the mapping, assigning a new key which is returned. + pub fn push(&mut self, v: V) -> K { + let k = K::new(self.elems.len()); + self.elems.push(v); + k + } +} + +/// Immutable indexing into an `EntityMap`. +/// The indexed value must have been accessed mutably previously, or the key passed to `ensure()`. +impl Index for EntityMap + where K: EntityRef, + V: Clone + Default +{ + type Output = V; + + fn index(&self, k: K) -> &V { + &self.elems[k.index()] + } +} + +/// Mutable indexing into an `EntityMap`. +/// The map is resized automatically if the key has not been used before. +impl IndexMut for EntityMap + where K: EntityRef, + V: Clone + Default +{ + fn index_mut(&mut self, k: K) -> &mut V { + self.ensure(k); + &mut self.elems[k.index()] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // EntityRef impl for testing. + #[derive(Clone, Copy)] + struct E(u32); + + impl EntityRef for E { + fn new(i: usize) -> Self { + E(i as u32) + } + fn index(self) -> usize { + self.0 as usize + } + } + + #[test] + fn basic() { + let r0 = E(0); + let r1 = E(1); + let r2 = E(2); + let mut m = EntityMap::new(); + + m[r2] = 3; + m[r1] = 5; + + assert_eq!(m[r1], 5); + assert_eq!(m[r2], 3); + + let shared = &m; + assert_eq!(shared[r0], 0); + assert_eq!(shared[r1], 5); + assert_eq!(shared[r2], 3); + } + + #[test] + fn push() { + let mut m = EntityMap::new(); + let k1: E = m.push(12); + let k2 = m.push(33); + + assert_eq!(m[k1], 12); + assert_eq!(m[k2], 33); + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index f0bf55af39..55d601d38b 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -15,3 +15,5 @@ pub mod instructions; pub mod repr; pub mod write; pub mod cfg; + +pub mod entity_map; From 99464bc29de93ac2a4d7759cf5d248dc4b6d8b86 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 15 Jul 2016 15:31:13 -0700 Subject: [PATCH 132/968] Implement EntityRef for most of the entities module. The only exception is Value which has two dimensions. --- src/libcretonne/cfg.rs | 1 + src/libcretonne/entities.rs | 34 +++++++++++++++++++--------------- src/libcretonne/entity_map.rs | 12 +++++++++--- src/libcretonne/repr.rs | 1 + 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 4fc2f1b292..8c423c23ac 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -95,6 +95,7 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { use instructions::*; + use entity_map::EntityRef; use entities::{Ebb, Inst, NO_VALUE}; use repr::Function; use super::*; diff --git a/src/libcretonne/entities.rs b/src/libcretonne/entities.rs index c87ed6a818..33b41e5273 100644 --- a/src/libcretonne/entities.rs +++ b/src/libcretonne/entities.rs @@ -19,6 +19,7 @@ //! The entity references all implement the `Display` trait in a way that matches the textual IL //! format. +use entity_map::EntityRef; use std::default::Default; use std::fmt::{self, Display, Formatter}; use std::u32; @@ -27,12 +28,18 @@ use std::u32; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Ebb(u32); -impl Ebb { - pub fn new(index: usize) -> Ebb { +impl EntityRef for Ebb { + fn new(index: usize) -> Self { assert!(index < (u32::MAX as usize)); Ebb(index as u32) } + fn index(self) -> usize { + self.0 as usize + } +} + +impl Ebb { /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. pub fn with_number(n: u32) -> Option { if n < u32::MAX { @@ -41,10 +48,6 @@ impl Ebb { None } } - - pub fn index(&self) -> usize { - self.0 as usize - } } /// Display an `Ebb` reference as "ebb12". @@ -67,13 +70,13 @@ impl Default for Ebb { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Inst(u32); -impl Inst { - pub fn new(index: usize) -> Inst { +impl EntityRef for Inst { + fn new(index: usize) -> Self { assert!(index < (u32::MAX as usize)); Inst(index as u32) } - pub fn index(&self) -> usize { + fn index(self) -> usize { self.0 as usize } } @@ -188,13 +191,13 @@ impl Default for Value { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct StackSlot(u32); -impl StackSlot { - pub fn new(index: usize) -> StackSlot { +impl EntityRef for StackSlot { + fn new(index: usize) -> StackSlot { assert!(index < (u32::MAX as usize)); StackSlot(index as u32) } - pub fn index(&self) -> usize { + fn index(self) -> usize { self.0 as usize } } @@ -219,13 +222,13 @@ impl Default for StackSlot { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct JumpTable(u32); -impl JumpTable { - pub fn new(index: usize) -> JumpTable { +impl EntityRef for JumpTable { + fn new(index: usize) -> JumpTable { assert!(index < (u32::MAX as usize)); JumpTable(index as u32) } - pub fn index(&self) -> usize { + fn index(self) -> usize { self.0 as usize } } @@ -250,6 +253,7 @@ impl Default for JumpTable { mod tests { use super::*; use std::u32; + use entity_map::EntityRef; #[test] fn value_with_number() { diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 3e1f16a595..fbf8109d9f 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -41,11 +41,15 @@ impl EntityMap } } + /// Check if `k` is a valid key in the map. + pub fn is_valid(&self, k: K) -> bool { + k.index() < self.elems.len() + } + /// Ensure that `k` is a valid key but adding default entries if necesssary. pub fn ensure(&mut self, k: K) { - let idx = k.index(); - if idx >= self.elems.len() { - self.elems.resize(idx + 1, V::default()) + if !self.is_valid(k) { + self.elems.resize(k.index() + 1, V::default()) } } @@ -106,7 +110,9 @@ mod tests { let r2 = E(2); let mut m = EntityMap::new(); + assert!(!m.is_valid(r0)); m[r2] = 3; + assert!(m.is_valid(r1)); m[r1] = 5; assert_eq!(m[r1], 5); diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 5a36115fbd..27de00ea66 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,6 +1,7 @@ //! Representation of Cretonne IL functions. use types::{Type, FunctionName, Signature, VOID}; +use entity_map::EntityRef; use entities::{Ebb, NO_EBB, Inst, NO_INST, Value, NO_VALUE, ExpandedValue, StackSlot}; use instructions::*; use std::fmt::{self, Display, Formatter}; From 5c15dcdebb5127cc88a1e5b475054a83050d6a32 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 15 Jul 2016 11:33:10 -0700 Subject: [PATCH 133/968] Begin a layout module. The Layout data structure will keep track of the order of EBBs and their instructions. WIP. --- src/libcretonne/layout.rs | 142 ++++++++++++++++++++++++++++++++++++++ src/libcretonne/lib.rs | 1 + 2 files changed, 143 insertions(+) create mode 100644 src/libcretonne/layout.rs diff --git a/src/libcretonne/layout.rs b/src/libcretonne/layout.rs new file mode 100644 index 0000000000..a870e56167 --- /dev/null +++ b/src/libcretonne/layout.rs @@ -0,0 +1,142 @@ +//! Function layout. +//! +//! The order of extended basic blocks in a function and the order of instructions in an EBB is +//! determined by the `Layout` data structure defined in this module. + +use entity_map::EntityMap; +use entities::{Ebb, NO_EBB, Inst, NO_INST}; + +/// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not +/// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references +/// being defined elsewhere. +/// +/// This data structure determines: +/// +/// - The order of EBBs in the function. +/// - Which EBB contains a given instruction. +/// - The order of instructions with an EBB. +/// +/// While data dependencies are not recorded, instruction ordering does affect control +/// dependencies, so part of the semantics of the program are determined by the layout. +/// +pub struct Layout { + // Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in + // both ends by NO_EBB. + ebbs: EntityMap, + + // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, + // terminated in both ends by NO_INST. + insts: EntityMap, + + // First EBB in the layout order, or `NO_EBB` when no EBBs have been laid out. + first_ebb: Ebb, + + // Last EBB in the layout order, or `NO_EBB` when no EBBs have been laid out. + last_ebb: Ebb, +} + +impl Layout { + /// Create a new empty `Layout`. + pub fn new() -> Layout { + Layout { + ebbs: EntityMap::new(), + insts: EntityMap::new(), + first_ebb: NO_EBB, + last_ebb: NO_EBB, + } + } +} + +/// Methods for laying out EBBs. +/// +/// An unknown EBB starts out as *not inserted* in the EBB layout. The layout is a linear order of +/// inserted EBBs. Once an EBB has been inserted in the layout, instructions can be added. An EBB +/// can only be removed from the layout when it is empty. +/// +/// Since every EBB must end with a terminator instruction which cannot fall through, the layout of +/// EBBs does not affect the semantics of the program. +/// +impl Layout { + /// Is `ebb` currently part of the layout? + pub fn is_ebb_inserted(&self, ebb: Ebb) -> bool { + ebb != self.first_ebb && self.ebbs.is_valid(ebb) && self.ebbs[ebb].prev == NO_EBB + } + + /// Insert `ebb` as the last EBB in the layout. + pub fn append_ebb(&mut self, ebb: Ebb) { + assert!(!self.is_ebb_inserted(ebb), + "Cannot append EBB that is already in the layout"); + let node = &mut self.ebbs[ebb]; + assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); + node.prev = self.last_ebb; + node.next = NO_EBB; + self.last_ebb = ebb; + } + + /// Insert `ebb` in the layout before the existing EBB `before`. + pub fn insert_ebb(&mut self, ebb: Ebb, before: Ebb) { + assert!(!self.is_ebb_inserted(ebb), + "Cannot insert EBB that is already in the layout"); + assert!(self.is_ebb_inserted(before), + "EBB Insertion point not in the layout"); + let after = self.ebbs[before].prev; + self.ebbs[ebb].next = before; + self.ebbs[ebb].prev = after; + self.ebbs[before].prev = ebb; + if after != NO_EBB { + self.ebbs[after].next = ebb; + } + } +} + +#[derive(Clone, Debug, Default)] +struct EbbNode { + prev: Ebb, + next: Ebb, + first_inst: Inst, + last_inst: Inst, +} + +/// Methods for arranging instructions. +/// +/// An instruction starts out as *not inserted* in the layout. An instruction can be inserted into +/// an EBB at a given position. +impl Layout { + /// Get the EBB containing `inst`, or `None` if `inst` is not inserted in the layout. + pub fn inst_ebb(&self, inst: Inst) -> Option { + if self.insts.is_valid(inst) { + let ebb = self.insts[inst].ebb; + if ebb == NO_EBB { + None + } else { + Some(ebb) + } + } else { + None + } + } + + /// Append `inst` to the end of `ebb`. + pub fn append_inst(&self, inst: Inst, ebb: Ebb) { + assert_eq!(self.inst_ebb(inst), None); + assert!(self.is_ebb_inserted(ebb), + "Cannot append instructions to EBB not in layout"); + unimplemented!(); + } + + /// Insert `inst` before the instruction `before` in the same EBB. + pub fn insert_inst(&self, inst: Inst, before: Inst) { + assert_eq!(self.inst_ebb(inst), None); + let ebb = self.inst_ebb(before) + .expect("Instruction before insertion point not in the layout"); + assert!(ebb != NO_EBB); + unimplemented!(); + } +} + +#[derive(Clone, Debug, Default)] +struct InstNode { + ebb: Ebb, + prev: Inst, + next: Inst, +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 55d601d38b..100d3121e2 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -13,6 +13,7 @@ pub mod immediates; pub mod entities; pub mod instructions; pub mod repr; +pub mod layout; pub mod write; pub mod cfg; From 8bbc75e39f5d7f186a4190050cf00c666b1c7bca Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Sat, 16 Jul 2016 15:33:30 -0700 Subject: [PATCH 134/968] Remove extra newline --- src/tools/print_cfg.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index fc85cbebec..002c206e07 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -21,7 +21,6 @@ pub fn run(files: Vec) -> CommandResult { Ok(()) } - struct CFGPrinter { level: usize, writer: T, From b9975f77af140ad43447f9842b11699f683251c2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 14:25:28 -0700 Subject: [PATCH 135/968] Add an EntityRef::wrap() method. This is the opposite of unwrap(). It converts the ad-hoc null references like NO_EBB and NO_INST into the more standard Option type which unfortunately takes twice as much space in data structures. --- src/libcretonne/entity_map.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index fbf8109d9f..2aa101f5c0 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -11,13 +11,31 @@ use std::ops::{Index, IndexMut}; /// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key /// of an `EntityMap`. -pub trait EntityRef: Copy { +pub trait EntityRef: Copy + Eq { /// Create a new entity reference from a small integer. /// This should crash if the requested index is not representable. fn new(usize) -> Self; /// Get the index that was used to create this entity reference. fn index(self) -> usize; + + /// Convert an `EntityRef` to an `Optional` by using the default value as the null + /// reference. + /// + /// Entity references are often used in compact data structures like linked lists where a + /// sentinel 'null' value is needed. Normally we would use an `Optional` for that, but + /// currently that uses twice the memory of a plain `EntityRef`. + /// + /// This method is called `wrap()` because it is the inverse of `unwrap()`. + fn wrap(self) -> Option + where Self: Default + { + if self == Self::default() { + None + } else { + Some(self) + } + } } /// A mapping `K -> V` for densely indexed entity references. @@ -91,7 +109,7 @@ mod tests { use super::*; // EntityRef impl for testing. - #[derive(Clone, Copy)] + #[derive(Clone, Copy, PartialEq, Eq)] struct E(u32); impl EntityRef for E { From 28c1eda4f691c5b0c7d23004b32cfd746c817540 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 18 Jul 2016 14:28:00 -0700 Subject: [PATCH 136/968] Move test utility functions to their own module --- src/libcretonne/cfg.rs | 41 +++++-------------------- src/libcretonne/lib.rs | 2 ++ src/libcretonne/test_utils/make_inst.rs | 35 +++++++++++++++++++++ src/libcretonne/test_utils/mod.rs | 3 ++ 4 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 src/libcretonne/test_utils/make_inst.rs create mode 100644 src/libcretonne/test_utils/mod.rs diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 8c423c23ac..668f5b51ef 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -94,37 +94,10 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { - use instructions::*; - use entity_map::EntityRef; - use entities::{Ebb, Inst, NO_VALUE}; - use repr::Function; use super::*; - use types; + use repr::Function; - // Some instructions will be re-used in several tests. - - fn jump(func: &mut Function, dest: Ebb) -> Inst { - func.make_inst(InstructionData::Jump { - opcode: Opcode::Jump, - ty: types::VOID, - data: Box::new(JumpData { - destination: dest, - arguments: VariableArgs::new(), - }), - }) - } - - fn branch(func: &mut Function, dest: Ebb) -> Inst { - func.make_inst(InstructionData::Branch { - opcode: Opcode::Brz, - ty: types::VOID, - data: Box::new(BranchData { - arg: NO_VALUE, - destination: dest, - arguments: VariableArgs::new(), - }), - }) - } + use test_utils::make_inst; #[test] fn empty() { @@ -145,7 +118,7 @@ mod tests { let mut fun_ebbs = func.ebbs_numerically(); for (ebb, predecessors) in nodes { - assert_eq!(ebb.index(), fun_ebbs.next().unwrap().index()); + assert_eq!(*ebb, fun_ebbs.next().unwrap()); assert_eq!(predecessors.len(), 0); } } @@ -157,16 +130,16 @@ mod tests { let ebb1 = func.make_ebb(); let ebb2 = func.make_ebb(); - let br_ebb0_ebb2 = branch(&mut func, ebb2); + let br_ebb0_ebb2 = make_inst::branch(&mut func, ebb2); func.append_inst(ebb0, br_ebb0_ebb2); - let jmp_ebb0_ebb1 = jump(&mut func, ebb1); + let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); func.append_inst(ebb0, jmp_ebb0_ebb1); - let br_ebb1_ebb1 = branch(&mut func, ebb1); + let br_ebb1_ebb1 = make_inst::branch(&mut func, ebb1); func.append_inst(ebb1, br_ebb1_ebb1); - let jmp_ebb1_ebb2 = jump(&mut func, ebb2); + let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); func.append_inst(ebb1, jmp_ebb1_ebb2); let cfg = ControlFlowGraph::new(&func); diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 100d3121e2..d5c501477c 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -18,3 +18,5 @@ pub mod write; pub mod cfg; pub mod entity_map; + +#[cfg(test)] pub mod test_utils; diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs new file mode 100644 index 0000000000..167512b8b5 --- /dev/null +++ b/src/libcretonne/test_utils/make_inst.rs @@ -0,0 +1,35 @@ +///! Helper functions for generating dummy instructions. + +use repr::Function; +use entities::{Ebb, Inst, NO_VALUE}; +use instructions::{ + InstructionData, + Opcode, + VariableArgs, + JumpData, + BranchData, +}; +use types; + +pub fn jump(func: &mut Function, dest: Ebb) -> Inst { + func.make_inst(InstructionData::Jump { + opcode: Opcode::Jump, + ty: types::VOID, + data: Box::new(JumpData { + destination: dest, + arguments: VariableArgs::new(), + }), + }) +} + +pub fn branch(func: &mut Function, dest: Ebb) -> Inst { + func.make_inst(InstructionData::Branch { + opcode: Opcode::Brz, + ty: types::VOID, + data: Box::new(BranchData { + arg: NO_VALUE, + destination: dest, + arguments: VariableArgs::new(), + }), + }) +} diff --git a/src/libcretonne/test_utils/mod.rs b/src/libcretonne/test_utils/mod.rs new file mode 100644 index 0000000000..49e6b23be8 --- /dev/null +++ b/src/libcretonne/test_utils/mod.rs @@ -0,0 +1,3 @@ +///! Test utility functions. + +pub mod make_inst; From a641bdb1f262f32f125c77d14edb2972600bb7f0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 14:06:03 -0700 Subject: [PATCH 137/968] Add Layout::ebbs() and the corresponding iterator. Implement some tests, fix bugs in is_ebb_inserted(). --- src/libcretonne/layout.rs | 101 +++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/src/libcretonne/layout.rs b/src/libcretonne/layout.rs index a870e56167..15748eda6a 100644 --- a/src/libcretonne/layout.rs +++ b/src/libcretonne/layout.rs @@ -3,7 +3,8 @@ //! The order of extended basic blocks in a function and the order of instructions in an EBB is //! determined by the `Layout` data structure defined in this module. -use entity_map::EntityMap; +use std::iter::Iterator; +use entity_map::{EntityMap, EntityRef}; use entities::{Ebb, NO_EBB, Inst, NO_INST}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not @@ -28,11 +29,11 @@ pub struct Layout { // terminated in both ends by NO_INST. insts: EntityMap, - // First EBB in the layout order, or `NO_EBB` when no EBBs have been laid out. - first_ebb: Ebb, + // First EBB in the layout order, or `None` when no EBBs have been laid out. + first_ebb: Option, - // Last EBB in the layout order, or `NO_EBB` when no EBBs have been laid out. - last_ebb: Ebb, + // Last EBB in the layout order, or `None` when no EBBs have been laid out. + last_ebb: Option, } impl Layout { @@ -41,8 +42,8 @@ impl Layout { Layout { ebbs: EntityMap::new(), insts: EntityMap::new(), - first_ebb: NO_EBB, - last_ebb: NO_EBB, + first_ebb: None, + last_ebb: None, } } } @@ -59,7 +60,7 @@ impl Layout { impl Layout { /// Is `ebb` currently part of the layout? pub fn is_ebb_inserted(&self, ebb: Ebb) -> bool { - ebb != self.first_ebb && self.ebbs.is_valid(ebb) && self.ebbs[ebb].prev == NO_EBB + Some(ebb) == self.first_ebb || (self.ebbs.is_valid(ebb) && self.ebbs[ebb].prev != NO_EBB) } /// Insert `ebb` as the last EBB in the layout. @@ -68,9 +69,12 @@ impl Layout { "Cannot append EBB that is already in the layout"); let node = &mut self.ebbs[ebb]; assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); - node.prev = self.last_ebb; + node.prev = self.last_ebb.unwrap_or_default(); node.next = NO_EBB; - self.last_ebb = ebb; + self.last_ebb = Some(ebb); + if self.first_ebb.is_none() { + self.first_ebb = Some(ebb); + } } /// Insert `ebb` in the layout before the existing EBB `before`. @@ -83,10 +87,20 @@ impl Layout { self.ebbs[ebb].next = before; self.ebbs[ebb].prev = after; self.ebbs[before].prev = ebb; - if after != NO_EBB { + if after == NO_EBB { + self.first_ebb = Some(ebb); + } else { self.ebbs[after].next = ebb; } } + + /// Return an iterator over all EBBs in layout order. + pub fn ebbs<'a>(&'a self) -> Ebbs<'a> { + Ebbs { + layout: self, + next: self.first_ebb, + } + } } #[derive(Clone, Debug, Default)] @@ -97,6 +111,26 @@ struct EbbNode { last_inst: Inst, } +/// Iterate over EBBs in layout order. See `Layout::ebbs()`. +pub struct Ebbs<'a> { + layout: &'a Layout, + next: Option, +} + +impl<'a> Iterator for Ebbs<'a> { + type Item = Ebb; + + fn next(&mut self) -> Option { + match self.next { + Some(ebb) => { + self.next = self.layout.ebbs[ebb].next.wrap(); + Some(ebb) + } + None => None, + } + } +} + /// Methods for arranging instructions. /// /// An instruction starts out as *not inserted* in the layout. An instruction can be inserted into @@ -140,3 +174,48 @@ struct InstNode { prev: Inst, next: Inst, } + +#[cfg(test)] +mod tests { + use super::Layout; + use entity_map::EntityRef; + use entities::Ebb; + + #[test] + fn insert_ebb() { + let mut layout = Layout::new(); + let e0 = Ebb::new(0); + let e1 = Ebb::new(1); + let e2 = Ebb::new(2); + + { + let imm = &layout; + assert!(!imm.is_ebb_inserted(e0)); + assert!(!imm.is_ebb_inserted(e1)); + + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, []); + } + + layout.append_ebb(e1); + assert!(!layout.is_ebb_inserted(e0)); + assert!(layout.is_ebb_inserted(e1)); + assert!(!layout.is_ebb_inserted(e2)); + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, [e1]); + + layout.insert_ebb(e2, e1); + assert!(!layout.is_ebb_inserted(e0)); + assert!(layout.is_ebb_inserted(e1)); + assert!(layout.is_ebb_inserted(e2)); + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, [e2, e1]); + + layout.insert_ebb(e0, e1); + assert!(layout.is_ebb_inserted(e0)); + assert!(layout.is_ebb_inserted(e1)); + assert!(layout.is_ebb_inserted(e2)); + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, [e2, e0, e1]); + } +} From 21c2474d4dac59256d7d2a51a19b2ea41d53cffb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 15:04:37 -0700 Subject: [PATCH 138/968] Implement instruction order. Each EBB has a linked list of instructions in layout order. --- src/libcretonne/layout.rs | 104 +++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/src/libcretonne/layout.rs b/src/libcretonne/layout.rs index 15748eda6a..d803858d0a 100644 --- a/src/libcretonne/layout.rs +++ b/src/libcretonne/layout.rs @@ -139,32 +139,53 @@ impl Layout { /// Get the EBB containing `inst`, or `None` if `inst` is not inserted in the layout. pub fn inst_ebb(&self, inst: Inst) -> Option { if self.insts.is_valid(inst) { - let ebb = self.insts[inst].ebb; - if ebb == NO_EBB { - None - } else { - Some(ebb) - } + self.insts[inst].ebb.wrap() } else { None } } /// Append `inst` to the end of `ebb`. - pub fn append_inst(&self, inst: Inst, ebb: Ebb) { + pub fn append_inst(&mut self, inst: Inst, ebb: Ebb) { assert_eq!(self.inst_ebb(inst), None); assert!(self.is_ebb_inserted(ebb), "Cannot append instructions to EBB not in layout"); - unimplemented!(); + let ebb_node = &mut self.ebbs[ebb]; + let inst_node = &mut self.insts[inst]; + inst_node.ebb = ebb; + inst_node.prev = ebb_node.last_inst; + assert_eq!(inst_node.next, NO_INST); + if ebb_node.first_inst == NO_INST { + ebb_node.first_inst = inst; + } } /// Insert `inst` before the instruction `before` in the same EBB. - pub fn insert_inst(&self, inst: Inst, before: Inst) { + pub fn insert_inst(&mut self, inst: Inst, before: Inst) { assert_eq!(self.inst_ebb(inst), None); let ebb = self.inst_ebb(before) .expect("Instruction before insertion point not in the layout"); - assert!(ebb != NO_EBB); - unimplemented!(); + let after = self.insts[before].prev; + { + let inst_node = &mut self.insts[inst]; + inst_node.ebb = ebb; + inst_node.next = before; + inst_node.prev = after; + } + self.insts[before].prev = inst; + if after == NO_INST { + self.ebbs[ebb].first_inst = inst; + } else { + self.insts[after].next = inst; + } + } + + /// Iterate over the instructions in `ebb` in layout order. + pub fn ebb_insts<'a>(&'a self, ebb: Ebb) -> Insts<'a> { + Insts { + layout: self, + next: self.ebbs[ebb].first_inst.wrap(), + } } } @@ -175,11 +196,31 @@ struct InstNode { next: Inst, } +/// Iterate over instructions in an EBB in layout order. See `Layout::ebb_insts()`. +pub struct Insts<'a> { + layout: &'a Layout, + next: Option, +} + +impl<'a> Iterator for Insts<'a> { + type Item = Inst; + + fn next(&mut self) -> Option { + match self.next { + Some(inst) => { + self.next = self.layout.insts[inst].next.wrap(); + Some(inst) + } + None => None, + } + } +} + #[cfg(test)] mod tests { use super::Layout; use entity_map::EntityRef; - use entities::Ebb; + use entities::{Ebb, Inst}; #[test] fn insert_ebb() { @@ -218,4 +259,43 @@ mod tests { let v: Vec = layout.ebbs().collect(); assert_eq!(v, [e2, e0, e1]); } + + #[test] + fn insert_inst() { + let mut layout = Layout::new(); + let e1 = Ebb::new(1); + + layout.append_ebb(e1); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, []); + + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let i2 = Inst::new(2); + + assert_eq!(layout.inst_ebb(i0), None); + assert_eq!(layout.inst_ebb(i1), None); + assert_eq!(layout.inst_ebb(i2), None); + + layout.append_inst(i1, e1); + assert_eq!(layout.inst_ebb(i0), None); + assert_eq!(layout.inst_ebb(i1), Some(e1)); + assert_eq!(layout.inst_ebb(i2), None); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, [i1]); + + layout.insert_inst(i2, i1); + assert_eq!(layout.inst_ebb(i0), None); + assert_eq!(layout.inst_ebb(i1), Some(e1)); + assert_eq!(layout.inst_ebb(i2), Some(e1)); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, [i2, i1]); + + layout.insert_inst(i0, i1); + assert_eq!(layout.inst_ebb(i0), Some(e1)); + assert_eq!(layout.inst_ebb(i1), Some(e1)); + assert_eq!(layout.inst_ebb(i2), Some(e1)); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, [i2, i0, i1]); + } } From 2f74efd5fc3258a6a1f3a9ddbc95c4109de16086 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 18:09:31 -0700 Subject: [PATCH 139/968] More layout tests and bugfixes. Fix bugs in append methods. Linked lists are hard. --- src/libcretonne/layout.rs | 132 +++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 10 deletions(-) diff --git a/src/libcretonne/layout.rs b/src/libcretonne/layout.rs index d803858d0a..e0ca2cc3cc 100644 --- a/src/libcretonne/layout.rs +++ b/src/libcretonne/layout.rs @@ -67,14 +67,18 @@ impl Layout { pub fn append_ebb(&mut self, ebb: Ebb) { assert!(!self.is_ebb_inserted(ebb), "Cannot append EBB that is already in the layout"); - let node = &mut self.ebbs[ebb]; - assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); - node.prev = self.last_ebb.unwrap_or_default(); - node.next = NO_EBB; - self.last_ebb = Some(ebb); - if self.first_ebb.is_none() { + { + let node = &mut self.ebbs[ebb]; + assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); + node.prev = self.last_ebb.unwrap_or_default(); + node.next = NO_EBB; + } + if let Some(last) = self.last_ebb { + self.ebbs[last].next = ebb; + } else { self.first_ebb = Some(ebb); } + self.last_ebb = Some(ebb); } /// Insert `ebb` in the layout before the existing EBB `before`. @@ -151,13 +155,18 @@ impl Layout { assert!(self.is_ebb_inserted(ebb), "Cannot append instructions to EBB not in layout"); let ebb_node = &mut self.ebbs[ebb]; - let inst_node = &mut self.insts[inst]; - inst_node.ebb = ebb; - inst_node.prev = ebb_node.last_inst; - assert_eq!(inst_node.next, NO_INST); + { + let inst_node = &mut self.insts[inst]; + inst_node.ebb = ebb; + inst_node.prev = ebb_node.last_inst; + assert_eq!(inst_node.next, NO_INST); + } if ebb_node.first_inst == NO_INST { ebb_node.first_inst = inst; + } else { + self.insts[ebb_node.last_inst].next = inst; } + ebb_node.last_inst = inst; } /// Insert `inst` before the instruction `before` in the same EBB. @@ -222,6 +231,44 @@ mod tests { use entity_map::EntityRef; use entities::{Ebb, Inst}; + #[test] + fn append_ebb() { + let mut layout = Layout::new(); + let e0 = Ebb::new(0); + let e1 = Ebb::new(1); + let e2 = Ebb::new(2); + + { + let imm = &layout; + assert!(!imm.is_ebb_inserted(e0)); + assert!(!imm.is_ebb_inserted(e1)); + + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, []); + } + + layout.append_ebb(e1); + assert!(!layout.is_ebb_inserted(e0)); + assert!(layout.is_ebb_inserted(e1)); + assert!(!layout.is_ebb_inserted(e2)); + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, [e1]); + + layout.append_ebb(e2); + assert!(!layout.is_ebb_inserted(e0)); + assert!(layout.is_ebb_inserted(e1)); + assert!(layout.is_ebb_inserted(e2)); + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, [e1, e2]); + + layout.append_ebb(e0); + assert!(layout.is_ebb_inserted(e0)); + assert!(layout.is_ebb_inserted(e1)); + assert!(layout.is_ebb_inserted(e2)); + let v: Vec = layout.ebbs().collect(); + assert_eq!(v, [e1, e2, e0]); + } + #[test] fn insert_ebb() { let mut layout = Layout::new(); @@ -260,6 +307,45 @@ mod tests { assert_eq!(v, [e2, e0, e1]); } + #[test] + fn append_inst() { + let mut layout = Layout::new(); + let e1 = Ebb::new(1); + + layout.append_ebb(e1); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, []); + + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let i2 = Inst::new(2); + + assert_eq!(layout.inst_ebb(i0), None); + assert_eq!(layout.inst_ebb(i1), None); + assert_eq!(layout.inst_ebb(i2), None); + + layout.append_inst(i1, e1); + assert_eq!(layout.inst_ebb(i0), None); + assert_eq!(layout.inst_ebb(i1), Some(e1)); + assert_eq!(layout.inst_ebb(i2), None); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, [i1]); + + layout.append_inst(i2, e1); + assert_eq!(layout.inst_ebb(i0), None); + assert_eq!(layout.inst_ebb(i1), Some(e1)); + assert_eq!(layout.inst_ebb(i2), Some(e1)); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, [i1, i2]); + + layout.append_inst(i0, e1); + assert_eq!(layout.inst_ebb(i0), Some(e1)); + assert_eq!(layout.inst_ebb(i1), Some(e1)); + assert_eq!(layout.inst_ebb(i2), Some(e1)); + let v: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v, [i1, i2, i0]); + } + #[test] fn insert_inst() { let mut layout = Layout::new(); @@ -298,4 +384,30 @@ mod tests { let v: Vec = layout.ebb_insts(e1).collect(); assert_eq!(v, [i2, i0, i1]); } + + #[test] + fn multiple_ebbs() { + let mut layout = Layout::new(); + + let e0 = Ebb::new(0); + let e1 = Ebb::new(1); + + layout.append_ebb(e0); + layout.append_ebb(e1); + + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let i3 = Inst::new(3); + + layout.append_inst(i0, e0); + layout.append_inst(i1, e0); + layout.append_inst(i2, e1); + layout.append_inst(i3, e1); + + let v0: Vec = layout.ebb_insts(e0).collect(); + let v1: Vec = layout.ebb_insts(e1).collect(); + assert_eq!(v0, [i0, i1]); + assert_eq!(v1, [i2, i3]); + } } From e926674b4e48ca61ff3d372e0694ae4470cc5007 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 18:23:32 -0700 Subject: [PATCH 140/968] Replace inst_order with Layout in Function. The Layout also handles EBB layout, so new append_ebb calls are necessary. - Rewrite callers to use the public data member 'layout'. - Implement Debug for Function in terms of the write module to avoid deriving it. --- src/libcretonne/cfg.rs | 23 +++++---- src/libcretonne/repr.rs | 101 ++++++++------------------------------- src/libcretonne/write.rs | 3 +- src/libreader/parser.rs | 3 +- src/tools/print_cfg.rs | 33 ++++++++----- 5 files changed, 58 insertions(+), 105 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 668f5b51ef..af877f4f9b 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -55,7 +55,7 @@ impl ControlFlowGraph { for ebb in func.ebbs_numerically() { // Flips to true when a terminating instruction is seen. So that if additional // instructions occur an error may be returned. - for inst in func.ebb_insts(ebb) { + for inst in func.layout.ebb_insts(ebb) { match func[inst] { InstructionData::Branch { ty: _, opcode: _, ref data } => { cfg.add_predecessor(data.destination, (ebb, inst)); @@ -109,9 +109,13 @@ mod tests { #[test] fn no_predecessors() { let mut func = Function::new(); - func.make_ebb(); - func.make_ebb(); - func.make_ebb(); + let ebb0 = func.make_ebb(); + let ebb1 = func.make_ebb(); + let ebb2 = func.make_ebb(); + func.layout.append_ebb(ebb0); + func.layout.append_ebb(ebb1); + func.layout.append_ebb(ebb2); + let cfg = ControlFlowGraph::new(&func); let nodes = cfg.iter().collect::>(); assert_eq!(nodes.len(), 3); @@ -129,18 +133,21 @@ mod tests { let ebb0 = func.make_ebb(); let ebb1 = func.make_ebb(); let ebb2 = func.make_ebb(); + func.layout.append_ebb(ebb0); + func.layout.append_ebb(ebb1); + func.layout.append_ebb(ebb2); let br_ebb0_ebb2 = make_inst::branch(&mut func, ebb2); - func.append_inst(ebb0, br_ebb0_ebb2); + func.layout.append_inst(br_ebb0_ebb2, ebb0); let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); - func.append_inst(ebb0, jmp_ebb0_ebb1); + func.layout.append_inst(jmp_ebb0_ebb1, ebb0); let br_ebb1_ebb1 = make_inst::branch(&mut func, ebb1); - func.append_inst(ebb1, br_ebb1_ebb1); + func.layout.append_inst(br_ebb1_ebb1, ebb1); let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); - func.append_inst(ebb1, jmp_ebb1_ebb2); + func.layout.append_inst(jmp_ebb1_ebb2, ebb1); let cfg = ControlFlowGraph::new(&func); let ebb0_predecessors = cfg.get_predecessors(ebb0).unwrap(); diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index 27de00ea66..ac47a540fe 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -2,9 +2,10 @@ use types::{Type, FunctionName, Signature, VOID}; use entity_map::EntityRef; -use entities::{Ebb, NO_EBB, Inst, NO_INST, Value, NO_VALUE, ExpandedValue, StackSlot}; +use entities::{Ebb, NO_EBB, Inst, Value, NO_VALUE, ExpandedValue, StackSlot}; use instructions::*; -use std::fmt::{self, Display, Formatter}; +use layout::Layout; +use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Index, IndexMut}; /// A function. @@ -12,7 +13,6 @@ use std::ops::{Index, IndexMut}; /// The `Function` struct owns all of its instructions and extended basic blocks, and it works as a /// container for those objects by implementing both `Index` and `Index`. /// -#[derive(Debug)] pub struct Function { /// Name of this function. Mostly used by `.cton` files. pub name: FunctionName, @@ -38,9 +38,8 @@ pub struct Function { /// Others index into this table. extended_values: Vec, - // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, - // terminated in both ends by NO_INST. - inst_order: Vec, + /// Layout of EBBs and instructions in the function body. + pub layout: Layout, } impl Function { @@ -54,7 +53,7 @@ impl Function { instructions: Vec::new(), extended_basic_blocks: Vec::new(), extended_values: Vec::new(), - inst_order: Vec::new(), + layout: Layout::new(), } } @@ -94,11 +93,6 @@ impl Function { pub fn make_inst(&mut self, data: InstructionData) -> Inst { let inst = Inst::new(self.instructions.len()); self.instructions.push(data); - self.inst_order.push(InstNode { - prev: NO_INST, - next: NO_INST, - }); - debug_assert_eq!(self.instructions.len(), self.inst_order.len()); inst } @@ -234,32 +228,6 @@ impl Function { } } - /// Append an instruction to a basic block. - pub fn append_inst(&mut self, ebb: Ebb, inst: Inst) { - let old_last = self[ebb].last_inst; - - self.inst_order[inst.index()] = InstNode { - prev: old_last, - next: NO_INST, - }; - - if old_last == NO_INST { - assert!(self[ebb].first_inst == NO_INST); - self[ebb].first_inst = inst; - } else { - self.inst_order[old_last.index()].next = inst; - } - self[ebb].last_inst = inst; - } - - /// Iterate through the instructions in `ebb`. - pub fn ebb_insts<'a>(&'a self, ebb: Ebb) -> EbbInsts<'a> { - EbbInsts { - func: self, - cur: self[ebb].first_inst, - } - } - // Values. /// Allocate an extended value entry. @@ -286,6 +254,13 @@ impl Function { } } +impl Debug for Function { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + use write::function_to_string; + fmt.write_str(&function_to_string(self)) + } +} + // ====--------------------------------------------------------------------------------------====// // // Stack slot implementation. @@ -362,12 +337,6 @@ pub struct EbbData { /// Last argument to this EBB, or `NO_VALUE` if the block has no arguments. last_arg: Value, - - /// First instruction in this block, or `NO_INST`. - first_inst: Inst, - - /// Last instruction in this block, or `NO_INST`. - last_inst: Inst, } impl EbbData { @@ -375,8 +344,6 @@ impl EbbData { EbbData { first_arg: NO_VALUE, last_arg: NO_VALUE, - first_inst: NO_INST, - last_inst: NO_INST, } } } @@ -395,26 +362,6 @@ impl IndexMut for Function { } } -pub struct EbbInsts<'a> { - func: &'a Function, - cur: Inst, -} - -impl<'a> Iterator for EbbInsts<'a> { - type Item = Inst; - - fn next(&mut self) -> Option { - let prev = self.cur; - if prev == NO_INST { - None - } else { - // Advance self.cur to the next inst. - self.cur = self.func.inst_order[prev.index()].next; - Some(prev) - } - } -} - /// Iterate through all EBBs in a function in numerical order. /// This order is stable, but has little significance to the semantics of the function. pub struct NumericalEbbs { @@ -460,13 +407,6 @@ impl IndexMut for Function { } } -/// A node in a double linked list of instructions is a basic block. -#[derive(Debug)] -struct InstNode { - prev: Inst, - next: Inst, -} - // ====--------------------------------------------------------------------------------------====// // // Value implementation. @@ -590,6 +530,7 @@ mod tests { let ebb = func.make_ebb(); assert_eq!(ebb.to_string(), "ebb0"); assert_eq!(func.ebb_args(ebb).next(), None); + func.layout.append_ebb(ebb); let arg1 = func.append_ebb_arg(ebb, types::F32); assert_eq!(arg1.to_string(), "vx0"); @@ -612,33 +553,29 @@ mod tests { assert_eq!(ebbs.next(), Some(ebb)); assert_eq!(ebbs.next(), None); - assert_eq!(func.ebb_insts(ebb).next(), None); + assert_eq!(func.layout.ebb_insts(ebb).next(), None); let inst = func.make_inst(InstructionData::Nullary { opcode: Opcode::Iconst, ty: types::I32, }); - func.append_inst(ebb, inst); + func.layout.append_inst(inst, ebb); { - let mut ii = func.ebb_insts(ebb); + let mut ii = func.layout.ebb_insts(ebb); assert_eq!(ii.next(), Some(inst)); assert_eq!(ii.next(), None); } - assert_eq!(func[ebb].first_inst, inst); - assert_eq!(func[ebb].last_inst, inst); let inst2 = func.make_inst(InstructionData::Nullary { opcode: Opcode::Iconst, ty: types::I32, }); - func.append_inst(ebb, inst2); + func.layout.append_inst(inst2, ebb); { - let mut ii = func.ebb_insts(ebb); + let mut ii = func.layout.ebb_insts(ebb); assert_eq!(ii.next(), Some(inst)); assert_eq!(ii.next(), Some(inst2)); assert_eq!(ii.next(), None); } - assert_eq!(func[ebb].first_inst, inst); - assert_eq!(func[ebb].last_inst, inst2); } } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index e49956970a..73cb341d96 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -114,7 +114,7 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { pub fn write_ebb(w: &mut Write, func: &Function, ebb: Ebb) -> Result { try!(write_ebb_header(w, func, ebb)); - for inst in func.ebb_insts(ebb) { + for inst in func.layout.ebb_insts(ebb) { try!(write_instruction(w, func, inst)); } Ok(()) @@ -251,6 +251,7 @@ mod tests { "function foo() {\n ss0 = stack_slot 4\n}\n"); let ebb = f.make_ebb(); + f.layout.append_ebb(ebb); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 34dd15f735..2ba382871d 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -101,6 +101,7 @@ impl Context { // Allocate a new EBB and add a mapping src_ebb -> Ebb. fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { let ebb = self.function.make_ebb(); + self.function.layout.append_ebb(ebb); if self.ebbs.insert(src_ebb, ebb).is_some() { err!(loc, "duplicate EBB: {}", src_ebb) } else { @@ -704,7 +705,7 @@ impl<'a> Parser<'a> { let ctrl_typevar = try!(self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data)); let inst = ctx.function.make_inst(inst_data); let num_results = ctx.function.make_inst_results(inst, ctrl_typevar); - ctx.function.append_inst(ebb, inst); + ctx.function.layout.append_inst(inst, ebb); ctx.add_inst_loc(inst, &opcode_loc); if results.len() != num_results { diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 002c206e07..6fe51f47e1 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -29,7 +29,11 @@ struct CFGPrinter { impl CFGPrinter { pub fn new(writer: T) -> CFGPrinter { - CFGPrinter{level: 0, writer: writer, buffer: String::new()} + CFGPrinter { + level: 0, + writer: writer, + buffer: String::new(), + } } pub fn print(&mut self, func: &Function) -> Result<(), String> { @@ -58,7 +62,7 @@ impl CFGPrinter { fn append(&mut self, s: &str) { let mut indent = String::new(); - for _ in 0 .. self.level { + for _ in 0..self.level { indent = indent + " "; } self.buffer.push_str(&(indent + s)); @@ -103,23 +107,24 @@ impl CFGPrinter { fn ebb_subgraphs(&mut self, func: &Function) { for ebb in func.ebbs_numerically() { - let inst_data = func.ebb_insts(ebb) + let inst_data = func.layout + .ebb_insts(ebb) .filter(|inst| { match func[*inst] { - InstructionData::Branch{ ty: _, opcode: _, data: _ } => true, - InstructionData::Jump{ ty: _, opcode: _, data: _ } => true, - _ => false + InstructionData::Branch { ty: _, opcode: _, data: _ } => true, + InstructionData::Jump { ty: _, opcode: _, data: _ } => true, + _ => false, } }) .map(|inst| { let op = match func[inst] { - InstructionData::Branch{ ty: _, opcode, ref data } => { + InstructionData::Branch { ty: _, opcode, ref data } => { Some((opcode, data.destination)) - }, - InstructionData::Jump{ ty: _, opcode, ref data } => { + } + InstructionData::Jump { ty: _, opcode, ref data } => { Some((opcode, data.destination)) - }, - _ => None + } + _ => None, }; (inst, op) }) @@ -132,7 +137,10 @@ impl CFGPrinter { } self.append(&format!("{} [shape=record, label=\"{}{}{}\"]", - ebb, "{", insts.join(" | "), "}")); + ebb, + "{", + insts.join(" | "), + "}")); self.newline(); } } @@ -145,7 +153,6 @@ impl CFGPrinter { } } } - } fn print_cfg(filename: String) -> CommandResult { From 4ee2ab5042c821dd7b9fa47df9cd63f86b74b784 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 18:47:42 -0700 Subject: [PATCH 141/968] Implement IntoIterator for Layout. --- src/libcretonne/layout.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libcretonne/layout.rs b/src/libcretonne/layout.rs index e0ca2cc3cc..3cc2ffc03e 100644 --- a/src/libcretonne/layout.rs +++ b/src/libcretonne/layout.rs @@ -3,7 +3,7 @@ //! The order of extended basic blocks in a function and the order of instructions in an EBB is //! determined by the `Layout` data structure defined in this module. -use std::iter::Iterator; +use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; use entities::{Ebb, NO_EBB, Inst, NO_INST}; @@ -135,6 +135,16 @@ impl<'a> Iterator for Ebbs<'a> { } } +/// Use a layout reference in a for loop. +impl<'a> IntoIterator for &'a Layout { + type Item = Ebb; + type IntoIter = Ebbs<'a>; + + fn into_iter(self) -> Ebbs<'a> { + self.ebbs() + } +} + /// Methods for arranging instructions. /// /// An instruction starts out as *not inserted* in the layout. An instruction can be inserted into @@ -267,6 +277,15 @@ mod tests { assert!(layout.is_ebb_inserted(e2)); let v: Vec = layout.ebbs().collect(); assert_eq!(v, [e1, e2, e0]); + + { + let imm = &layout; + let mut v = Vec::new(); + for e in imm { + v.push(e); + } + assert_eq!(v, [e1, e2, e0]); + } } #[test] From 8c58fe463169c596b3200e4b4ee4505bfe681665 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 18 Jul 2016 18:52:35 -0700 Subject: [PATCH 142/968] Use EBB layout order almost everywhere. The ebbs_numerically() function was a workaround for the unimplemented EBB layout order. --- src/libcretonne/cfg.rs | 6 +++--- src/libcretonne/write.rs | 2 +- src/libreader/parser.rs | 2 +- src/tools/print_cfg.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index af877f4f9b..3a5f554375 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -48,11 +48,11 @@ impl ControlFlowGraph { // Even ebbs without predecessors should show up in the CFG, albeit // with no entires. - for ebb in func.ebbs_numerically() { + for ebb in &func.layout { cfg.init_ebb(ebb); } - for ebb in func.ebbs_numerically() { + for ebb in &func.layout { // Flips to true when a terminating instruction is seen. So that if additional // instructions occur an error may be returned. for inst in func.layout.ebb_insts(ebb) { @@ -120,7 +120,7 @@ mod tests { let nodes = cfg.iter().collect::>(); assert_eq!(nodes.len(), 3); - let mut fun_ebbs = func.ebbs_numerically(); + let mut fun_ebbs = func.layout.ebbs(); for (ebb, predecessors) in nodes { assert_eq!(*ebb, fun_ebbs.next().unwrap()); assert_eq!(predecessors.len(), 0); diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 73cb341d96..c43634a636 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -16,7 +16,7 @@ pub fn write_function(w: &mut Write, func: &Function) -> Result { try!(write_spec(w, func)); try!(writeln!(w, " {{")); let mut any = try!(write_preamble(w, func)); - for ebb in func.ebbs_numerically() { + for ebb in &func.layout { if any { try!(writeln!(w, "")); } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 2ba382871d..95eb91dfd2 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -1116,7 +1116,7 @@ mod tests { .unwrap(); assert_eq!(func.name, "ebbs"); - let mut ebbs = func.ebbs_numerically(); + let mut ebbs = func.layout.ebbs(); let ebb0 = ebbs.next().unwrap(); assert_eq!(func.ebb_args(ebb0).next(), None); diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 6fe51f47e1..0709b358fd 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -106,7 +106,7 @@ impl CFGPrinter { } fn ebb_subgraphs(&mut self, func: &Function) { - for ebb in func.ebbs_numerically() { + for ebb in &func.layout { let inst_data = func.layout .ebb_insts(ebb) .filter(|inst| { From f63d7941ede9977847c3ea39f0ead12b63b39bb3 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 18 Jul 2016 20:30:33 -0700 Subject: [PATCH 143/968] Fix formatting --- src/libcretonne/lib.rs | 2 +- src/libcretonne/test_utils/make_inst.rs | 10 ++-------- src/libcretonne/test_utils/mod.rs | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index d5c501477c..0223e4a4b0 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -19,4 +19,4 @@ pub mod cfg; pub mod entity_map; -#[cfg(test)] pub mod test_utils; +#[cfg(test)]pub mod test_utils; diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index 167512b8b5..a12fb47191 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -1,14 +1,8 @@ -///! Helper functions for generating dummy instructions. +//! Helper functions for generating dummy instructions. use repr::Function; use entities::{Ebb, Inst, NO_VALUE}; -use instructions::{ - InstructionData, - Opcode, - VariableArgs, - JumpData, - BranchData, -}; +use instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; use types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { diff --git a/src/libcretonne/test_utils/mod.rs b/src/libcretonne/test_utils/mod.rs index 49e6b23be8..6273573d6d 100644 --- a/src/libcretonne/test_utils/mod.rs +++ b/src/libcretonne/test_utils/mod.rs @@ -1,3 +1,3 @@ -///! Test utility functions. +//! Test utility functions. pub mod make_inst; From d64e7fb576a86828e84007920fe1555cbb610d68 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 19 Jul 2016 09:25:05 -0700 Subject: [PATCH 144/968] Don't require Clone + Default for EntityMap values. There are two kinds of entity maps: - A primary map is used to allocate entity references and store the primary entity data. This map only grows when adding new entities with 'push'. - A secondary map contains additional information about entities in a primary map. This map always grows with 'ensure', making default entries for any unknown primary entities. Only require the 'Default + Clone' traits for values stored in a secondary map. Also remove the 'grow automatically' feature of the IndexMut implementation. This means that clients need to call 'ensure' whenever using a potentially unknown entity reference. The 'grow automatically' feature could not be implemented for the Index trait, and it seems unfortunate to have different semantics for Index and IndexMut. --- src/libcretonne/entity_map.rs | 55 +++++++++++++++++++++++------------ src/libcretonne/layout.rs | 13 +++++---- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 2aa101f5c0..7954996ed7 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -39,17 +39,23 @@ pub trait EntityRef: Copy + Eq { } /// A mapping `K -> V` for densely indexed entity references. +/// +/// A *primary* `EntityMap` contains the main definition of an entity, and it can be used to +/// allocate new entity references with the `push` method. +/// +/// A *secondary* `EntityMap` contains additional data about entities kept in a primary map. The +/// values need to implement `Clone + Default` traits so the map can be grown with `ensure`. +/// pub struct EntityMap - where K: EntityRef, - V: Clone + Default + where K: EntityRef { elems: Vec, unused: PhantomData, } +/// Shared `EntityMap` implementation for all value types. impl EntityMap - where K: EntityRef, - V: Clone + Default + where K: EntityRef { /// Create a new empty map. pub fn new() -> Self { @@ -64,13 +70,6 @@ impl EntityMap k.index() < self.elems.len() } - /// Ensure that `k` is a valid key but adding default entries if necesssary. - pub fn ensure(&mut self, k: K) { - if !self.is_valid(k) { - self.elems.resize(k.index() + 1, V::default()) - } - } - /// Append `v` to the mapping, assigning a new key which is returned. pub fn push(&mut self, v: V) -> K { let k = K::new(self.elems.len()); @@ -79,11 +78,32 @@ impl EntityMap } } -/// Immutable indexing into an `EntityMap`. -/// The indexed value must have been accessed mutably previously, or the key passed to `ensure()`. -impl Index for EntityMap +/// Additional methods for value types that implement `Clone` and `Default`. +/// +/// When the value type implements these additional traits, the `EntityMap` can be resized +/// explicitly with the `ensure` method. +/// +/// Use this for secondary maps that are mapping keys created by another primary map. +impl EntityMap where K: EntityRef, V: Clone + Default +{ + /// Ensure that `k` is a valid key but adding default entries if necesssary. + /// + /// Return a mutable reference to the corresponding entry. + pub fn ensure(&mut self, k: K) -> &mut V { + if !self.is_valid(k) { + self.elems.resize(k.index() + 1, V::default()) + } + &mut self.elems[k.index()] + } +} + +/// Immutable indexing into an `EntityMap`. +/// The indexed value must be in the map, either because it was created by `push`, or the key was +/// passed to `ensure`. +impl Index for EntityMap + where K: EntityRef { type Output = V; @@ -93,13 +113,11 @@ impl Index for EntityMap } /// Mutable indexing into an `EntityMap`. -/// The map is resized automatically if the key has not been used before. +/// Use `ensure` instead if the key is not known to be valid. impl IndexMut for EntityMap - where K: EntityRef, - V: Clone + Default + where K: EntityRef { fn index_mut(&mut self, k: K) -> &mut V { - self.ensure(k); &mut self.elems[k.index()] } } @@ -129,6 +147,7 @@ mod tests { let mut m = EntityMap::new(); assert!(!m.is_valid(r0)); + m.ensure(r2); m[r2] = 3; assert!(m.is_valid(r1)); m[r1] = 5; diff --git a/src/libcretonne/layout.rs b/src/libcretonne/layout.rs index 3cc2ffc03e..8ae6657dff 100644 --- a/src/libcretonne/layout.rs +++ b/src/libcretonne/layout.rs @@ -68,7 +68,7 @@ impl Layout { assert!(!self.is_ebb_inserted(ebb), "Cannot append EBB that is already in the layout"); { - let node = &mut self.ebbs[ebb]; + let node = self.ebbs.ensure(ebb); assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); node.prev = self.last_ebb.unwrap_or_default(); node.next = NO_EBB; @@ -88,8 +88,11 @@ impl Layout { assert!(self.is_ebb_inserted(before), "EBB Insertion point not in the layout"); let after = self.ebbs[before].prev; - self.ebbs[ebb].next = before; - self.ebbs[ebb].prev = after; + { + let node = self.ebbs.ensure(ebb); + node.next = before; + node.prev = after; + } self.ebbs[before].prev = ebb; if after == NO_EBB { self.first_ebb = Some(ebb); @@ -166,7 +169,7 @@ impl Layout { "Cannot append instructions to EBB not in layout"); let ebb_node = &mut self.ebbs[ebb]; { - let inst_node = &mut self.insts[inst]; + let inst_node = self.insts.ensure(inst); inst_node.ebb = ebb; inst_node.prev = ebb_node.last_inst; assert_eq!(inst_node.next, NO_INST); @@ -186,7 +189,7 @@ impl Layout { .expect("Instruction before insertion point not in the layout"); let after = self.insts[before].prev; { - let inst_node = &mut self.insts[inst]; + let inst_node = self.insts.ensure(inst); inst_node.ebb = ebb; inst_node.next = before; inst_node.prev = after; From 39d3a8e3d76db29cc495460a86990753cd67f219 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 19 Jul 2016 10:19:29 -0700 Subject: [PATCH 145/968] Implement separate data flow graph module. The DFG keeps track of instruction definitions, values, and EBBs. Store the primary definition of each instruction: Opcode and operands. Track SSA values as either the result of an instruction or EBB arguments. --- src/libcretonne/dfg.rs | 420 +++++++++++++++++++++++++++++++++++++++++ src/libcretonne/lib.rs | 1 + 2 files changed, 421 insertions(+) create mode 100644 src/libcretonne/dfg.rs diff --git a/src/libcretonne/dfg.rs b/src/libcretonne/dfg.rs new file mode 100644 index 0000000000..7cfdaa7fbf --- /dev/null +++ b/src/libcretonne/dfg.rs @@ -0,0 +1,420 @@ +//! Data flow graph tracking Instructions, Values, and EBBs. + +use entity_map::EntityMap; +use entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; +use instructions::InstructionData; +use types::Type; +use std::ops::{Index, IndexMut}; +use std::u16; + +/// A data flow graph defines all instuctions and extended basic blocks in a function as well as +/// the data flow dependencies between them. The DFG also tracks values which can be either +/// instruction results or EBB arguments. +/// +/// The layout of EBBs in the function and of instructions in each EBB is recorded by the +/// `FunctionLayout` data structure which form the other half of the function representation. +/// +pub struct DataFlowGraph { + /// Data about all of the instructions in the function, including opcodes and operands. + /// The instructions in this map are not in program order. That is tracked by `Layout`, along + /// with the EBB containing each instruction. + insts: EntityMap, + + /// Extended basic blocks in the function and their arguments. + /// This map is not in program order. That is handled by `Layout`, and so is the sequence of + /// instructions contained in each EBB. + ebbs: EntityMap, + + /// Extended value table. Most `Value` references refer directly to their defining instruction. + /// Others index into this table. + /// + /// This is implemented directly with a `Vec` rather than an `EntityMap` because + /// the Value entity references can refer to two things -- an instruction or an extended value. + extended_values: Vec, +} + +impl DataFlowGraph { + /// Create a new empty `DataFlowGraph`. + pub fn new() -> DataFlowGraph { + DataFlowGraph { + insts: EntityMap::new(), + ebbs: EntityMap::new(), + extended_values: Vec::new(), + } + } +} + +/// Handling values. +/// +/// Values are either EBB arguments or instruction results. +impl DataFlowGraph { + // Allocate an extended value entry. + fn make_value(&mut self, data: ValueData) -> Value { + let vref = Value::new_table(self.extended_values.len()); + self.extended_values.push(data); + vref + } + + /// Get the type of a value. + pub fn value_type(&self, v: Value) -> Type { + use entities::ExpandedValue::*; + match v.expand() { + Direct(i) => self.insts[i].first_type(), + Table(i) => { + match self.extended_values[i] { + ValueData::Inst { ty, .. } => ty, + ValueData::Arg { ty, .. } => ty, + } + } + None => panic!("NO_VALUE has no type"), + } + } + + /// Get the definition of a value. + /// + /// This is either the instruction that defined it or the Ebb that has the value as an + /// argument. + pub fn value_def(&self, v: Value) -> ValueDef { + use entities::ExpandedValue::*; + match v.expand() { + Direct(inst) => ValueDef::Res(inst, 0), + Table(idx) => { + match self.extended_values[idx] { + ValueData::Inst { inst, num, .. } => ValueDef::Res(inst, num as usize), + ValueData::Arg { ebb, num, .. } => ValueDef::Arg(ebb, num as usize), + } + } + None => panic!("NO_VALUE has no def"), + } + } +} + +/// Where did a value come from? +#[derive(Debug, PartialEq, Eq)] +pub enum ValueDef { + /// Value is the n'th result of an instruction. + Res(Inst, usize), + /// Value is the n'th argument to an EBB. + Arg(Ebb, usize), +} + +// Internal table storage for extended values. +enum ValueData { + // Value is defined by an instruction, but it is not the first result. + Inst { + ty: Type, + num: u16, // Result number starting from 0. + inst: Inst, + next: Value, // Next result defined by `def`. + }, + + // Value is an EBB argument. + Arg { + ty: Type, + num: u16, // Argument number, starting from 0. + ebb: Ebb, + next: Value, // Next argument to `ebb`. + }, +} + +/// Iterate through a list of related value references, such as: +/// +/// - All results defined by an instruction. See `DataFlowGraph::inst_results`. +/// - All arguments to an EBB. See `DataFlowGraph::ebb_args`. +/// +/// A value iterator borrows a `DataFlowGraph` reference. +pub struct Values<'a> { + dfg: &'a DataFlowGraph, + cur: Value, +} + +impl<'a> Iterator for Values<'a> { + type Item = Value; + + fn next(&mut self) -> Option { + let prev = self.cur; + + // Advance self.cur to the next value, or NO_VALUE. + self.cur = match prev.expand() { + ExpandedValue::Direct(inst) => self.dfg.insts[inst].second_result().unwrap_or_default(), + ExpandedValue::Table(index) => { + match self.dfg.extended_values[index] { + ValueData::Inst { next, .. } => next, + ValueData::Arg { next, .. } => next, + } + } + ExpandedValue::None => return None, + }; + + Some(prev) + } +} + +/// Instructions. +/// +impl DataFlowGraph { + /// Create a new instruction. + /// + /// The type of the first result is indicated by `data.ty`. If the instruction produces + /// multiple results, also call `make_inst_results` to allocate value table entries. + pub fn make_inst(&mut self, data: InstructionData) -> Inst { + self.insts.push(data) + } + + /// Create result values for an instruction that produces multiple results. + /// + /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If + /// the instruction may produce more than 1 result, call `make_inst_results` to allocate + /// value table entries for the additional results. + /// + /// The result value types are determined from the instruction's value type constraints and the + /// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic + /// instructions, `ctrl_typevar` is ignored, and `VOID` can be used. + /// + /// The type of the first result value is also set, even if it was already set in the + /// `InstructionData` passed to `make_inst`. If this function is called with a single-result + /// instruction, that is the only effect. + /// + /// Returns the number of results produced by the instruction. + pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { + let constraints = self.insts[inst].opcode().constraints(); + let fixed_results = constraints.fixed_results(); + + // Additional values form a linked list starting from the second result value. Generate + // the list backwards so we don't have to modify value table entries in place. (This + // causes additional result values to be numbered backwards which is not the aestetic + // choice, but since it is only visible in extremely rare instructions with 3+ results, + // we don't care). + let mut head = NO_VALUE; + let mut first_type = Type::default(); + + // TBD: Function call return values for direct and indirect function calls. + + if fixed_results > 0 { + for res_idx in (1..fixed_results).rev() { + head = self.make_value(ValueData::Inst { + ty: constraints.result_type(res_idx, ctrl_typevar), + num: res_idx as u16, + inst: inst, + next: head, + }); + } + first_type = constraints.result_type(0, ctrl_typevar); + } + + // Update the second_result pointer in `inst`. + if head != NO_VALUE { + *self.insts[inst] + .second_result_mut() + .expect("instruction format doesn't allow multiple results") = head; + } + *self.insts[inst].first_type_mut() = first_type; + + fixed_results + } + + /// Get the first result of an instruction. + /// + /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. + pub fn first_result(&self, inst: Inst) -> Value { + Value::new_direct(inst) + } + + /// Iterate through all the results of an instruction. + pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> { + Values { + dfg: self, + cur: if self.insts[inst].first_type().is_void() { + NO_VALUE + } else { + Value::new_direct(inst) + }, + } + } +} + +/// Allow immutable access to instructions via indexing. +impl Index for DataFlowGraph { + type Output = InstructionData; + + fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData { + &self.insts[inst] + } +} + +/// Allow mutable access to instructions via indexing. +impl IndexMut for DataFlowGraph { + fn index_mut<'a>(&'a mut self, inst: Inst) -> &'a mut InstructionData { + &mut self.insts[inst] + } +} + +/// Extended basic blocks. +impl DataFlowGraph { + /// Create a new basic block. + pub fn make_ebb(&mut self) -> Ebb { + self.ebbs.push(EbbData::new()) + } + + /// Get the number of arguments on `ebb`. + pub fn num_ebb_args(&self, ebb: Ebb) -> usize { + let last_arg = self.ebbs[ebb].last_arg; + match last_arg.expand() { + ExpandedValue::None => 0, + ExpandedValue::Table(idx) => { + if let ValueData::Arg { num, .. } = self.extended_values[idx] { + num as usize + 1 + } else { + panic!("inconsistent value table entry for EBB arg"); + } + } + ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"), + } + } + + /// Append an argument with type `ty` to `ebb`. + pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { + let num_args = self.num_ebb_args(ebb); + assert!(num_args <= u16::MAX as usize, "Too many arguments to EBB"); + let val = self.make_value(ValueData::Arg { + ty: ty, + ebb: ebb, + num: num_args as u16, + next: NO_VALUE, + }); + let last_arg = self.ebbs[ebb].last_arg; + match last_arg.expand() { + // If last_arg is NO_VALUE, we're adding the first EBB argument. + ExpandedValue::None => { + self.ebbs[ebb].first_arg = val; + } + // Append to linked list of arguments. + ExpandedValue::Table(idx) => { + if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] { + *next = val; + } else { + panic!("inconsistent value table entry for EBB arg"); + } + } + ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"), + }; + self.ebbs[ebb].last_arg = val; + val + } + + /// Iterate through the arguments to an EBB. + pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> { + Values { + dfg: self, + cur: self.ebbs[ebb].first_arg, + } + } +} + +// Contents of an extended basic block. +// +// Arguments for an extended basic block are values that dominate everything in the EBB. All +// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must +// match the function arguments. +struct EbbData { + // First argument to this EBB, or `NO_VALUE` if the block has no arguments. + // + // The arguments are all ValueData::Argument entries that form a linked list from `first_arg` + // to `last_arg`. + first_arg: Value, + + // Last argument to this EBB, or `NO_VALUE` if the block has no arguments. + last_arg: Value, +} + +impl EbbData { + fn new() -> EbbData { + EbbData { + first_arg: NO_VALUE, + last_arg: NO_VALUE, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use types; + use instructions::{Opcode, InstructionData}; + + #[test] + fn make_inst() { + let mut dfg = DataFlowGraph::new(); + + let idata = InstructionData::Nullary { + opcode: Opcode::Iconst, + ty: types::I32, + }; + let inst = dfg.make_inst(idata); + assert_eq!(inst.to_string(), "inst0"); + + // Immutable reference resolution. + { + let immdfg = &dfg; + let ins = &immdfg[inst]; + assert_eq!(ins.opcode(), Opcode::Iconst); + assert_eq!(ins.first_type(), types::I32); + } + + // Result iterator. + let mut res = dfg.inst_results(inst); + let val = res.next().unwrap(); + assert!(res.next().is_none()); + + assert_eq!(dfg.value_def(val), ValueDef::Res(inst, 0)); + assert_eq!(dfg.value_type(val), types::I32); + } + + #[test] + fn no_results() { + let mut dfg = DataFlowGraph::new(); + + let idata = InstructionData::Nullary { + opcode: Opcode::Trap, + ty: types::VOID, + }; + let inst = dfg.make_inst(idata); + + // Result iterator should be empty. + let mut res = dfg.inst_results(inst); + assert_eq!(res.next(), None); + } + + #[test] + fn ebb() { + let mut dfg = DataFlowGraph::new(); + + let ebb = dfg.make_ebb(); + assert_eq!(ebb.to_string(), "ebb0"); + assert_eq!(dfg.num_ebb_args(ebb), 0); + assert_eq!(dfg.ebb_args(ebb).next(), None); + + let arg1 = dfg.append_ebb_arg(ebb, types::F32); + assert_eq!(arg1.to_string(), "vx0"); + assert_eq!(dfg.num_ebb_args(ebb), 1); + { + let mut args1 = dfg.ebb_args(ebb); + assert_eq!(args1.next(), Some(arg1)); + assert_eq!(args1.next(), None); + } + let arg2 = dfg.append_ebb_arg(ebb, types::I16); + assert_eq!(arg2.to_string(), "vx1"); + assert_eq!(dfg.num_ebb_args(ebb), 2); + { + let mut args2 = dfg.ebb_args(ebb); + assert_eq!(args2.next(), Some(arg1)); + assert_eq!(args2.next(), Some(arg2)); + assert_eq!(args2.next(), None); + } + + assert_eq!(dfg.value_def(arg1), ValueDef::Arg(ebb, 0)); + assert_eq!(dfg.value_def(arg2), ValueDef::Arg(ebb, 1)); + assert_eq!(dfg.value_type(arg1), types::F32); + assert_eq!(dfg.value_type(arg2), types::I16); + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 0223e4a4b0..bc2edd9d59 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -13,6 +13,7 @@ pub mod immediates; pub mod entities; pub mod instructions; pub mod repr; +pub mod dfg; pub mod layout; pub mod write; pub mod cfg; From c1806d0ab04ec6d240216e3fb82a623df5fb880b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 19 Jul 2016 13:05:28 -0700 Subject: [PATCH 146/968] Use DataFlowGraph in Function. Replace the three tables instructions, extended_basic_blocks, and extended_values with a single 'dfg' public member. Clients using Function are changed to refer to func.layout and func.dfg respectively. --- src/libcretonne/cfg.rs | 14 +- src/libcretonne/repr.rs | 444 +----------------------- src/libcretonne/test_utils/make_inst.rs | 4 +- src/libcretonne/write.rs | 20 +- src/libreader/parser.rs | 20 +- src/tools/print_cfg.rs | 4 +- 6 files changed, 38 insertions(+), 468 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 3a5f554375..e9ecd9d982 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -56,7 +56,7 @@ impl ControlFlowGraph { // Flips to true when a terminating instruction is seen. So that if additional // instructions occur an error may be returned. for inst in func.layout.ebb_insts(ebb) { - match func[inst] { + match func.dfg[inst] { InstructionData::Branch { ty: _, opcode: _, ref data } => { cfg.add_predecessor(data.destination, (ebb, inst)); } @@ -109,9 +109,9 @@ mod tests { #[test] fn no_predecessors() { let mut func = Function::new(); - let ebb0 = func.make_ebb(); - let ebb1 = func.make_ebb(); - let ebb2 = func.make_ebb(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); func.layout.append_ebb(ebb1); func.layout.append_ebb(ebb2); @@ -130,9 +130,9 @@ mod tests { #[test] fn branches_and_jumps() { let mut func = Function::new(); - let ebb0 = func.make_ebb(); - let ebb1 = func.make_ebb(); - let ebb2 = func.make_ebb(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); func.layout.append_ebb(ebb1); func.layout.append_ebb(ebb2); diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr.rs index ac47a540fe..6f7075ae4d 100644 --- a/src/libcretonne/repr.rs +++ b/src/libcretonne/repr.rs @@ -1,18 +1,14 @@ //! Representation of Cretonne IL functions. -use types::{Type, FunctionName, Signature, VOID}; +use types::{FunctionName, Signature}; use entity_map::EntityRef; -use entities::{Ebb, NO_EBB, Inst, Value, NO_VALUE, ExpandedValue, StackSlot}; -use instructions::*; +use entities::{Ebb, NO_EBB, StackSlot}; +use dfg::DataFlowGraph; use layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Index, IndexMut}; +use std::ops::Index; /// A function. -/// -/// The `Function` struct owns all of its instructions and extended basic blocks, and it works as a -/// container for those objects by implementing both `Index` and `Index`. -/// pub struct Function { /// Name of this function. Mostly used by `.cton` files. pub name: FunctionName, @@ -26,17 +22,8 @@ pub struct Function { /// Stack slots allocated in this function. stack_slots: Vec, - /// Data about all of the instructions in the function. The instructions in this vector is not - /// necessarily in program order. The `Inst` reference indexes into this vector. - instructions: Vec, - - /// Extended basic blocks in the function, not necessarily in program order. The `Ebb` - /// reference indexes into this vector. - extended_basic_blocks: Vec, - - /// Extended value table. Most `Value` references refer directly to their defining instruction. - /// Others index into this table. - extended_values: Vec, + /// Data flow graph containing the primary definition of all instructions, EBBs and values. + pub dfg: DataFlowGraph, /// Layout of EBBs and instructions in the function body. pub layout: Layout, @@ -50,9 +37,7 @@ impl Function { signature: sig, entry_block: NO_EBB, stack_slots: Vec::new(), - instructions: Vec::new(), - extended_basic_blocks: Vec::new(), - extended_values: Vec::new(), + dfg: DataFlowGraph::new(), layout: Layout::new(), } } @@ -83,175 +68,6 @@ impl Function { end: self.stack_slots.len(), } } - - // Instructions. - - /// Create a new instruction. - /// - /// The type of the first result is indicated by `data.ty`. If the instruction produces - /// multiple results, also call `make_inst_results` to allocate value table entries. - pub fn make_inst(&mut self, data: InstructionData) -> Inst { - let inst = Inst::new(self.instructions.len()); - self.instructions.push(data); - inst - } - - /// Create result values for an instruction that produces multiple results. - /// - /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If - /// the instruction may produce more than 1 result, call `make_inst_results` to allocate - /// `Value` table entries for the additional results. - /// - /// The result value types are determined from the instruction's value type constraints and the - /// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic - /// instructions, `ctrl_typevar` is ignored, and `VOID` can be used. - /// - /// The type of the first result value is also set, even if it was already set in the - /// `InstructionData` passed to `make_inst`. If this function is called with a single-result - /// instruction, that is the only effect. - /// - /// Returns the number of results produced by the instruction. - pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { - let constraints = self[inst].opcode().constraints(); - let fixed_results = constraints.fixed_results(); - - // Additional values form a linked list starting from the second result value. Generate - // the list backwards so we don't have to modify value table entries in place. (This - // causes additional result values to be numbered backwards which is not the aestetic - // choice, but since it is only visible in extremely rare instructions with 3+ results, - // we don't care). - let mut head = NO_VALUE; - let mut first_type = Type::default(); - - // TBD: Function call return values for direct and indirect function calls. - - if fixed_results > 0 { - for res_idx in (1..fixed_results).rev() { - head = self.make_value(ValueData::Def { - ty: constraints.result_type(res_idx, ctrl_typevar), - def: inst, - next: head, - }); - } - first_type = constraints.result_type(0, ctrl_typevar); - } - - // Update the second_result pointer in `inst`. - if head != NO_VALUE { - *self[inst] - .second_result_mut() - .expect("instruction format doesn't allow multiple results") = head; - } - *self[inst].first_type_mut() = first_type; - - fixed_results - } - - /// Get the first result of an instruction. - /// - /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. - pub fn first_result(&self, inst: Inst) -> Value { - Value::new_direct(inst) - } - - /// Iterate through all the results of an instruction. - pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> { - Values { - func: self, - cur: if self[inst].first_type() == VOID { - NO_VALUE - } else { - Value::new_direct(inst) - }, - } - } - - // Basic blocks - - /// Create a new basic block. - pub fn make_ebb(&mut self) -> Ebb { - let ebb = Ebb::new(self.extended_basic_blocks.len()); - self.extended_basic_blocks.push(EbbData::new()); - ebb - } - - /// Reference the representation of an EBB. - fn ebb(&self, ebb: Ebb) -> &EbbData { - &self.extended_basic_blocks[ebb.index()] - } - - /// Mutably reference the representation of an EBB. - fn ebb_mut(&mut self, ebb: Ebb) -> &mut EbbData { - &mut self.extended_basic_blocks[ebb.index()] - } - - /// Iterate over all the EBBs in order of creation. - pub fn ebbs_numerically(&self) -> NumericalEbbs { - NumericalEbbs { - cur: 0, - limit: self.extended_basic_blocks.len(), - } - } - - /// Append an argument with type `ty` to `ebb`. - pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { - let val = self.make_value(ValueData::Argument { - ty: ty, - ebb: ebb, - next: NO_VALUE, - }); - - let last_arg = self.ebb(ebb).last_arg; - match last_arg.expand() { - // If last_arg = NO_VALUE, we're adding the first EBB argument. - ExpandedValue::None => self.ebb_mut(ebb).first_arg = val, - ExpandedValue::Table(index) => { - // Append to linked list of arguments. - if let ValueData::Argument { ref mut next, .. } = self.extended_values[index] { - *next = val; - } else { - panic!("wrong type of extended value referenced by Ebb::last_arg"); - } - } - ExpandedValue::Direct(_) => panic!("Direct value cannot appear as EBB argument"), - } - self.ebb_mut(ebb).last_arg = val; - - val - } - - /// Iterate through the arguments to an EBB. - pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> { - Values { - func: self, - cur: self.ebb(ebb).first_arg, - } - } - - // Values. - - /// Allocate an extended value entry. - fn make_value(&mut self, data: ValueData) -> Value { - let vref = Value::new_table(self.extended_values.len()); - self.extended_values.push(data); - vref - } - - /// Get the type of a value. - pub fn value_type(&self, v: Value) -> Type { - use entities::ExpandedValue::*; - use self::ValueData::*; - match v.expand() { - Direct(i) => self[i].first_type(), - Table(i) => { - match self.extended_values[i] { - Def { ty, .. } => ty, - Argument { ty, .. } => ty, - } - } - None => panic!("NO_VALUE has no type"), - } - } } impl Debug for Function { @@ -316,197 +132,9 @@ impl Iterator for StackSlotIter { } } -// ====--------------------------------------------------------------------------------------====// -// -// Extended basic block implementation. -// -// ====--------------------------------------------------------------------------------------====// - -/// Contents of an extended basic block. -/// -/// Arguments for an extended basic block are values that dominate everything in the EBB. All -/// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must -/// match the function arguments. -#[derive(Debug)] -pub struct EbbData { - /// First argument to this EBB, or `NO_VALUE` if the block has no arguments. - /// - /// The arguments are all ValueData::Argument entries that form a linked list from `first_arg` - /// to `last_arg`. - first_arg: Value, - - /// Last argument to this EBB, or `NO_VALUE` if the block has no arguments. - last_arg: Value, -} - -impl EbbData { - fn new() -> EbbData { - EbbData { - first_arg: NO_VALUE, - last_arg: NO_VALUE, - } - } -} - -impl Index for Function { - type Output = EbbData; - - fn index<'a>(&'a self, ebb: Ebb) -> &'a EbbData { - &self.extended_basic_blocks[ebb.index()] - } -} - -impl IndexMut for Function { - fn index_mut<'a>(&'a mut self, ebb: Ebb) -> &'a mut EbbData { - &mut self.extended_basic_blocks[ebb.index()] - } -} - -/// Iterate through all EBBs in a function in numerical order. -/// This order is stable, but has little significance to the semantics of the function. -pub struct NumericalEbbs { - cur: usize, - limit: usize, -} - -impl Iterator for NumericalEbbs { - type Item = Ebb; - - fn next(&mut self) -> Option { - if self.cur < self.limit { - let prev = Ebb::new(self.cur); - self.cur += 1; - Some(prev) - } else { - None - } - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Instruction implementation. -// -// The InstructionData layout is defined in the `instructions` module. -// -// ====--------------------------------------------------------------------------------------====// - -/// Allow immutable access to instructions via function indexing. -impl Index for Function { - type Output = InstructionData; - - fn index<'a>(&'a self, inst: Inst) -> &'a InstructionData { - &self.instructions[inst.index()] - } -} - -/// Allow mutable access to instructions via function indexing. -impl IndexMut for Function { - fn index_mut<'a>(&'a mut self, inst: Inst) -> &'a mut InstructionData { - &mut self.instructions[inst.index()] - } -} - -// ====--------------------------------------------------------------------------------------====// -// -// Value implementation. -// -// ====--------------------------------------------------------------------------------------====// - -// Most values are simply the first value produced by an instruction. -// Other values have an entry in the value table. -#[derive(Debug)] -enum ValueData { - // Value is defined by an instruction, but it is not the first result. - Def { - ty: Type, - def: Inst, - next: Value, // Next result defined by `def`. - }, - - // Value is an EBB argument. - Argument { - ty: Type, - ebb: Ebb, - next: Value, // Next argument to `ebb`. - }, -} - -/// Iterate through a list of related value references, such as: -/// -/// - All results defined by an instruction. -/// - All arguments to an EBB -/// -/// A value iterator borrows a Function reference. -pub struct Values<'a> { - func: &'a Function, - cur: Value, -} - -impl<'a> Iterator for Values<'a> { - type Item = Value; - - fn next(&mut self) -> Option { - let prev = self.cur; - - // Advance self.cur to the next value, or NO_VALUE. - self.cur = match prev.expand() { - ExpandedValue::Direct(inst) => self.func[inst].second_result().unwrap_or_default(), - ExpandedValue::Table(index) => { - match self.func.extended_values[index] { - ValueData::Def { next, .. } => next, - ValueData::Argument { next, .. } => next, - } - } - ExpandedValue::None => return None, - }; - - Some(prev) - } -} - #[cfg(test)] mod tests { use super::*; - use types; - use instructions::*; - - #[test] - fn make_inst() { - let mut func = Function::new(); - - let idata = InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }; - let inst = func.make_inst(idata); - assert_eq!(inst.to_string(), "inst0"); - - // Immutable reference resolution. - let ins = &func[inst]; - assert_eq!(ins.opcode(), Opcode::Iconst); - assert_eq!(ins.first_type(), types::I32); - - // Result iterator. - let mut res = func.inst_results(inst); - assert!(res.next().is_some()); - assert!(res.next().is_none()); - } - - #[test] - fn no_results() { - let mut func = Function::new(); - - let idata = InstructionData::Nullary { - opcode: Opcode::Trap, - ty: types::VOID, - }; - let inst = func.make_inst(idata); - - // Result iterator should be empty. - let mut res = func.inst_results(inst); - assert_eq!(res.next(), None); - } #[test] fn stack_slot() { @@ -520,62 +148,4 @@ mod tests { assert_eq!(func[ss0].size, 4); assert_eq!(func[ss1].size, 8); } - - #[test] - fn ebb() { - let mut func = Function::new(); - - assert_eq!(func.ebbs_numerically().next(), None); - - let ebb = func.make_ebb(); - assert_eq!(ebb.to_string(), "ebb0"); - assert_eq!(func.ebb_args(ebb).next(), None); - func.layout.append_ebb(ebb); - - let arg1 = func.append_ebb_arg(ebb, types::F32); - assert_eq!(arg1.to_string(), "vx0"); - { - let mut args1 = func.ebb_args(ebb); - assert_eq!(args1.next(), Some(arg1)); - assert_eq!(args1.next(), None); - } - let arg2 = func.append_ebb_arg(ebb, types::I16); - assert_eq!(arg2.to_string(), "vx1"); - { - let mut args2 = func.ebb_args(ebb); - assert_eq!(args2.next(), Some(arg1)); - assert_eq!(args2.next(), Some(arg2)); - assert_eq!(args2.next(), None); - } - - // The numerical ebb iterator doesn't capture the function. - let mut ebbs = func.ebbs_numerically(); - assert_eq!(ebbs.next(), Some(ebb)); - assert_eq!(ebbs.next(), None); - - assert_eq!(func.layout.ebb_insts(ebb).next(), None); - - let inst = func.make_inst(InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }); - func.layout.append_inst(inst, ebb); - { - let mut ii = func.layout.ebb_insts(ebb); - assert_eq!(ii.next(), Some(inst)); - assert_eq!(ii.next(), None); - } - - let inst2 = func.make_inst(InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::I32, - }); - func.layout.append_inst(inst2, ebb); - { - let mut ii = func.layout.ebb_insts(ebb); - assert_eq!(ii.next(), Some(inst)); - assert_eq!(ii.next(), Some(inst2)); - assert_eq!(ii.next(), None); - } - } } diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index a12fb47191..2f45a09cba 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -6,7 +6,7 @@ use instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; use types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { - func.make_inst(InstructionData::Jump { + func.dfg.make_inst(InstructionData::Jump { opcode: Opcode::Jump, ty: types::VOID, data: Box::new(JumpData { @@ -17,7 +17,7 @@ pub fn jump(func: &mut Function, dest: Ebb) -> Inst { } pub fn branch(func: &mut Function, dest: Ebb) -> Inst { - func.make_inst(InstructionData::Branch { + func.dfg.make_inst(InstructionData::Branch { opcode: Opcode::Brz, ty: types::VOID, data: Box::new(BranchData { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index c43634a636..10181e0f63 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -85,7 +85,7 @@ fn write_preamble(w: &mut Write, func: &Function) -> io::Result { // ====--------------------------------------------------------------------------------------====// pub fn write_arg(w: &mut Write, func: &Function, arg: Value) -> Result { - write!(w, "{}: {}", arg, func.value_type(arg)) + write!(w, "{}: {}", arg, func.dfg.value_type(arg)) } pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { @@ -96,7 +96,7 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // ebb10(vx4: f64, vx5: b1): // - let mut args = func.ebb_args(ebb); + let mut args = func.dfg.ebb_args(ebb); match args.next() { None => return writeln!(w, "{}:", ebb), Some(arg) => { @@ -133,7 +133,7 @@ pub fn write_ebb(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // if it can't be trivially inferred. // fn type_suffix(func: &Function, inst: Inst) -> Option { - let constraints = func[inst].opcode().constraints(); + let constraints = func.dfg[inst].opcode().constraints(); if !constraints.is_polymorphic() { return None; @@ -150,7 +150,7 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { // This polymorphic instruction doesn't support basic type inference. // The controlling type variable is required to be the type of the first result. - let rtype = func.value_type(func.first_result(inst)); + let rtype = func.dfg.value_type(func.dfg.first_result(inst)); assert!(!rtype.is_void(), "Polymorphic instruction must produce a result"); Some(rtype) @@ -161,7 +161,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { // First write out the result values, if any. let mut has_results = false; - for r in func.inst_results(inst) { + for r in func.dfg.inst_results(inst) { if !has_results { has_results = true; try!(write!(w, "{}", r)); @@ -174,7 +174,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { } // Then the opcode, possibly with a '.type' suffix. - let opcode = func[inst].opcode(); + let opcode = func.dfg[inst].opcode(); match type_suffix(func, inst) { Some(suf) => try!(write!(w, "{}.{}", opcode, suf)), @@ -183,7 +183,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { // Then the operands, depending on format. use instructions::InstructionData::*; - match func[inst] { + match func.dfg[inst] { Nullary { .. } => writeln!(w, ""), Unary { arg, .. } => writeln!(w, " {}", arg), UnaryImm { imm, .. } => writeln!(w, " {}", imm), @@ -250,16 +250,16 @@ mod tests { assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n}\n"); - let ebb = f.make_ebb(); + let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); - f.append_ebb_arg(ebb, types::I8); + f.dfg.append_ebb_arg(ebb, types::I8); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8):\n}\n"); - f.append_ebb_arg(ebb, types::F32.by(4).unwrap()); + f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8, vx1: f32x4):\n}\n"); } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 95eb91dfd2..b79ff16cc3 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -100,7 +100,7 @@ impl Context { // Allocate a new EBB and add a mapping src_ebb -> Ebb. fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { - let ebb = self.function.make_ebb(); + let ebb = self.function.dfg.make_ebb(); self.function.layout.append_ebb(ebb); if self.ebbs.insert(src_ebb, ebb).is_some() { err!(loc, "duplicate EBB: {}", src_ebb) @@ -163,7 +163,7 @@ impl Context { // Rewrite all EBB and value references in the function. fn rewrite_references(&mut self) -> Result<()> { for &(inst, loc) in &self.inst_locs { - match self.function[inst] { + match self.function.dfg[inst] { InstructionData::Nullary { .. } | InstructionData::UnaryImm { .. } | InstructionData::UnaryIeee32 { .. } | @@ -646,7 +646,7 @@ impl<'a> Parser<'a> { // ebb-arg ::= Value(vx) ":" * Type(t) let t = try!(self.match_type("expected EBB argument type")); // Allocate the EBB argument and add the mapping. - let value = ctx.function.append_ebb_arg(ebb, t); + let value = ctx.function.dfg.append_ebb_arg(ebb, t); ctx.add_value(vx, value, &vx_location) } @@ -703,8 +703,8 @@ impl<'a> Parser<'a> { // or function call signature. We also need to create values with the right type for all // the instruction results. let ctrl_typevar = try!(self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data)); - let inst = ctx.function.make_inst(inst_data); - let num_results = ctx.function.make_inst_results(inst, ctrl_typevar); + let inst = ctx.function.dfg.make_inst(inst_data); + let num_results = ctx.function.dfg.make_inst_results(inst, ctrl_typevar); ctx.function.layout.append_inst(inst, ebb); ctx.add_inst_loc(inst, &opcode_loc); @@ -720,7 +720,7 @@ impl<'a> Parser<'a> { // holds a reference to `ctx.function`. self.add_values(&mut ctx.values, results.into_iter(), - ctx.function.inst_results(inst)) + ctx.function.dfg.inst_results(inst)) } // Type inference for polymorphic instructions. @@ -750,7 +750,7 @@ impl<'a> Parser<'a> { // layout of the blocks. let ctrl_src_value = inst_data.typevar_operand() .expect("Constraints <-> Format inconsistency"); - ctx.function.value_type(match ctx.values.get(&ctrl_src_value) { + ctx.function.dfg.value_type(match ctx.values.get(&ctrl_src_value) { Some(&v) => v, None => { return err!(self.loc, @@ -1119,12 +1119,12 @@ mod tests { let mut ebbs = func.layout.ebbs(); let ebb0 = ebbs.next().unwrap(); - assert_eq!(func.ebb_args(ebb0).next(), None); + assert_eq!(func.dfg.ebb_args(ebb0).next(), None); let ebb4 = ebbs.next().unwrap(); - let mut ebb4_args = func.ebb_args(ebb4); + let mut ebb4_args = func.dfg.ebb_args(ebb4); let arg0 = ebb4_args.next().unwrap(); - assert_eq!(func.value_type(arg0), types::I32); + assert_eq!(func.dfg.value_type(arg0), types::I32); assert_eq!(ebb4_args.next(), None); } } diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 0709b358fd..1c7f0e7a19 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -110,14 +110,14 @@ impl CFGPrinter { let inst_data = func.layout .ebb_insts(ebb) .filter(|inst| { - match func[*inst] { + match func.dfg[*inst] { InstructionData::Branch { ty: _, opcode: _, data: _ } => true, InstructionData::Jump { ty: _, opcode: _, data: _ } => true, _ => false, } }) .map(|inst| { - let op = match func[inst] { + let op = match func.dfg[inst] { InstructionData::Branch { ty: _, opcode, ref data } => { Some((opcode, data.destination)) } From 6e04ec5df9091d97d50fb55aad354e593119f414 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 19 Jul 2016 13:53:02 -0700 Subject: [PATCH 147/968] Prepare for repr sub-modules. --- src/libcretonne/{repr.rs => repr/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libcretonne/{repr.rs => repr/mod.rs} (100%) diff --git a/src/libcretonne/repr.rs b/src/libcretonne/repr/mod.rs similarity index 100% rename from src/libcretonne/repr.rs rename to src/libcretonne/repr/mod.rs From 89ba9626c730be10edf4fa97d019f95dd140691e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 19 Jul 2016 14:10:30 -0700 Subject: [PATCH 148/968] Move IR modules under repr/. Use the cretonne::repr module as a common namespace for sub-modules defining the in-memory representation of Cretonn IL. --- src/libcretonne/cfg.rs | 4 ++-- src/libcretonne/lib.rs | 7 ------- src/libcretonne/{ => repr}/condcodes.rs | 0 src/libcretonne/{ => repr}/dfg.rs | 14 +++++++------- src/libcretonne/{ => repr}/entities.rs | 0 src/libcretonne/{ => repr}/immediates.rs | 0 src/libcretonne/{ => repr}/instructions.rs | 10 +++++----- src/libcretonne/{ => repr}/layout.rs | 4 ++-- src/libcretonne/repr/mod.rs | 16 ++++++++++++---- src/libcretonne/{ => repr}/types.rs | 0 src/libcretonne/test_utils/make_inst.rs | 6 +++--- src/libcretonne/write.rs | 8 ++++---- src/libreader/lexer.rs | 8 ++++---- src/libreader/parser.rs | 12 ++++++------ src/tools/print_cfg.rs | 2 +- 15 files changed, 46 insertions(+), 45 deletions(-) rename src/libcretonne/{ => repr}/condcodes.rs (100%) rename src/libcretonne/{ => repr}/dfg.rs (97%) rename src/libcretonne/{ => repr}/entities.rs (100%) rename src/libcretonne/{ => repr}/immediates.rs (100%) rename src/libcretonne/{ => repr}/instructions.rs (99%) rename src/libcretonne/{ => repr}/layout.rs (99%) rename src/libcretonne/{ => repr}/types.rs (100%) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index e9ecd9d982..c2a2b6dcf2 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -21,8 +21,8 @@ //! and (Ebb0, `jmp Ebb2`) respectively. use repr::Function; -use entities::{Inst, Ebb}; -use instructions::InstructionData; +use repr::entities::{Inst, Ebb}; +use repr::instructions::InstructionData; use std::collections::{BTreeSet, BTreeMap, btree_map}; /// A basic block denoted by its enclosing Ebb and last instruction. diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index bc2edd9d59..0b1ac8678e 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -7,14 +7,7 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub mod types; -pub mod condcodes; -pub mod immediates; -pub mod entities; -pub mod instructions; pub mod repr; -pub mod dfg; -pub mod layout; pub mod write; pub mod cfg; diff --git a/src/libcretonne/condcodes.rs b/src/libcretonne/repr/condcodes.rs similarity index 100% rename from src/libcretonne/condcodes.rs rename to src/libcretonne/repr/condcodes.rs diff --git a/src/libcretonne/dfg.rs b/src/libcretonne/repr/dfg.rs similarity index 97% rename from src/libcretonne/dfg.rs rename to src/libcretonne/repr/dfg.rs index 7cfdaa7fbf..29a462f73e 100644 --- a/src/libcretonne/dfg.rs +++ b/src/libcretonne/repr/dfg.rs @@ -1,9 +1,9 @@ //! Data flow graph tracking Instructions, Values, and EBBs. use entity_map::EntityMap; -use entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; -use instructions::InstructionData; -use types::Type; +use repr::entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; +use repr::instructions::InstructionData; +use repr::types::Type; use std::ops::{Index, IndexMut}; use std::u16; @@ -57,7 +57,7 @@ impl DataFlowGraph { /// Get the type of a value. pub fn value_type(&self, v: Value) -> Type { - use entities::ExpandedValue::*; + use repr::entities::ExpandedValue::*; match v.expand() { Direct(i) => self.insts[i].first_type(), Table(i) => { @@ -75,7 +75,7 @@ impl DataFlowGraph { /// This is either the instruction that defined it or the Ebb that has the value as an /// argument. pub fn value_def(&self, v: Value) -> ValueDef { - use entities::ExpandedValue::*; + use repr::entities::ExpandedValue::*; match v.expand() { Direct(inst) => ValueDef::Res(inst, 0), Table(idx) => { @@ -339,8 +339,8 @@ impl EbbData { #[cfg(test)] mod tests { use super::*; - use types; - use instructions::{Opcode, InstructionData}; + use repr::types; + use repr::instructions::{Opcode, InstructionData}; #[test] fn make_inst() { diff --git a/src/libcretonne/entities.rs b/src/libcretonne/repr/entities.rs similarity index 100% rename from src/libcretonne/entities.rs rename to src/libcretonne/repr/entities.rs diff --git a/src/libcretonne/immediates.rs b/src/libcretonne/repr/immediates.rs similarity index 100% rename from src/libcretonne/immediates.rs rename to src/libcretonne/repr/immediates.rs diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/repr/instructions.rs similarity index 99% rename from src/libcretonne/instructions.rs rename to src/libcretonne/repr/instructions.rs index 8ebd63d411..7ebb71bbf4 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/repr/instructions.rs @@ -10,10 +10,10 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use entities::*; -use immediates::*; -use condcodes::*; -use types::{self, Type}; +use repr::entities::*; +use repr::immediates::*; +use repr::condcodes::*; +use repr::types::{self, Type}; // Include code generated by `meta/gen_instr.py`. This file contains: // @@ -560,7 +560,7 @@ mod tests { #[test] fn value_set() { - use types::*; + use repr::types::*; let vts = ValueTypeSet { allow_scalars: true, diff --git a/src/libcretonne/layout.rs b/src/libcretonne/repr/layout.rs similarity index 99% rename from src/libcretonne/layout.rs rename to src/libcretonne/repr/layout.rs index 8ae6657dff..36ee2dacf8 100644 --- a/src/libcretonne/layout.rs +++ b/src/libcretonne/repr/layout.rs @@ -5,7 +5,7 @@ use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; -use entities::{Ebb, NO_EBB, Inst, NO_INST}; +use repr::entities::{Ebb, NO_EBB, Inst, NO_INST}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -242,7 +242,7 @@ impl<'a> Iterator for Insts<'a> { mod tests { use super::Layout; use entity_map::EntityRef; - use entities::{Ebb, Inst}; + use repr::entities::{Ebb, Inst}; #[test] fn append_ebb() { diff --git a/src/libcretonne/repr/mod.rs b/src/libcretonne/repr/mod.rs index 6f7075ae4d..a62527ccbe 100644 --- a/src/libcretonne/repr/mod.rs +++ b/src/libcretonne/repr/mod.rs @@ -1,10 +1,18 @@ //! Representation of Cretonne IL functions. -use types::{FunctionName, Signature}; +pub mod entities; +pub mod types; +pub mod condcodes; +pub mod immediates; +pub mod instructions; +pub mod dfg; +pub mod layout; + +use repr::types::{FunctionName, Signature}; use entity_map::EntityRef; -use entities::{Ebb, NO_EBB, StackSlot}; -use dfg::DataFlowGraph; -use layout::Layout; +use repr::entities::{Ebb, NO_EBB, StackSlot}; +use repr::dfg::DataFlowGraph; +use repr::layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Index; diff --git a/src/libcretonne/types.rs b/src/libcretonne/repr/types.rs similarity index 100% rename from src/libcretonne/types.rs rename to src/libcretonne/repr/types.rs diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index 2f45a09cba..00955a5be5 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -1,9 +1,9 @@ //! Helper functions for generating dummy instructions. use repr::Function; -use entities::{Ebb, Inst, NO_VALUE}; -use instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; -use types; +use repr::entities::{Ebb, Inst, NO_VALUE}; +use repr::instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; +use repr::types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { func.dfg.make_inst(InstructionData::Jump { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 10181e0f63..5b66b81065 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -6,8 +6,8 @@ use std::io::{self, Write}; use repr::Function; -use entities::{Inst, Ebb, Value}; -use types::Type; +use repr::entities::{Inst, Ebb, Value}; +use repr::types::Type; pub type Result = io::Result<()>; @@ -182,7 +182,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { } // Then the operands, depending on format. - use instructions::InstructionData::*; + use repr::instructions::InstructionData::*; match func.dfg[inst] { Nullary { .. } => writeln!(w, ""), Unary { arg, .. } => writeln!(w, " {}", arg), @@ -218,7 +218,7 @@ mod tests { use super::*; use super::{needs_quotes, escaped}; use repr::{Function, StackSlotData}; - use types; + use repr::types; #[test] fn quoting() { diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 4d3dfdf7c1..87c7482589 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -6,8 +6,8 @@ // ====--------------------------------------------------------------------------------------====// use std::str::CharIndices; -use cretonne::types; -use cretonne::entities::{Value, Ebb}; +use cretonne::repr::types; +use cretonne::repr::entities::{Value, Ebb}; /// The location of a `Token` or `Error`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -373,8 +373,8 @@ impl<'a> Lexer<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::types; - use cretonne::entities::{Value, Ebb}; + use cretonne::repr::types; + use cretonne::repr::entities::{Value, Ebb}; fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index b79ff16cc3..0c573558c7 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -11,11 +11,11 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; use lexer::{self, Lexer, Token}; -use cretonne::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; -use cretonne::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::entities::*; -use cretonne::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, JumpData, - BranchData, ReturnData}; +use cretonne::repr::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; +use cretonne::repr::immediates::{Imm64, Ieee32, Ieee64}; +use cretonne::repr::entities::*; +use cretonne::repr::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, + JumpData, BranchData, ReturnData}; use cretonne::repr::{Function, StackSlotData}; pub use lexer::Location; @@ -1039,7 +1039,7 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::types::{self, ArgumentType, ArgumentExtension}; + use cretonne::repr::types::{self, ArgumentType, ArgumentExtension}; #[test] fn argument_type() { diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 1c7f0e7a19..155de0e4a1 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -8,7 +8,7 @@ use std::io::{Read, Write, stdout}; use CommandResult; use cretonne::repr::Function; use cretonne::cfg::ControlFlowGraph; -use cretonne::instructions::InstructionData; +use cretonne::repr::instructions::InstructionData; use cton_reader::parser::Parser; pub fn run(files: Vec) -> CommandResult { From 2caa802f50ca9f5ee5a65dde1ed5cc9942b483d8 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 21 Jul 2016 12:08:02 -0700 Subject: [PATCH 149/968] Use EntityMap instead of BTreeMap --- src/libcretonne/cfg.rs | 73 +++++++++++++++++++++++------------ src/libcretonne/entity_map.rs | 5 +++ 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index c2a2b6dcf2..c6363ef21a 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -1,5 +1,5 @@ //! A control flow graph represented as mappings of extended basic blocks to their predecessors. -//! Predecessors are denoted by tuples of EBB and branch/jump instructions. Each predecessor +//! BasicBlocks are denoted by tuples of EBB and branch/jump instructions. Each predecessor //! tuple corresponds to the end of a basic block. //! //! ```c @@ -23,20 +23,21 @@ use repr::Function; use repr::entities::{Inst, Ebb}; use repr::instructions::InstructionData; -use std::collections::{BTreeSet, BTreeMap, btree_map}; +use entity_map::EntityMap; +use std::collections::BTreeSet; /// A basic block denoted by its enclosing Ebb and last instruction. -pub type Predecessor = (Ebb, Inst); +pub type BasicBlock = (Ebb, Inst); /// Storing predecessors in a BTreeSet ensures that their ordering is /// stable with no duplicates. -pub type PredecessorSet = BTreeSet; +pub type BasicBlockSet = BTreeSet; /// The Control Flow Graph maintains a mapping of ebbs to their predecessors /// where predecessors are basic blocks. #[derive(Debug)] pub struct ControlFlowGraph { - data: BTreeMap, + data: EntityMap, } impl ControlFlowGraph { @@ -44,12 +45,12 @@ impl ControlFlowGraph { /// blocks within the CFG's associated function. Basic sanity checks will /// also be performed to ensure that the blocks are well formed. pub fn new(func: &Function) -> ControlFlowGraph { - let mut cfg = ControlFlowGraph { data: BTreeMap::new() }; + let mut cfg = ControlFlowGraph { data: EntityMap::new() }; // Even ebbs without predecessors should show up in the CFG, albeit // with no entires. - for ebb in &func.layout { - cfg.init_ebb(ebb); + for _ in &func.layout { + cfg.push_ebb(); } for ebb in &func.layout { @@ -70,25 +71,48 @@ impl ControlFlowGraph { cfg } - /// Initializes a predecessor set for some ebb. If an ebb already has an - /// entry it will be clobbered. - pub fn init_ebb(&mut self, ebb: Ebb) -> &mut PredecessorSet { - self.data.insert(ebb, BTreeSet::new()); - self.data.get_mut(&ebb).unwrap() + pub fn push_ebb(&mut self) { + self.data.push(BTreeSet::new()); } - pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: Predecessor) { - self.data.get_mut(&ebb).unwrap().insert(predecessor); + pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: BasicBlock) { + self.data[ebb].insert(predecessor); } /// Returns all of the predecessors for some ebb, if it has an entry. - pub fn get_predecessors(&self, ebb: Ebb) -> Option<&PredecessorSet> { - self.data.get(&ebb) + pub fn get_predecessors(&self, ebb: Ebb) -> &BasicBlockSet { + &self.data[ebb] } - /// An iterator over all of the ebb to predecessor mappings in the CFG. - pub fn iter<'a>(&'a self) -> btree_map::Iter<'a, Ebb, PredecessorSet> { - self.data.iter() + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn iter(&self) -> CFGIter { + CFGIter { + cur: 0, + cfg: &self, + } + } +} + +pub struct CFGIter<'a> { + cfg: &'a ControlFlowGraph, + cur: usize, +} + +impl<'a> Iterator for CFGIter<'a> { + type Item = (Ebb, &'a BasicBlockSet); + + fn next(&mut self) -> Option { + if self.cur < self.cfg.len() { + let ebb = Ebb::with_number(self.cur as u32).unwrap(); + let bbs = self.cfg.get_predecessors(ebb); + self.cur += 1; + Some((ebb, bbs)) + } else { + None + } } } @@ -122,7 +146,7 @@ mod tests { let mut fun_ebbs = func.layout.ebbs(); for (ebb, predecessors) in nodes { - assert_eq!(*ebb, fun_ebbs.next().unwrap()); + assert_eq!(ebb, fun_ebbs.next().unwrap()); assert_eq!(predecessors.len(), 0); } } @@ -150,9 +174,9 @@ mod tests { func.layout.append_inst(jmp_ebb1_ebb2, ebb1); let cfg = ControlFlowGraph::new(&func); - let ebb0_predecessors = cfg.get_predecessors(ebb0).unwrap(); - let ebb1_predecessors = cfg.get_predecessors(ebb1).unwrap(); - let ebb2_predecessors = cfg.get_predecessors(ebb2).unwrap(); + let ebb0_predecessors = cfg.get_predecessors(ebb0); + let ebb1_predecessors = cfg.get_predecessors(ebb1); + let ebb2_predecessors = cfg.get_predecessors(ebb2); assert_eq!(ebb0_predecessors.len(), 0); assert_eq!(ebb1_predecessors.len(), 2); assert_eq!(ebb2_predecessors.len(), 2); @@ -162,5 +186,4 @@ mod tests { assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), true); assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true); } - } diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 7954996ed7..0a7faccb41 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -46,6 +46,7 @@ pub trait EntityRef: Copy + Eq { /// A *secondary* `EntityMap` contains additional data about entities kept in a primary map. The /// values need to implement `Clone + Default` traits so the map can be grown with `ensure`. /// +#[derive(Debug)] pub struct EntityMap where K: EntityRef { @@ -76,6 +77,10 @@ impl EntityMap self.elems.push(v); k } + + pub fn len(&self) -> usize { + self.elems.len() + } } /// Additional methods for value types that implement `Clone` and `Default`. From 30eb25d013ece38a16576156bb91910bc2622a3b Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 21 Jul 2016 12:36:51 -0700 Subject: [PATCH 150/968] Track predecessors as well as successors in the CFG --- src/libcretonne/cfg.rs | 77 ++++++++++++++++++++++++++++++++---------- src/tools/print_cfg.rs | 2 +- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index c6363ef21a..9fabdff160 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -1,4 +1,6 @@ -//! A control flow graph represented as mappings of extended basic blocks to their predecessors. +//! A control flow graph represented as mappings of extended basic blocks to their predecessors +//! and successors. Successors are represented as extended basic blocks while predecessors are +//! represented by basic blocks. //! BasicBlocks are denoted by tuples of EBB and branch/jump instructions. Each predecessor //! tuple corresponds to the end of a basic block. //! @@ -33,17 +35,33 @@ pub type BasicBlock = (Ebb, Inst); /// stable with no duplicates. pub type BasicBlockSet = BTreeSet; +/// A container for the successors and predecessors of some Ebb. +#[derive(Debug)] +pub struct CFGNode { + pub successors: BTreeSet, + pub predecessors: BasicBlockSet, +} + +impl CFGNode { + pub fn new() -> CFGNode { + CFGNode { + successors: BTreeSet::new(), + predecessors: BTreeSet::new(), + } + } +} + /// The Control Flow Graph maintains a mapping of ebbs to their predecessors -/// where predecessors are basic blocks. +/// and successors where predecessors are basic blocks and successors are +/// extended basic blocks. #[derive(Debug)] pub struct ControlFlowGraph { - data: EntityMap, + data: EntityMap, } impl ControlFlowGraph { /// During initialization mappings will be generated for any existing - /// blocks within the CFG's associated function. Basic sanity checks will - /// also be performed to ensure that the blocks are well formed. + /// blocks within the CFG's associated function. pub fn new(func: &Function) -> ControlFlowGraph { let mut cfg = ControlFlowGraph { data: EntityMap::new() }; @@ -54,14 +72,14 @@ impl ControlFlowGraph { } for ebb in &func.layout { - // Flips to true when a terminating instruction is seen. So that if additional - // instructions occur an error may be returned. for inst in func.layout.ebb_insts(ebb) { match func.dfg[inst] { InstructionData::Branch { ty: _, opcode: _, ref data } => { + cfg.add_successor(ebb, data.destination); cfg.add_predecessor(data.destination, (ebb, inst)); } InstructionData::Jump { ty: _, opcode: _, ref data } => { + cfg.add_successor(ebb, data.destination); cfg.add_predecessor(data.destination, (ebb, inst)); } _ => (), @@ -72,36 +90,44 @@ impl ControlFlowGraph { } pub fn push_ebb(&mut self) { - self.data.push(BTreeSet::new()); + self.data.push(CFGNode::new()); + } + + pub fn add_successor(&mut self, from: Ebb, to: Ebb) { + self.data[from].successors.insert(to); } pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: BasicBlock) { - self.data[ebb].insert(predecessor); + self.data[ebb].predecessors.insert(predecessor); } - /// Returns all of the predecessors for some ebb, if it has an entry. pub fn get_predecessors(&self, ebb: Ebb) -> &BasicBlockSet { - &self.data[ebb] + &self.data[ebb].predecessors + } + + pub fn get_successors(&self, ebb: Ebb) -> &BTreeSet { + &self.data[ebb].successors } pub fn len(&self) -> usize { self.data.len() } - pub fn iter(&self) -> CFGIter { - CFGIter { + pub fn predecessors_iter(&self) -> CFGPredecessorsIter { + CFGPredecessorsIter { cur: 0, cfg: &self, } } } -pub struct CFGIter<'a> { +/// Iterate through every mapping of ebb to predecessors in the CFG +pub struct CFGPredecessorsIter<'a> { cfg: &'a ControlFlowGraph, cur: usize, } -impl<'a> Iterator for CFGIter<'a> { +impl<'a> Iterator for CFGPredecessorsIter<'a> { type Item = (Ebb, &'a BasicBlockSet); fn next(&mut self) -> Option { @@ -127,7 +153,7 @@ mod tests { fn empty() { let func = Function::new(); let cfg = ControlFlowGraph::new(&func); - assert_eq!(None, cfg.iter().next()); + assert_eq!(None, cfg.predecessors_iter().next()); } #[test] @@ -141,13 +167,15 @@ mod tests { func.layout.append_ebb(ebb2); let cfg = ControlFlowGraph::new(&func); - let nodes = cfg.iter().collect::>(); + let nodes = cfg.predecessors_iter().collect::>(); assert_eq!(nodes.len(), 3); let mut fun_ebbs = func.layout.ebbs(); for (ebb, predecessors) in nodes { assert_eq!(ebb, fun_ebbs.next().unwrap()); assert_eq!(predecessors.len(), 0); + assert_eq!(predecessors.len(), 0); + assert_eq!(cfg.get_successors(ebb).len(), 0); } } @@ -174,9 +202,15 @@ mod tests { func.layout.append_inst(jmp_ebb1_ebb2, ebb1); let cfg = ControlFlowGraph::new(&func); + let ebb0_predecessors = cfg.get_predecessors(ebb0); let ebb1_predecessors = cfg.get_predecessors(ebb1); let ebb2_predecessors = cfg.get_predecessors(ebb2); + + let ebb0_successors = cfg.get_successors(ebb0); + let ebb1_successors = cfg.get_successors(ebb1); + let ebb2_successors = cfg.get_successors(ebb2); + assert_eq!(ebb0_predecessors.len(), 0); assert_eq!(ebb1_predecessors.len(), 2); assert_eq!(ebb2_predecessors.len(), 2); @@ -185,5 +219,14 @@ mod tests { assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true); assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), true); assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true); + + assert_eq!(ebb0_successors.len(), 2); + assert_eq!(ebb1_successors.len(), 2); + assert_eq!(ebb2_successors.len(), 0); + + assert_eq!(ebb0_successors.contains(&ebb1), true); + assert_eq!(ebb0_successors.contains(&ebb2), true); + assert_eq!(ebb1_successors.contains(&ebb1), true); + assert_eq!(ebb1_successors.contains(&ebb2), true); } } diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 155de0e4a1..dfa0113fee 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -146,7 +146,7 @@ impl CFGPrinter { } fn cfg_connections(&mut self, cfg: &ControlFlowGraph) { - for (ref ebb, ref predecessors) in cfg.iter() { + for (ref ebb, ref predecessors) in cfg.predecessors_iter() { for &(parent, inst) in *predecessors { self.append(&format!("{}:{} -> {}", parent, inst, ebb)); self.newline(); From 761fb54d8ae3f420abcefeaa11b8c09c666fa6d7 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 21 Jul 2016 15:22:27 -0700 Subject: [PATCH 151/968] Add support for postorder traversal of the cfg. --- src/libcretonne/cfg.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 9fabdff160..e73e0d04be 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -42,6 +42,15 @@ pub struct CFGNode { pub predecessors: BasicBlockSet, } +impl CFGNode { + /// CFG Node successors stripped of loop edges. + pub fn children(&self) -> Vec { + let pred_ebbs = self.predecessors.iter().map(|&(ebb, _)| { ebb }).collect(); + let children = self.successors.difference(&pred_ebbs).cloned().collect(); + children + } +} + impl CFGNode { pub fn new() -> CFGNode { CFGNode { @@ -109,6 +118,26 @@ impl ControlFlowGraph { &self.data[ebb].successors } + pub fn get_children(&self, ebb: Ebb) -> Vec { + self.data[ebb].children() + } + + pub fn postorder_ebbs(&self) -> Vec { + if self.len() < 1 { + return Vec::new(); + } + let mut stack_a = vec![Ebb::with_number(0).unwrap()]; + let mut stack_b = Vec::new(); + while stack_a.len() > 0 { + let cur = stack_a.pop().unwrap(); + for child in self.get_children(cur) { + stack_a.push(child); + } + stack_b.push(cur); + } + stack_b + } + pub fn len(&self) -> usize { self.data.len() } @@ -228,5 +257,45 @@ mod tests { assert_eq!(ebb0_successors.contains(&ebb2), true); assert_eq!(ebb1_successors.contains(&ebb1), true); assert_eq!(ebb1_successors.contains(&ebb2), true); + + assert_eq!(cfg.get_children(ebb0), vec![ebb1, ebb2]); + assert_eq!(cfg.get_children(ebb1), vec![ebb2]); + assert_eq!(cfg.get_children(ebb2), Vec::new()); + } + + #[test] + fn postorder_traversal() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + let ebb3 = func.dfg.make_ebb(); + let ebb4 = func.dfg.make_ebb(); + let ebb5 = func.dfg.make_ebb(); + + func.layout.append_ebb(ebb0); + func.layout.append_ebb(ebb1); + func.layout.append_ebb(ebb2); + func.layout.append_ebb(ebb3); + func.layout.append_ebb(ebb4); + func.layout.append_ebb(ebb5); + + let br_ebb0_ebb1 = make_inst::branch(&mut func, ebb1); + func.layout.append_inst(br_ebb0_ebb1, ebb0); + + let jmp_ebb0_ebb2 = make_inst::jump(&mut func, ebb2); + func.layout.append_inst(jmp_ebb0_ebb2, ebb0); + + let jmp_ebb1_ebb3 = make_inst::jump(&mut func, ebb3); + func.layout.append_inst(jmp_ebb1_ebb3, ebb1); + + let br_ebb2_ebb4 = make_inst::branch(&mut func, ebb4); + func.layout.append_inst(br_ebb2_ebb4, ebb2); + + let jmp_ebb2_ebb5 = make_inst::jump(&mut func, ebb5); + func.layout.append_inst(jmp_ebb2_ebb5, ebb2); + + let cfg = ControlFlowGraph::new(&func); + assert_eq!(cfg.postorder_ebbs(), vec![ebb0, ebb2, ebb5, ebb4, ebb1, ebb3]); } } From bdab73b0c7ad5ce21591035ccda4c8263074b683 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 21 Jul 2016 15:24:07 -0700 Subject: [PATCH 152/968] Cargo-fmt fixes --- src/libcretonne/cfg.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index e73e0d04be..77f40a6f9a 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -45,7 +45,7 @@ pub struct CFGNode { impl CFGNode { /// CFG Node successors stripped of loop edges. pub fn children(&self) -> Vec { - let pred_ebbs = self.predecessors.iter().map(|&(ebb, _)| { ebb }).collect(); + let pred_ebbs = self.predecessors.iter().map(|&(ebb, _)| ebb).collect(); let children = self.successors.difference(&pred_ebbs).cloned().collect(); children } @@ -296,6 +296,7 @@ mod tests { func.layout.append_inst(jmp_ebb2_ebb5, ebb2); let cfg = ControlFlowGraph::new(&func); - assert_eq!(cfg.postorder_ebbs(), vec![ebb0, ebb2, ebb5, ebb4, ebb1, ebb3]); + assert_eq!(cfg.postorder_ebbs(), + vec![ebb0, ebb2, ebb5, ebb4, ebb1, ebb3]); } } From 367752be1d337b41bfb293aa75f6063295c60fdc Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 21 Jul 2016 22:44:13 -0700 Subject: [PATCH 153/968] Replace btreesets with vectors. --- src/libcretonne/cfg.rs | 78 ++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 77f40a6f9a..c018ab4671 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -26,36 +26,22 @@ use repr::Function; use repr::entities::{Inst, Ebb}; use repr::instructions::InstructionData; use entity_map::EntityMap; -use std::collections::BTreeSet; /// A basic block denoted by its enclosing Ebb and last instruction. pub type BasicBlock = (Ebb, Inst); -/// Storing predecessors in a BTreeSet ensures that their ordering is -/// stable with no duplicates. -pub type BasicBlockSet = BTreeSet; - /// A container for the successors and predecessors of some Ebb. #[derive(Debug)] pub struct CFGNode { - pub successors: BTreeSet, - pub predecessors: BasicBlockSet, -} - -impl CFGNode { - /// CFG Node successors stripped of loop edges. - pub fn children(&self) -> Vec { - let pred_ebbs = self.predecessors.iter().map(|&(ebb, _)| ebb).collect(); - let children = self.successors.difference(&pred_ebbs).cloned().collect(); - children - } + pub successors: Vec, + pub predecessors: Vec, } impl CFGNode { pub fn new() -> CFGNode { CFGNode { - successors: BTreeSet::new(), - predecessors: BTreeSet::new(), + successors: Vec::new(), + predecessors: Vec::new(), } } } @@ -103,25 +89,21 @@ impl ControlFlowGraph { } pub fn add_successor(&mut self, from: Ebb, to: Ebb) { - self.data[from].successors.insert(to); + self.data[from].successors.push(to); } pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: BasicBlock) { - self.data[ebb].predecessors.insert(predecessor); + self.data[ebb].predecessors.push(predecessor); } - pub fn get_predecessors(&self, ebb: Ebb) -> &BasicBlockSet { + pub fn get_predecessors(&self, ebb: Ebb) -> &Vec { &self.data[ebb].predecessors } - pub fn get_successors(&self, ebb: Ebb) -> &BTreeSet { + pub fn get_successors(&self, ebb: Ebb) -> &Vec { &self.data[ebb].successors } - pub fn get_children(&self, ebb: Ebb) -> Vec { - self.data[ebb].children() - } - pub fn postorder_ebbs(&self) -> Vec { if self.len() < 1 { return Vec::new(); @@ -130,8 +112,10 @@ impl ControlFlowGraph { let mut stack_b = Vec::new(); while stack_a.len() > 0 { let cur = stack_a.pop().unwrap(); - for child in self.get_children(cur) { - stack_a.push(child); + for child in &self.data[cur].successors { + if *child != cur && !stack_a.contains(child) { + stack_a.push(child.clone()); + } } stack_b.push(cur); } @@ -157,7 +141,7 @@ pub struct CFGPredecessorsIter<'a> { } impl<'a> Iterator for CFGPredecessorsIter<'a> { - type Item = (Ebb, &'a BasicBlockSet); + type Item = (Ebb, &'a Vec); fn next(&mut self) -> Option { if self.cur < self.cfg.len() { @@ -257,10 +241,6 @@ mod tests { assert_eq!(ebb0_successors.contains(&ebb2), true); assert_eq!(ebb1_successors.contains(&ebb1), true); assert_eq!(ebb1_successors.contains(&ebb2), true); - - assert_eq!(cfg.get_children(ebb0), vec![ebb1, ebb2]); - assert_eq!(cfg.get_children(ebb1), vec![ebb2]); - assert_eq!(cfg.get_children(ebb2), Vec::new()); } #[test] @@ -286,6 +266,12 @@ mod tests { let jmp_ebb0_ebb2 = make_inst::jump(&mut func, ebb2); func.layout.append_inst(jmp_ebb0_ebb2, ebb0); + let br_ebb2_ebb2 = make_inst::branch(&mut func, ebb2); + func.layout.append_inst(br_ebb2_ebb2, ebb2); + + let br_ebb2_ebb1 = make_inst::branch(&mut func, ebb1); + func.layout.append_inst(br_ebb2_ebb1, ebb2); + let jmp_ebb1_ebb3 = make_inst::jump(&mut func, ebb3); func.layout.append_inst(jmp_ebb1_ebb3, ebb1); @@ -299,4 +285,30 @@ mod tests { assert_eq!(cfg.postorder_ebbs(), vec![ebb0, ebb2, ebb5, ebb4, ebb1, ebb3]); } + + #[test] + fn loop_edge() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + let ebb3 = func.dfg.make_ebb(); + func.layout.append_ebb(ebb0); + func.layout.append_ebb(ebb1); + func.layout.append_ebb(ebb2); + func.layout.append_ebb(ebb3); + + let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); + let br_ebb1_ebb3 = make_inst::branch(&mut func, ebb3); + let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); + let jmp_ebb2_ebb3 = make_inst::jump(&mut func, ebb3); + + func.layout.append_inst(jmp_ebb0_ebb1, ebb0); + func.layout.append_inst(br_ebb1_ebb3, ebb1); + func.layout.append_inst(jmp_ebb1_ebb2, ebb1); + func.layout.append_inst(jmp_ebb2_ebb3, ebb2); + + let cfg = ControlFlowGraph::new(&func); + assert_eq!(cfg.postorder_ebbs(), vec![ebb0, ebb1, ebb2, ebb3]); + } } From 38815dcca33babe2095ac4d2d305aa5a4c41d720 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jul 2016 09:34:28 -0700 Subject: [PATCH 154/968] Rename the 'repr' module to 'ir'. This module and its submodules define the Intermidiate Representation of the Cretonne IL. --- src/libcretonne/cfg.rs | 8 ++++---- src/libcretonne/{repr => ir}/condcodes.rs | 0 src/libcretonne/{repr => ir}/dfg.rs | 14 +++++++------- src/libcretonne/{repr => ir}/entities.rs | 0 src/libcretonne/{repr => ir}/immediates.rs | 0 src/libcretonne/{repr => ir}/instructions.rs | 10 +++++----- src/libcretonne/{repr => ir}/layout.rs | 4 ++-- src/libcretonne/{repr => ir}/mod.rs | 8 ++++---- src/libcretonne/{repr => ir}/types.rs | 0 src/libcretonne/lib.rs | 2 +- src/libcretonne/test_utils/make_inst.rs | 8 ++++---- src/libcretonne/write.rs | 12 ++++++------ src/libreader/lexer.rs | 8 ++++---- src/libreader/parser.rs | 12 ++++++------ src/tools/print_cfg.rs | 4 ++-- 15 files changed, 45 insertions(+), 45 deletions(-) rename src/libcretonne/{repr => ir}/condcodes.rs (100%) rename src/libcretonne/{repr => ir}/dfg.rs (97%) rename src/libcretonne/{repr => ir}/entities.rs (100%) rename src/libcretonne/{repr => ir}/immediates.rs (100%) rename src/libcretonne/{repr => ir}/instructions.rs (99%) rename src/libcretonne/{repr => ir}/layout.rs (99%) rename src/libcretonne/{repr => ir}/mod.rs (96%) rename src/libcretonne/{repr => ir}/types.rs (100%) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index c018ab4671..970af69afd 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -22,9 +22,9 @@ //! Here Ebb1 and Ebb2 would each have a single predecessor denoted as (Ebb0, `brz vx, Ebb1`) //! and (Ebb0, `jmp Ebb2`) respectively. -use repr::Function; -use repr::entities::{Inst, Ebb}; -use repr::instructions::InstructionData; +use ir::Function; +use ir::entities::{Inst, Ebb}; +use ir::instructions::InstructionData; use entity_map::EntityMap; /// A basic block denoted by its enclosing Ebb and last instruction. @@ -158,7 +158,7 @@ impl<'a> Iterator for CFGPredecessorsIter<'a> { #[cfg(test)] mod tests { use super::*; - use repr::Function; + use ir::Function; use test_utils::make_inst; diff --git a/src/libcretonne/repr/condcodes.rs b/src/libcretonne/ir/condcodes.rs similarity index 100% rename from src/libcretonne/repr/condcodes.rs rename to src/libcretonne/ir/condcodes.rs diff --git a/src/libcretonne/repr/dfg.rs b/src/libcretonne/ir/dfg.rs similarity index 97% rename from src/libcretonne/repr/dfg.rs rename to src/libcretonne/ir/dfg.rs index 29a462f73e..d377813544 100644 --- a/src/libcretonne/repr/dfg.rs +++ b/src/libcretonne/ir/dfg.rs @@ -1,9 +1,9 @@ //! Data flow graph tracking Instructions, Values, and EBBs. use entity_map::EntityMap; -use repr::entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; -use repr::instructions::InstructionData; -use repr::types::Type; +use ir::entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; +use ir::instructions::InstructionData; +use ir::types::Type; use std::ops::{Index, IndexMut}; use std::u16; @@ -57,7 +57,7 @@ impl DataFlowGraph { /// Get the type of a value. pub fn value_type(&self, v: Value) -> Type { - use repr::entities::ExpandedValue::*; + use ir::entities::ExpandedValue::*; match v.expand() { Direct(i) => self.insts[i].first_type(), Table(i) => { @@ -75,7 +75,7 @@ impl DataFlowGraph { /// This is either the instruction that defined it or the Ebb that has the value as an /// argument. pub fn value_def(&self, v: Value) -> ValueDef { - use repr::entities::ExpandedValue::*; + use ir::entities::ExpandedValue::*; match v.expand() { Direct(inst) => ValueDef::Res(inst, 0), Table(idx) => { @@ -339,8 +339,8 @@ impl EbbData { #[cfg(test)] mod tests { use super::*; - use repr::types; - use repr::instructions::{Opcode, InstructionData}; + use ir::types; + use ir::instructions::{Opcode, InstructionData}; #[test] fn make_inst() { diff --git a/src/libcretonne/repr/entities.rs b/src/libcretonne/ir/entities.rs similarity index 100% rename from src/libcretonne/repr/entities.rs rename to src/libcretonne/ir/entities.rs diff --git a/src/libcretonne/repr/immediates.rs b/src/libcretonne/ir/immediates.rs similarity index 100% rename from src/libcretonne/repr/immediates.rs rename to src/libcretonne/ir/immediates.rs diff --git a/src/libcretonne/repr/instructions.rs b/src/libcretonne/ir/instructions.rs similarity index 99% rename from src/libcretonne/repr/instructions.rs rename to src/libcretonne/ir/instructions.rs index 7ebb71bbf4..a6c7bd03f1 100644 --- a/src/libcretonne/repr/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -10,10 +10,10 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use repr::entities::*; -use repr::immediates::*; -use repr::condcodes::*; -use repr::types::{self, Type}; +use ir::entities::*; +use ir::immediates::*; +use ir::condcodes::*; +use ir::types::{self, Type}; // Include code generated by `meta/gen_instr.py`. This file contains: // @@ -560,7 +560,7 @@ mod tests { #[test] fn value_set() { - use repr::types::*; + use ir::types::*; let vts = ValueTypeSet { allow_scalars: true, diff --git a/src/libcretonne/repr/layout.rs b/src/libcretonne/ir/layout.rs similarity index 99% rename from src/libcretonne/repr/layout.rs rename to src/libcretonne/ir/layout.rs index 36ee2dacf8..a01d49ef53 100644 --- a/src/libcretonne/repr/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -5,7 +5,7 @@ use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; -use repr::entities::{Ebb, NO_EBB, Inst, NO_INST}; +use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -242,7 +242,7 @@ impl<'a> Iterator for Insts<'a> { mod tests { use super::Layout; use entity_map::EntityRef; - use repr::entities::{Ebb, Inst}; + use ir::entities::{Ebb, Inst}; #[test] fn append_ebb() { diff --git a/src/libcretonne/repr/mod.rs b/src/libcretonne/ir/mod.rs similarity index 96% rename from src/libcretonne/repr/mod.rs rename to src/libcretonne/ir/mod.rs index a62527ccbe..fb93e9ec99 100644 --- a/src/libcretonne/repr/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -8,11 +8,11 @@ pub mod instructions; pub mod dfg; pub mod layout; -use repr::types::{FunctionName, Signature}; +use ir::types::{FunctionName, Signature}; use entity_map::EntityRef; -use repr::entities::{Ebb, NO_EBB, StackSlot}; -use repr::dfg::DataFlowGraph; -use repr::layout::Layout; +use ir::entities::{Ebb, NO_EBB, StackSlot}; +use ir::dfg::DataFlowGraph; +use ir::layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Index; diff --git a/src/libcretonne/repr/types.rs b/src/libcretonne/ir/types.rs similarity index 100% rename from src/libcretonne/repr/types.rs rename to src/libcretonne/ir/types.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 0b1ac8678e..f81bfb5720 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -7,7 +7,7 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub mod repr; +pub mod ir; pub mod write; pub mod cfg; diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index 00955a5be5..adc45cdb67 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -1,9 +1,9 @@ //! Helper functions for generating dummy instructions. -use repr::Function; -use repr::entities::{Ebb, Inst, NO_VALUE}; -use repr::instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; -use repr::types; +use ir::Function; +use ir::entities::{Ebb, Inst, NO_VALUE}; +use ir::instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; +use ir::types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { func.dfg.make_inst(InstructionData::Jump { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 5b66b81065..b42fdf02fd 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -5,9 +5,9 @@ //! `cretonne-reader` crate. use std::io::{self, Write}; -use repr::Function; -use repr::entities::{Inst, Ebb, Value}; -use repr::types::Type; +use ir::Function; +use ir::entities::{Inst, Ebb, Value}; +use ir::types::Type; pub type Result = io::Result<()>; @@ -182,7 +182,7 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { } // Then the operands, depending on format. - use repr::instructions::InstructionData::*; + use ir::instructions::InstructionData::*; match func.dfg[inst] { Nullary { .. } => writeln!(w, ""), Unary { arg, .. } => writeln!(w, " {}", arg), @@ -217,8 +217,8 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { mod tests { use super::*; use super::{needs_quotes, escaped}; - use repr::{Function, StackSlotData}; - use repr::types; + use ir::{Function, StackSlotData}; + use ir::types; #[test] fn quoting() { diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 87c7482589..3803260c05 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -6,8 +6,8 @@ // ====--------------------------------------------------------------------------------------====// use std::str::CharIndices; -use cretonne::repr::types; -use cretonne::repr::entities::{Value, Ebb}; +use cretonne::ir::types; +use cretonne::ir::entities::{Value, Ebb}; /// The location of a `Token` or `Error`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -373,8 +373,8 @@ impl<'a> Lexer<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::repr::types; - use cretonne::repr::entities::{Value, Ebb}; + use cretonne::ir::types; + use cretonne::ir::entities::{Value, Ebb}; fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 0c573558c7..e333c0d3e9 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -11,12 +11,12 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; use lexer::{self, Lexer, Token}; -use cretonne::repr::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; -use cretonne::repr::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::repr::entities::*; -use cretonne::repr::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, +use cretonne::ir::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; +use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; +use cretonne::ir::entities::*; +use cretonne::ir::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; -use cretonne::repr::{Function, StackSlotData}; +use cretonne::ir::{Function, StackSlotData}; pub use lexer::Location; @@ -1039,7 +1039,7 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::repr::types::{self, ArgumentType, ArgumentExtension}; + use cretonne::ir::types::{self, ArgumentType, ArgumentExtension}; #[test] fn argument_type() { diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index dfa0113fee..9a08c0287e 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -6,9 +6,9 @@ use std::fs::File; use std::io::{Read, Write, stdout}; use CommandResult; -use cretonne::repr::Function; +use cretonne::ir::Function; use cretonne::cfg::ControlFlowGraph; -use cretonne::repr::instructions::InstructionData; +use cretonne::ir::instructions::InstructionData; use cton_reader::parser::Parser; pub fn run(files: Vec) -> CommandResult { From f116f03327ddb270f0d9551372bd88e6f191ed5c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jul 2016 10:06:51 -0700 Subject: [PATCH 155/968] Move entry_block() into Layout. The single entry block in a function is simply the first block in the layout. Remove the 'entry' keyword from the textual IL, the lexer and parser. --- docs/cton_lexer.py | 2 +- docs/example.cton | 2 +- docs/langref.rst | 16 ++++++++-------- src/libcretonne/cfg.rs | 12 +++++------- src/libcretonne/ir/layout.rs | 9 +++++++++ src/libcretonne/ir/mod.rs | 6 +----- src/libreader/lexer.rs | 5 +---- src/libreader/parser.rs | 14 +++----------- 8 files changed, 29 insertions(+), 37 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 0139de337c..28a1be25b4 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -26,7 +26,7 @@ class CretonneLexer(RegexLexer): (r'[-+]?(\d+\.\d+([eE]\d+)?|[sq]NaN|Inf)', Number.Float), (r'[-+]?\d+', Number.Integer), # Reserved words. - (keywords('function', 'entry'), Keyword), + (keywords('function'), Keyword), # Known attributes. (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), Name.Attribute), # Well known value types. diff --git a/docs/example.cton b/docs/example.cton index 190d3a25fd..cdfc61bf4b 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -1,7 +1,7 @@ function average(i32, i32) -> f32 { ss1 = stack_slot 8, align 4 ; Stack slot for ``sum``. -entry ebb1(v1: i32, v2: i32): +ebb1(v1: i32, v2: i32): v3 = f64const 0x0.0 stack_store v3, ss1 brz v2, ebb3 ; Handle count == 0. diff --git a/docs/langref.rst b/docs/langref.rst index c6e7b3ced6..848d140db8 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -41,9 +41,9 @@ that can be referenced inside the function. In the example above, the preamble declares a single local variable, ``ss1``. After the preamble follows the :term:`function body` which consists of -:term:`extended basic block`\s, one of which is marked as the :term:`entry -block`. Every EBB ends with a :term:`terminator instruction`, so execution can -never fall through to the next EBB without an explicit branch. +:term:`extended basic block`\s, the first of which is the :term:`entry block`. +Every EBB ends with a :term:`terminator instruction`, so execution can never +fall through to the next EBB without an explicit branch. A ``.cton`` file consists of a sequence of independent function definitions: @@ -395,7 +395,7 @@ This simple example illustrates direct function calls and signatures:: function gcd(i32 uext, i32 uext) -> i32 uext "C" { f1 = function divmod(i32 uext, i32 uext) -> i32 uext, i32 uext - entry ebb1(v1: i32, v2: i32): + ebb1(v1: i32, v2: i32): brz v2, ebb2 v3, v4 = call f1(v1, v2) br ebb1(v2, v4) @@ -625,7 +625,7 @@ A small example using heaps:: function vdup(i32, i32) { h1 = heap "main" - entry ebb1(v1: i32, v2: i32): + ebb1(v1: i32, v2: i32): v3 = heap_load.i32x4 h1, v1, 0 v4 = heap_addr h1, v2, 32 ; Shared range check for two stores. store v3, v4, 0 @@ -878,9 +878,9 @@ Glossary entry block The :term:`EBB` that is executed first in a function. Currently, a - Cretonne function must have exactly one entry block. The types of the - entry block arguments must match the types of arguments in the function - signature. + Cretonne function must have exactly one entry block which must be the + first block in the function. The types of the entry block arguments must + match the types of arguments in the function signature. stack slot A fixed size memory allocation in the current function's activation diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 970af69afd..964a07a572 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -104,11 +104,8 @@ impl ControlFlowGraph { &self.data[ebb].successors } - pub fn postorder_ebbs(&self) -> Vec { - if self.len() < 1 { - return Vec::new(); - } - let mut stack_a = vec![Ebb::with_number(0).unwrap()]; + pub fn postorder_ebbs(&self, entry: Ebb) -> Vec { + let mut stack_a = vec![entry]; let mut stack_b = Vec::new(); while stack_a.len() > 0 { let cur = stack_a.pop().unwrap(); @@ -282,7 +279,7 @@ mod tests { func.layout.append_inst(jmp_ebb2_ebb5, ebb2); let cfg = ControlFlowGraph::new(&func); - assert_eq!(cfg.postorder_ebbs(), + assert_eq!(cfg.postorder_ebbs(func.layout.entry_block().unwrap()), vec![ebb0, ebb2, ebb5, ebb4, ebb1, ebb3]); } @@ -309,6 +306,7 @@ mod tests { func.layout.append_inst(jmp_ebb2_ebb3, ebb2); let cfg = ControlFlowGraph::new(&func); - assert_eq!(cfg.postorder_ebbs(), vec![ebb0, ebb1, ebb2, ebb3]); + assert_eq!(cfg.postorder_ebbs(func.layout.entry_block().unwrap()), + vec![ebb0, ebb1, ebb2, ebb3]); } } diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index a01d49ef53..af21ee5234 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -108,6 +108,12 @@ impl Layout { next: self.first_ebb, } } + + /// Get the function's entry block. + /// This is simply the first EBB in the layout order. + pub fn entry_block(&self) -> Option { + self.first_ebb + } } #[derive(Clone, Debug, Default)] @@ -414,8 +420,11 @@ mod tests { let e0 = Ebb::new(0); let e1 = Ebb::new(1); + assert_eq!(layout.entry_block(), None); layout.append_ebb(e0); + assert_eq!(layout.entry_block(), Some(e0)); layout.append_ebb(e1); + assert_eq!(layout.entry_block(), Some(e0)); let i0 = Inst::new(0); let i1 = Inst::new(1); diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index fb93e9ec99..0d7e9da3a4 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -10,7 +10,7 @@ pub mod layout; use ir::types::{FunctionName, Signature}; use entity_map::EntityRef; -use ir::entities::{Ebb, NO_EBB, StackSlot}; +use ir::entities::StackSlot; use ir::dfg::DataFlowGraph; use ir::layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; @@ -24,9 +24,6 @@ pub struct Function { /// Signature of this function. signature: Signature, - /// The entry block. - pub entry_block: Ebb, - /// Stack slots allocated in this function. stack_slots: Vec, @@ -43,7 +40,6 @@ impl Function { Function { name: name, signature: sig, - entry_block: NO_EBB, stack_slots: Vec::new(), dfg: DataFlowGraph::new(), layout: Layout::new(), diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 3803260c05..61dd7feb9b 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -32,7 +32,6 @@ pub enum Token<'a> { Equal, // '=' Arrow, // '->' Function, // 'function' - Entry, // 'entry' Float(&'a str), // Floating point immediate Integer(&'a str), // Integer immediate Type(types::Type), // i32, f32, b32x4, ... @@ -270,7 +269,6 @@ impl<'a> Lexer<'a> { fn keyword(text: &str) -> Option> { match text { "function" => Some(Token::Function), - "entry" => Some(Token::Entry), _ => None, } } @@ -444,7 +442,7 @@ mod tests { #[test] fn lex_identifiers() { - let mut lex = Lexer::new("v0 v00 vx01 ebb1234567890 ebb5234567890 entry v1x vx1 vxvx4 \ + let mut lex = Lexer::new("v0 v00 vx01 ebb1234567890 ebb5234567890 v1x vx1 vxvx4 \ function0 function b1 i32x4 f32x5"); assert_eq!(lex.next(), token(Token::Value(Value::direct_with_number(0).unwrap()), 1)); @@ -453,7 +451,6 @@ mod tests { assert_eq!(lex.next(), token(Token::Ebb(Ebb::with_number(1234567890).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("ebb5234567890"), 1)); - assert_eq!(lex.next(), token(Token::Entry, 1)); assert_eq!(lex.next(), token(Token::Identifier("v1x"), 1)); assert_eq!(lex.next(), token(Token::Value(Value::table_with_number(1).unwrap()), 1)); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index e333c0d3e9..a961ccdf60 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -15,7 +15,7 @@ use cretonne::ir::types::{Type, VOID, FunctionName, Signature, ArgumentType, Arg use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::*; use cretonne::ir::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, - JumpData, BranchData, ReturnData}; + JumpData, BranchData, ReturnData}; use cretonne::ir::{Function, StackSlotData}; pub use lexer::Location; @@ -578,22 +578,14 @@ impl<'a> Parser<'a> { // Parse an extended basic block, add contents to `ctx`. // // extended-basic-block ::= * ebb-header { instruction } - // ebb-header ::= ["entry"] Ebb(ebb) [ebb-args] ":" + // ebb-header ::= Ebb(ebb) [ebb-args] ":" // fn parse_extended_basic_block(&mut self, ctx: &mut Context) -> Result<()> { - let is_entry = self.optional(Token::Entry); let ebb_num = try!(self.match_ebb("expected EBB header")); let ebb = try!(ctx.add_ebb(ebb_num, &self.loc)); - if is_entry { - if ctx.function.entry_block != NO_EBB { - return err!(self.loc, "multiple entry blocks in function"); - } - ctx.function.entry_block = ebb; - } - if !self.optional(Token::Colon) { - // ebb-header ::= ["entry"] Ebb(ebb) [ * ebb-args ] ":" + // ebb-header ::= Ebb(ebb) [ * ebb-args ] ":" try!(self.parse_ebb_args(ctx, ebb)); try!(self.match_token(Token::Colon, "expected ':' after EBB arguments")); } From 410c1390d12e064113eca81f1d32ee62ea7fc787 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jul 2016 14:10:39 -0700 Subject: [PATCH 156/968] Add a keys() iterator to EntityMap. --- src/libcretonne/entity_map.rs | 42 ++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 0a7faccb41..9ce39bec19 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -81,6 +81,15 @@ impl EntityMap pub fn len(&self) -> usize { self.elems.len() } + + /// Iterate over all the keys in this map. + pub fn keys(&self) -> Keys { + Keys { + pos: 0, + len: self.elems.len(), + unused: PhantomData, + } + } } /// Additional methods for value types that implement `Clone` and `Default`. @@ -127,12 +136,37 @@ impl IndexMut for EntityMap } } +/// Iterate over all keys in order. +pub struct Keys + where K: EntityRef +{ + pos: usize, + len: usize, + unused: PhantomData, +} + +impl Iterator for Keys + where K: EntityRef +{ + type Item = K; + + fn next(&mut self) -> Option { + if self.pos < self.len { + let k = K::new(self.pos); + self.pos += 1; + Some(k) + } else { + None + } + } +} + #[cfg(test)] mod tests { use super::*; // EntityRef impl for testing. - #[derive(Clone, Copy, PartialEq, Eq)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct E(u32); impl EntityRef for E { @@ -151,6 +185,9 @@ mod tests { let r2 = E(2); let mut m = EntityMap::new(); + let v: Vec = m.keys().collect(); + assert_eq!(v, []); + assert!(!m.is_valid(r0)); m.ensure(r2); m[r2] = 3; @@ -160,6 +197,9 @@ mod tests { assert_eq!(m[r1], 5); assert_eq!(m[r2], 3); + let v: Vec = m.keys().collect(); + assert_eq!(v, [r0, r1, r2]); + let shared = &m; assert_eq!(shared[r0], 0); assert_eq!(shared[r1], 5); From 274671d12a3b9231dd839fd6c5b59479703f47de Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jul 2016 11:41:30 -0700 Subject: [PATCH 157/968] Implement jump tables. - Add a ir::jumptable module with a JumpTableData struct representing the vector of destinations. - Add an entity map of jump tables to the Function. - Parse and write jump tables in the function preamble. - Rewrite EBB references in jumptables after parsing. --- src/libcretonne/ir/jumptable.rs | 156 ++++++++++++++++++++++++++++++++ src/libcretonne/ir/mod.rs | 10 +- src/libcretonne/write.rs | 5 + src/libreader/lexer.rs | 2 + src/libreader/parser.rs | 82 ++++++++++++++++- tests/parser/branch.cton | 14 +++ tests/parser/branch.cton.ref | 17 ++++ 7 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 src/libcretonne/ir/jumptable.rs diff --git a/src/libcretonne/ir/jumptable.rs b/src/libcretonne/ir/jumptable.rs new file mode 100644 index 0000000000..31ea86f643 --- /dev/null +++ b/src/libcretonne/ir/jumptable.rs @@ -0,0 +1,156 @@ +//! Jump table representation. +//! +//! Jump tables are declared in the preamble and assigned an `ir::entities::JumpTable` reference. +//! The actual table of destinations is stored in a `JumpTableData` struct defined in this module. + +use ir::entities::{Ebb, NO_EBB}; +use std::iter; +use std::slice; +use std::fmt::{self, Display, Formatter}; + +/// Contents of a jump table. +/// +/// All jump tables use 0-based indexing and are expected to be densely populated. They don't need +/// to be completely populated, though. Individual entries can be missing. +pub struct JumpTableData { + // Table entries, using NO_EBB as a placeholder for missing entries. + table: Vec, + + // How many `NO_EBB` holes in table? + holes: usize, +} + +impl JumpTableData { + /// Create a new empty jump table. + pub fn new() -> JumpTableData { + JumpTableData { + table: Vec::new(), + holes: 0, + } + } + + /// Set a table entry. + /// + /// The table will grow as needed to fit 'idx'. + pub fn set_entry(&mut self, idx: usize, dest: Ebb) { + assert!(dest != NO_EBB); + // Resize table to fit `idx`. + if idx >= self.table.len() { + self.holes += idx - self.table.len(); + self.table.resize(idx + 1, NO_EBB); + } else if self.table[idx] == NO_EBB { + // We're filling in an existing hole. + self.holes -= 1; + } + self.table[idx] = dest; + } + + /// Clear a table entry. + /// + /// The `br_table` instruction will fall through if given an index corresponding to a cleared + /// table entry. + pub fn clear_entry(&mut self, idx: usize) { + if idx < self.table.len() && self.table[idx] != NO_EBB { + self.holes += 1; + self.table[idx] = NO_EBB; + } + } + + /// Get the entry for `idx`, or `None`. + pub fn get_entry(&self, idx: usize) -> Option { + if idx < self.table.len() && self.table[idx] != NO_EBB { + Some(self.table[idx]) + } else { + None + } + } + + /// Enumerate over all `(idx, dest)` pairs in the table in order. + /// + /// This returns an iterator that skips any empty slots in the table. + pub fn entries<'a>(&'a self) -> Entries { + Entries(self.table.iter().cloned().enumerate()) + } + + /// Access the whole table as a mutable slice. + pub fn as_mut_slice(&mut self) -> &mut [Ebb] { + self.table.as_mut_slice() + } +} + +/// Enumerate `(idx, dest)` pairs in order. +pub struct Entries<'a>(iter::Enumerate>>); + +impl<'a> Iterator for Entries<'a> { + type Item = (usize, Ebb); + + fn next(&mut self) -> Option { + loop { + if let Some((idx, dest)) = self.0.next() { + if dest != NO_EBB { + return Some((idx, dest)); + } + } else { + return None; + } + } + } +} + +impl Display for JumpTableData { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + let first = self.table.first().cloned().unwrap_or_default(); + if first == NO_EBB { + try!(write!(fmt, "jump_table 0")); + } else { + try!(write!(fmt, "jump_table {}", first)); + } + + for dest in self.table.iter().cloned().skip(1) { + if dest == NO_EBB { + try!(write!(fmt, ", 0")); + } else { + try!(write!(fmt, ", {}", dest)); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::JumpTableData; + use ir::entities::Ebb; + use entity_map::EntityRef; + + #[test] + fn empty() { + let jt = JumpTableData::new(); + + assert_eq!(jt.get_entry(0), None); + assert_eq!(jt.get_entry(10), None); + + assert_eq!(jt.to_string(), "jump_table 0"); + + let v: Vec<(usize, Ebb)> = jt.entries().collect(); + assert_eq!(v, []); + } + + #[test] + fn insert() { + let e1 = Ebb::new(1); + let e2 = Ebb::new(2); + + let mut jt = JumpTableData::new(); + + jt.set_entry(0, e1); + jt.set_entry(0, e2); + jt.set_entry(10, e1); + + assert_eq!(jt.to_string(), + "jump_table ebb2, 0, 0, 0, 0, 0, 0, 0, 0, 0, ebb1"); + + let v: Vec<(usize, Ebb)> = jt.entries().collect(); + assert_eq!(v, [(0, e2), (10, e1)]); + } +} diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 0d7e9da3a4..8d5ed99742 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -5,12 +5,14 @@ pub mod types; pub mod condcodes; pub mod immediates; pub mod instructions; +pub mod jumptable; pub mod dfg; pub mod layout; use ir::types::{FunctionName, Signature}; -use entity_map::EntityRef; -use ir::entities::StackSlot; +use entity_map::{EntityRef, EntityMap}; +use ir::entities::{StackSlot, JumpTable}; +use ir::jumptable::JumpTableData; use ir::dfg::DataFlowGraph; use ir::layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; @@ -27,6 +29,9 @@ pub struct Function { /// Stack slots allocated in this function. stack_slots: Vec, + /// Jump tables used in this function. + pub jump_tables: EntityMap, + /// Data flow graph containing the primary definition of all instructions, EBBs and values. pub dfg: DataFlowGraph, @@ -41,6 +46,7 @@ impl Function { name: name, signature: sig, stack_slots: Vec::new(), + jump_tables: EntityMap::new(), dfg: DataFlowGraph::new(), layout: Layout::new(), } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index b42fdf02fd..e7b35e806a 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -75,6 +75,11 @@ fn write_preamble(w: &mut Write, func: &Function) -> io::Result { try!(writeln!(w, " {} = {}", ss, func[ss])); } + for jt in func.jump_tables.keys() { + any = true; + try!(writeln!(w, " {} = {}", jt, func.jump_tables[jt])); + } + Ok(any) } diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 61dd7feb9b..a19c14adfd 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -38,6 +38,7 @@ pub enum Token<'a> { Value(Value), // v12, vx7 Ebb(Ebb), // ebb3 StackSlot(u32), // ss3 + JumpTable(u32), // jt2 Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) } @@ -291,6 +292,7 @@ impl<'a> Lexer<'a> { "vx" => Value::table_with_number(value).map(|v| Token::Value(v)), "ebb" => Ebb::with_number(value).map(|ebb| Token::Ebb(ebb)), "ss" => Some(Token::StackSlot(value)), + "jt" => Some(Token::JumpTable(value)), _ => None, } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index a961ccdf60..f55604f32a 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -17,6 +17,7 @@ use cretonne::ir::entities::*; use cretonne::ir::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; use cretonne::ir::{Function, StackSlotData}; +use cretonne::ir::jumptable::JumpTableData; pub use lexer::Location; @@ -71,6 +72,7 @@ pub struct Parser<'a> { struct Context { function: Function, stack_slots: HashMap, // ssNN + jump_tables: HashMap, // jtNN ebbs: HashMap, // ebbNN values: HashMap, // vNN, vxNN @@ -83,6 +85,7 @@ impl Context { Context { function: f, stack_slots: HashMap::new(), + jump_tables: HashMap::new(), ebbs: HashMap::new(), values: HashMap::new(), inst_locs: Vec::new(), @@ -98,6 +101,15 @@ impl Context { } } + // Allocate a new jump table and add a mapping number -> JumpTable. + fn add_jt(&mut self, number: u32, data: JumpTableData, loc: &Location) -> Result<()> { + if self.jump_tables.insert(number, self.function.jump_tables.push(data)).is_some() { + err!(loc, "duplicate jump table: jt{}", number) + } else { + Ok(()) + } + } + // Allocate a new EBB and add a mapping src_ebb -> Ebb. fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { let ebb = self.function.dfg.make_ebb(); @@ -211,8 +223,15 @@ impl Context { } } - // TODO: Rewrite EBB references in jump tables. (Once jump table data structures are - // defined). + // Rewrite EBB references in jump tables. + let loc = Location { line_number: 0 }; + for jt in self.function.jump_tables.keys() { + for ebb in self.function.jump_tables[jt].as_mut_slice() { + if *ebb != NO_EBB { + try!(Self::rewrite_ebb(&self.ebbs, ebb, &loc)); + } + } + } Ok(()) } @@ -312,6 +331,16 @@ impl<'a> Parser<'a> { } } + // Match and consume a jump table reference. + fn match_jt(&mut self, err_msg: &str) -> Result { + if let Some(Token::JumpTable(jt)) = self.token() { + self.consume(); + Ok(jt) + } else { + err!(self.loc, err_msg) + } + } + // Match and consume an ebb reference. fn match_ebb(&mut self, err_msg: &str) -> Result { if let Some(Token::Ebb(ebb)) = self.token() { @@ -530,6 +559,7 @@ impl<'a> Parser<'a> { // preamble-decl ::= * stack-slot-decl // * function-decl // * signature-decl + // * jump-table-decl // // The parsed decls are added to `ctx` rather than returned. fn parse_preamble(&mut self, ctx: &mut Context) -> Result<()> { @@ -539,13 +569,17 @@ impl<'a> Parser<'a> { self.parse_stack_slot_decl() .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) } + Some(Token::JumpTable(..)) => { + self.parse_jump_table_decl() + .and_then(|(num, dat)| ctx.add_jt(num, dat, &self.loc)) + } // More to come.. _ => return Ok(()), }); } } - // Parse a stack slot decl, add to `func`. + // Parse a stack slot decl. // // stack-slot-decl ::= * StackSlot(ss) "=" "stack_slot" Bytes {"," stack-slot-flag} fn parse_stack_slot_decl(&mut self) -> Result<(u32, StackSlotData)> { @@ -564,6 +598,48 @@ impl<'a> Parser<'a> { Ok((number, data)) } + // Parse a jump table decl. + // + // jump-table-decl ::= * JumpTable(jt) "=" "jump_table" jt-entry {"," jt-entry} + fn parse_jump_table_decl(&mut self) -> Result<(u32, JumpTableData)> { + let number = try!(self.match_jt("expected jump table number: jt«n»")); + try!(self.match_token(Token::Equal, "expected '=' in jump_table decl")); + try!(self.match_identifier("jump_table", "expected 'jump_table'")); + + let mut data = JumpTableData::new(); + + // jump-table-decl ::= JumpTable(jt) "=" "jump_table" * jt-entry {"," jt-entry} + for idx in 0usize.. { + if let Some(dest) = try!(self.parse_jump_table_entry()) { + data.set_entry(idx, dest); + } + if !self.optional(Token::Comma) { + return Ok((number, data)); + } + } + + err!(self.loc, "jump_table too long") + } + + // jt-entry ::= * Ebb(dest) | "0" + fn parse_jump_table_entry(&mut self) -> Result> { + match self.token() { + Some(Token::Integer(s)) => { + if s == "0" { + self.consume(); + Ok(None) + } else { + err!(self.loc, "invalid jump_table entry '{}'", s) + } + } + Some(Token::Ebb(dest)) => { + self.consume(); + Ok(Some(dest)) + } + _ => err!(self.loc, "expected jump_table entry"), + } + } + // Parse a function body, add contents to `ctx`. // // function-body ::= * { extended-basic-block } diff --git a/tests/parser/branch.cton b/tests/parser/branch.cton index bf49ba8a9f..148b59325c 100644 --- a/tests/parser/branch.cton +++ b/tests/parser/branch.cton @@ -43,3 +43,17 @@ ebb0(vx0: i32, vx1: f32): ebb1(vx2: i32, vx3: f32): brnz vx0, ebb0(vx2, vx3) } + +function jumptable() { + jt200 = jump_table 0, 0 + jt2 = jump_table 0, 0, ebb10, ebb40, ebb20, ebb30 + +ebb10: + trap +ebb20: + trap +ebb30: + trap +ebb40: + trap +} diff --git a/tests/parser/branch.cton.ref b/tests/parser/branch.cton.ref index 02444326b1..c640a6b793 100644 --- a/tests/parser/branch.cton.ref +++ b/tests/parser/branch.cton.ref @@ -37,3 +37,20 @@ ebb0(vx0: i32, vx1: f32): ebb1(vx2: i32, vx3: f32): brnz vx0, ebb0(vx2, vx3) } + +function jumptable() { + jt0 = jump_table 0 + jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 + +ebb0: + trap + +ebb1: + trap + +ebb2: + trap + +ebb3: + trap +} From 20fc675fc0a0fdf09084bdcb4fc2c68c40de10d3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jul 2016 15:16:14 -0700 Subject: [PATCH 158/968] Parse the BranchTable instruction format. Resolve the jump table reference immediately since all jump tables are declared in the preamble. --- src/libreader/parser.rs | 30 ++++++++++++++++++++++++------ tests/parser/branch.cton | 5 +++-- tests/parser/branch.cton.ref | 5 +++-- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index f55604f32a..41742b54e7 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -110,6 +110,14 @@ impl Context { } } + // Resolve a reference to a jump table. + fn get_jt(&self, number: u32, loc: &Location) -> Result { + match self.jump_tables.get(&number) { + Some(&jt) => Ok(jt), + None => err!(loc, "undefined jump table jt{}", number), + } + } + // Allocate a new EBB and add a mapping src_ebb -> Ebb. fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { let ebb = self.function.dfg.make_ebb(); @@ -332,12 +340,12 @@ impl<'a> Parser<'a> { } // Match and consume a jump table reference. - fn match_jt(&mut self, err_msg: &str) -> Result { + fn match_jt(&mut self) -> Result { if let Some(Token::JumpTable(jt)) = self.token() { self.consume(); Ok(jt) } else { - err!(self.loc, err_msg) + err!(self.loc, "expected jump table number: jt«n»") } } @@ -602,7 +610,7 @@ impl<'a> Parser<'a> { // // jump-table-decl ::= * JumpTable(jt) "=" "jump_table" jt-entry {"," jt-entry} fn parse_jump_table_decl(&mut self) -> Result<(u32, JumpTableData)> { - let number = try!(self.match_jt("expected jump table number: jt«n»")); + let number = try!(self.match_jt()); try!(self.match_token(Token::Equal, "expected '=' in jump_table decl")); try!(self.match_identifier("jump_table", "expected 'jump_table'")); @@ -763,7 +771,7 @@ impl<'a> Parser<'a> { }; // instruction ::= [inst-results "="] Opcode(opc) ["." Type] * ... - let inst_data = try!(self.parse_inst_operands(opcode)); + let inst_data = try!(self.parse_inst_operands(ctx, opcode)); // We're done parsing the instruction now. // @@ -914,7 +922,7 @@ impl<'a> Parser<'a> { // Parse the operands following the instruction opcode. // This depends on the format of the opcode. - fn parse_inst_operands(&mut self, opcode: Opcode) -> Result { + fn parse_inst_operands(&mut self, ctx: &Context, opcode: Opcode) -> Result { Ok(match opcode.format().unwrap() { InstructionFormat::Nullary => { InstructionData::Nullary { @@ -1096,7 +1104,17 @@ impl<'a> Parser<'a> { data: Box::new(ReturnData { args: args }), } } - InstructionFormat::BranchTable | + InstructionFormat::BranchTable => { + let arg = try!(self.match_value("expected SSA value operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let table = try!(self.match_jt().and_then(|num| ctx.get_jt(num, &self.loc))); + InstructionData::BranchTable { + opcode: opcode, + ty: VOID, + arg: arg, + table: table, + } + } InstructionFormat::Call => { unimplemented!(); } diff --git a/tests/parser/branch.cton b/tests/parser/branch.cton index 148b59325c..05526fd910 100644 --- a/tests/parser/branch.cton +++ b/tests/parser/branch.cton @@ -44,11 +44,12 @@ ebb1(vx2: i32, vx3: f32): brnz vx0, ebb0(vx2, vx3) } -function jumptable() { +function jumptable(i32) { jt200 = jump_table 0, 0 jt2 = jump_table 0, 0, ebb10, ebb40, ebb20, ebb30 -ebb10: +ebb10(v3: i32): + br_table v3, jt2 trap ebb20: trap diff --git a/tests/parser/branch.cton.ref b/tests/parser/branch.cton.ref index c640a6b793..8f395c885c 100644 --- a/tests/parser/branch.cton.ref +++ b/tests/parser/branch.cton.ref @@ -38,11 +38,12 @@ ebb1(vx2: i32, vx3: f32): brnz vx0, ebb0(vx2, vx3) } -function jumptable() { +function jumptable(i32) { jt0 = jump_table 0 jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 -ebb0: +ebb0(vx0: i32): + br_table vx0, jt1 trap ebb1: From ae98edf8cc6fcc70ddd42b552ca1c9199b1f4f12 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 22 Jul 2016 15:38:53 -0700 Subject: [PATCH 159/968] Add an analyze_branch method to InstructionData. Rather than switching on instruction formats to discover the destination of a branch, use the analyze_branch method which returns a BranchInfo enum with just the relevant information. This makes CFG algorithms independent of future instruction formats for branches. Only analyze_branch needs to be updated when adding a new format. --- src/libcretonne/cfg.rs | 20 +++++++++-------- src/libcretonne/ir/instructions.rs | 36 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 964a07a572..e221f55fcd 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -24,7 +24,7 @@ use ir::Function; use ir::entities::{Inst, Ebb}; -use ir::instructions::InstructionData; +use ir::instructions::BranchInfo; use entity_map::EntityMap; /// A basic block denoted by its enclosing Ebb and last instruction. @@ -68,16 +68,18 @@ impl ControlFlowGraph { for ebb in &func.layout { for inst in func.layout.ebb_insts(ebb) { - match func.dfg[inst] { - InstructionData::Branch { ty: _, opcode: _, ref data } => { - cfg.add_successor(ebb, data.destination); - cfg.add_predecessor(data.destination, (ebb, inst)); + match func.dfg[inst].analyze_branch() { + BranchInfo::SingleDest(dest, _) => { + cfg.add_successor(ebb, dest); + cfg.add_predecessor(dest, (ebb, inst)); } - InstructionData::Jump { ty: _, opcode: _, ref data } => { - cfg.add_successor(ebb, data.destination); - cfg.add_predecessor(data.destination, (ebb, inst)); + BranchInfo::Table(jt) => { + for (_, dest) in func.jump_tables[jt].entries() { + cfg.add_successor(ebb, dest); + cfg.add_predecessor(dest, (ebb, inst)); + } } - _ => (), + BranchInfo::NotABranch => {} } } } diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index a6c7bd03f1..4087e246d5 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -344,6 +344,42 @@ impl InstructionData { } } +/// Analyzing an instruction. +/// +/// Avoid large matches on instruction formats by using the methods efined here to examine +/// instructions. +impl InstructionData { + /// Return information about the destination of a branch or jump instruction. + /// + /// Any instruction that can transfer control to another EBB reveals its possible destinations + /// here. + pub fn analyze_branch<'a>(&'a self) -> BranchInfo<'a> { + match self { + &InstructionData::Jump { ref data, .. } => { + BranchInfo::SingleDest(data.destination, &data.arguments) + } + &InstructionData::Branch { ref data, .. } => { + BranchInfo::SingleDest(data.destination, &data.arguments) + } + &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), + _ => BranchInfo::NotABranch, + } + } +} + +/// Information about branch and jump instructions. +pub enum BranchInfo<'a> { + /// This is not a branch or jump instruction. + /// This instruction will not transfer control to another EBB in the function, but it may still + /// affect control flow by returning or trapping. + NotABranch, + + /// This is a branch or jump to a single destination EBB, possibly taking value arguments. + SingleDest(Ebb, &'a [Value]), + + /// This is a jump table branch which can have many destination EBBs. + Table(JumpTable), +} /// Value type constraints for a given opcode. /// From 91ced8df90ea180f2923f16b205151d22662aff5 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 25 Jul 2016 00:41:34 -0700 Subject: [PATCH 160/968] make postorder_ebbs into actually reverse_postorder_ebbs. This is a more accurate description. Further return the ebbs in a btreemap so that the index of each Ebb (its order of visitation) is quick to lookup. --- src/libcretonne/cfg.rs | 112 ++++++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 23 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index e221f55fcd..c853d81831 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -24,8 +24,9 @@ use ir::Function; use ir::entities::{Inst, Ebb}; -use ir::instructions::BranchInfo; +use ir::instructions::InstructionData; use entity_map::EntityMap; +use std::collections::{HashSet, BTreeMap}; /// A basic block denoted by its enclosing Ebb and last instruction. pub type BasicBlock = (Ebb, Inst); @@ -51,6 +52,7 @@ impl CFGNode { /// extended basic blocks. #[derive(Debug)] pub struct ControlFlowGraph { + entry_block: Option, data: EntityMap, } @@ -58,7 +60,11 @@ impl ControlFlowGraph { /// During initialization mappings will be generated for any existing /// blocks within the CFG's associated function. pub fn new(func: &Function) -> ControlFlowGraph { - let mut cfg = ControlFlowGraph { data: EntityMap::new() }; + + let mut cfg = ControlFlowGraph { + data: EntityMap::new(), + entry_block: func.layout.entry_block(), + }; // Even ebbs without predecessors should show up in the CFG, albeit // with no entires. @@ -68,18 +74,16 @@ impl ControlFlowGraph { for ebb in &func.layout { for inst in func.layout.ebb_insts(ebb) { - match func.dfg[inst].analyze_branch() { - BranchInfo::SingleDest(dest, _) => { - cfg.add_successor(ebb, dest); - cfg.add_predecessor(dest, (ebb, inst)); + match func.dfg[inst] { + InstructionData::Branch { ty: _, opcode: _, ref data } => { + cfg.add_successor(ebb, data.destination); + cfg.add_predecessor(data.destination, (ebb, inst)); } - BranchInfo::Table(jt) => { - for (_, dest) in func.jump_tables[jt].entries() { - cfg.add_successor(ebb, dest); - cfg.add_predecessor(dest, (ebb, inst)); - } + InstructionData::Jump { ty: _, opcode: _, ref data } => { + cfg.add_successor(ebb, data.destination); + cfg.add_predecessor(data.destination, (ebb, inst)); } - BranchInfo::NotABranch => {} + _ => (), } } } @@ -106,19 +110,30 @@ impl ControlFlowGraph { &self.data[ebb].successors } - pub fn postorder_ebbs(&self, entry: Ebb) -> Vec { - let mut stack_a = vec![entry]; - let mut stack_b = Vec::new(); + /// Return ebbs in reverse postorder along with a mapping of + /// the ebb to its order of visitation. + pub fn reverse_postorder_ebbs(&self) -> BTreeMap { + let entry_block = match self.entry_block { + None => { + return BTreeMap::new(); + } + Some(eb) => eb, + }; + let mut seen = HashSet::new(); + let mut stack_a = vec![entry_block]; + let mut finished = BTreeMap::new(); while stack_a.len() > 0 { let cur = stack_a.pop().unwrap(); for child in &self.data[cur].successors { - if *child != cur && !stack_a.contains(child) { + if *child != cur && !seen.contains(&child) { + seen.insert(child); stack_a.push(child.clone()); } } - stack_b.push(cur); + let index = finished.len(); + finished.insert(cur, index); } - stack_b + finished } pub fn len(&self) -> usize { @@ -281,12 +296,15 @@ mod tests { func.layout.append_inst(jmp_ebb2_ebb5, ebb2); let cfg = ControlFlowGraph::new(&func); - assert_eq!(cfg.postorder_ebbs(func.layout.entry_block().unwrap()), - vec![ebb0, ebb2, ebb5, ebb4, ebb1, ebb3]); + let mut postorder = vec![ebb3, ebb1, ebb4, ebb5, ebb2, ebb0]; + postorder.reverse(); + for (ebb, key) in cfg.reverse_postorder_ebbs() { + assert_eq!(ebb, postorder[key]); + } } #[test] - fn loop_edge() { + fn loops_one() { let mut func = Function::new(); let ebb0 = func.dfg.make_ebb(); let ebb1 = func.dfg.make_ebb(); @@ -308,7 +326,55 @@ mod tests { func.layout.append_inst(jmp_ebb2_ebb3, ebb2); let cfg = ControlFlowGraph::new(&func); - assert_eq!(cfg.postorder_ebbs(func.layout.entry_block().unwrap()), - vec![ebb0, ebb1, ebb2, ebb3]); + let mut postorder = vec![ebb3, ebb2, ebb1, ebb0]; + postorder.reverse(); + for (ebb, key) in cfg.reverse_postorder_ebbs() { + assert_eq!(ebb, postorder[key]); + } + } + + #[test] + fn loops_two() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + let ebb3 = func.dfg.make_ebb(); + let ebb4 = func.dfg.make_ebb(); + let ebb5 = func.dfg.make_ebb(); + + func.layout.append_ebb(ebb0); + func.layout.append_ebb(ebb1); + func.layout.append_ebb(ebb2); + func.layout.append_ebb(ebb3); + func.layout.append_ebb(ebb4); + func.layout.append_ebb(ebb5); + + let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); + let jmp_ebb0_ebb2 = make_inst::jump(&mut func, ebb2); + let jmp_ebb1_ebb3 = make_inst::jump(&mut func, ebb3); + let br_ebb2_ebb4 = make_inst::jump(&mut func, ebb4); + let jmp_ebb2_ebb5 = make_inst::jump(&mut func, ebb5); + let jmp_ebb3_ebb4 = make_inst::jump(&mut func, ebb4); + let br_ebb4_ebb3 = make_inst::branch(&mut func, ebb3); + let jmp_ebb4_ebb5 = make_inst::jump(&mut func, ebb5); + let jmp_ebb5_ebb4 = make_inst::jump(&mut func, ebb4); + + func.layout.append_inst(jmp_ebb0_ebb1, ebb0); + func.layout.append_inst(jmp_ebb0_ebb2, ebb0); + func.layout.append_inst(jmp_ebb1_ebb3, ebb1); + func.layout.append_inst(br_ebb2_ebb4, ebb2); + func.layout.append_inst(jmp_ebb2_ebb5, ebb2); + func.layout.append_inst(jmp_ebb3_ebb4, ebb3); + func.layout.append_inst(br_ebb4_ebb3, ebb4); + func.layout.append_inst(jmp_ebb4_ebb5, ebb4); + func.layout.append_inst(jmp_ebb5_ebb4, ebb5); + + let cfg = ControlFlowGraph::new(&func); + let mut postorder = vec![ebb1, ebb3, ebb4, ebb5, ebb2, ebb0]; + postorder.reverse(); + for (ebb, key) in cfg.reverse_postorder_ebbs() { + assert_eq!(ebb, postorder[key]); + } } } From bb7ecc8753d4f86d8319c724f70e5246f3d60b40 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 25 Jul 2016 01:05:15 -0700 Subject: [PATCH 161/968] Change variable name to something more descriptive. --- src/libcretonne/cfg.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index c853d81831..696c364af2 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -120,14 +120,14 @@ impl ControlFlowGraph { Some(eb) => eb, }; let mut seen = HashSet::new(); - let mut stack_a = vec![entry_block]; + let mut open_nodes = vec![entry_block]; let mut finished = BTreeMap::new(); - while stack_a.len() > 0 { - let cur = stack_a.pop().unwrap(); + while open_nodes.len() > 0 { + let cur = open_nodes.pop().unwrap(); for child in &self.data[cur].successors { if *child != cur && !seen.contains(&child) { seen.insert(child); - stack_a.push(child.clone()); + open_nodes.push(child.clone()); } } let index = finished.len(); From 63b58214f7f2526324b73a858594549047aa8117 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 25 Jul 2016 18:49:39 -0700 Subject: [PATCH 162/968] Use cton_reader to simplify cfg traversal tests. --- src/libcretonne/tests/cfg.rs | 85 ++++++++++++++++++++++++++++++++++++ src/libcretonne/tests/lib.rs | 1 + 2 files changed, 86 insertions(+) create mode 100644 src/libcretonne/tests/cfg.rs create mode 100644 src/libcretonne/tests/lib.rs diff --git a/src/libcretonne/tests/cfg.rs b/src/libcretonne/tests/cfg.rs new file mode 100644 index 0000000000..6ad024aefd --- /dev/null +++ b/src/libcretonne/tests/cfg.rs @@ -0,0 +1,85 @@ +extern crate cretonne; +extern crate cton_reader; + +use self::cton_reader::parser::Parser; +use self::cretonne::ir::entities::Ebb; +use self::cretonne::cfg::ControlFlowGraph; + +fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { + let func = &Parser::parse(function_source).unwrap()[0]; + let cfg = ControlFlowGraph::new(&func); + let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()) + .collect::>(); + for (ebb, key) in cfg.reverse_postorder_ebbs() { + assert_eq!(ebb, ebbs[key]); + } +} + +#[test] +fn simple_traversal() { + test_reverse_postorder_traversal(" + function test(i32) { + ebb0(v0: i32): + brz v0, ebb1 + jump ebb2 + ebb1: + jump ebb3 + ebb2: + v1 = iconst.i32 1 + v2 = iadd v1, v0 + brz v2, ebb2 + v3 = iadd v1, v2 + brz v3, ebb1 + v4 = iadd v1, v3 + brz v4, ebb4 + jump ebb5 + ebb3: + trap + ebb4: + trap + ebb5: + trap + } + ", vec![0, 2, 5, 4, 1, 3]); +} + +#[test] +fn loops_one() { + test_reverse_postorder_traversal(" + function test(i32) { + ebb0(v0: i32): + jump ebb1 + ebb1: + brnz v0, ebb3 + jump ebb2 + ebb2: + jump ebb3 + ebb3: + return + } + ", vec![0, 1, 2, 3]); +} + +#[test] +fn loops_two() { + test_reverse_postorder_traversal(" + function test(i32) { + ebb0(v0: i32): + brz v0, ebb1 + jump ebb2 + ebb1: + jump ebb3 + ebb2: + brz v0, ebb4 + jump ebb5 + ebb3: + jump ebb4 + ebb4: + brz v0, ebb3 + jump ebb5 + ebb5: + brz v0, ebb4 + return + } + ", vec![0, 2, 5, 4, 3, 1]); +} diff --git a/src/libcretonne/tests/lib.rs b/src/libcretonne/tests/lib.rs new file mode 100644 index 0000000000..26cb1041ac --- /dev/null +++ b/src/libcretonne/tests/lib.rs @@ -0,0 +1 @@ +pub mod cfg; From 400504d3211ad2301505e6d64aea2ee690808475 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 25 Jul 2016 18:50:50 -0700 Subject: [PATCH 163/968] Cargo-fmt and cleanup. --- src/libcretonne/Cargo.toml | 3 + src/libcretonne/cfg.rs | 121 ------------------------------------- 2 files changed, 3 insertions(+), 121 deletions(-) diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index e6158af94f..cfd886391c 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -12,4 +12,7 @@ build = "build.rs" name = "cretonne" path = "lib.rs" +[dev-dependencies] +cretonne-reader = { path = "../libreader" } + [dependencies] diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 696c364af2..d30262e4cd 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -256,125 +256,4 @@ mod tests { assert_eq!(ebb1_successors.contains(&ebb1), true); assert_eq!(ebb1_successors.contains(&ebb2), true); } - - #[test] - fn postorder_traversal() { - let mut func = Function::new(); - let ebb0 = func.dfg.make_ebb(); - let ebb1 = func.dfg.make_ebb(); - let ebb2 = func.dfg.make_ebb(); - let ebb3 = func.dfg.make_ebb(); - let ebb4 = func.dfg.make_ebb(); - let ebb5 = func.dfg.make_ebb(); - - func.layout.append_ebb(ebb0); - func.layout.append_ebb(ebb1); - func.layout.append_ebb(ebb2); - func.layout.append_ebb(ebb3); - func.layout.append_ebb(ebb4); - func.layout.append_ebb(ebb5); - - let br_ebb0_ebb1 = make_inst::branch(&mut func, ebb1); - func.layout.append_inst(br_ebb0_ebb1, ebb0); - - let jmp_ebb0_ebb2 = make_inst::jump(&mut func, ebb2); - func.layout.append_inst(jmp_ebb0_ebb2, ebb0); - - let br_ebb2_ebb2 = make_inst::branch(&mut func, ebb2); - func.layout.append_inst(br_ebb2_ebb2, ebb2); - - let br_ebb2_ebb1 = make_inst::branch(&mut func, ebb1); - func.layout.append_inst(br_ebb2_ebb1, ebb2); - - let jmp_ebb1_ebb3 = make_inst::jump(&mut func, ebb3); - func.layout.append_inst(jmp_ebb1_ebb3, ebb1); - - let br_ebb2_ebb4 = make_inst::branch(&mut func, ebb4); - func.layout.append_inst(br_ebb2_ebb4, ebb2); - - let jmp_ebb2_ebb5 = make_inst::jump(&mut func, ebb5); - func.layout.append_inst(jmp_ebb2_ebb5, ebb2); - - let cfg = ControlFlowGraph::new(&func); - let mut postorder = vec![ebb3, ebb1, ebb4, ebb5, ebb2, ebb0]; - postorder.reverse(); - for (ebb, key) in cfg.reverse_postorder_ebbs() { - assert_eq!(ebb, postorder[key]); - } - } - - #[test] - fn loops_one() { - let mut func = Function::new(); - let ebb0 = func.dfg.make_ebb(); - let ebb1 = func.dfg.make_ebb(); - let ebb2 = func.dfg.make_ebb(); - let ebb3 = func.dfg.make_ebb(); - func.layout.append_ebb(ebb0); - func.layout.append_ebb(ebb1); - func.layout.append_ebb(ebb2); - func.layout.append_ebb(ebb3); - - let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); - let br_ebb1_ebb3 = make_inst::branch(&mut func, ebb3); - let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); - let jmp_ebb2_ebb3 = make_inst::jump(&mut func, ebb3); - - func.layout.append_inst(jmp_ebb0_ebb1, ebb0); - func.layout.append_inst(br_ebb1_ebb3, ebb1); - func.layout.append_inst(jmp_ebb1_ebb2, ebb1); - func.layout.append_inst(jmp_ebb2_ebb3, ebb2); - - let cfg = ControlFlowGraph::new(&func); - let mut postorder = vec![ebb3, ebb2, ebb1, ebb0]; - postorder.reverse(); - for (ebb, key) in cfg.reverse_postorder_ebbs() { - assert_eq!(ebb, postorder[key]); - } - } - - #[test] - fn loops_two() { - let mut func = Function::new(); - let ebb0 = func.dfg.make_ebb(); - let ebb1 = func.dfg.make_ebb(); - let ebb2 = func.dfg.make_ebb(); - let ebb3 = func.dfg.make_ebb(); - let ebb4 = func.dfg.make_ebb(); - let ebb5 = func.dfg.make_ebb(); - - func.layout.append_ebb(ebb0); - func.layout.append_ebb(ebb1); - func.layout.append_ebb(ebb2); - func.layout.append_ebb(ebb3); - func.layout.append_ebb(ebb4); - func.layout.append_ebb(ebb5); - - let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); - let jmp_ebb0_ebb2 = make_inst::jump(&mut func, ebb2); - let jmp_ebb1_ebb3 = make_inst::jump(&mut func, ebb3); - let br_ebb2_ebb4 = make_inst::jump(&mut func, ebb4); - let jmp_ebb2_ebb5 = make_inst::jump(&mut func, ebb5); - let jmp_ebb3_ebb4 = make_inst::jump(&mut func, ebb4); - let br_ebb4_ebb3 = make_inst::branch(&mut func, ebb3); - let jmp_ebb4_ebb5 = make_inst::jump(&mut func, ebb5); - let jmp_ebb5_ebb4 = make_inst::jump(&mut func, ebb4); - - func.layout.append_inst(jmp_ebb0_ebb1, ebb0); - func.layout.append_inst(jmp_ebb0_ebb2, ebb0); - func.layout.append_inst(jmp_ebb1_ebb3, ebb1); - func.layout.append_inst(br_ebb2_ebb4, ebb2); - func.layout.append_inst(jmp_ebb2_ebb5, ebb2); - func.layout.append_inst(jmp_ebb3_ebb4, ebb3); - func.layout.append_inst(br_ebb4_ebb3, ebb4); - func.layout.append_inst(jmp_ebb4_ebb5, ebb4); - func.layout.append_inst(jmp_ebb5_ebb4, ebb5); - - let cfg = ControlFlowGraph::new(&func); - let mut postorder = vec![ebb1, ebb3, ebb4, ebb5, ebb2, ebb0]; - postorder.reverse(); - for (ebb, key) in cfg.reverse_postorder_ebbs() { - assert_eq!(ebb, postorder[key]); - } - } } From 71bf589af36f0c89c92317f7ed2195f6be824d5a Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 25 Jul 2016 19:19:46 -0700 Subject: [PATCH 164/968] Fix the test-all script so that it works with directories --- test-all.sh | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/test-all.sh b/test-all.sh index 31638a6db3..7ddd65a21d 100755 --- a/test-all.sh +++ b/test-all.sh @@ -17,16 +17,20 @@ set -e cd $(dirname "$0") topdir=$(pwd) -# Run cargo from the src/tools directory which includes all our crates for -# building cton-util. -cd "$topdir/src/tools" -PKGS="-p cretonne -p cretonne-reader -p cretonne-tools" -echo ====== Rust unit tests and debug build ====== -cargo test $PKGS -cargo build $PKGS -cargo doc +PKGS="libcretonne libreader tools" +echo ====== Rust unit tests and debug builds ====== +for PKG in $PKGS +do + pushd $topdir/src/$PKG + cargo test + cargo build + popd +done -echo ====== Rust release build ====== +# Build cton-util for parser testing. +echo ====== Rust release build and documentation ====== +cd "$topdir/src/tools" +cargo doc cargo build --release export CTONUTIL="$topdir/src/tools/target/release/cton-util" From 42b98353636c03c830ef47d9c3ed8beaf0548e97 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 26 Jul 2016 02:54:42 -0700 Subject: [PATCH 165/968] Fix broken reverse_postorder_ebbs implementation. The previous implementation was actually a reverse preorder walk. --- src/libcretonne/cfg.rs | 111 ++++++++++++++++++++++++++++++----- src/libcretonne/tests/cfg.rs | 11 +++- 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index d30262e4cd..bc78a3e904 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -26,7 +26,7 @@ use ir::Function; use ir::entities::{Inst, Ebb}; use ir::instructions::InstructionData; use entity_map::EntityMap; -use std::collections::{HashSet, BTreeMap}; +use std::collections::{HashMap, BTreeMap}; /// A basic block denoted by its enclosing Ebb and last instruction. pub type BasicBlock = (Ebb, Inst); @@ -111,7 +111,7 @@ impl ControlFlowGraph { } /// Return ebbs in reverse postorder along with a mapping of - /// the ebb to its order of visitation. + /// the ebb to its [post]order of visitation. pub fn reverse_postorder_ebbs(&self) -> BTreeMap { let entry_block = match self.entry_block { None => { @@ -119,21 +119,14 @@ impl ControlFlowGraph { } Some(eb) => eb, }; - let mut seen = HashSet::new(); - let mut open_nodes = vec![entry_block]; - let mut finished = BTreeMap::new(); - while open_nodes.len() > 0 { - let cur = open_nodes.pop().unwrap(); - for child in &self.data[cur].successors { - if *child != cur && !seen.contains(&child) { - seen.insert(child); - open_nodes.push(child.clone()); - } - } - let index = finished.len(); - finished.insert(cur, index); + let mut postorder = CFGPostorderWalker::new(&self, entry_block).walk(); + postorder.reverse(); + let mut result = BTreeMap::new(); + for (offset, ebb) in postorder.iter().enumerate() { + let i = postorder.len() - offset; + result.insert(ebb.clone(), i); } - finished + result } pub fn len(&self) -> usize { @@ -148,6 +141,92 @@ impl ControlFlowGraph { } } +/// A helper for iteratively walking a CFG in postorder. +pub struct CFGPostorderWalker<'a> { + cfg: &'a ControlFlowGraph, + start: Ebb, + // Ebbs are mapped to a tuple of booleans where the first bool + // is true if the node has been visited, and the second is + // true if its children have been visited. + visited: HashMap, + levels: Vec>, + // Our node index within the current level. + level_index: usize, +} + +impl<'a> CFGPostorderWalker<'a> { + fn new(cfg: &ControlFlowGraph, start: Ebb) -> CFGPostorderWalker { + CFGPostorderWalker { + cfg: cfg, + start: start, + visited: HashMap::new(), + levels: Vec::new(), + level_index: 0, + } + } + + fn visited(&self, ebb: Ebb) -> bool { + match self.visited.get(&ebb) { + Some(b) => b.0, + None => false, + } + } + + fn children_visited(&self, ebb: Ebb) -> bool { + match self.visited.get(&ebb) { + Some(b) => b.1, + None => false, + } + } + + fn mark_visited(&mut self, ebb: Ebb) { + let status = self.visited.entry(ebb).or_insert((false, false)).1; + self.visited.insert(ebb, (true, status)); + } + + fn mark_children_visited(&mut self, ebb: Ebb) { + let status = self.visited.entry(ebb).or_insert((false, false)).0; + self.visited.insert(ebb, (status, true)); + } + + fn walk(&mut self) -> Vec { + let mut postorder = Vec::new(); + + self.levels.push(vec![self.start.clone()]); + while self.levels.len() > 0 { + let level = &self.levels[self.levels.len() - 1].clone(); + + if self.level_index >= level.len() { + self.levels.pop(); + self.level_index = 0; + continue; + } + + let node = level[self.level_index].clone(); + if !self.visited(node) { + if self.children_visited(node) { + self.mark_visited(node); + postorder.push(node.clone()); + self.level_index += 1; + } else { + let edges = self.cfg.get_successors(node); + let outgoing = edges.iter() + .filter(|e| !self.children_visited(**e)) + .cloned() + .collect::>(); + if outgoing.len() > 0 { + self.levels.push(outgoing); + } + self.mark_children_visited(node); + } + } else { + self.level_index += 1; + } + } + postorder + } +} + /// Iterate through every mapping of ebb to predecessors in the CFG pub struct CFGPredecessorsIter<'a> { cfg: &'a ControlFlowGraph, diff --git a/src/libcretonne/tests/cfg.rs b/src/libcretonne/tests/cfg.rs index 6ad024aefd..3024b8a8fb 100644 --- a/src/libcretonne/tests/cfg.rs +++ b/src/libcretonne/tests/cfg.rs @@ -10,8 +10,13 @@ fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) let cfg = ControlFlowGraph::new(&func); let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()) .collect::>(); - for (ebb, key) in cfg.reverse_postorder_ebbs() { - assert_eq!(ebb, ebbs[key]); + + let reverse_order_ebbs = cfg.reverse_postorder_ebbs(); + + assert_eq!(reverse_order_ebbs.len(), ebbs.len()); + + for (ebb, key) in reverse_order_ebbs { + assert_eq!(ebb, ebbs[ebbs.len() - key]); } } @@ -81,5 +86,5 @@ fn loops_two() { brz v0, ebb4 return } - ", vec![0, 2, 5, 4, 3, 1]); + ", vec![0, 2, 1, 3, 4, 5]); } From 1cd6e35a427882f9b227722c0e3fa59b7b6a5899 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 26 Jul 2016 12:07:18 -0700 Subject: [PATCH 166/968] Give the test module a more apt name. --- .../tests/{cfg.rs => cfg_traversal.rs} | 36 ++++++++++++++++--- src/libcretonne/tests/lib.rs | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) rename src/libcretonne/tests/{cfg.rs => cfg_traversal.rs} (71%) diff --git a/src/libcretonne/tests/cfg.rs b/src/libcretonne/tests/cfg_traversal.rs similarity index 71% rename from src/libcretonne/tests/cfg.rs rename to src/libcretonne/tests/cfg_traversal.rs index 3024b8a8fb..056b72dc15 100644 --- a/src/libcretonne/tests/cfg.rs +++ b/src/libcretonne/tests/cfg_traversal.rs @@ -11,11 +11,10 @@ fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()) .collect::>(); - let reverse_order_ebbs = cfg.reverse_postorder_ebbs(); + let reverse_postorder_ebbs = cfg.reverse_postorder_ebbs(); - assert_eq!(reverse_order_ebbs.len(), ebbs.len()); - - for (ebb, key) in reverse_order_ebbs { + assert_eq!(reverse_postorder_ebbs.len(), ebbs.len()); + for (ebb, key) in reverse_postorder_ebbs { assert_eq!(ebb, ebbs[ebbs.len() - key]); } } @@ -88,3 +87,32 @@ fn loops_two() { } ", vec![0, 2, 1, 3, 4, 5]); } + +#[test] +fn loops_three() { + test_reverse_postorder_traversal(" + function test(i32) { + ebb0(v0: i32): + brz v0, ebb1 + jump ebb2 + ebb1: + jump ebb3 + ebb2: + brz v0, ebb4 + jump ebb5 + ebb3: + jump ebb4 + ebb4: + brz v0, ebb3 + jump ebb6 + brz v0, ebb5 + ebb5: + brz v0, ebb4 + trap + ebb6: + jump ebb7 + ebb7: + return + } + ", vec![0, 2, 1, 3, 4, 5, 6, 7]); +} diff --git a/src/libcretonne/tests/lib.rs b/src/libcretonne/tests/lib.rs index 26cb1041ac..70d0709a9c 100644 --- a/src/libcretonne/tests/lib.rs +++ b/src/libcretonne/tests/lib.rs @@ -1 +1 @@ -pub mod cfg; +pub mod cfg_traversal; From e94d7c2a9991a4d9e44026be694e0efdb76e6f81 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 26 Jul 2016 17:13:11 -0700 Subject: [PATCH 167/968] Avoid cloning levels --- src/libcretonne/cfg.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index bc78a3e904..5d1cbaadfe 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -142,7 +142,7 @@ impl ControlFlowGraph { } /// A helper for iteratively walking a CFG in postorder. -pub struct CFGPostorderWalker<'a> { +struct CFGPostorderWalker<'a> { cfg: &'a ControlFlowGraph, start: Ebb, // Ebbs are mapped to a tuple of booleans where the first bool @@ -194,15 +194,14 @@ impl<'a> CFGPostorderWalker<'a> { self.levels.push(vec![self.start.clone()]); while self.levels.len() > 0 { - let level = &self.levels[self.levels.len() - 1].clone(); - - if self.level_index >= level.len() { + let level_len = self.levels[self.levels.len() - 1].len(); + if self.level_index >= level_len { self.levels.pop(); self.level_index = 0; continue; } - let node = level[self.level_index].clone(); + let node = self.levels[self.levels.len() - 1][self.level_index].clone(); if !self.visited(node) { if self.children_visited(node) { self.mark_visited(node); From ad79ad753d53f64a7b5e29cc07bfc36c5c7dabfe Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 27 Jul 2016 16:10:02 -0700 Subject: [PATCH 168/968] Documentation typos. --- docs/metaref.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index 65b8f88654..fc9c5e4e29 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -19,7 +19,7 @@ top-level directory. The descriptions are processed in two steps: and other properties. 2. The static data structures are processed to produce Rust source code and - constant dables tables. + constant tables. The main driver for this source code generation process is the :file:`meta/build.py` script which is invoked as part of the build process if @@ -115,7 +115,7 @@ Concrete value types are represented as instances of :class:`cretonne.ValueType` subclasses to represent scalar and vector types. .. autoclass:: ValueType -.. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType +.. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType BoolType :parts: 1 .. autoclass:: ScalarType :members: @@ -125,6 +125,8 @@ subclasses to represent scalar and vector types. :members: .. autoclass:: FloatType :members: +.. autoclass:: BoolType + :members: .. automodule:: cretonne.types :members: From 82ff64820c06895ff498538d0777372992e2936e Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 28 Jul 2016 17:49:25 -0700 Subject: [PATCH 169/968] Simplify the reverse_postorder_ebbs implementation. --- src/libcretonne/cfg.rs | 134 +++++++------------------ src/libcretonne/tests/cfg_traversal.rs | 30 +++++- 2 files changed, 64 insertions(+), 100 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 5d1cbaadfe..f9df5e2997 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -24,9 +24,9 @@ use ir::Function; use ir::entities::{Inst, Ebb}; -use ir::instructions::InstructionData; +use ir::instructions::BranchInfo; use entity_map::EntityMap; -use std::collections::{HashMap, BTreeMap}; +use std::collections::{HashSet, BTreeMap}; /// A basic block denoted by its enclosing Ebb and last instruction. pub type BasicBlock = (Ebb, Inst); @@ -74,16 +74,18 @@ impl ControlFlowGraph { for ebb in &func.layout { for inst in func.layout.ebb_insts(ebb) { - match func.dfg[inst] { - InstructionData::Branch { ty: _, opcode: _, ref data } => { - cfg.add_successor(ebb, data.destination); - cfg.add_predecessor(data.destination, (ebb, inst)); + match func.dfg[inst].analyze_branch() { + BranchInfo::SingleDest(dest, _) => { + cfg.add_successor(ebb, dest); + cfg.add_predecessor(dest, (ebb, inst)); } - InstructionData::Jump { ty: _, opcode: _, ref data } => { - cfg.add_successor(ebb, data.destination); - cfg.add_predecessor(data.destination, (ebb, inst)); + BranchInfo::Table(jt) => { + for (_, dest) in func.jump_tables[jt].entries() { + cfg.add_successor(ebb, dest); + cfg.add_predecessor(dest, (ebb, inst)); + } } - _ => (), + BranchInfo::NotABranch => {} } } } @@ -119,8 +121,33 @@ impl ControlFlowGraph { } Some(eb) => eb, }; - let mut postorder = CFGPostorderWalker::new(&self, entry_block).walk(); + + let mut grey = HashSet::new(); + let mut black = HashSet::new(); + let mut stack = vec![entry_block.clone()]; + let mut postorder = Vec::new(); + + while !stack.is_empty() { + let node = stack.pop().unwrap(); + if !grey.contains(&node) { + // This is a white node. Mark it as gray. + grey.insert(node); + stack.push(node); + // Get any children we’ve never seen before. + for child in self.get_successors(node) { + if !grey.contains(child) { + stack.push(child.clone()); + } + } + } else if !black.contains(&node) { + // This is a gray node, now becoming black. + // We don’t need to mark it since we won’t see it again. + postorder.push(node.clone()); + black.insert(node.clone()); + } + } postorder.reverse(); + let mut result = BTreeMap::new(); for (offset, ebb) in postorder.iter().enumerate() { let i = postorder.len() - offset; @@ -141,91 +168,6 @@ impl ControlFlowGraph { } } -/// A helper for iteratively walking a CFG in postorder. -struct CFGPostorderWalker<'a> { - cfg: &'a ControlFlowGraph, - start: Ebb, - // Ebbs are mapped to a tuple of booleans where the first bool - // is true if the node has been visited, and the second is - // true if its children have been visited. - visited: HashMap, - levels: Vec>, - // Our node index within the current level. - level_index: usize, -} - -impl<'a> CFGPostorderWalker<'a> { - fn new(cfg: &ControlFlowGraph, start: Ebb) -> CFGPostorderWalker { - CFGPostorderWalker { - cfg: cfg, - start: start, - visited: HashMap::new(), - levels: Vec::new(), - level_index: 0, - } - } - - fn visited(&self, ebb: Ebb) -> bool { - match self.visited.get(&ebb) { - Some(b) => b.0, - None => false, - } - } - - fn children_visited(&self, ebb: Ebb) -> bool { - match self.visited.get(&ebb) { - Some(b) => b.1, - None => false, - } - } - - fn mark_visited(&mut self, ebb: Ebb) { - let status = self.visited.entry(ebb).or_insert((false, false)).1; - self.visited.insert(ebb, (true, status)); - } - - fn mark_children_visited(&mut self, ebb: Ebb) { - let status = self.visited.entry(ebb).or_insert((false, false)).0; - self.visited.insert(ebb, (status, true)); - } - - fn walk(&mut self) -> Vec { - let mut postorder = Vec::new(); - - self.levels.push(vec![self.start.clone()]); - while self.levels.len() > 0 { - let level_len = self.levels[self.levels.len() - 1].len(); - if self.level_index >= level_len { - self.levels.pop(); - self.level_index = 0; - continue; - } - - let node = self.levels[self.levels.len() - 1][self.level_index].clone(); - if !self.visited(node) { - if self.children_visited(node) { - self.mark_visited(node); - postorder.push(node.clone()); - self.level_index += 1; - } else { - let edges = self.cfg.get_successors(node); - let outgoing = edges.iter() - .filter(|e| !self.children_visited(**e)) - .cloned() - .collect::>(); - if outgoing.len() > 0 { - self.levels.push(outgoing); - } - self.mark_children_visited(node); - } - } else { - self.level_index += 1; - } - } - postorder - } -} - /// Iterate through every mapping of ebb to predecessors in the CFG pub struct CFGPredecessorsIter<'a> { cfg: &'a ControlFlowGraph, diff --git a/src/libcretonne/tests/cfg_traversal.rs b/src/libcretonne/tests/cfg_traversal.rs index 056b72dc15..8a3ca97594 100644 --- a/src/libcretonne/tests/cfg_traversal.rs +++ b/src/libcretonne/tests/cfg_traversal.rs @@ -44,7 +44,7 @@ fn simple_traversal() { ebb5: trap } - ", vec![0, 2, 5, 4, 1, 3]); + ", vec![0, 2, 1, 3, 4, 5]); } #[test] @@ -85,7 +85,7 @@ fn loops_two() { brz v0, ebb4 return } - ", vec![0, 2, 1, 3, 4, 5]); + ", vec![0, 1, 2, 5, 4, 3]); } #[test] @@ -104,8 +104,8 @@ fn loops_three() { jump ebb4 ebb4: brz v0, ebb3 + brnz v0, ebb5 jump ebb6 - brz v0, ebb5 ebb5: brz v0, ebb4 trap @@ -114,5 +114,27 @@ fn loops_three() { ebb7: return } - ", vec![0, 2, 1, 3, 4, 5, 6, 7]); + ", vec![0, 1, 2, 5, 4, 3, 6, 7]); +} + +#[test] +fn back_edge_one() { + test_reverse_postorder_traversal(" + function test(i32) { + ebb0(v0: i32): + brz v0, ebb1 + jump ebb2 + ebb1: + jump ebb3 + ebb2: + brz v0, ebb0 + jump ebb4 + ebb3: + brz v0, ebb2 + brnz v0, ebb0 + return + ebb4: + trap + } + ", vec![0, 1, 3, 2, 4]); } From a9748dff0276f653b6ad6af334ff91a0602aceb6 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 28 Jul 2016 17:51:50 -0700 Subject: [PATCH 170/968] Remove innacurate comments. --- src/libcretonne/cfg.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index f9df5e2997..ff45b59ae8 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -140,8 +140,6 @@ impl ControlFlowGraph { } } } else if !black.contains(&node) { - // This is a gray node, now becoming black. - // We don’t need to mark it since we won’t see it again. postorder.push(node.clone()); black.insert(node.clone()); } From 14027660c5443b28aa1c56ab4dfa6ae5b34b2796 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Jul 2016 12:35:56 -0700 Subject: [PATCH 171/968] Use sub-shells instead of pushd / popd. The push and pop commands print the directory stack to stdout, while subshells and cd is quiet. --- test-all.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test-all.sh b/test-all.sh index 7ddd65a21d..28013ec2e3 100755 --- a/test-all.sh +++ b/test-all.sh @@ -21,10 +21,11 @@ PKGS="libcretonne libreader tools" echo ====== Rust unit tests and debug builds ====== for PKG in $PKGS do - pushd $topdir/src/$PKG + ( + cd $topdir/src/$PKG cargo test cargo build - popd + ) done # Build cton-util for parser testing. From bbdae39cb96e45c922e5487721786a9806155517 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Jul 2016 15:59:09 -0700 Subject: [PATCH 172/968] Add EntityMap::with_capacity. Create a secondary entity map with space reserved for a known range of entity references. Add dfg.num_ebbs() and dfg.num_insts() methods to provide capacities. --- src/libcretonne/entity_map.rs | 15 +++++++++++++++ src/libcretonne/ir/dfg.rs | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 9ce39bec19..a0f4f3fa6b 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -102,6 +102,21 @@ impl EntityMap where K: EntityRef, V: Clone + Default { + /// Create a new secondary `EntityMap` that is prepared to hold `n` elements. + /// + /// Use this when the length of the primary map is known: + /// ``` + /// let secondary_map = EntityMap::with_capacity(primary_map.len()); + /// ``` + pub fn with_capacity(n: usize) -> Self { + let mut map = EntityMap { + elems: Vec::with_capacity(n), + unused: PhantomData, + }; + map.elems.resize(n, V::default()); + map + } + /// Ensure that `k` is a valid key but adding default entries if necesssary. /// /// Return a mutable reference to the corresponding entry. diff --git a/src/libcretonne/ir/dfg.rs b/src/libcretonne/ir/dfg.rs index d377813544..aba45354d7 100644 --- a/src/libcretonne/ir/dfg.rs +++ b/src/libcretonne/ir/dfg.rs @@ -42,6 +42,22 @@ impl DataFlowGraph { extended_values: Vec::new(), } } + + /// Get the total number of instructions created in this function, whether they are currently + /// inserted in the layout or not. + /// + /// This is intended for use with `EntityMap::with_capacity`. + pub fn num_insts(&self) -> usize { + self.insts.len() + } + + /// Get the total number of extended basic blocks created in this function, whether they are + /// currently inserted in the layout or not. + /// + /// This is intended for use with `EntityMap::with_capacity`. + pub fn num_ebbs(&self) -> usize { + self.ebbs.len() + } } /// Handling values. From 0ce01c1b8dffd46691284ba1f4170da3d5296c8d Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Sun, 31 Jul 2016 21:31:18 -0700 Subject: [PATCH 173/968] Clean up the CFG representation. --- src/libcretonne/cfg.rs | 41 ++++++++------------------ src/libcretonne/tests/cfg_traversal.rs | 4 +-- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index ff45b59ae8..25b5f2409a 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -26,13 +26,13 @@ use ir::Function; use ir::entities::{Inst, Ebb}; use ir::instructions::BranchInfo; use entity_map::EntityMap; -use std::collections::{HashSet, BTreeMap}; +use std::collections::HashSet; /// A basic block denoted by its enclosing Ebb and last instruction. pub type BasicBlock = (Ebb, Inst); /// A container for the successors and predecessors of some Ebb. -#[derive(Debug)] +#[derive(Debug, Clone, Default)] pub struct CFGNode { pub successors: Vec, pub predecessors: Vec, @@ -62,27 +62,19 @@ impl ControlFlowGraph { pub fn new(func: &Function) -> ControlFlowGraph { let mut cfg = ControlFlowGraph { - data: EntityMap::new(), + data: EntityMap::with_capacity(func.dfg.num_ebbs()), entry_block: func.layout.entry_block(), }; - // Even ebbs without predecessors should show up in the CFG, albeit - // with no entires. - for _ in &func.layout { - cfg.push_ebb(); - } - for ebb in &func.layout { for inst in func.layout.ebb_insts(ebb) { match func.dfg[inst].analyze_branch() { BranchInfo::SingleDest(dest, _) => { - cfg.add_successor(ebb, dest); - cfg.add_predecessor(dest, (ebb, inst)); + cfg.add_edge((ebb, inst), dest); } BranchInfo::Table(jt) => { for (_, dest) in func.jump_tables[jt].entries() { - cfg.add_successor(ebb, dest); - cfg.add_predecessor(dest, (ebb, inst)); + cfg.add_edge((ebb, inst), dest); } } BranchInfo::NotABranch => {} @@ -92,19 +84,12 @@ impl ControlFlowGraph { cfg } - pub fn push_ebb(&mut self) { - self.data.push(CFGNode::new()); + fn add_edge(&mut self, from: BasicBlock, to: Ebb) { + self.data[from.0].successors.push(to); + self.data[to].predecessors.push(from); } - pub fn add_successor(&mut self, from: Ebb, to: Ebb) { - self.data[from].successors.push(to); - } - - pub fn add_predecessor(&mut self, ebb: Ebb, predecessor: BasicBlock) { - self.data[ebb].predecessors.push(predecessor); - } - - pub fn get_predecessors(&self, ebb: Ebb) -> &Vec { + fn get_predecessors(&self, ebb: Ebb) -> &Vec { &self.data[ebb].predecessors } @@ -114,10 +99,10 @@ impl ControlFlowGraph { /// Return ebbs in reverse postorder along with a mapping of /// the ebb to its [post]order of visitation. - pub fn reverse_postorder_ebbs(&self) -> BTreeMap { + pub fn reverse_postorder_ebbs(&self) -> EntityMap { let entry_block = match self.entry_block { None => { - return BTreeMap::new(); + return EntityMap::new(); } Some(eb) => eb, }; @@ -146,10 +131,10 @@ impl ControlFlowGraph { } postorder.reverse(); - let mut result = BTreeMap::new(); + let mut result = EntityMap::with_capacity(postorder.len()); for (offset, ebb) in postorder.iter().enumerate() { let i = postorder.len() - offset; - result.insert(ebb.clone(), i); + result[ebb.clone()] = i; } result } diff --git a/src/libcretonne/tests/cfg_traversal.rs b/src/libcretonne/tests/cfg_traversal.rs index 8a3ca97594..ae74445dd4 100644 --- a/src/libcretonne/tests/cfg_traversal.rs +++ b/src/libcretonne/tests/cfg_traversal.rs @@ -14,8 +14,8 @@ fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) let reverse_postorder_ebbs = cfg.reverse_postorder_ebbs(); assert_eq!(reverse_postorder_ebbs.len(), ebbs.len()); - for (ebb, key) in reverse_postorder_ebbs { - assert_eq!(ebb, ebbs[ebbs.len() - key]); + for ebb in reverse_postorder_ebbs.keys() { + assert_eq!(ebb, ebbs[ebbs.len() - reverse_postorder_ebbs[ebb]]); } } From 19a47d57b3b27860735644e6c98637c14232a6db Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Sun, 31 Jul 2016 21:40:11 -0700 Subject: [PATCH 174/968] Add a dominator tree implementation. --- src/libcretonne/cfg.rs | 2 +- src/libcretonne/dominator_tree.rs | 138 ++++++++++++++++++++++++ src/libcretonne/lib.rs | 2 +- src/libcretonne/tests/dominator_tree.rs | 60 +++++++++++ src/libcretonne/tests/lib.rs | 1 + 5 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/libcretonne/dominator_tree.rs create mode 100644 src/libcretonne/tests/dominator_tree.rs diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 25b5f2409a..28a39861e2 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -89,7 +89,7 @@ impl ControlFlowGraph { self.data[to].predecessors.push(from); } - fn get_predecessors(&self, ebb: Ebb) -> &Vec { + pub fn get_predecessors(&self, ebb: Ebb) -> &Vec { &self.data[ebb].predecessors } diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs new file mode 100644 index 0000000000..e4569ea112 --- /dev/null +++ b/src/libcretonne/dominator_tree.rs @@ -0,0 +1,138 @@ +/// ! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. + +use cfg::*; +use ir::entities::Ebb; +use entity_map::EntityMap; + +pub struct DominatorTree { + data: EntityMap>, +} + +impl DominatorTree { + pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { + let mut dt = DominatorTree { data: EntityMap::new() }; + dt.build(cfg); + dt + } + + pub fn build(&mut self, cfg: &ControlFlowGraph) { + let reverse_postorder_map = cfg.reverse_postorder_ebbs(); + let ebbs = reverse_postorder_map.keys().collect::>(); + let len = reverse_postorder_map.len(); + + for (i, ebb) in ebbs.iter().enumerate() { + if i > 0 { + self.data.push(None); + } else { + self.data.push(Some(ebb.clone())); + } + } + + let mut changed = len > 0; + + while changed { + changed = false; + for i in 1..len { + let ebb = ebbs[i]; + let preds = cfg.get_predecessors(ebb); + let mut new_idom = None; + + for &(p, _) in preds { + if new_idom == None { + new_idom = Some(p); + continue; + } + if let Some(_) = self.data[p] { + new_idom = + Some(self.intersect(&reverse_postorder_map, p, new_idom.unwrap())); + } + } + match self.data[ebb] { + None => { + self.data[ebb] = new_idom; + changed = true; + } + Some(idom) => { + // Old idom != New idom + if idom != new_idom.unwrap() { + self.data[ebb] = new_idom; + changed = true; + } + } + } + } + } + } + + fn intersect(&self, ordering: &EntityMap, first: Ebb, second: Ebb) -> Ebb { + println!("A {} B {}", first, second); + let mut a = first; + let mut b = second; + while a != b { + while ordering[a] < ordering[b] { + a = self.data[a].unwrap(); + } + while ordering[b] < ordering[a] { + b = self.data[b].unwrap(); + } + } + a + } + + pub fn idom(&self, ebb: Ebb) -> Option { + self.data[ebb].clone() + } + + pub fn len(&self) -> usize { + self.data.len() + } +} + +#[cfg(test)] +mod test { + use super::*; + use ir::Function; + use cfg::ControlFlowGraph; + use test_utils::make_inst; + + #[test] + fn empty() { + let func = Function::new(); + let cfg = ControlFlowGraph::new(&func); + let dtree = DominatorTree::new(&cfg); + assert_eq!(dtree.len(), 0); + } + + #[test] + fn non_zero_entry_block() { + let mut func = Function::new(); + let ebb3 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + let ebb0 = func.dfg.make_ebb(); + func.layout.append_ebb(ebb3); + func.layout.append_ebb(ebb1); + func.layout.append_ebb(ebb2); + func.layout.append_ebb(ebb0); + + let jmp_ebb3_ebb1 = make_inst::jump(&mut func, ebb1); + let br_ebb1_ebb0 = make_inst::branch(&mut func, ebb0); + let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); + let jmp_ebb2_ebb0 = make_inst::jump(&mut func, ebb0); + + func.layout.append_inst(br_ebb1_ebb0, ebb1); + func.layout.append_inst(jmp_ebb1_ebb2, ebb1); + func.layout.append_inst(jmp_ebb2_ebb0, ebb2); + func.layout.append_inst(jmp_ebb3_ebb1, ebb3); + + let cfg = ControlFlowGraph::new(&func); + let dt = DominatorTree::new(&cfg); + + assert_eq!(func.layout.entry_block().unwrap(), ebb3); + assert_eq!(dt.len(), cfg.len()); + assert_eq!(dt.idom(ebb3).unwrap(), ebb3); + assert_eq!(dt.idom(ebb1).unwrap(), ebb3); + assert_eq!(dt.idom(ebb2).unwrap(), ebb1); + assert_eq!(dt.idom(ebb0).unwrap(), ebb1); + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index f81bfb5720..34151d187b 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -10,7 +10,7 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod ir; pub mod write; pub mod cfg; - +pub mod dominator_tree; pub mod entity_map; #[cfg(test)]pub mod test_utils; diff --git a/src/libcretonne/tests/dominator_tree.rs b/src/libcretonne/tests/dominator_tree.rs new file mode 100644 index 0000000000..6820acd152 --- /dev/null +++ b/src/libcretonne/tests/dominator_tree.rs @@ -0,0 +1,60 @@ +extern crate cretonne; +extern crate cton_reader; + +use self::cton_reader::parser::Parser; +use self::cretonne::ir::entities::Ebb; +use self::cretonne::cfg::ControlFlowGraph; +use self::cretonne::dominator_tree::DominatorTree; + +fn test_dominator_tree(function_source: &str, idoms: Vec) { + let func = &Parser::parse(function_source).unwrap()[0]; + let cfg = ControlFlowGraph::new(&func); + let dtree = DominatorTree::new(&cfg); + assert_eq!(dtree.len(), idoms.len()); + for (i, j) in idoms.iter().enumerate() { + let ebb = Ebb::with_number(i.clone() as u32); + let idom = Ebb::with_number(*j); + assert_eq!(dtree.idom(ebb.unwrap()), idom); + } +} + +#[test] +fn basic() { + test_dominator_tree(" + function test(i32) { + ebb0(v0: i32): + jump ebb1 + ebb1: + brz v0, ebb3 + jump ebb2 + ebb2: + jump ebb3 + ebb3: + return + } + ", vec![0, 0, 1, 1]); +} + +#[test] +fn loops() { + test_dominator_tree(" + function test(i32) { + ebb0(v0: i32): + brz v0, ebb1 + jump ebb2 + ebb1: + jump ebb3 + ebb2: + brz v0, ebb4 + jump ebb5 + ebb3: + jump ebb4 + ebb4: + brz v0, ebb3 + jump ebb5 + ebb5: + brz v0, ebb4 + return + } + ", vec![0, 0, 0, 0, 0, 0]); +} diff --git a/src/libcretonne/tests/lib.rs b/src/libcretonne/tests/lib.rs index 70d0709a9c..fcd2535269 100644 --- a/src/libcretonne/tests/lib.rs +++ b/src/libcretonne/tests/lib.rs @@ -1 +1,2 @@ pub mod cfg_traversal; +pub mod dominator_tree; From 5a38ca2db72c699d2b165160ec2f2653b7532ba9 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 1 Aug 2016 12:15:08 -0700 Subject: [PATCH 175/968] Remove println\! --- src/libcretonne/dominator_tree.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index e4569ea112..8dcc4e2899 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -65,7 +65,6 @@ impl DominatorTree { } fn intersect(&self, ordering: &EntityMap, first: Ebb, second: Ebb) -> Ebb { - println!("A {} B {}", first, second); let mut a = first; let mut b = second; while a != b { From e9cfcf4f780a5a25d41bc3f51a7fe9d7f3eca8bf Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 1 Aug 2016 15:00:08 -0700 Subject: [PATCH 176/968] Improve the structure and comments of the module. --- src/libcretonne/dominator_tree.rs | 39 +++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index 8dcc4e2899..c58726a1ab 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -10,25 +10,25 @@ pub struct DominatorTree { impl DominatorTree { pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { - let mut dt = DominatorTree { data: EntityMap::new() }; + let mut dt = DominatorTree { data: EntityMap::with_capacity(cfg.len()) }; dt.build(cfg); dt } - pub fn build(&mut self, cfg: &ControlFlowGraph) { + + /// Build a dominator tree from a control flow graph using Keith D. Cooper's + /// "Simple, Fast Dominator Algorithm." + fn build(&mut self, cfg: &ControlFlowGraph) { let reverse_postorder_map = cfg.reverse_postorder_ebbs(); let ebbs = reverse_postorder_map.keys().collect::>(); let len = reverse_postorder_map.len(); - for (i, ebb) in ebbs.iter().enumerate() { - if i > 0 { - self.data.push(None); - } else { - self.data.push(Some(ebb.clone())); - } - } + let mut changed = false; - let mut changed = len > 0; + if len > 0 { + self.data[ebbs[0]] = Some(ebbs[0]); + changed = true; + } while changed { changed = false; @@ -42,9 +42,15 @@ impl DominatorTree { new_idom = Some(p); continue; } + // If this predecessor `p` has an idom available find its common + // ancestor with the current value of new_idom. if let Some(_) = self.data[p] { - new_idom = - Some(self.intersect(&reverse_postorder_map, p, new_idom.unwrap())); + new_idom = match new_idom { + Some(cur_idom) => { + Some(self.intersect(&reverse_postorder_map, p, cur_idom)) + } + None => panic!("A 'current idom' should have been set!"), + } } } match self.data[ebb] { @@ -64,9 +70,15 @@ impl DominatorTree { } } + /// Find the common dominator of two ebbs. fn intersect(&self, ordering: &EntityMap, first: Ebb, second: Ebb) -> Ebb { let mut a = first; let mut b = second; + + // Here we use 'ordering', a mapping of ebbs to their postorder + // visitation number, to ensure that we move upward through the tree. + // Walking upward means that we may always expect self.data[a] and + // self.data[b] to contain non-None entries. while a != b { while ordering[a] < ordering[b] { a = self.data[a].unwrap(); @@ -78,10 +90,13 @@ impl DominatorTree { a } + /// Returns the immediate dominator of some ebb or None if the + /// node is unreachable. pub fn idom(&self, ebb: Ebb) -> Option { self.data[ebb].clone() } + /// The total number of nodes in the tree. pub fn len(&self) -> usize { self.data.len() } From 02c1bb8f2c44a39997b426ef5dda67bd2e497658 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 1 Aug 2016 15:03:56 -0700 Subject: [PATCH 177/968] Print CFG edges from func.layout instead of cfg.predecessors_iter. EBBs not in the layout should never be printed as part of the CFG. --- src/tools/print_cfg.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 9a08c0287e..dd1aa3de18 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -41,8 +41,8 @@ impl CFGPrinter { self.header(func); self.push_indent(); self.ebb_subgraphs(func); - let cfg = ControlFlowGraph::new(&func); - self.cfg_connections(&cfg); + let cfg = ControlFlowGraph::new(func); + self.cfg_connections(func, &cfg); self.pop_indent(); self.footer(); self.write() @@ -145,9 +145,9 @@ impl CFGPrinter { } } - fn cfg_connections(&mut self, cfg: &ControlFlowGraph) { - for (ref ebb, ref predecessors) in cfg.predecessors_iter() { - for &(parent, inst) in *predecessors { + fn cfg_connections(&mut self, func: &Function, cfg: &ControlFlowGraph) { + for ebb in &func.layout { + for &(parent, inst) in cfg.get_predecessors(ebb) { self.append(&format!("{}:{} -> {}", parent, inst, ebb)); self.newline(); } From 2c1e80b0e0654840d9266d6838413fc556d5918c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 1 Aug 2016 15:14:32 -0700 Subject: [PATCH 178/968] Remove the cfg::predecessors_iter() method and iterator. This iterator enumerates all EBB references whether they are in the layout or not. That is usually not what is needed when working with the CFG. It is better to iterate over EBB referrences in layout order, or in reverse post-order and then call the get_predecessors() method for each Ebb reference. See the new implementation of print_cfg::cfg_connections(). --- src/libcretonne/cfg.rs | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 28a39861e2..30f16b5df5 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -142,34 +142,6 @@ impl ControlFlowGraph { pub fn len(&self) -> usize { self.data.len() } - - pub fn predecessors_iter(&self) -> CFGPredecessorsIter { - CFGPredecessorsIter { - cur: 0, - cfg: &self, - } - } -} - -/// Iterate through every mapping of ebb to predecessors in the CFG -pub struct CFGPredecessorsIter<'a> { - cfg: &'a ControlFlowGraph, - cur: usize, -} - -impl<'a> Iterator for CFGPredecessorsIter<'a> { - type Item = (Ebb, &'a Vec); - - fn next(&mut self) -> Option { - if self.cur < self.cfg.len() { - let ebb = Ebb::with_number(self.cur as u32).unwrap(); - let bbs = self.cfg.get_predecessors(ebb); - self.cur += 1; - Some((ebb, bbs)) - } else { - None - } - } } #[cfg(test)] @@ -183,7 +155,7 @@ mod tests { fn empty() { let func = Function::new(); let cfg = ControlFlowGraph::new(&func); - assert_eq!(None, cfg.predecessors_iter().next()); + assert_eq!(cfg.reverse_postorder_ebbs().keys().count(), 0); } #[test] @@ -197,7 +169,8 @@ mod tests { func.layout.append_ebb(ebb2); let cfg = ControlFlowGraph::new(&func); - let nodes = cfg.predecessors_iter().collect::>(); + let nodes = + [ebb0, ebb1, ebb2].iter().map(|&e| (e, cfg.get_predecessors(e))).collect::>(); assert_eq!(nodes.len(), 3); let mut fun_ebbs = func.layout.ebbs(); From 80abf8b1f00761debb41f0f6a2d0914c98931080 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 1 Aug 2016 18:04:25 -0700 Subject: [PATCH 179/968] Remove uses of EntityMap::len. --- src/libcretonne/cfg.rs | 60 +++++------------------- src/libcretonne/dominator_tree.rs | 61 ++++++++++++++----------- src/libcretonne/tests/cfg_traversal.rs | 14 ++++-- src/libcretonne/tests/dominator_tree.rs | 2 +- 4 files changed, 58 insertions(+), 79 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 28a39861e2..022c3372e4 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -25,7 +25,7 @@ use ir::Function; use ir::entities::{Inst, Ebb}; use ir::instructions::BranchInfo; -use entity_map::EntityMap; +use entity_map::{EntityMap, Keys}; use std::collections::HashSet; /// A basic block denoted by its enclosing Ebb and last instruction. @@ -97,12 +97,11 @@ impl ControlFlowGraph { &self.data[ebb].successors } - /// Return ebbs in reverse postorder along with a mapping of - /// the ebb to its [post]order of visitation. - pub fn reverse_postorder_ebbs(&self) -> EntityMap { + /// Return [reachable] ebbs in postorder. + pub fn postorder_ebbs(&self) -> Vec { let entry_block = match self.entry_block { None => { - return EntityMap::new(); + return Vec::new(); } Some(eb) => eb, }; @@ -129,46 +128,12 @@ impl ControlFlowGraph { black.insert(node.clone()); } } - postorder.reverse(); - - let mut result = EntityMap::with_capacity(postorder.len()); - for (offset, ebb) in postorder.iter().enumerate() { - let i = postorder.len() - offset; - result[ebb.clone()] = i; - } - result + postorder } - pub fn len(&self) -> usize { - self.data.len() - } - - pub fn predecessors_iter(&self) -> CFGPredecessorsIter { - CFGPredecessorsIter { - cur: 0, - cfg: &self, - } - } -} - -/// Iterate through every mapping of ebb to predecessors in the CFG -pub struct CFGPredecessorsIter<'a> { - cfg: &'a ControlFlowGraph, - cur: usize, -} - -impl<'a> Iterator for CFGPredecessorsIter<'a> { - type Item = (Ebb, &'a Vec); - - fn next(&mut self) -> Option { - if self.cur < self.cfg.len() { - let ebb = Ebb::with_number(self.cur as u32).unwrap(); - let bbs = self.cfg.get_predecessors(ebb); - self.cur += 1; - Some((ebb, bbs)) - } else { - None - } + /// An iterator across all of the ebbs stored in the cfg. + pub fn ebbs(&self) -> Keys { + self.data.keys() } } @@ -183,7 +148,7 @@ mod tests { fn empty() { let func = Function::new(); let cfg = ControlFlowGraph::new(&func); - assert_eq!(None, cfg.predecessors_iter().next()); + assert_eq!(None, cfg.ebbs().next()); } #[test] @@ -197,14 +162,13 @@ mod tests { func.layout.append_ebb(ebb2); let cfg = ControlFlowGraph::new(&func); - let nodes = cfg.predecessors_iter().collect::>(); + let nodes = cfg.ebbs().collect::>(); assert_eq!(nodes.len(), 3); let mut fun_ebbs = func.layout.ebbs(); - for (ebb, predecessors) in nodes { + for ebb in nodes { assert_eq!(ebb, fun_ebbs.next().unwrap()); - assert_eq!(predecessors.len(), 0); - assert_eq!(predecessors.len(), 0); + assert_eq!(cfg.get_predecessors(ebb).len(), 0); assert_eq!(cfg.get_successors(ebb).len(), 0); } } diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index c58726a1ab..8420bcf541 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -2,31 +2,33 @@ use cfg::*; use ir::entities::Ebb; -use entity_map::EntityMap; +use entity_map::{EntityMap, Keys}; pub struct DominatorTree { data: EntityMap>, } impl DominatorTree { - pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { - let mut dt = DominatorTree { data: EntityMap::with_capacity(cfg.len()) }; - dt.build(cfg); - dt - } - - /// Build a dominator tree from a control flow graph using Keith D. Cooper's /// "Simple, Fast Dominator Algorithm." - fn build(&mut self, cfg: &ControlFlowGraph) { - let reverse_postorder_map = cfg.reverse_postorder_ebbs(); - let ebbs = reverse_postorder_map.keys().collect::>(); - let len = reverse_postorder_map.len(); + pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { + let mut ebbs = cfg.postorder_ebbs(); + ebbs.reverse(); + + let len = ebbs.len(); + + // The mappings which designate the dominator tree. + let mut data = EntityMap::with_capacity(len); + + let mut postorder_map = EntityMap::with_capacity(len); + for (i, ebb) in ebbs.iter().enumerate() { + postorder_map[ebb.clone()] = len - i; + } let mut changed = false; if len > 0 { - self.data[ebbs[0]] = Some(ebbs[0]); + data[ebbs[0]] = Some(ebbs[0]); changed = true; } @@ -44,34 +46,42 @@ impl DominatorTree { } // If this predecessor `p` has an idom available find its common // ancestor with the current value of new_idom. - if let Some(_) = self.data[p] { + if let Some(_) = data[p] { new_idom = match new_idom { Some(cur_idom) => { - Some(self.intersect(&reverse_postorder_map, p, cur_idom)) + Some(DominatorTree::intersect(&mut data, + &postorder_map, + p, + cur_idom)) } None => panic!("A 'current idom' should have been set!"), } } } - match self.data[ebb] { + match data[ebb] { None => { - self.data[ebb] = new_idom; + data[ebb] = new_idom; changed = true; } Some(idom) => { // Old idom != New idom if idom != new_idom.unwrap() { - self.data[ebb] = new_idom; + data[ebb] = new_idom; changed = true; } } } } } + DominatorTree { data: data } } /// Find the common dominator of two ebbs. - fn intersect(&self, ordering: &EntityMap, first: Ebb, second: Ebb) -> Ebb { + fn intersect(data: &EntityMap>, + ordering: &EntityMap, + first: Ebb, + second: Ebb) + -> Ebb { let mut a = first; let mut b = second; @@ -81,10 +91,10 @@ impl DominatorTree { // self.data[b] to contain non-None entries. while a != b { while ordering[a] < ordering[b] { - a = self.data[a].unwrap(); + a = data[a].unwrap(); } while ordering[b] < ordering[a] { - b = self.data[b].unwrap(); + b = data[b].unwrap(); } } a @@ -96,9 +106,9 @@ impl DominatorTree { self.data[ebb].clone() } - /// The total number of nodes in the tree. - pub fn len(&self) -> usize { - self.data.len() + /// An iterator across all of the ebbs stored in the tree. + pub fn ebbs(&self) -> Keys { + self.data.keys() } } @@ -114,7 +124,7 @@ mod test { let func = Function::new(); let cfg = ControlFlowGraph::new(&func); let dtree = DominatorTree::new(&cfg); - assert_eq!(dtree.len(), 0); + assert_eq!(None, dtree.ebbs().next()); } #[test] @@ -143,7 +153,6 @@ mod test { let dt = DominatorTree::new(&cfg); assert_eq!(func.layout.entry_block().unwrap(), ebb3); - assert_eq!(dt.len(), cfg.len()); assert_eq!(dt.idom(ebb3).unwrap(), ebb3); assert_eq!(dt.idom(ebb1).unwrap(), ebb3); assert_eq!(dt.idom(ebb2).unwrap(), ebb1); diff --git a/src/libcretonne/tests/cfg_traversal.rs b/src/libcretonne/tests/cfg_traversal.rs index ae74445dd4..8908b279d8 100644 --- a/src/libcretonne/tests/cfg_traversal.rs +++ b/src/libcretonne/tests/cfg_traversal.rs @@ -4,6 +4,7 @@ extern crate cton_reader; use self::cton_reader::parser::Parser; use self::cretonne::ir::entities::Ebb; use self::cretonne::cfg::ControlFlowGraph; +use self::cretonne::entity_map::EntityMap; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &Parser::parse(function_source).unwrap()[0]; @@ -11,11 +12,16 @@ fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()) .collect::>(); - let reverse_postorder_ebbs = cfg.reverse_postorder_ebbs(); + let mut postorder_ebbs = cfg.postorder_ebbs(); + let mut postorder_map = EntityMap::with_capacity(postorder_ebbs.len()); + for (i, ebb) in postorder_ebbs.iter().enumerate() { + postorder_map[ebb.clone()] = i + 1; + } + postorder_ebbs.reverse(); - assert_eq!(reverse_postorder_ebbs.len(), ebbs.len()); - for ebb in reverse_postorder_ebbs.keys() { - assert_eq!(ebb, ebbs[ebbs.len() - reverse_postorder_ebbs[ebb]]); + assert_eq!(postorder_ebbs.len(), ebbs.len()); + for ebb in postorder_ebbs { + assert_eq!(ebb, ebbs[ebbs.len() - postorder_map[ebb]]); } } diff --git a/src/libcretonne/tests/dominator_tree.rs b/src/libcretonne/tests/dominator_tree.rs index 6820acd152..8e10307a21 100644 --- a/src/libcretonne/tests/dominator_tree.rs +++ b/src/libcretonne/tests/dominator_tree.rs @@ -10,7 +10,7 @@ fn test_dominator_tree(function_source: &str, idoms: Vec) { let func = &Parser::parse(function_source).unwrap()[0]; let cfg = ControlFlowGraph::new(&func); let dtree = DominatorTree::new(&cfg); - assert_eq!(dtree.len(), idoms.len()); + assert_eq!(dtree.ebbs().collect::>().len(), idoms.len()); for (i, j) in idoms.iter().enumerate() { let ebb = Ebb::with_number(i.clone() as u32); let idom = Ebb::with_number(*j); From f5008567c9a479423a1fce1801fdce268e013b2f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 1 Aug 2016 14:45:59 -0700 Subject: [PATCH 180/968] Add PrimaryEntityData marker trait. Require this trait on the value type stored in an EntityMap to 'unlock' the methods intended for primary entity maps that are allowed to create references with the 'push method. This prevents accidentally depending on these methods in secondary maps. --- src/libcretonne/entity_map.rs | 55 +++++++++++++++++++++++------------ src/libcretonne/ir/dfg.rs | 5 +++- src/libcretonne/ir/mod.rs | 5 +++- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index a0f4f3fa6b..91ff7505a3 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -2,7 +2,13 @@ //! //! This module defines an `EntityRef` trait that should be implemented by reference types wrapping //! a small integer index. The `EntityMap` data structure uses the dense index space to implement a -//! map with a vector. +//! map with a vector. There are primary and secondary entity maps: +//! +//! - A *primary* `EntityMap` contains the main definition of an entity, and it can be used to +//! allocate new entity references with the `push` method. The values stores in a primary map +//! must implement the `PrimaryEntityData` marker trait. +//! - A *secondary* `EntityMap` contains additional data about entities kept in a primary map. The +//! values need to implement `Clone + Default` traits so the map can be grown with `ensure`. use std::vec::Vec; use std::default::Default; @@ -39,13 +45,6 @@ pub trait EntityRef: Copy + Eq { } /// A mapping `K -> V` for densely indexed entity references. -/// -/// A *primary* `EntityMap` contains the main definition of an entity, and it can be used to -/// allocate new entity references with the `push` method. -/// -/// A *secondary* `EntityMap` contains additional data about entities kept in a primary map. The -/// values need to implement `Clone + Default` traits so the map can be grown with `ensure`. -/// #[derive(Debug)] pub struct EntityMap where K: EntityRef @@ -71,17 +70,6 @@ impl EntityMap k.index() < self.elems.len() } - /// Append `v` to the mapping, assigning a new key which is returned. - pub fn push(&mut self, v: V) -> K { - let k = K::new(self.elems.len()); - self.elems.push(v); - k - } - - pub fn len(&self) -> usize { - self.elems.len() - } - /// Iterate over all the keys in this map. pub fn keys(&self) -> Keys { Keys { @@ -92,6 +80,33 @@ impl EntityMap } } +/// A marker trait for data stored in primary entity maps. +/// +/// A primary entity map can be used to allocate new entity references with the `push` method. It +/// is important that entity references can't be created anywhere else, so the data stored in a +/// primary entity map must be tagged as `PrimaryEntityData` to unlock the `push` method. +pub trait PrimaryEntityData {} + +/// Additional methods for primary entry maps only. +/// +/// These are identified by the `PrimaryEntityData` marker trait. +impl EntityMap + where K: EntityRef, + V: PrimaryEntityData +{ + /// Append `v` to the mapping, assigning a new key which is returned. + pub fn push(&mut self, v: V) -> K { + let k = K::new(self.elems.len()); + self.elems.push(v); + k + } + + /// Get the total number of entity references created. + pub fn len(&self) -> usize { + self.elems.len() + } +} + /// Additional methods for value types that implement `Clone` and `Default`. /// /// When the value type implements these additional traits, the `EntityMap` can be resized @@ -193,6 +208,8 @@ mod tests { } } + impl PrimaryEntityData for isize {} + #[test] fn basic() { let r0 = E(0); diff --git a/src/libcretonne/ir/dfg.rs b/src/libcretonne/ir/dfg.rs index aba45354d7..4776220a6f 100644 --- a/src/libcretonne/ir/dfg.rs +++ b/src/libcretonne/ir/dfg.rs @@ -1,6 +1,6 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use entity_map::EntityMap; +use entity_map::{EntityMap, PrimaryEntityData}; use ir::entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; use ir::instructions::InstructionData; use ir::types::Type; @@ -33,6 +33,9 @@ pub struct DataFlowGraph { extended_values: Vec, } +impl PrimaryEntityData for InstructionData {} +impl PrimaryEntityData for EbbData {} + impl DataFlowGraph { /// Create a new empty `DataFlowGraph`. pub fn new() -> DataFlowGraph { diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 8d5ed99742..f0e8eda741 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -10,7 +10,7 @@ pub mod dfg; pub mod layout; use ir::types::{FunctionName, Signature}; -use entity_map::{EntityRef, EntityMap}; +use entity_map::{EntityRef, EntityMap, PrimaryEntityData}; use ir::entities::{StackSlot, JumpTable}; use ir::jumptable::JumpTableData; use ir::dfg::DataFlowGraph; @@ -39,6 +39,9 @@ pub struct Function { pub layout: Layout, } +// Tag JumpTableData as a primary entity so jump_tables above . +impl PrimaryEntityData for JumpTableData {} + impl Function { /// Create a function with the given name and signature. pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function { From bd72439fbc1a70a1f8de166a339155ad8b18b0b0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 29 Jul 2016 14:50:19 -0700 Subject: [PATCH 181/968] Document binary encodings. Describe the meta-language data structures that are built to represent instruction encodings. Begin a metaref glossary. --- docs/metaref.rst | 127 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index fc9c5e4e29..058cc6459f 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -203,11 +203,99 @@ This means that the type of an input operand can either be computed from the controlling type variable, or it can vary independently of the other operands. +Encodings +========= + +Encodings describe how Cretonne instructions are mapped to binary machine code +for the target architecture. After the lealization pass, all remaining +instructions are expected to map 1-1 to native instruction encodings. Cretonne +instructions that can't be encoded for the current architecture are called +:term:`illegal instruction`\s. + +Some instruction set architectures have different :term:`CPU mode`\s with +incompatible encodings. For example, a modern ARMv8 CPU might support three +different CPU modes: *A64* where instructions are encoded in 32 bits, *A32* +where all instuctions are 32 bits, and *T32* which has a mix of 16-bit and +32-bit instruction encodings. These are incompatible encoding spaces, and while +an :cton:inst:`iadd` instruction can be encoded in 32 bits in each of them, it's +not the same 32 bits. It's a judgement call if CPU modes should be modelled as +separate targets, or as sub-modes of the same target. In the ARMv8 case, the +different register banks means that it makes sense to model A64 as a separate +target architecture, while A32 and T32 are CPU modes of the 32-bit ARM target. + +In a given CPU mode, there may be multiple valid encodings of the same +instruction. Both RISC-V and ARMv8's T32 mode have 32-bit encodings of all +instructions with 16-bit encodings available for some opcodes if certain +constraints are satisfied. + +Encodings are guarded by :term:`sub-target predicate`\s. For example, the RISC-V +"C" extension which specifies the compressed encodings may not be supported, and +a predicate would be used to disable all of the 16-bit encodings in that case. +This can also affect whether an instruction is legal. For example, x86 has a +predicate that controls the SSE 4.1 instruction encodings. When that predicate +is false, the SSE 4.1 instructions are not available. + +Encodings also have a :term:`instruction predicate` which depends on the +specific values of the instruction's immediate fields. This is used to ensure +that immediate address offsets are within range, for example. The instructions +in the base Cretonne instruction set can often represent a wider range of +immediates than any specific encoding. The fixed-size RISC-style encodings tend +to have more range limitations than CISC-style variable length encodings like +x86. + +The diagram below shows the relationship between the classes involved in +specifying instruction encodings: + +.. digraph:: encoding + + node [shape=record] + CPUMode -> Target + EncRecipe -> CPUMode + EncRecipe -> SubtargetPred + EncRecipe -> InstrFormat + EncRecipe -> InstrPred + Encoding [label="{Encoding|Opcode+TypeVars}"] + Encoding -> EncRecipe [label="+EncBits"] + Encoding -> SubtargetPred + Encoding -> InstrPred + Encoding -> Opcode + Opcode -> InstrFormat + +An :py:class:`Encoding` instance specifies the encoding of a concrete +instruction. The following properties are used to select instructions to be +encoded: + +- An opcode, i.e. :cton:inst:`iadd_imm`, that must match the instruction's + opcode. +- Values for any type variables if the opcode represents a polymorphic + instruction. +- An :term:`instruction predicate` that must be satisfied by the instruction's + immediate operands. +- A :term:`sub-target predicate` that must be satisfied by the currently active + sub-target. +- :term:`Register constraint`\s that must be satisfied by the instruction's value + operands and results. + +An encoding specifies an *encoding recipe* along with some *encoding bits* that +the recipe can use for native opcode fields etc. The encoding recipe has +additional constraints that must be satisfied: + +- The CPU mode that must be active to enable encodings. +- An :py:class:`InstructionFormat` that must match the format required by the + opcodes of any encodings that use this recipe. +- An additional :term:`instruction predicate`. +- An additional :term:`sub-target predicate`. + +The additional predicates in the :py:class:`EncRecipe` are merged with the +per-encoding predicates when generating the encoding matcher code. Often +encodings only need the recipe predicates. + + Targets ======= Cretonne can be compiled with support for multiple target instruction set -architectures. Each ISA is represented by a :py:class`cretonne.Target` instance. +architectures. Each ISA is represented by a :py:class:`cretonne.Target` instance. .. autoclass:: Target @@ -218,3 +306,40 @@ The definitions for each supported target live in a package under :members: .. automodule:: target.riscv + + +Glossary +======== + +.. glossary:: + + Illegal instruction + An instruction is considered illegal if there is no encoding available + for the current CPU mode. The legality of an instruction depends on the + value of :term:`sub-target predicate`\s, so it can't always be + determined ahead of time. + + CPU mode + Every target defines one or more CPU modes that determine how the CPU + decodes binary instructions. Some CPUs can switch modes dynamically with + a branch instruction (like ARM/Thumb), while other modes are + process-wide (like x86 32/64-bit). + + Sub-target predicate + A predicate that depends on the current sub-target configuration. + Examples are "Use SSE 4.1 instructions", "Use RISC-V compressed + encodings". Sub-target predicates can depend on both detected CPU + features and configuration settings. + + Instruction predicate + A predicate that depends on the immediate fields of an instruction. An + example is "the load address offset must be a 10-bit signed integer". + Instruction predicates do not depend on the registers selected for value + operands. + + Register constraint + Value operands and results correspond to machine registers. Encodings may + constrain operands to either a fixed register or a register class. There + may also be register constraints between operands, for example some + encodings require that the result register is one of the input + registers. From c4faef196ee1436a9c26b8715db760eeb7b7c5a5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 3 Aug 2016 11:20:13 -0700 Subject: [PATCH 182/968] Add a CPUMode meta-language class. --- docs/metaref.rst | 2 ++ meta/cretonne/__init__.py | 17 +++++++++++++++++ meta/target/riscv/__init__.py | 6 +++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index 058cc6459f..b7f607f91f 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -228,6 +228,8 @@ instruction. Both RISC-V and ARMv8's T32 mode have 32-bit encodings of all instructions with 16-bit encodings available for some opcodes if certain constraints are satisfied. +.. autoclass:: CPUMode + Encodings are guarded by :term:`sub-target predicate`\s. For example, the RISC-V "C" extension which specifies the compressed encodings may not be supported, and a predicate would be used to disable all of the 16-bit encodings in that case. diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 9660e9e602..d5e6530280 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -656,6 +656,23 @@ class Target(object): self.name = name self.instruction_groups = instrution_groups + +class CPUMode(object): + """ + A CPU mode determines which instruction encodings are active. + + All instruction encodings are associated with exactly one `CPUMode`, and + all CPU modes are associated with exactly one `Target`. + + :param name: Short mnemonic name for the CPU mode. + :param target: Associated `Target`. + """ + + def __init__(self, name, target): + self.name = name + self.target = target + + # Import the fixed instruction formats now so they can be added to the # registry. importlib.import_module('cretonne.formats') diff --git a/meta/target/riscv/__init__.py b/meta/target/riscv/__init__.py index bdb5b63091..918602c78e 100644 --- a/meta/target/riscv/__init__.py +++ b/meta/target/riscv/__init__.py @@ -25,7 +25,11 @@ RV32G / RV64G """ -from cretonne import Target +from cretonne import Target, CPUMode import cretonne.base target = Target('riscv', [cretonne.base.instructions]) + +# CPU modes for 32-bit and 64-bit operation. +RV32 = CPUMode('RV32', target) +RV64 = CPUMode('RV64', target) From d215b622e4c40371fe8a6191acb9f310386682d4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 3 Aug 2016 12:06:21 -0700 Subject: [PATCH 183/968] Add an EncRecipe meta-language class. Move the CPUMode reference from EncRecipe to the Encoding itself, allowing EncRecipes to be shared between CPU modes. At least RISC-V should be able to share some recipes between RV32 and RV64 modes. --- docs/metaref.rst | 8 +++++--- meta/cretonne/__init__.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index b7f607f91f..3b9ac07f70 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -251,17 +251,17 @@ specifying instruction encodings: .. digraph:: encoding node [shape=record] - CPUMode -> Target - EncRecipe -> CPUMode EncRecipe -> SubtargetPred EncRecipe -> InstrFormat EncRecipe -> InstrPred Encoding [label="{Encoding|Opcode+TypeVars}"] Encoding -> EncRecipe [label="+EncBits"] + Encoding -> CPUMode Encoding -> SubtargetPred Encoding -> InstrPred Encoding -> Opcode Opcode -> InstrFormat + CPUMode -> Target An :py:class:`Encoding` instance specifies the encoding of a concrete instruction. The following properties are used to select instructions to be @@ -273,6 +273,7 @@ encoded: instruction. - An :term:`instruction predicate` that must be satisfied by the instruction's immediate operands. +- The CPU mode that must be active. - A :term:`sub-target predicate` that must be satisfied by the currently active sub-target. - :term:`Register constraint`\s that must be satisfied by the instruction's value @@ -282,7 +283,6 @@ An encoding specifies an *encoding recipe* along with some *encoding bits* that the recipe can use for native opcode fields etc. The encoding recipe has additional constraints that must be satisfied: -- The CPU mode that must be active to enable encodings. - An :py:class:`InstructionFormat` that must match the format required by the opcodes of any encodings that use this recipe. - An additional :term:`instruction predicate`. @@ -292,6 +292,8 @@ The additional predicates in the :py:class:`EncRecipe` are merged with the per-encoding predicates when generating the encoding matcher code. Often encodings only need the recipe predicates. +.. autoclass:: EncRecipe + Targets ======= diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index d5e6530280..4ee491f806 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -673,6 +673,22 @@ class CPUMode(object): self.target = target +class EncRecipe(object): + """ + A recipe for encoding instructions with a given format. + + Many different instructions can be encoded by the same recipe, but they + must all have the same instruction format. + + :param name: Short mnemonic name for this recipe. + :param format: All encoded instructions must have this + :py:class:`InstructionFormat`. + """ + + def __init__(self, name, format): + self.name = name + self.format = format + # Import the fixed instruction formats now so they can be added to the # registry. importlib.import_module('cretonne.formats') From 9cdf6edd6202a32b853d13fb66350817e316c650 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 3 Aug 2016 15:58:41 -0700 Subject: [PATCH 184/968] Add an Encoding meta-language class. Start adding some RISC-V encodings too as a way of testing the ergonomics. --- meta/cretonne/__init__.py | 50 ++++++++++++++++++++++++++++++++++ meta/target/riscv/__init__.py | 10 +++---- meta/target/riscv/defs.py | 14 ++++++++++ meta/target/riscv/encodings.py | 21 ++++++++++++++ meta/target/riscv/recipes.py | 44 ++++++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 meta/target/riscv/defs.py create mode 100644 meta/target/riscv/encodings.py create mode 100644 meta/target/riscv/recipes.py diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 4ee491f806..483fb1f8e3 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -671,6 +671,16 @@ class CPUMode(object): def __init__(self, name, target): self.name = name self.target = target + self.encodings = [] + + def enc(self, *args, **kwargs): + """ + Add a new encoding to this CPU mode. + + Arguments are the `Encoding constructor arguments, except for the first + `CPUMode argument which is implied. + """ + self.encodings.append(Encoding(self, *args, **kwargs)) class EncRecipe(object): @@ -689,6 +699,46 @@ class EncRecipe(object): self.name = name self.format = format + +class Encoding(object): + """ + Encoding for a concrete instruction. + + An `Encoding` object ties an instruction opcode with concrete type + variables together with and encoding recipe and encoding bits. + + :param cpumode: The CPU mode where the encoding is active. + :param inst: The :py:class:`Instruction` being encoded. + :param typevars: Concete types for `inst`'s type variables, if any. + :param recipe: The :py:class:`EncRecipe` to use. + :param encbits: Additional encoding bits to be interpreted by `recipe`. + """ + + def __init__(self, cpumode, inst, typevars, recipe, encbits): + assert isinstance(cpumode, CPUMode) + assert isinstance(inst, Instruction) + assert isinstance(recipe, EncRecipe) + self.cpumode = cpumode + assert inst.format == recipe.format, ( + "Format {} must match recipe: {}".format( + inst.format, recipe.format)) + self.inst = inst + self.typevars = self._to_type_tuple(typevars) + self.recipe = recipe + self.encbits = encbits + + @staticmethod + def _to_type_tuple(x): + # Allow a single ValueType instance instead of the awkward singleton + # tuple syntax. + if isinstance(x, ValueType): + x = (x,) + else: + x = tuple(x) + for ty in x: + assert isinstance(ty, ValueType) + return x + # Import the fixed instruction formats now so they can be added to the # registry. importlib.import_module('cretonne.formats') diff --git a/meta/target/riscv/__init__.py b/meta/target/riscv/__init__.py index 918602c78e..48d916f089 100644 --- a/meta/target/riscv/__init__.py +++ b/meta/target/riscv/__init__.py @@ -25,11 +25,9 @@ RV32G / RV64G """ -from cretonne import Target, CPUMode -import cretonne.base +import defs +import encodings -target = Target('riscv', [cretonne.base.instructions]) +# Re-export the primary target definition. +target = defs.target -# CPU modes for 32-bit and 64-bit operation. -RV32 = CPUMode('RV32', target) -RV64 = CPUMode('RV64', target) diff --git a/meta/target/riscv/defs.py b/meta/target/riscv/defs.py new file mode 100644 index 0000000000..aab9279360 --- /dev/null +++ b/meta/target/riscv/defs.py @@ -0,0 +1,14 @@ +""" +RISC-V definitions. + +Commonly used definitions. +""" + +from cretonne import Target, CPUMode +import cretonne.base + +target = Target('riscv', [cretonne.base.instructions]) + +# CPU modes for 32-bit and 64-bit operation. +RV32 = CPUMode('RV32', target) +RV64 = CPUMode('RV64', target) diff --git a/meta/target/riscv/encodings.py b/meta/target/riscv/encodings.py new file mode 100644 index 0000000000..0177cb57f9 --- /dev/null +++ b/meta/target/riscv/encodings.py @@ -0,0 +1,21 @@ +""" +RISC-V Encodings. +""" +from cretonne import base +from cretonne.types import i32, i64 +from defs import RV32, RV64 +from recipes import OP, R + +# Basic arithmetic binary instructions are encoded in an R-type instruction. +for inst, f3, f7 in [ + (base.iadd, 0b000, 0b0000000), + (base.isub, 0b000, 0b0100000), + (base.ishl, 0b001, 0b0000000), + (base.ushr, 0b101, 0b0000000), + (base.sshr, 0b101, 0b0100000), + (base.bxor, 0b100, 0b0000000), + (base.bor, 0b110, 0b0000000), + (base.band, 0b111, 0b0000000) + ]: + RV32.enc(inst, i32, R, OP(f3, f7)) + RV64.enc(inst, i64, R, OP(f3, f7)) diff --git a/meta/target/riscv/recipes.py b/meta/target/riscv/recipes.py new file mode 100644 index 0000000000..ed96836cd4 --- /dev/null +++ b/meta/target/riscv/recipes.py @@ -0,0 +1,44 @@ +""" +RISC-V Encoding recipes. + +The encoding recipes defined here more or less correspond to the RISC-V native +instruction formats described in the reference: + + The RISC-V Instruction Set Manual + Volume I: User-Level ISA + Version 2.1 +""" +from cretonne import EncRecipe +from cretonne.formats import Binary + +# The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit +# instructions have 11 as the two low bits, with bits 6:2 determining the base +# opcode. +# +# Encbits for the 32-bit recipes are opcode[6:2] | (funct3 << 5) | ... +# The functions below encode the encbits. + +def LOAD(funct3): + assert funct3 <= 0b111 + return 0b00000 | (funct3 << 5) + +def STORE(funct3): + assert funct3 <= 0b111 + return 0b01000 | (funct3 << 5) + +def BRANCH(funct3): + assert funct3 <= 0b111 + return 0b11000 | (funct3 << 5) + +def OPIMM(funct3): + assert funct3 <= 0b111 + return 0b00100 | (funct3 << 5) + +def OP(funct3, funct7): + assert funct3 <= 0b111 + assert funct7 <= 0b1111111 + return 0b01100 | (funct3 << 5) | (funct7 << 8) + +# R-type 32-bit instructions: These are mostly binary arithmetic instructions. +# The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) +R = EncRecipe('R', Binary) From c1d32167dc65b5225bd0ac472ed69775ba597032 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 3 Aug 2016 16:30:47 -0700 Subject: [PATCH 185/968] Use dot syntax to bind type variables on instructions. Encodings need to refer to concrete instances of polymorphic instructions by binding type variables. Allow dot syntax like iadd.i32 to do that. --- meta/cretonne/__init__.py | 68 +++++++++++++++++++++++++++++++--- meta/target/riscv/encodings.py | 4 +- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 483fb1f8e3..01b74747b5 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -115,10 +115,15 @@ class ValueType(object): or one of its subclasses. """ + # map name -> ValueType. + _registry = dict() + def __init__(self, name, membytes, doc): self.name = name self.membytes = membytes self.__doc__ = doc + assert name not in ValueType._registry + ValueType._registry[name] = self def __str__(self): return self.name @@ -133,6 +138,13 @@ class ValueType(object): def free_typevar(self): return None + @staticmethod + def by_name(name): + if name in ValueType._registry: + return ValueType._registry[name] + else: + raise AttributeError("No type named '{}'".format(name)) + class ScalarType(ValueType): """ @@ -637,6 +649,47 @@ class Instruction(object): assert isinstance(op, Operand) return x + def bind(self, *args): + """ + Bind a polymorphic instruction to a concrete list of type variable + values. + """ + assert self.is_polymorphic + return BoundInstruction(self, args) + + def __getattr__(self, name): + """ + Bind a polymorphic instruction to a single type variable with dot + syntax: + + >>> iadd.i32 + """ + return self.bind(ValueType.by_name(name)) + + +class BoundInstruction(object): + """ + A polymorphic `Instruction` bound to concrete type variables. + """ + + def __init__(self, inst, typevars): + self.inst = inst + self.typevars = typevars + + def bind(self, *args): + """ + Bind additional typevars. + """ + return BoundInstruction(self.inst, self.typevars + args) + + def __getattr__(self, name): + """ + Bind an additional typevar dot syntax: + + >>> uext.i32.i8 + """ + return self.bind(ValueType.by_name(name)) + # Defining targets @@ -708,22 +761,25 @@ class Encoding(object): variables together with and encoding recipe and encoding bits. :param cpumode: The CPU mode where the encoding is active. - :param inst: The :py:class:`Instruction` being encoded. - :param typevars: Concete types for `inst`'s type variables, if any. + :param inst: The :py:class:`Instruction` or :py:class:`BoundInstruction` + being encoded. :param recipe: The :py:class:`EncRecipe` to use. :param encbits: Additional encoding bits to be interpreted by `recipe`. """ - def __init__(self, cpumode, inst, typevars, recipe, encbits): + def __init__(self, cpumode, inst, recipe, encbits): assert isinstance(cpumode, CPUMode) - assert isinstance(inst, Instruction) + if isinstance(inst, BoundInstruction): + real_inst = inst.inst + else: + real_inst = inst + assert isinstance(real_inst, Instruction) assert isinstance(recipe, EncRecipe) self.cpumode = cpumode - assert inst.format == recipe.format, ( + assert real_inst.format == recipe.format, ( "Format {} must match recipe: {}".format( inst.format, recipe.format)) self.inst = inst - self.typevars = self._to_type_tuple(typevars) self.recipe = recipe self.encbits = encbits diff --git a/meta/target/riscv/encodings.py b/meta/target/riscv/encodings.py index 0177cb57f9..fa3f7d055d 100644 --- a/meta/target/riscv/encodings.py +++ b/meta/target/riscv/encodings.py @@ -17,5 +17,5 @@ for inst, f3, f7 in [ (base.bor, 0b110, 0b0000000), (base.band, 0b111, 0b0000000) ]: - RV32.enc(inst, i32, R, OP(f3, f7)) - RV64.enc(inst, i64, R, OP(f3, f7)) + RV32.enc(inst.i32, R, OP(f3, f7)) + RV64.enc(inst.i64, R, OP(f3, f7)) From 24c97ce6e5d85c484e43632f78d8e8509c08b0f4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 4 Aug 2016 10:21:48 -0700 Subject: [PATCH 186/968] Verify that type variables have been fully bound. The shift instructions have two type variables since the shift amount can be a differently sized integer. Fix the RISC-V shift encodings to reflect this, and allow i64 registers to be shifted by an i32 amount. --- meta/cretonne/__init__.py | 37 ++++++++++++++++++++++++++-------- meta/target/riscv/encodings.py | 14 ++++++++++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 01b74747b5..7214cac75f 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -666,6 +666,17 @@ class Instruction(object): """ return self.bind(ValueType.by_name(name)) + def fully_bound(self): + """ + Verify that all typevars have been bound, and return a + `(inst, typevars)` pair. + + This version in `Instruction` itself allows non-polymorphic + instructions to duck-type as `BoundInstruction`\s. + """ + assert not self.is_polymorphic, self + return (self, ()) + class BoundInstruction(object): """ @@ -675,6 +686,10 @@ class BoundInstruction(object): def __init__(self, inst, typevars): self.inst = inst self.typevars = typevars + assert len(typevars) <= 1 + len(inst.other_typevars) + + def __str__(self): + return '.'.join([self.inst.name,] + map(str, self.typevars)) def bind(self, *args): """ @@ -690,6 +705,17 @@ class BoundInstruction(object): """ return self.bind(ValueType.by_name(name)) + def fully_bound(self): + """ + Verify that all typevars have been bound, and return a + `(inst, typevars)` pair. + """ + if len(self.typevars) < 1 + len(self.inst.other_typevars): + unb = ', '.join(str(tv) for tv in self.inst.other_typevars[len(self.typevars) - 1:]) + raise AssertionError("Unbound typevar {} in {}".format(unb, self)) + assert len(self.typevars) == 1 + len(self.inst.other_typevars) + return (self.inst, self.typevars) + # Defining targets @@ -769,17 +795,12 @@ class Encoding(object): def __init__(self, cpumode, inst, recipe, encbits): assert isinstance(cpumode, CPUMode) - if isinstance(inst, BoundInstruction): - real_inst = inst.inst - else: - real_inst = inst - assert isinstance(real_inst, Instruction) assert isinstance(recipe, EncRecipe) + self.inst, self.typevars = inst.fully_bound() self.cpumode = cpumode - assert real_inst.format == recipe.format, ( + assert self.inst.format == recipe.format, ( "Format {} must match recipe: {}".format( - inst.format, recipe.format)) - self.inst = inst + self.inst.format, recipe.format)) self.recipe = recipe self.encbits = encbits diff --git a/meta/target/riscv/encodings.py b/meta/target/riscv/encodings.py index fa3f7d055d..fb6602184c 100644 --- a/meta/target/riscv/encodings.py +++ b/meta/target/riscv/encodings.py @@ -10,12 +10,20 @@ from recipes import OP, R for inst, f3, f7 in [ (base.iadd, 0b000, 0b0000000), (base.isub, 0b000, 0b0100000), - (base.ishl, 0b001, 0b0000000), - (base.ushr, 0b101, 0b0000000), - (base.sshr, 0b101, 0b0100000), (base.bxor, 0b100, 0b0000000), (base.bor, 0b110, 0b0000000), (base.band, 0b111, 0b0000000) ]: RV32.enc(inst.i32, R, OP(f3, f7)) RV64.enc(inst.i64, R, OP(f3, f7)) + +# Dynamic shifts have the same masking semantics as the cton base instructions +for inst, f3, f7 in [ + (base.ishl, 0b001, 0b0000000), + (base.ushr, 0b101, 0b0000000), + (base.sshr, 0b101, 0b0100000), + ]: + RV32.enc(inst.i32.i32, R, OP(f3, f7)) + RV64.enc(inst.i64.i64, R, OP(f3, f7)) + # Allow i32 shift amounts in 64-bit shifts. + RV64.enc(inst.i64.i32, R, OP(f3, f7)) From 5bd2117ad705bb39da472ee678d5100811e76ceb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 4 Aug 2016 11:28:38 -0700 Subject: [PATCH 187/968] Don't sphinx-autobuild on Vim .swp file changes. --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index 598a13b496..56137e8b2c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -58,7 +58,7 @@ html: @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." autohtml: html - $(SPHINXABUILD) -z ../meta -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXABUILD) -z ../meta --ignore '*.swp' -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml From c47c52401782a6c17b61a98b44a7cd0f7bc85c97 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 4 Aug 2016 11:39:25 -0700 Subject: [PATCH 188/968] Add an empty isa/riscv module scaffold. Targeted ISAs will be defined as sub-modules of isa. --- src/libcretonne/isa/mod.rs | 6 ++++++ src/libcretonne/isa/riscv/mod.rs | 1 + src/libcretonne/lib.rs | 1 + 3 files changed, 8 insertions(+) create mode 100644 src/libcretonne/isa/mod.rs create mode 100644 src/libcretonne/isa/riscv/mod.rs diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs new file mode 100644 index 0000000000..e2f48e7bbe --- /dev/null +++ b/src/libcretonne/isa/mod.rs @@ -0,0 +1,6 @@ +//! Instruction Set Architectures. +//! +//! The sub-modules of this `isa` module provide definitions for the instruction sets that Cretonne +//! can target. + +pub mod riscv; diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs new file mode 100644 index 0000000000..4c24cbb8b7 --- /dev/null +++ b/src/libcretonne/isa/riscv/mod.rs @@ -0,0 +1 @@ +//! RISC-V Instruction Set Architecture. diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 34151d187b..9d696fab53 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -8,6 +8,7 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod ir; +pub mod isa; pub mod write; pub mod cfg; pub mod dominator_tree; From 92f84c655ba7f1b6335238bf5fe9bad0065aa5ca Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 4 Aug 2016 11:50:19 -0700 Subject: [PATCH 189/968] Rename meta/target -> meta/isa. Clarify terminology by always referring to a 'Target ISA' instead of just 'Target'. Use 'isa' as a module name instead of 'target' both in Rust and Python code. This is only to clarify terminology and not at all because Cargo insists on using the 'target' sub-directory for build products. Oh, no. Not at all. --- docs/metaref.rst | 10 +++++----- meta/build.py | 6 +++--- meta/cretonne/__init__.py | 14 +++++++------- meta/gen_instr.py | 12 ++++++------ meta/isa/__init__.py | 17 +++++++++++++++++ meta/{target => isa}/riscv/__init__.py | 4 ++-- meta/isa/riscv/defs.py | 14 ++++++++++++++ meta/{target => isa}/riscv/encodings.py | 0 meta/{target => isa}/riscv/recipes.py | 0 meta/target/__init__.py | 17 ----------------- meta/target/riscv/defs.py | 14 -------------- 11 files changed, 54 insertions(+), 54 deletions(-) create mode 100644 meta/isa/__init__.py rename meta/{target => isa}/riscv/__init__.py (90%) create mode 100644 meta/isa/riscv/defs.py rename meta/{target => isa}/riscv/encodings.py (100%) rename meta/{target => isa}/riscv/recipes.py (100%) delete mode 100644 meta/target/__init__.py delete mode 100644 meta/target/riscv/defs.py diff --git a/docs/metaref.rst b/docs/metaref.rst index 3b9ac07f70..4ac81096cc 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -299,17 +299,17 @@ Targets ======= Cretonne can be compiled with support for multiple target instruction set -architectures. Each ISA is represented by a :py:class:`cretonne.Target` instance. +architectures. Each ISA is represented by a :py:class:`cretonne.TargetISA` instance. -.. autoclass:: Target +.. autoclass:: TargetISA The definitions for each supported target live in a package under -:file:`meta/target`. +:file:`meta/isa`. -.. automodule:: target +.. automodule:: isa :members: -.. automodule:: target.riscv +.. automodule:: isa.riscv Glossary diff --git a/meta/build.py b/meta/build.py index 24d9f286ee..5dadc112e2 100644 --- a/meta/build.py +++ b/meta/build.py @@ -3,7 +3,7 @@ # This script is run from src/libcretonne/build.rs to generate Rust files. import argparse -import target +import isa import gen_instr import gen_build_deps @@ -13,7 +13,7 @@ parser.add_argument('--out-dir', help='set output directory') args = parser.parse_args() out_dir = args.out_dir -targets = target.all_targets() +isas = isa.all_isas() -gen_instr.generate(targets, out_dir) +gen_instr.generate(isas, out_dir) gen_build_deps.generate() diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 7214cac75f..ee523cfec5 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -717,14 +717,14 @@ class BoundInstruction(object): return (self.inst, self.typevars) -# Defining targets +# Defining target ISAs. -class Target(object): +class TargetISA(object): """ A target instruction set architecture. - The `Target` class collects everything known about a target ISA. + The `TargetISA` class collects everything known about a target ISA. :param name: Short mnemonic name for the ISA. :param instruction_groups: List of `InstructionGroup` instances that are @@ -741,15 +741,15 @@ class CPUMode(object): A CPU mode determines which instruction encodings are active. All instruction encodings are associated with exactly one `CPUMode`, and - all CPU modes are associated with exactly one `Target`. + all CPU modes are associated with exactly one `TargetISA`. :param name: Short mnemonic name for the CPU mode. - :param target: Associated `Target`. + :param target: Associated `TargetISA`. """ - def __init__(self, name, target): + def __init__(self, name, isa): self.name = name - self.target = target + self.isa = isa self.encodings = [] def enc(self, *args, **kwargs): diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 590dd13659..64c726ad9d 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -161,11 +161,11 @@ def gen_instruction_data_impl(fmt): .format(i)) -def collect_instr_groups(targets): +def collect_instr_groups(isas): seen = set() groups = [] - for t in targets: - for g in t.instruction_groups: + for isa in isas: + for g in isa.instruction_groups: if g not in seen: groups.append(g) seen.add(g) @@ -181,7 +181,7 @@ def gen_opcodes(groups, fmt): fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') - fmt.doc_comment('All instructions from all supported targets are present.') + fmt.doc_comment('All instructions from all supported ISAs are present.') fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') instrs = [] with fmt.indented('pub enum Opcode {', '}'): @@ -360,8 +360,8 @@ def gen_type_constraints(fmt, instrs): fmt.line('OperandConstraint::{},'.format(c)) -def generate(targets, out_dir): - groups = collect_instr_groups(targets) +def generate(isas, out_dir): + groups = collect_instr_groups(isas) # opcodes.rs fmt = srcgen.Formatter() diff --git a/meta/isa/__init__.py b/meta/isa/__init__.py new file mode 100644 index 0000000000..b566a521ef --- /dev/null +++ b/meta/isa/__init__.py @@ -0,0 +1,17 @@ +""" +Cretonne target ISA definitions +------------------------------- + +The :py:mod:`isa` package contains sub-packages for each target instruction set +architecture supported by Cretonne. +""" + +from . import riscv + + +def all_isas(): + """ + Get a list of all the supported target ISAs. Each target ISA is represented + as a :py:class:`cretonne.TargetISA` instance. + """ + return [riscv.isa] diff --git a/meta/target/riscv/__init__.py b/meta/isa/riscv/__init__.py similarity index 90% rename from meta/target/riscv/__init__.py rename to meta/isa/riscv/__init__.py index 48d916f089..7d51b69b03 100644 --- a/meta/target/riscv/__init__.py +++ b/meta/isa/riscv/__init__.py @@ -28,6 +28,6 @@ RV32G / RV64G import defs import encodings -# Re-export the primary target definition. -target = defs.target +# Re-export the primary target ISA definition. +isa = defs.isa diff --git a/meta/isa/riscv/defs.py b/meta/isa/riscv/defs.py new file mode 100644 index 0000000000..6e009ccdf4 --- /dev/null +++ b/meta/isa/riscv/defs.py @@ -0,0 +1,14 @@ +""" +RISC-V definitions. + +Commonly used definitions. +""" + +from cretonne import TargetISA, CPUMode +import cretonne.base + +isa = TargetISA('riscv', [cretonne.base.instructions]) + +# CPU modes for 32-bit and 64-bit operation. +RV32 = CPUMode('RV32', isa) +RV64 = CPUMode('RV64', isa) diff --git a/meta/target/riscv/encodings.py b/meta/isa/riscv/encodings.py similarity index 100% rename from meta/target/riscv/encodings.py rename to meta/isa/riscv/encodings.py diff --git a/meta/target/riscv/recipes.py b/meta/isa/riscv/recipes.py similarity index 100% rename from meta/target/riscv/recipes.py rename to meta/isa/riscv/recipes.py diff --git a/meta/target/__init__.py b/meta/target/__init__.py deleted file mode 100644 index 1f9f7a0565..0000000000 --- a/meta/target/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Cretonne target definitions ---------------------------- - -The :py:mod:`target` package contains sub-packages for each target instruction -set architecture supported by Cretonne. -""" - -from . import riscv - - -def all_targets(): - """ - Get a list of all the supported targets. Each target is represented as a - :py:class:`cretonne.Target` instance. - """ - return [riscv.target] diff --git a/meta/target/riscv/defs.py b/meta/target/riscv/defs.py deleted file mode 100644 index aab9279360..0000000000 --- a/meta/target/riscv/defs.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -RISC-V definitions. - -Commonly used definitions. -""" - -from cretonne import Target, CPUMode -import cretonne.base - -target = Target('riscv', [cretonne.base.instructions]) - -# CPU modes for 32-bit and 64-bit operation. -RV32 = CPUMode('RV32', target) -RV64 = CPUMode('RV64', target) From 6b6939128967de36097838ac3e954ff38104037d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 5 Aug 2016 09:55:53 -0700 Subject: [PATCH 190/968] Scaffold implementation of the TargetIsa trait. More to come here. --- src/libcretonne/isa/mod.rs | 42 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index e2f48e7bbe..cd9749e6ee 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -1,6 +1,44 @@ //! Instruction Set Architectures. //! -//! The sub-modules of this `isa` module provide definitions for the instruction sets that Cretonne -//! can target. +//! The `isa` module provides a `TargetIsa` trait which provides the behavior specialization needed +//! by the ISA-independent code generator. +//! +//! The sub-modules of this module provide definitions for the instruction sets that Cretonne +//! can target. Each sub-module has it's own implementation of `TargetIsa`. pub mod riscv; + +use ir::dfg::DataFlowGraph; +use ir::entities::Inst; + +pub trait TargetIsa { + /// Encode an instruction after determining it is legal. + /// + /// If `inst` can legally be encoded in this ISA, produce the corresponding `Encoding` object. + /// Otherwise, return `None`. + /// + /// This is also the main entry point for determining if an instruction is legal. + fn encode(&self, dfg: &DataFlowGraph, inst: &Inst) -> Option; +} + +/// Bits needed to encode an instruction as binary machine code. +/// +/// The encoding consists of two parts, both specific to the target ISA: An encoding *recipe*, and +/// encoding *bits*. The recipe determines the native instruction format and the mapping of +/// operands to encoded bits. The encoding bits provide additional information to the recipe, +/// typically parts of the opcode. +pub struct Encoding(u32); + +impl Encoding { + /// Create a new `Encoding` containing `(recipe, bits)`. The `num_bits` parameter is the + /// ISA-dependent size of `bits`. + pub fn new(recipe: u32, bits: u32, num_bits: u8) -> Encoding { + Encoding((recipe << num_bits) | bits) + } + + /// Split the encoding into two parts: `(recipe, bits)`. Only the target ISA knows how many + /// bits are in each part. + pub fn split(&self, num_bits: u8) -> (u32, u32) { + (self.0 >> num_bits, self.0 & ((1 << num_bits) - 1)) + } +} From cfeefde3fc829ae8217e1378662d289b2ffd9a07 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 5 Aug 2016 13:38:43 -0700 Subject: [PATCH 191/968] Scaffolding for defining settings. Settings can be defined globally or per-ISA. They are available to code through a generated Settings struct with accessor methods per setting. --- docs/metaref.rst | 39 ++++++++++ meta/build.py | 2 + meta/cretonne/__init__.py | 146 +++++++++++++++++++++++++++++++++++- meta/cretonne/settings.py | 13 ++++ meta/gen_settings.py | 104 +++++++++++++++++++++++++ src/libcretonne/lib.rs | 1 + src/libcretonne/settings.rs | 7 ++ 7 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 meta/cretonne/settings.py create mode 100644 meta/gen_settings.py create mode 100644 src/libcretonne/settings.rs diff --git a/docs/metaref.rst b/docs/metaref.rst index 4ac81096cc..8df75d80a2 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -25,6 +25,45 @@ The main driver for this source code generation process is the :file:`meta/build.py` script which is invoked as part of the build process if anything in the :file:`meta` directory has changed since the last build. + +Settings +======== + +Settings are used by the environment embedding Cretonne to control the details +of code generation. Each setting is defined in the meta language so a compact +and consistent Rust representation can be generated. Shared settings are defined +in the :mod:`cretonne.settings` module. Some settings are specific to a target +ISA, and defined in a `settings` module under the appropriate :file:`meta/isa/*` +directory. + +Settings can take boolean on/off values, small numbers, or explicitly enumerated +symbolic values. Each type is represented by a sub-class of :class:`Setting`: + +.. inheritance-diagram:: Setting BoolSetting NumSetting EnumSetting + :parts: 1 + +.. autoclass:: Setting +.. autoclass:: BoolSetting +.. autoclass:: NumSetting +.. autoclass:: EnumSetting + +All settings must belong to a *group*, represented by a :class:`SettingGroup` +object. + +.. autoclass:: SettingGroup + +Normally, a setting group corresponds to all settings defined in a module. Such +a module looks like this:: + + group = SettingGroup('example') + + foo = BoolSetting('use the foo') + bar = BoolSetting('enable bars', True) + opt = EnumSetting('optimization level', 'Debug', 'Release') + + group.close(globals()) + + Instruction descriptions ======================== diff --git a/meta/build.py b/meta/build.py index 5dadc112e2..6ca4425823 100644 --- a/meta/build.py +++ b/meta/build.py @@ -5,6 +5,7 @@ import argparse import isa import gen_instr +import gen_settings import gen_build_deps parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') @@ -16,4 +17,5 @@ out_dir = args.out_dir isas = isa.all_isas() gen_instr.generate(isas, out_dir) +gen_settings.generate(isas, out_dir) gen_build_deps.generate() diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index ee523cfec5..54f20cc678 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -18,6 +18,146 @@ def camel_case(s): return camel_re.sub(lambda m: m.group(2).upper(), s) +class Setting(object): + """ + A named setting variable that can be configured externally to Cretonne. + + Settings are normally not named when they are created. They get their name + from the `extract_names` method. + """ + + def __init__(self, doc): + self.name = None # Assigned later by `extract_names()`. + self.__doc__ = doc + # Offset of byte in settings vector containing this setting. + self.byte_offset = None + SettingGroup.append(self) + + @staticmethod + def extract_names(globs): + """ + Given a dict mapping name -> object as returned by `globals()`, find + all the Setting objects and set their name from the dict key. This is + used to name a bunch of global variables in a module. + """ + for name, obj in globs.iteritems(): + if isinstance(obj, Setting): + assert obj.name is None + obj.name = name + + +class BoolSetting(Setting): + """ + A named setting with a boolean on/off value. + + :param doc: Documentation string. + :param default: The default value of this setting. + """ + + def __init__(self, doc, default=False): + super(BoolSetting, self).__init__(doc) + self.default = default + + def default_byte(self): + """ + Get the default value of this setting, as a byte that can be bitwise + or'ed with the other booleans sharing the same byte. + """ + if self.default: + return 1 << self.bit_offset + else: + return 0 + + +class NumSetting(Setting): + """ + A named setting with an integral value in the range 0--255. + + :param doc: Documentation string. + :param default: The default value of this setting. + """ + + def __init__(self, doc, default=0): + super(NumSetting, self).__init__(doc) + assert default == int(default) + assert default >= 0 and default <= 255 + self.default = default + + def default_byte(self): + return self.default + + +class EnumSetting(Setting): + """ + A named setting with an enumerated set of possible values. + + The default value is always the first enumerator. + + :param doc: Documentation string. + :param args: Tuple of unique strings representing the possible values. + """ + + def __init__(self, doc, *args): + super(EnumSetting, self).__init__(doc) + assert len(args) > 0, "EnumSetting must have at least one value" + self.values = tuple(str(x) for x in args) + self.default = self.values[0] + + def default_byte(self): + return 0 + + +class SettingGroup(object): + """ + A group of settings. + + Whenever a :class:`Setting` object is created, it is added to the currently + open group. A setting group must be closed explicitly before another can be + opened. + + :param name: Short mnemonic name for setting group. + """ + + # The currently open setting group. + _current = None + + def __init__(self, name): + self.name = name + self.settings = [] + self.open() + + def open(self): + """ + Open this setting group such that future new settings are added to this + group. + """ + assert SettingGroup._current is None, ( + "Can't open {} since {} is already open" + .format(self, SettingGroup._current)) + SettingGroup._current = self + + def close(self, globs=None): + """ + Close this setting group. This function must be called before opening + another setting group. + + :param globs: Pass in `globals()` to run `extract_names` on all + settings defined in the module. + """ + assert SettingGroup._current is self, ( + "Can't close {}, the open setting group is {}" + .format(self, SettingGroup._current)) + SettingGroup._current = None + if globs: + Setting.extract_names(globs) + + @staticmethod + def append(setting): + assert SettingGroup._current, \ + "Open a setting group before defining settings." + SettingGroup._current.settings.append(setting) + + # Kinds of operands. # # Each instruction has an opcode and a number of operands. The opcode @@ -689,7 +829,7 @@ class BoundInstruction(object): assert len(typevars) <= 1 + len(inst.other_typevars) def __str__(self): - return '.'.join([self.inst.name,] + map(str, self.typevars)) + return '.'.join([self.inst.name, ] + map(str, self.typevars)) def bind(self, *args): """ @@ -711,7 +851,9 @@ class BoundInstruction(object): `(inst, typevars)` pair. """ if len(self.typevars) < 1 + len(self.inst.other_typevars): - unb = ', '.join(str(tv) for tv in self.inst.other_typevars[len(self.typevars) - 1:]) + unb = ', '.join( + str(tv) for tv in + self.inst.other_typevars[len(self.typevars) - 1:]) raise AssertionError("Unbound typevar {} in {}".format(unb, self)) assert len(self.typevars) == 1 + len(self.inst.other_typevars) return (self.inst, self.typevars) diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py new file mode 100644 index 0000000000..f06c69bfef --- /dev/null +++ b/meta/cretonne/settings.py @@ -0,0 +1,13 @@ +""" +Cretonne shared settings. + +This module defines settings are are relevant for all code generators. +""" + +from . import SettingGroup, BoolSetting + +group = SettingGroup('shared') + +enable_simd = BoolSetting("Enable the use of SIMD instructions", default=True) + +group.close(globals()) diff --git a/meta/gen_settings.py b/meta/gen_settings.py new file mode 100644 index 0000000000..accd739cf0 --- /dev/null +++ b/meta/gen_settings.py @@ -0,0 +1,104 @@ +""" +Generate sources with settings. +""" + +import srcgen +from cretonne import BoolSetting, NumSetting, settings + + +def layout_group(sgrp): + """ + Layout the settings in sgrp, assigning byte and bit offsets. + + Return the next unused byte offset. + """ + # Byte offset where booleans are allocated. + bool_byte = -1 + # Next available bit number in bool_byte. + bool_bit = 10 + # Next available whole byte. + next_byte = 0 + + for setting in sgrp.settings: + if isinstance(setting, BoolSetting): + # Allocate a bit from bool_byte. + if bool_bit > 7: + bool_byte = next_byte + next_byte += 1 + bool_bit = 0 + setting.byte_offset = bool_byte + setting.bit_offset = bool_bit + bool_bit += 1 + else: + # This is a numerical or enumerated setting. Allocate a single + # byte. + setting.byte_offset = next_byte + next_byte += 1 + + return next_byte + + +def gen_getter(setting, fmt): + """ + Emit a getter function for `setting`. + """ + fmt.doc_comment(setting.__doc__ + '.') + + if isinstance(setting, BoolSetting): + proto = 'pub fn {}(&self) -> bool'.format(setting.name) + with fmt.indented(proto + ' {', '}'): + fmt.line('(self.bytes[{}] & (1 << {})) != 0'.format( + setting.byte_offset, + setting.bit_offset)) + elif isinstance(setting, NumSetting): + proto = 'pub fn {}(&self) -> u8'.format(setting.name) + with fmt.indented(proto + ' {', '}'): + fmt.line('self.bytes[{}]'.format(setting.byte_offset)) + else: + raise AssertionError("Unknown setting kind") + + +def gen_getters(sgrp, fmt): + """ + Emit getter functions for all the settings in fmt. + """ + fmt.doc_comment("User-defined settings.") + with fmt.indented('impl Settings {', '}'): + for setting in sgrp.settings: + gen_getter(setting, fmt) + + +def gen_default(sgrp, byte_size, fmt): + """ + Emit a Default impl for Settings. + """ + v = [0] * byte_size + for setting in sgrp.settings: + v[setting.byte_offset] |= setting.default_byte() + + with fmt.indented('impl Default for Settings {', '}'): + fmt.doc_comment('Return a `Settings` object with default values.') + with fmt.indented('fn default() -> Settings {', '}'): + with fmt.indented('Settings {', '}'): + vs = ', '.join('{:#04x}'.format(x) for x in v) + fmt.line('bytes: [ {} ],'.format(vs)) + + +def gen_group(sgrp, fmt): + """ + Generate a Settings struct representing `sgrp`. + """ + byte_size = layout_group(sgrp) + + fmt.doc_comment('Settings group `{}`.'.format(sgrp.name)) + with fmt.indented('pub struct Settings {', '}'): + fmt.line('bytes: [u8; {}],'.format(byte_size)) + + gen_getters(sgrp, fmt) + gen_default(sgrp, byte_size, fmt) + + +def generate(isas, out_dir): + fmt = srcgen.Formatter() + gen_group(settings.group, fmt) + fmt.update_file('settings.rs', out_dir) diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 9d696fab53..766c33abcf 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -13,5 +13,6 @@ pub mod write; pub mod cfg; pub mod dominator_tree; pub mod entity_map; +pub mod settings; #[cfg(test)]pub mod test_utils; diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs new file mode 100644 index 0000000000..0b21bc2eb0 --- /dev/null +++ b/src/libcretonne/settings.rs @@ -0,0 +1,7 @@ +//! Shared settings module. +//! +//! This module defines data structures to access the settings defined in the meta language. + +// Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct +// with an impl for all of the settings defined in `meta/cretonne/settings.py`. +include!(concat!(env!("OUT_DIR"), "/settings.rs")); From 36ad7da3ece901f826cb658ca7b9cdbd71940627 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 5 Aug 2016 16:19:46 -0700 Subject: [PATCH 192/968] Add ISA-dependent settings for RISC-V. --- meta/cretonne/__init__.py | 1 + meta/gen_settings.py | 8 ++++++++ meta/isa/riscv/__init__.py | 2 +- meta/isa/riscv/encodings.py | 1 - meta/isa/riscv/recipes.py | 6 ++++++ meta/isa/riscv/settings.py | 15 +++++++++++++++ src/libcretonne/isa/riscv/mod.rs | 2 ++ src/libcretonne/isa/riscv/settings.rs | 5 +++++ 8 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 meta/isa/riscv/settings.py create mode 100644 src/libcretonne/isa/riscv/settings.rs diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 54f20cc678..801ac5a171 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -875,6 +875,7 @@ class TargetISA(object): def __init__(self, name, instrution_groups): self.name = name + self.settings = None self.instruction_groups = instrution_groups diff --git a/meta/gen_settings.py b/meta/gen_settings.py index accd739cf0..c2545a8c90 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -99,6 +99,14 @@ def gen_group(sgrp, fmt): def generate(isas, out_dir): + # Generate shared settings. fmt = srcgen.Formatter() gen_group(settings.group, fmt) fmt.update_file('settings.rs', out_dir) + + # Generate ISA-specific settings. + for isa in isas: + if isa.settings: + fmt = srcgen.Formatter() + gen_group(isa.settings, fmt) + fmt.update_file('settings-{}.rs'.format(isa.name), out_dir) diff --git a/meta/isa/riscv/__init__.py b/meta/isa/riscv/__init__.py index 7d51b69b03..ce8c47ea3d 100644 --- a/meta/isa/riscv/__init__.py +++ b/meta/isa/riscv/__init__.py @@ -27,7 +27,7 @@ RV32G / RV64G import defs import encodings +import settings # Re-export the primary target ISA definition. isa = defs.isa - diff --git a/meta/isa/riscv/encodings.py b/meta/isa/riscv/encodings.py index fb6602184c..c148247524 100644 --- a/meta/isa/riscv/encodings.py +++ b/meta/isa/riscv/encodings.py @@ -2,7 +2,6 @@ RISC-V Encodings. """ from cretonne import base -from cretonne.types import i32, i64 from defs import RV32, RV64 from recipes import OP, R diff --git a/meta/isa/riscv/recipes.py b/meta/isa/riscv/recipes.py index ed96836cd4..9719a4b2ec 100644 --- a/meta/isa/riscv/recipes.py +++ b/meta/isa/riscv/recipes.py @@ -18,27 +18,33 @@ from cretonne.formats import Binary # Encbits for the 32-bit recipes are opcode[6:2] | (funct3 << 5) | ... # The functions below encode the encbits. + def LOAD(funct3): assert funct3 <= 0b111 return 0b00000 | (funct3 << 5) + def STORE(funct3): assert funct3 <= 0b111 return 0b01000 | (funct3 << 5) + def BRANCH(funct3): assert funct3 <= 0b111 return 0b11000 | (funct3 << 5) + def OPIMM(funct3): assert funct3 <= 0b111 return 0b00100 | (funct3 << 5) + def OP(funct3, funct7): assert funct3 <= 0b111 assert funct7 <= 0b1111111 return 0b01100 | (funct3 << 5) | (funct7 << 8) + # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) R = EncRecipe('R', Binary) diff --git a/meta/isa/riscv/settings.py b/meta/isa/riscv/settings.py new file mode 100644 index 0000000000..d2366ad829 --- /dev/null +++ b/meta/isa/riscv/settings.py @@ -0,0 +1,15 @@ +""" +RISC-V settings. +""" + +from cretonne import SettingGroup, BoolSetting +from defs import isa + +isa.settings = SettingGroup('riscv') + +supports_m = BoolSetting("CPU supports the 'M' extension (mul/div)") +supports_a = BoolSetting("CPU supports the 'A' extension (atomics)") +supports_f = BoolSetting("CPU supports the 'F' extension (float)") +supports_d = BoolSetting("CPU supports the 'D' extension (double)") + +isa.settings.close(globals()) diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 4c24cbb8b7..775154a3d6 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -1 +1,3 @@ //! RISC-V Instruction Set Architecture. + +pub mod settings; diff --git a/src/libcretonne/isa/riscv/settings.rs b/src/libcretonne/isa/riscv/settings.rs new file mode 100644 index 0000000000..74d02d5aac --- /dev/null +++ b/src/libcretonne/isa/riscv/settings.rs @@ -0,0 +1,5 @@ +//! RISC-V Settings. + +// Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct +// with an impl for all of the settings defined in `meta/cretonne/settings.py`. +include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs")); From d152719d93fffa1656a6e273b12e99f828fa0d44 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 9 Aug 2016 11:28:51 -0700 Subject: [PATCH 193/968] Move simple_hash into its own module. --- src/libcretonne/ir/instructions.rs | 11 +---------- src/libcretonne/lib.rs | 2 ++ src/libcretonne/simple_hash.rs | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 src/libcretonne/simple_hash.rs diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 4087e246d5..9d5c59eb7d 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -54,16 +54,6 @@ impl Opcode { } } -// A primitive hash function for matching opcodes. -// Must match `meta/constant_hash.py`. -fn simple_hash(s: &str) -> u32 { - let mut h: u32 = 5381; - for c in s.chars() { - h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); - } - h -} - // This trait really belongs in libreader where it is used by the .cton file parser, but since it // critically depends on the `opcode_name()` function which is needed here anyway, it lives in this // module. This also saves us from runing the build script twice to generate code for the two @@ -73,6 +63,7 @@ impl FromStr for Opcode { /// Parse an Opcode name from a string. fn from_str(s: &str) -> Result { + use simple_hash::simple_hash; let tlen = OPCODE_HASH_TABLE.len(); assert!(tlen.is_power_of_two()); let mut idx = simple_hash(s) as usize; diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 766c33abcf..a8cd09ab8d 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -15,4 +15,6 @@ pub mod dominator_tree; pub mod entity_map; pub mod settings; +mod simple_hash; + #[cfg(test)]pub mod test_utils; diff --git a/src/libcretonne/simple_hash.rs b/src/libcretonne/simple_hash.rs new file mode 100644 index 0000000000..99cd80015c --- /dev/null +++ b/src/libcretonne/simple_hash.rs @@ -0,0 +1,21 @@ +/// A primitive hash function for matching opcodes. +/// Must match `meta/constant_hash.py`. +pub fn simple_hash(s: &str) -> u32 { + let mut h: u32 = 5381; + for c in s.chars() { + h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); + } + h +} + +#[cfg(test)] +mod tests { + use super::simple_hash; + + #[test] + fn basic() { + // c.f. meta/constant_hash.py tests. + assert_eq!(simple_hash("Hello"), 0x2fa70c01); + assert_eq!(simple_hash("world"), 0x5b0c31d5); + } +} From 07e851a222f8741d86cd0dfc2ba11dbf3a09e219 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 9 Aug 2016 11:52:36 -0700 Subject: [PATCH 194/968] Add settings::Stringwise. This trait allows settings to be manipulated as strings, using descriptors and constant hash-table lookups. Amend gen_settings.py to generate the necessary constant tables. --- meta/gen_settings.py | 113 +++++++++++++++++++++++ src/libcretonne/isa/riscv/settings.rs | 19 ++++ src/libcretonne/settings.rs | 127 ++++++++++++++++++++++++++ 3 files changed, 259 insertions(+) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index c2545a8c90..ce1e031e97 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -3,6 +3,8 @@ Generate sources with settings. """ import srcgen +from unique_table import UniqueSeqTable +import constant_hash from cretonne import BoolSetting, NumSetting, settings @@ -84,6 +86,114 @@ def gen_default(sgrp, byte_size, fmt): fmt.line('bytes: [ {} ],'.format(vs)) +def gen_descriptors(sgrp, fmt): + """ + Generate the DESCRIPTORS and ENUMERATORS tables. + """ + + enums = UniqueSeqTable() + + with fmt.indented( + 'const DESCRIPTORS: [Descriptor; {}] = [' + .format(len(sgrp.settings)), + '];'): + for idx, setting in enumerate(sgrp.settings): + setting.descriptor_index = idx + with fmt.indented('Descriptor {', '},'): + fmt.line('name: "{}",'.format(setting.name)) + fmt.line('offset: {},'.format(setting.byte_offset)) + if isinstance(setting, BoolSetting): + fmt.line( + 'detail: Detail::Bool {{ bit: {} }},' + .format(setting.bit_offset)) + elif isinstance(setting, NumSetting): + fmt.line('detail: Detail::Num,') + else: + raise AssertionError("Unknown setting kind") + + with fmt.indented( + 'const ENUMERATORS: [&\'static str; {}] = [' + .format(len(enums.table)), + '];'): + for txt in enums.table: + fmt.line('"{}",'.format(txt)) + + def hash_setting(s): + return constant_hash.simple_hash(s.name) + + hash_table = constant_hash.compute_quadratic(sgrp.settings, hash_setting) + if len(sgrp.settings) > 0xffff: + ty = 'u32' + elif len(sgrp.settings) > 0xff: + ty = 'u16' + else: + ty = 'u8' + + with fmt.indented( + 'const HASH_TABLE: [{}; {}] = [' + .format(ty, len(hash_table)), + '];'): + for h in hash_table: + if h is None: + fmt.line('{},'.format(len(sgrp.settings))) + else: + fmt.line('{},'.format(h.descriptor_index)) + + +def gen_stringwise(sgrp, fmt): + """ + Generate the Stringwise implementation and supporting tables. + """ + + with fmt.indented('impl Stringwise for Settings {', '}'): + with fmt.indented( + 'fn lookup_mut(&mut self, name: &str)' + + '-> Result<(Detail, &mut u8)> {', + '}'): + fmt.line('use simple_hash::simple_hash;') + fmt.line('let tlen = HASH_TABLE.len();') + fmt.line('assert!(tlen.is_power_of_two());') + fmt.line('let mut idx = simple_hash(name) as usize;') + fmt.line('let mut step: usize = 0;') + with fmt.indented('loop {', '}'): + fmt.line('idx = idx % tlen;') + fmt.line('let entry = HASH_TABLE[idx] as usize;') + with fmt.indented('if entry >= DESCRIPTORS.len() {', '}'): + fmt.line('return Err(Error::BadName)') + with fmt.indented('if DESCRIPTORS[entry].name == name {', '}'): + fmt.line( + 'return Ok((DESCRIPTORS[entry].detail, ' + + '&mut self.bytes[DESCRIPTORS[entry].offset ' + + 'as usize]))') + fmt.line('step += 1;') + fmt.line('assert!(step < tlen);') + fmt.line('idx += step;') + + with fmt.indented( + 'fn enumerator(&self, enums: u16, value: u8)' + + '-> &\'static str {', + '}'): + fmt.line('ENUMERATORS[enums as usize + value as usize]') + + +def gen_display(sgrp, fmt): + """ + Generate the Display impl for Settings. + """ + with fmt.indented('impl fmt::Display for Settings {', '}'): + with fmt.indented( + 'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {', + '}'): + fmt.line('try!(writeln!(f, "[{}]"));'.format(sgrp.name)) + with fmt.indented('for d in &DESCRIPTORS {', '}'): + fmt.line('try!(write!(f, "{} = ", d.name));') + fmt.line( + 'try!(self.format_toml_value(d.detail,' + + 'self.bytes[d.offset as usize], f));') + fmt.line('try!(writeln!(f, ""));') + fmt.line('Ok(())') + + def gen_group(sgrp, fmt): """ Generate a Settings struct representing `sgrp`. @@ -96,6 +206,9 @@ def gen_group(sgrp, fmt): gen_getters(sgrp, fmt) gen_default(sgrp, byte_size, fmt) + gen_descriptors(sgrp, fmt) + gen_stringwise(sgrp, fmt) + gen_display(sgrp, fmt) def generate(isas, out_dir): diff --git a/src/libcretonne/isa/riscv/settings.rs b/src/libcretonne/isa/riscv/settings.rs index 74d02d5aac..0ee83b3015 100644 --- a/src/libcretonne/isa/riscv/settings.rs +++ b/src/libcretonne/isa/riscv/settings.rs @@ -1,5 +1,24 @@ //! RISC-V Settings. +use settings::{Descriptor, Detail, Stringwise, Result, Error}; +use std::fmt; + // Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct // with an impl for all of the settings defined in `meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs")); + +#[cfg(test)] +mod tests { + use super::Settings; + + #[test] + fn display_default() { + let s = Settings::default(); + assert_eq!(s.to_string(), + "[riscv]\n\ + supports_m = false\n\ + supports_a = false\n\ + supports_f = false\n\ + supports_d = false\n"); + } +} diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index 0b21bc2eb0..80948e3ed3 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -1,7 +1,134 @@ //! Shared settings module. //! //! This module defines data structures to access the settings defined in the meta language. +//! +//! Each settings group is translated to a `Settings` struct either in this module or in its +//! ISA-specific `settings` module. The struct provides individual getter methods for all of the +//! settings. It also implements the `Stringwise` trait which allows settings to be manipulated by +//! name. + +use std::fmt; +use std::result; + +/// A setting descriptor holds the information needed to generically set and print a setting. +/// +/// Each settings group will be represented as a constant DESCRIPTORS array. +pub struct Descriptor { + /// Lower snake-case name of setting as defined in meta. + pub name: &'static str, + + /// Offset of byte containing this setting. + pub offset: u32, + + /// Additional details, depending on the kind of setting. + pub detail: Detail, +} + +/// The different kind of settings along with descriptor bits that depend on the kind. +#[derive(Clone, Copy)] +pub enum Detail { + /// A boolean setting only uses one bit, numbered from LSB. + Bool { + bit: u8, + }, + + /// A numerical setting uses the whole byte. + Num, + + /// An Enum setting uses a range of enumerators. + Enum { + /// Numerical value of last enumerator, allowing for 1-256 enumerators. + last: u8, + + /// First enumerator in the ENUMERATORS table. + enumerators: u16, + }, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// No setting by this name exists. + BadName, + + /// Type mismatch for setting (e.g., setting an enum setting as a bool). + BadType, + + /// This is not a valid value for this setting. + BadValue, +} + +pub type Result = result::Result; + +/// Interface for working with a group of settings as strings. +pub trait Stringwise { + /// Look up a setting by name, return the details of the setting along with a reference to the + /// byte holding the value of the setting. + fn lookup_mut(&mut self, name: &str) -> Result<(Detail, &mut u8)>; + + /// Get an enumerator string from the `Detail::enumerators` value and an offset. + fn enumerator(&self, enums: u16, value: u8) -> &'static str; + + /// Format a setting value as a TOML string. This is mostly for use by the generateed `Display` + /// implementation. + fn format_toml_value(&self, detail: Detail, byte: u8, f: &mut fmt::Formatter) -> fmt::Result { + match detail { + Detail::Bool { bit } => write!(f, "{}", (byte & (1 << bit)) != 0), + Detail::Num => write!(f, "{}", byte), + Detail::Enum { last, enumerators } => { + if byte <= last { + write!(f, "\"{}\"", self.enumerator(enumerators, byte)) + } else { + write!(f, "{}", byte) + } + } + } + } + + /// Set a boolean setting by name. + fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { + let (detail, byte) = try!(self.lookup_mut(name)); + if let Detail::Bool { bit } = detail { + let mask = 1 << bit; + if value { + *byte |= mask; + } else { + *byte &= !mask; + } + Ok(()) + } else { + Err(Error::BadType) + } + } +} // Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct // with an impl for all of the settings defined in `meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings.rs")); + +#[cfg(test)] +mod tests { + use super::Settings; + use super::Error::*; + use super::Stringwise; + + #[test] + fn display_default() { + let s = Settings::default(); + assert_eq!(s.to_string(), + "[shared]\n\ + enable_simd = true\n"); + } + + #[test] + fn modify_bool() { + let mut s = Settings::default(); + assert_eq!(s.enable_simd(), true); + assert_eq!(s.set_bool("not_there", true), Err(BadName)); + + assert_eq!(s.set_bool("enable_simd", true), Ok(())); + assert_eq!(s.enable_simd(), true); + + assert_eq!(s.set_bool("enable_simd", false), Ok(())); + assert_eq!(s.enable_simd(), false); + } +} From 1ef72dd5ec5704de81c07adcc74c92aeeff48e29 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 9 Aug 2016 14:12:36 -0700 Subject: [PATCH 195/968] Add support for enumerated settings. The EnumSetting objects can take one of 256 named values. --- meta/cretonne/settings.py | 16 +++++++- meta/gen_settings.py | 44 +++++++++++++++++---- meta/srcgen.py | 6 ++- src/libcretonne/settings.rs | 77 ++++++++++++++++++++++++++++++++++--- 4 files changed, 127 insertions(+), 16 deletions(-) diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py index f06c69bfef..2876dfd84f 100644 --- a/meta/cretonne/settings.py +++ b/meta/cretonne/settings.py @@ -4,10 +4,22 @@ Cretonne shared settings. This module defines settings are are relevant for all code generators. """ -from . import SettingGroup, BoolSetting +from . import SettingGroup, BoolSetting, EnumSetting group = SettingGroup('shared') -enable_simd = BoolSetting("Enable the use of SIMD instructions", default=True) +opt_level = EnumSetting( + """ + Optimization level: + + - default: Very profitable optimizations enabled, none slow. + - best: Enable all optimizations + - fastest: Optimize for compile time by disabling most optimizations. + """, + 'default', 'best', 'fastest') + +enable_simd = BoolSetting( + """Enable the use of SIMD instructions.""", + default=True) group.close(globals()) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index ce1e031e97..d8b0e4c919 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -5,7 +5,7 @@ Generate sources with settings. import srcgen from unique_table import UniqueSeqTable import constant_hash -from cretonne import BoolSetting, NumSetting, settings +from cretonne import camel_case, BoolSetting, NumSetting, EnumSetting, settings def layout_group(sgrp): @@ -40,11 +40,25 @@ def layout_group(sgrp): return next_byte +def gen_enum_types(sgrp, fmt): + """ + Emit enum types for any enum settings. + """ + for setting in sgrp.settings: + if not isinstance(setting, EnumSetting): + continue + ty = camel_case(setting.name) + fmt.line('#[derive(Debug, PartialEq, Eq)]') + fmt.line( + 'pub enum {} {{ {} }}' + .format(ty, ", ".join(camel_case(v) for v in setting.values))) + + def gen_getter(setting, fmt): """ Emit a getter function for `setting`. """ - fmt.doc_comment(setting.__doc__ + '.') + fmt.doc_comment(setting.__doc__) if isinstance(setting, BoolSetting): proto = 'pub fn {}(&self) -> bool'.format(setting.name) @@ -56,6 +70,15 @@ def gen_getter(setting, fmt): proto = 'pub fn {}(&self) -> u8'.format(setting.name) with fmt.indented(proto + ' {', '}'): fmt.line('self.bytes[{}]'.format(setting.byte_offset)) + elif isinstance(setting, EnumSetting): + ty = camel_case(setting.name) + proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty) + with fmt.indented(proto + ' {', '}'): + with fmt.indented( + 'match self.bytes[{}] {{'.format(setting.byte_offset), '}'): + for i, v in enumerate(setting.values): + fmt.line( '{} => {}::{},'.format(i, ty, camel_case(v))) + fmt.line( '_ => panic!("Invalid enum value")') else: raise AssertionError("Unknown setting kind") @@ -108,6 +131,11 @@ def gen_descriptors(sgrp, fmt): .format(setting.bit_offset)) elif isinstance(setting, NumSetting): fmt.line('detail: Detail::Num,') + elif isinstance(setting, EnumSetting): + offs = enums.add(setting.values) + fmt.line( + 'detail: Detail::Enum {{ last: {}, enumerators: {} }},' + .format(len(setting.values)-1, offs)) else: raise AssertionError("Unknown setting kind") @@ -147,8 +175,8 @@ def gen_stringwise(sgrp, fmt): with fmt.indented('impl Stringwise for Settings {', '}'): with fmt.indented( - 'fn lookup_mut(&mut self, name: &str)' + - '-> Result<(Detail, &mut u8)> {', + 'fn lookup(&self, name: &str)' + + '-> Result<(usize, Detail)> {', '}'): fmt.line('use simple_hash::simple_hash;') fmt.line('let tlen = HASH_TABLE.len();') @@ -162,9 +190,8 @@ def gen_stringwise(sgrp, fmt): fmt.line('return Err(Error::BadName)') with fmt.indented('if DESCRIPTORS[entry].name == name {', '}'): fmt.line( - 'return Ok((DESCRIPTORS[entry].detail, ' + - '&mut self.bytes[DESCRIPTORS[entry].offset ' + - 'as usize]))') + 'return Ok((DESCRIPTORS[entry].offset as usize, ' + + 'DESCRIPTORS[entry].detail))') fmt.line('step += 1;') fmt.line('assert!(step < tlen);') fmt.line('idx += step;') @@ -175,6 +202,8 @@ def gen_stringwise(sgrp, fmt): '}'): fmt.line('ENUMERATORS[enums as usize + value as usize]') + with fmt.indented('fn raw_bytes_mut(&mut self) -> &mut [u8] {', '}'): + fmt.line('&mut self.bytes') def gen_display(sgrp, fmt): """ @@ -204,6 +233,7 @@ def gen_group(sgrp, fmt): with fmt.indented('pub struct Settings {', '}'): fmt.line('bytes: [u8; {}],'.format(byte_size)) + gen_enum_types(sgrp, fmt) gen_getters(sgrp, fmt) gen_default(sgrp, byte_size, fmt) gen_descriptors(sgrp, fmt) diff --git a/meta/srcgen.py b/meta/srcgen.py index 1ebd745286..42f39c96de 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -8,6 +8,7 @@ source code. import sys import os +import re class Formatter(object): @@ -108,8 +109,9 @@ class Formatter(object): self.line('// ' + s) def doc_comment(self, s): - """Add a documentation comment line.""" - self.line('/// ' + s) + """Add a (multi-line) documentation comment.""" + s = re.sub('^', self.indent + '/// ', s, flags=re.M) + '\n' + self.lines.append(s) if __name__ == "__main__": import doctest diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index 80948e3ed3..af25dd458a 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -59,16 +59,26 @@ pub enum Error { pub type Result = result::Result; +fn parse_bool_value(value: &str) -> Result { + match value { + "true" | "on" | "yes" | "1" => Ok(true), + "false" | "off" | "no" | "0" => Ok(false), + _ => Err(Error::BadValue), + } +} + /// Interface for working with a group of settings as strings. pub trait Stringwise { - /// Look up a setting by name, return the details of the setting along with a reference to the - /// byte holding the value of the setting. - fn lookup_mut(&mut self, name: &str) -> Result<(Detail, &mut u8)>; + /// Look up a setting by name, return the byte offset and details of the setting. + fn lookup(&self, name: &str) -> Result<(usize, Detail)>; /// Get an enumerator string from the `Detail::enumerators` value and an offset. fn enumerator(&self, enums: u16, value: u8) -> &'static str; - /// Format a setting value as a TOML string. This is mostly for use by the generateed `Display` + /// Get the underlying byte array used to store settings. + fn raw_bytes_mut(&mut self) -> &mut [u8]; + + /// Format a setting value as a TOML string. This is mostly for use by the generated `Display` /// implementation. fn format_toml_value(&self, detail: Detail, byte: u8, f: &mut fmt::Formatter) -> fmt::Result { match detail { @@ -86,9 +96,10 @@ pub trait Stringwise { /// Set a boolean setting by name. fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { - let (detail, byte) = try!(self.lookup_mut(name)); + let (offset, detail) = try!(self.lookup(name)); if let Detail::Bool { bit } = detail { let mask = 1 << bit; + let byte = &mut self.raw_bytes_mut()[offset]; if value { *byte |= mask; } else { @@ -99,6 +110,43 @@ pub trait Stringwise { Err(Error::BadType) } } + + /// Set the string value of a named setting. + /// + /// For boolean settings, any of the values accepted by `parse_bool_value` above are accepted + /// (true/false, on/off, yes/no, 1/0). + /// + /// For enumerated settings, the value must match one of the allowed values exactly. + fn set(&mut self, name: &str, value: &str) -> Result<()> { + let (offset, detail) = try!(self.lookup(name)); + match detail { + Detail::Bool { bit } => { + let mask = 1 << bit; + let byte = &mut self.raw_bytes_mut()[offset]; + if try!(parse_bool_value(value)) { + *byte |= mask; + } else { + *byte &= !mask; + } + } + Detail::Num => { + self.raw_bytes_mut()[offset] = try!(value.parse().map_err(|_| Error::BadValue)); + } + Detail::Enum { last, enumerators } => { + // Linear search.. + for i in 0.. { + if value == self.enumerator(enumerators, i) { + self.raw_bytes_mut()[offset] = i; + break; + } + if i == last { + return Err(Error::BadValue); + } + } + } + } + Ok(()) + } } // Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct @@ -116,6 +164,7 @@ mod tests { let s = Settings::default(); assert_eq!(s.to_string(), "[shared]\n\ + opt_level = \"default\"\n\ enable_simd = true\n"); } @@ -131,4 +180,22 @@ mod tests { assert_eq!(s.set_bool("enable_simd", false), Ok(())); assert_eq!(s.enable_simd(), false); } + + #[test] + fn modify_string() { + let mut s = Settings::default(); + assert_eq!(s.enable_simd(), true); + assert_eq!(s.opt_level(), super::OptLevel::Default); + + assert_eq!(s.set("not_there", "true"), Err(BadName)); + assert_eq!(s.set("enable_simd", ""), Err(BadValue)); + assert_eq!(s.set("enable_simd", "best"), Err(BadValue)); + assert_eq!(s.set("opt_level", "true"), Err(BadValue)); + + assert_eq!(s.set("enable_simd", "no"), Ok(())); + assert_eq!(s.enable_simd(), false); + + assert_eq!(s.set("opt_level", "best"), Ok(())); + assert_eq!(s.opt_level(), super::OptLevel::Best); + } } From b9baf06fb750986650766737a33e8d1b2594a441 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 10 Aug 2016 15:28:17 -0700 Subject: [PATCH 196/968] Add a settings::Builder data type. - Move detail data structures into a settings::detail module to avoid polluting the settings namespace. - Rename generated data types to 'Flags' in anticipation of computed predicate flags that can't be set. The Flags struct is immutable. - Use a settings::Builder struct to manipulate settings, then pass it to Flags::new(). --- meta/gen_settings.py | 132 ++++------ src/libcretonne/isa/riscv/settings.rs | 11 +- src/libcretonne/settings.rs | 366 ++++++++++++++++---------- 3 files changed, 292 insertions(+), 217 deletions(-) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index d8b0e4c919..a10dd5352a 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -75,10 +75,11 @@ def gen_getter(setting, fmt): proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty) with fmt.indented(proto + ' {', '}'): with fmt.indented( - 'match self.bytes[{}] {{'.format(setting.byte_offset), '}'): + 'match self.bytes[{}] {{' + .format(setting.byte_offset), '}'): for i, v in enumerate(setting.values): - fmt.line( '{} => {}::{},'.format(i, ty, camel_case(v))) - fmt.line( '_ => panic!("Invalid enum value")') + fmt.line('{} => {}::{},'.format(i, ty, camel_case(v))) + fmt.line('_ => panic!("Invalid enum value")') else: raise AssertionError("Unknown setting kind") @@ -88,27 +89,11 @@ def gen_getters(sgrp, fmt): Emit getter functions for all the settings in fmt. """ fmt.doc_comment("User-defined settings.") - with fmt.indented('impl Settings {', '}'): + with fmt.indented('impl Flags {', '}'): for setting in sgrp.settings: gen_getter(setting, fmt) -def gen_default(sgrp, byte_size, fmt): - """ - Emit a Default impl for Settings. - """ - v = [0] * byte_size - for setting in sgrp.settings: - v[setting.byte_offset] |= setting.default_byte() - - with fmt.indented('impl Default for Settings {', '}'): - fmt.doc_comment('Return a `Settings` object with default values.') - with fmt.indented('fn default() -> Settings {', '}'): - with fmt.indented('Settings {', '}'): - vs = ', '.join('{:#04x}'.format(x) for x in v) - fmt.line('bytes: [ {} ],'.format(vs)) - - def gen_descriptors(sgrp, fmt): """ Generate the DESCRIPTORS and ENUMERATORS tables. @@ -117,30 +102,31 @@ def gen_descriptors(sgrp, fmt): enums = UniqueSeqTable() with fmt.indented( - 'const DESCRIPTORS: [Descriptor; {}] = [' + 'static DESCRIPTORS: [detail::Descriptor; {}] = [' .format(len(sgrp.settings)), '];'): for idx, setting in enumerate(sgrp.settings): setting.descriptor_index = idx - with fmt.indented('Descriptor {', '},'): + with fmt.indented('detail::Descriptor {', '},'): fmt.line('name: "{}",'.format(setting.name)) fmt.line('offset: {},'.format(setting.byte_offset)) if isinstance(setting, BoolSetting): fmt.line( - 'detail: Detail::Bool {{ bit: {} }},' + 'detail: detail::Detail::Bool {{ bit: {} }},' .format(setting.bit_offset)) elif isinstance(setting, NumSetting): - fmt.line('detail: Detail::Num,') + fmt.line('detail: detail::Detail::Num,') elif isinstance(setting, EnumSetting): offs = enums.add(setting.values) fmt.line( - 'detail: Detail::Enum {{ last: {}, enumerators: {} }},' + 'detail: detail::Detail::Enum ' + + '{{ last: {}, enumerators: {} }},' .format(len(setting.values)-1, offs)) else: raise AssertionError("Unknown setting kind") with fmt.indented( - 'const ENUMERATORS: [&\'static str; {}] = [' + 'static ENUMERATORS: [&\'static str; {}] = [' .format(len(enums.table)), '];'): for txt in enums.table: @@ -150,66 +136,46 @@ def gen_descriptors(sgrp, fmt): return constant_hash.simple_hash(s.name) hash_table = constant_hash.compute_quadratic(sgrp.settings, hash_setting) - if len(sgrp.settings) > 0xffff: - ty = 'u32' - elif len(sgrp.settings) > 0xff: - ty = 'u16' - else: - ty = 'u8' - with fmt.indented( - 'const HASH_TABLE: [{}; {}] = [' - .format(ty, len(hash_table)), + 'static HASH_TABLE: [u16; {}] = [' + .format(len(hash_table)), '];'): for h in hash_table: if h is None: - fmt.line('{},'.format(len(sgrp.settings))) + fmt.line('0xffff,') else: fmt.line('{},'.format(h.descriptor_index)) -def gen_stringwise(sgrp, fmt): +def gen_template(sgrp, byte_size, fmt): """ - Generate the Stringwise implementation and supporting tables. + Emit a Template constant. """ + v = [0] * byte_size + for setting in sgrp.settings: + v[setting.byte_offset] |= setting.default_byte() - with fmt.indented('impl Stringwise for Settings {', '}'): - with fmt.indented( - 'fn lookup(&self, name: &str)' + - '-> Result<(usize, Detail)> {', - '}'): - fmt.line('use simple_hash::simple_hash;') - fmt.line('let tlen = HASH_TABLE.len();') - fmt.line('assert!(tlen.is_power_of_two());') - fmt.line('let mut idx = simple_hash(name) as usize;') - fmt.line('let mut step: usize = 0;') - with fmt.indented('loop {', '}'): - fmt.line('idx = idx % tlen;') - fmt.line('let entry = HASH_TABLE[idx] as usize;') - with fmt.indented('if entry >= DESCRIPTORS.len() {', '}'): - fmt.line('return Err(Error::BadName)') - with fmt.indented('if DESCRIPTORS[entry].name == name {', '}'): - fmt.line( - 'return Ok((DESCRIPTORS[entry].offset as usize, ' + - 'DESCRIPTORS[entry].detail))') - fmt.line('step += 1;') - fmt.line('assert!(step < tlen);') - fmt.line('idx += step;') + with fmt.indented( + 'static TEMPLATE: detail::Template = detail::Template {', '};'): + fmt.line('name: "{}",'.format(sgrp.name)) + fmt.line('descriptors: &DESCRIPTORS,') + fmt.line('enumerators: &ENUMERATORS,') + fmt.line('hash_table: &HASH_TABLE,') + vs = ', '.join('{:#04x}'.format(x) for x in v) + fmt.line('defaults: &[ {} ],'.format(vs)) - with fmt.indented( - 'fn enumerator(&self, enums: u16, value: u8)' + - '-> &\'static str {', - '}'): - fmt.line('ENUMERATORS[enums as usize + value as usize]') + fmt.doc_comment( + 'Create a `settings::Builder` for the {} settings group.' + .format(sgrp.name)) + with fmt.indented('pub fn builder() -> Builder {', '}'): + fmt.line('Builder::new(&TEMPLATE)') - with fmt.indented('fn raw_bytes_mut(&mut self) -> &mut [u8] {', '}'): - fmt.line('&mut self.bytes') def gen_display(sgrp, fmt): """ - Generate the Display impl for Settings. + Generate the Display impl for Flags. """ - with fmt.indented('impl fmt::Display for Settings {', '}'): + with fmt.indented('impl fmt::Display for Flags {', '}'): with fmt.indented( 'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {', '}'): @@ -217,27 +183,43 @@ def gen_display(sgrp, fmt): with fmt.indented('for d in &DESCRIPTORS {', '}'): fmt.line('try!(write!(f, "{} = ", d.name));') fmt.line( - 'try!(self.format_toml_value(d.detail,' + + 'try!(TEMPLATE.format_toml_value(d.detail,' + 'self.bytes[d.offset as usize], f));') fmt.line('try!(writeln!(f, ""));') fmt.line('Ok(())') +def gen_constructor(sgrp, byte_size, parent, fmt): + """ + Generate a Flags constructor. + """ + + with fmt.indented('impl Flags {', '}'): + with fmt.indented('pub fn new(builder: Builder) -> Flags {', '}'): + fmt.line('let bvec = builder.finish("{}");'.format(sgrp.name)) + fmt.line('let mut bytes = [0; {}];'.format(byte_size)) + fmt.line('assert_eq!(bytes.len(), bvec.len());') + with fmt.indented( + 'for (i, b) in bvec.into_iter().enumerate() {', '}'): + fmt.line('bytes[i] = b;') + fmt.line('Flags { bytes: bytes }') + + def gen_group(sgrp, fmt): """ - Generate a Settings struct representing `sgrp`. + Generate a Flags struct representing `sgrp`. """ byte_size = layout_group(sgrp) - fmt.doc_comment('Settings group `{}`.'.format(sgrp.name)) - with fmt.indented('pub struct Settings {', '}'): + fmt.doc_comment('Flags group `{}`.'.format(sgrp.name)) + with fmt.indented('pub struct Flags {', '}'): fmt.line('bytes: [u8; {}],'.format(byte_size)) + gen_constructor(sgrp, byte_size, None, fmt) gen_enum_types(sgrp, fmt) gen_getters(sgrp, fmt) - gen_default(sgrp, byte_size, fmt) gen_descriptors(sgrp, fmt) - gen_stringwise(sgrp, fmt) + gen_template(sgrp, byte_size, fmt) gen_display(sgrp, fmt) diff --git a/src/libcretonne/isa/riscv/settings.rs b/src/libcretonne/isa/riscv/settings.rs index 0ee83b3015..335314a3c9 100644 --- a/src/libcretonne/isa/riscv/settings.rs +++ b/src/libcretonne/isa/riscv/settings.rs @@ -1,20 +1,21 @@ //! RISC-V Settings. -use settings::{Descriptor, Detail, Stringwise, Result, Error}; +use settings::{detail, Builder}; use std::fmt; -// Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct +// Include code generated by `meta/gen_settings.py`. This file contains a public `Flags` struct // with an impl for all of the settings defined in `meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs")); #[cfg(test)] mod tests { - use super::Settings; + use super::{builder, Flags}; #[test] fn display_default() { - let s = Settings::default(); - assert_eq!(s.to_string(), + let b = builder(); + let f = Flags::new(b); + assert_eq!(f.to_string(), "[riscv]\n\ supports_m = false\n\ supports_a = false\n\ diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index af25dd458a..c65c5eeb68 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -2,49 +2,151 @@ //! //! This module defines data structures to access the settings defined in the meta language. //! -//! Each settings group is translated to a `Settings` struct either in this module or in its +//! Each settings group is translated to a `Flags` struct either in this module or in its //! ISA-specific `settings` module. The struct provides individual getter methods for all of the -//! settings. It also implements the `Stringwise` trait which allows settings to be manipulated by -//! name. +//! settings as well as computed predicate flags. +//! +//! The `Flags` struct is immutable once it has been created. A `Builder` instance is used to +//! create it. +//! +//! # Example +//! ``` +//! use cretonne::settings::{self, Configurable}; +//! +//! let mut b = settings::builder(); +//! b.set("opt_level", "fastest"); +//! +//! let f = settings::Flags::new(b); +//! assert_eq!(f.opt_level(), settings::OptLevel::Fastest); +//! ``` use std::fmt; use std::result; -/// A setting descriptor holds the information needed to generically set and print a setting. +use simple_hash::simple_hash; + +/// A string-based configurator for settings groups. /// -/// Each settings group will be represented as a constant DESCRIPTORS array. -pub struct Descriptor { - /// Lower snake-case name of setting as defined in meta. - pub name: &'static str, +/// The `Configurable` protocol allows settings to be modified by name before a finished `Flags` +/// struct is created. +pub trait Configurable { + /// Set the string value of any setting by name. + /// + /// This can set any type of setting whether it is numeric, boolean, or enumerated. + fn set(&mut self, name: &str, value: &str) -> Result<()>; - /// Offset of byte containing this setting. - pub offset: u32, - - /// Additional details, depending on the kind of setting. - pub detail: Detail, + /// Set the value of a boolean setting by name. + /// + /// If the identified setting isn't a boolean, a `BadType` error is returned. + fn set_bool(&mut self, name: &str, value: bool) -> Result<()>; } -/// The different kind of settings along with descriptor bits that depend on the kind. -#[derive(Clone, Copy)] -pub enum Detail { - /// A boolean setting only uses one bit, numbered from LSB. - Bool { - bit: u8, - }, - - /// A numerical setting uses the whole byte. - Num, - - /// An Enum setting uses a range of enumerators. - Enum { - /// Numerical value of last enumerator, allowing for 1-256 enumerators. - last: u8, - - /// First enumerator in the ENUMERATORS table. - enumerators: u16, - }, +/// Collect settings values based on a template. +pub struct Builder { + template: &'static detail::Template, + bytes: Vec, } +impl Builder { + /// Create a new builder with defaults and names from the given template. + pub fn new(tmpl: &'static detail::Template) -> Builder { + Builder { + template: tmpl, + bytes: tmpl.defaults.into(), + } + } + + /// Extract contents of builder once everything is configured. + pub fn finish(self, name: &str) -> Vec { + assert_eq!(name, self.template.name); + self.bytes + } + + /// Set the value of a single bit. + fn set_bit(&mut self, offset: usize, bit: u8, value: bool) { + let byte = &mut self.bytes[offset]; + let mask = 1 << bit; + if value { + *byte |= mask; + } else { + *byte &= !mask; + } + } + + /// Look up a descriptor by name. + fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> { + let table = self.template.hash_table; + let descs = self.template.descriptors; + let mask = table.len() - 1; + assert!((mask + 1).is_power_of_two()); + + let mut idx = simple_hash(name) as usize; + let mut step: usize = 0; + + loop { + idx = idx & mask; + let entry = table[idx] as usize; + if entry >= descs.len() { + return Err(Error::BadName); + } + let desc = &descs[entry]; + if desc.name == name { + return Ok((desc.offset as usize, desc.detail)); + } + step += 1; + assert!(step <= mask); + idx += step; + } + } +} + +fn parse_bool_value(value: &str) -> Result { + match value { + "true" | "on" | "yes" | "1" => Ok(true), + "false" | "off" | "no" | "0" => Ok(false), + _ => Err(Error::BadValue), + } +} + +fn parse_enum_value(value: &str, choices: &[&str]) -> Result { + match choices.iter().position(|&tag| tag == value) { + Some(idx) => Ok(idx as u8), + None => Err(Error::BadValue), + } +} + +impl Configurable for Builder { + fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { + use self::detail::Detail; + let (offset, detail) = try!(self.lookup(name)); + if let Detail::Bool { bit } = detail { + self.set_bit(offset, bit, value); + Ok(()) + } else { + Err(Error::BadType) + } + } + + fn set(&mut self, name: &str, value: &str) -> Result<()> { + use self::detail::Detail; + let (offset, detail) = try!(self.lookup(name)); + match detail { + Detail::Bool { bit } => { + self.set_bit(offset, bit, try!(parse_bool_value(value))); + } + Detail::Num => { + self.bytes[offset] = try!(value.parse().map_err(|_| Error::BadValue)); + } + Detail::Enum { last, enumerators } => { + self.bytes[offset] = try!(parse_enum_value(value, + self.template.enums(last, enumerators))); + } + } + Ok(()) + } +} + +/// An error produced when changing a setting. #[derive(Debug, PartialEq, Eq)] pub enum Error { /// No setting by this name exists. @@ -59,143 +161,133 @@ pub enum Error { pub type Result = result::Result; -fn parse_bool_value(value: &str) -> Result { - match value { - "true" | "on" | "yes" | "1" => Ok(true), - "false" | "off" | "no" | "0" => Ok(false), - _ => Err(Error::BadValue), +/// Implementation details for generated code. +/// +/// This module holds definitions that need to be public so the can be instantiated by generated +/// code in other modules. +pub mod detail { + use std::fmt; + + /// An instruction group template. + pub struct Template { + pub name: &'static str, + pub descriptors: &'static [Descriptor], + pub enumerators: &'static [&'static str], + pub hash_table: &'static [u16], + pub defaults: &'static [u8], } -} -/// Interface for working with a group of settings as strings. -pub trait Stringwise { - /// Look up a setting by name, return the byte offset and details of the setting. - fn lookup(&self, name: &str) -> Result<(usize, Detail)>; - - /// Get an enumerator string from the `Detail::enumerators` value and an offset. - fn enumerator(&self, enums: u16, value: u8) -> &'static str; - - /// Get the underlying byte array used to store settings. - fn raw_bytes_mut(&mut self) -> &mut [u8]; - - /// Format a setting value as a TOML string. This is mostly for use by the generated `Display` - /// implementation. - fn format_toml_value(&self, detail: Detail, byte: u8, f: &mut fmt::Formatter) -> fmt::Result { - match detail { - Detail::Bool { bit } => write!(f, "{}", (byte & (1 << bit)) != 0), - Detail::Num => write!(f, "{}", byte), - Detail::Enum { last, enumerators } => { - if byte <= last { - write!(f, "\"{}\"", self.enumerator(enumerators, byte)) - } else { - write!(f, "{}", byte) - } - } + impl Template { + /// Get enumerators corresponding to a `Details::Enum`. + pub fn enums(&self, last: u8, enumerators: u16) -> &[&'static str] { + let from = enumerators as usize; + let len = last as usize + 1; + &self.enumerators[from..from + len] } - } - /// Set a boolean setting by name. - fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { - let (offset, detail) = try!(self.lookup(name)); - if let Detail::Bool { bit } = detail { - let mask = 1 << bit; - let byte = &mut self.raw_bytes_mut()[offset]; - if value { - *byte |= mask; - } else { - *byte &= !mask; - } - Ok(()) - } else { - Err(Error::BadType) - } - } - - /// Set the string value of a named setting. - /// - /// For boolean settings, any of the values accepted by `parse_bool_value` above are accepted - /// (true/false, on/off, yes/no, 1/0). - /// - /// For enumerated settings, the value must match one of the allowed values exactly. - fn set(&mut self, name: &str, value: &str) -> Result<()> { - let (offset, detail) = try!(self.lookup(name)); - match detail { - Detail::Bool { bit } => { - let mask = 1 << bit; - let byte = &mut self.raw_bytes_mut()[offset]; - if try!(parse_bool_value(value)) { - *byte |= mask; - } else { - *byte &= !mask; - } - } - Detail::Num => { - self.raw_bytes_mut()[offset] = try!(value.parse().map_err(|_| Error::BadValue)); - } - Detail::Enum { last, enumerators } => { - // Linear search.. - for i in 0.. { - if value == self.enumerator(enumerators, i) { - self.raw_bytes_mut()[offset] = i; - break; - } - if i == last { - return Err(Error::BadValue); + /// Format a setting value as a TOML string. This is mostly for use by the generated + /// `Display` implementation. + pub fn format_toml_value(&self, + detail: Detail, + byte: u8, + f: &mut fmt::Formatter) + -> fmt::Result { + match detail { + Detail::Bool { bit } => write!(f, "{}", (byte & (1 << bit)) != 0), + Detail::Num => write!(f, "{}", byte), + Detail::Enum { last, enumerators } => { + if byte <= last { + let tags = self.enums(last, enumerators); + write!(f, "\"{}\"", tags[byte as usize]) + } else { + write!(f, "{}", byte) } } } } - Ok(()) + } + + /// A setting descriptor holds the information needed to generically set and print a setting. + /// + /// Each settings group will be represented as a constant DESCRIPTORS array. + pub struct Descriptor { + /// Lower snake-case name of setting as defined in meta. + pub name: &'static str, + + /// Offset of byte containing this setting. + pub offset: u32, + + /// Additional details, depending on the kind of setting. + pub detail: Detail, + } + + /// The different kind of settings along with descriptor bits that depend on the kind. + #[derive(Clone, Copy)] + pub enum Detail { + /// A boolean setting only uses one bit, numbered from LSB. + Bool { + bit: u8, + }, + + /// A numerical setting uses the whole byte. + Num, + + /// An Enum setting uses a range of enumerators. + Enum { + /// Numerical value of last enumerator, allowing for 1-256 enumerators. + last: u8, + + /// First enumerator in the ENUMERATORS table. + enumerators: u16, + }, } } -// Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct +// Include code generated by `meta/gen_settings.py`. This file contains a public `Flags` struct // with an impl for all of the settings defined in `meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings.rs")); #[cfg(test)] mod tests { - use super::Settings; + use super::{builder, Flags}; use super::Error::*; - use super::Stringwise; + use super::Configurable; #[test] fn display_default() { - let s = Settings::default(); - assert_eq!(s.to_string(), + let b = builder(); + let f = Flags::new(b); + assert_eq!(f.to_string(), "[shared]\n\ opt_level = \"default\"\n\ enable_simd = true\n"); + assert_eq!(f.opt_level(), super::OptLevel::Default); + assert_eq!(f.enable_simd(), true); } #[test] fn modify_bool() { - let mut s = Settings::default(); - assert_eq!(s.enable_simd(), true); - assert_eq!(s.set_bool("not_there", true), Err(BadName)); + let mut b = builder(); + assert_eq!(b.set_bool("not_there", true), Err(BadName)); + assert_eq!(b.set_bool("enable_simd", true), Ok(())); + assert_eq!(b.set_bool("enable_simd", false), Ok(())); - assert_eq!(s.set_bool("enable_simd", true), Ok(())); - assert_eq!(s.enable_simd(), true); - - assert_eq!(s.set_bool("enable_simd", false), Ok(())); - assert_eq!(s.enable_simd(), false); + let f = Flags::new(b); + assert_eq!(f.enable_simd(), false); } #[test] fn modify_string() { - let mut s = Settings::default(); - assert_eq!(s.enable_simd(), true); - assert_eq!(s.opt_level(), super::OptLevel::Default); + let mut b = builder(); + assert_eq!(b.set("not_there", "true"), Err(BadName)); + assert_eq!(b.set("enable_simd", ""), Err(BadValue)); + assert_eq!(b.set("enable_simd", "best"), Err(BadValue)); + assert_eq!(b.set("opt_level", "true"), Err(BadValue)); + assert_eq!(b.set("opt_level", "best"), Ok(())); + assert_eq!(b.set("enable_simd", "0"), Ok(())); - assert_eq!(s.set("not_there", "true"), Err(BadName)); - assert_eq!(s.set("enable_simd", ""), Err(BadValue)); - assert_eq!(s.set("enable_simd", "best"), Err(BadValue)); - assert_eq!(s.set("opt_level", "true"), Err(BadValue)); - - assert_eq!(s.set("enable_simd", "no"), Ok(())); - assert_eq!(s.enable_simd(), false); - - assert_eq!(s.set("opt_level", "best"), Ok(())); - assert_eq!(s.opt_level(), super::OptLevel::Best); + let f = Flags::new(b); + assert_eq!(f.enable_simd(), false); + assert_eq!(f.opt_level(), super::OptLevel::Best); } } From 8c48739afd35c4c36dd3fb266daa09b496a39e0a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 10 Aug 2016 12:07:32 -0700 Subject: [PATCH 197/968] Document ISA builder. --- src/libcretonne/isa/mod.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index cd9749e6ee..577f6e400e 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -1,10 +1,30 @@ //! Instruction Set Architectures. //! //! The `isa` module provides a `TargetIsa` trait which provides the behavior specialization needed -//! by the ISA-independent code generator. +//! by the ISA-independent code generator. The sub-modules of this module provide definitions for +//! the instruction sets that Cretonne can target. Each sub-module has it's own implementation of +//! `TargetIsa`. +//! +//! # Constructing a `TargetIsa` instance +//! +//! The target ISA is build from the following information: +//! +//! - The name of the target ISA as a string. Cretonne is a cross-compiler, so the ISA to target +//! can be selected dynamically. Individual ISAs can be left out when Cretonne is compiled, so a +//! string is used to identify the proper sub-module. +//! - Values for settings that apply to all ISAs. This is represented by a `settings::Flags` +//! instance. +//! - Values for ISA-specific settings. +//! +//! The `isa::lookup()` function is the main entry point which returns an `isa::Builder` +//! appropriate for the requested ISA: +//! +//! ```ignore +//! let isa_builder = isa::lookup("riscv").unwrap(); +//! adjust_isa_settings(&mut isa_builder.settings); +//! let isa = builder.finish(shared_settings()); +//! ``` //! -//! The sub-modules of this module provide definitions for the instruction sets that Cretonne -//! can target. Each sub-module has it's own implementation of `TargetIsa`. pub mod riscv; From aeb376227e818d102739a357ac54af4752f2eab5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 Aug 2016 11:39:42 -0700 Subject: [PATCH 198/968] Implement the machinery to create a TargetIsa. Add an isa::lookup() function which serves as a target registry for creating Box trait objects. An isa::Builder makes it possible to confugure the trait object before it is created. --- meta/gen_settings.py | 1 + src/libcretonne/isa/mod.rs | 64 +++++++++++++++++++++++++++++--- src/libcretonne/isa/riscv/mod.rs | 34 +++++++++++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index a10dd5352a..486e71371e 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -211,6 +211,7 @@ def gen_group(sgrp, fmt): """ byte_size = layout_group(sgrp) + fmt.line('#[derive(Clone)]') fmt.doc_comment('Flags group `{}`.'.format(sgrp.name)) with fmt.indented('pub struct Flags {', '}'): fmt.line('bytes: [u8; {}],'.format(byte_size)) diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 577f6e400e..43834be3ef 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -7,7 +7,7 @@ //! //! # Constructing a `TargetIsa` instance //! -//! The target ISA is build from the following information: +//! The target ISA is built from the following information: //! //! - The name of the target ISA as a string. Cretonne is a cross-compiler, so the ISA to target //! can be selected dynamically. Individual ISAs can be left out when Cretonne is compiled, so a @@ -19,18 +19,72 @@ //! The `isa::lookup()` function is the main entry point which returns an `isa::Builder` //! appropriate for the requested ISA: //! -//! ```ignore -//! let isa_builder = isa::lookup("riscv").unwrap(); -//! adjust_isa_settings(&mut isa_builder.settings); -//! let isa = builder.finish(shared_settings()); +//! ``` +//! use cretonne::settings::{self, Configurable}; +//! use cretonne::isa; +//! +//! let shared_builder = settings::builder(); +//! let shared_flags = settings::Flags::new(shared_builder); +//! +//! match isa::lookup("riscv") { +//! None => { +//! // The RISC-V target ISA is not available. +//! } +//! Some(mut isa_builder) => { +//! isa_builder.set("supports_m", "on"); +//! let isa = isa_builder.finish(shared_flags); +//! } +//! } //! ``` //! +//! The configured target ISA trait object is a `Box` which can be used for multiple +//! concurrent function compilations. pub mod riscv; +use settings; use ir::dfg::DataFlowGraph; use ir::entities::Inst; +/// Look for a supported ISA with the given `name`. +/// Return a builder that can create a corresponding `TargetIsa`. +pub fn lookup(name: &str) -> Option { + match name { + "riscv" => riscv_builder(), + _ => None, + } +} + +// Make a builder for RISC-V. +fn riscv_builder() -> Option { + Some(riscv::isa_builder()) +} + +/// Builder for a `TargetIsa`. +/// Modify the ISA-specific settings before creating the `TargetIsa` trait object with `finish`. +pub struct Builder { + setup: settings::Builder, + constructor: fn(settings::Flags, settings::Builder) -> Box, +} + +impl Builder { + /// Combine the ISA-specific settings with the provided ISA-independent settings and allocate a + /// fully configured `TargetIsa` trait object. + pub fn finish(self, shared_flags: settings::Flags) -> Box { + (self.constructor)(shared_flags, self.setup) + } +} + +impl settings::Configurable for Builder { + fn set(&mut self, name: &str, value: &str) -> settings::Result<()> { + self.setup.set(name, value) + } + + fn set_bool(&mut self, name: &str, value: bool) -> settings::Result<()> { + self.setup.set_bool(name, value) + } +} + pub trait TargetIsa { /// Encode an instruction after determining it is legal. /// diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 775154a3d6..641263b9f6 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -1,3 +1,37 @@ //! RISC-V Instruction Set Architecture. pub mod settings; + +use super::super::settings as shared_settings; +use super::Builder as IsaBuilder; +use super::{TargetIsa, Encoding}; +use ir::dfg::DataFlowGraph; +use ir::entities::Inst; + +#[allow(dead_code)] +struct Isa { + shared_flags: shared_settings::Flags, + isa_flags: settings::Flags, +} + +pub fn isa_builder() -> IsaBuilder { + IsaBuilder { + setup: settings::builder(), + constructor: isa_constructor, + } +} + +fn isa_constructor(shared_flags: shared_settings::Flags, + builder: shared_settings::Builder) + -> Box { + Box::new(Isa { + shared_flags: shared_flags, + isa_flags: settings::Flags::new(builder), + }) +} + +impl TargetIsa for Isa { + fn encode(&self, _: &DataFlowGraph, _: &Inst) -> Option { + unimplemented!() + } +} From 8457f67e34e2a8b27c7aa6c49a366b8f79c98c47 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 Aug 2016 14:22:23 -0700 Subject: [PATCH 199/968] Introduce predicates. Predcates are boolean functions. There will be ISA predicates and instruction predicates. The ISA predicates will be turned into member functions on the generated Flags structs. --- meta/cretonne/__init__.py | 37 +++++++++------ meta/cretonne/predicates.py | 94 +++++++++++++++++++++++++++++++++++++ meta/isa/riscv/settings.py | 6 ++- 3 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 meta/cretonne/predicates.py diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 801ac5a171..f576380f65 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -31,19 +31,14 @@ class Setting(object): self.__doc__ = doc # Offset of byte in settings vector containing this setting. self.byte_offset = None - SettingGroup.append(self) + self.group = SettingGroup.append(self) - @staticmethod - def extract_names(globs): + def predicate_context(self): """ - Given a dict mapping name -> object as returned by `globals()`, find - all the Setting objects and set their name from the dict key. This is - used to name a bunch of global variables in a module. + Return the context where this setting can be evaluated as a (leaf) + predicate. """ - for name, obj in globs.iteritems(): - if isinstance(obj, Setting): - assert obj.name is None - obj.name = name + return self.group class BoolSetting(Setting): @@ -116,14 +111,17 @@ class SettingGroup(object): opened. :param name: Short mnemonic name for setting group. + :param parent: Parent settings group. """ # The currently open setting group. _current = None - def __init__(self, name): + def __init__(self, name, parent=None): self.name = name + self.parent = parent self.settings = [] + self.predicates = [] self.open() def open(self): @@ -149,13 +147,22 @@ class SettingGroup(object): .format(self, SettingGroup._current)) SettingGroup._current = None if globs: - Setting.extract_names(globs) + from predicates import Predicate + for name, obj in globs.iteritems(): + if isinstance(obj, Setting): + assert obj.name is None, obj.name + obj.name = name + if isinstance(obj, Predicate): + assert obj.name is None + obj.name = name + self.predicates.append(obj) @staticmethod def append(setting): - assert SettingGroup._current, \ - "Open a setting group before defining settings." - SettingGroup._current.settings.append(setting) + g = SettingGroup._current + assert g, "Open a setting group before defining settings." + g.settings.append(setting) + return g # Kinds of operands. diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py new file mode 100644 index 0000000000..2c34e489e5 --- /dev/null +++ b/meta/cretonne/predicates.py @@ -0,0 +1,94 @@ +""" +Cretonne predicates. + +A *predicate* is a function that computes a boolean result. The inputs to the +function determine the kind of predicate: + +- An *ISA predicate* is evaluated on the current ISA settings together with the + shared settings defined in the :py:mod:`settings` module. Once a target ISA + has been configured, the value of all ISA predicates is known. + +- An *Instruction predicate* is evaluated on an instruction instance, so it can + inspect all the immediate fields and type variables of the instruction. + Instruction predicates can be evaluatd before register allocation, so they + can not depend on specific register assignments to the value operands or + outputs. + +Predicates can also be computed from other predicates using the `And`, `Or`, +and `Not` combinators defined in this module. + +All predicates have a *context* which determines where they can be evaluated. +For an ISA predicate, the context is the ISA settings group. For an instruction +predicate, the context is the instruction format. +""" + + +def _is_parent(a, b): + """ + Return true if a is a parent of b, or equal to it. + """ + while b and a is not b: + b = getattr(b, 'parent', None) + return a is b + + +def _descendant(a, b): + """ + If a is a parent of b or b is a parent of a, return the descendant of the + two. + + If neiher is a parent of the other, return None. + """ + if _is_parent(a, b): + return b + if _is_parent(b, a): + return a + return None + + +class Predicate(object): + """ + Superclass for all computed predicates. + + Leaf predicates can have other types, such as `Setting`. + + :param parts: Tuple of components in the predicate expression. + """ + + def __init__(self, parts): + self.name = None + self.parts = parts + self.context = reduce( + _descendant, + (p.predicate_context() for p in parts)) + assert self.context, "Incompatible predicate parts" + + def predicate_context(self): + return self.context + + +class And(Predicate): + """ + Computed predicate that is true if all parts are true. + """ + + def __init__(self, *args): + super(And, self).__init__(args) + + +class Or(Predicate): + """ + Computed predicate that is true if any parts are true. + """ + + def __init__(self, *args): + super(Or, self).__init__(args) + + +class Not(Predicate): + """ + Computed predicate that is true if its single part is false. + """ + + def __init__(self, part): + super(Not, self).__init__((part,)) diff --git a/meta/isa/riscv/settings.py b/meta/isa/riscv/settings.py index d2366ad829..ade60372d6 100644 --- a/meta/isa/riscv/settings.py +++ b/meta/isa/riscv/settings.py @@ -3,13 +3,17 @@ RISC-V settings. """ from cretonne import SettingGroup, BoolSetting +from cretonne.predicates import And +import cretonne.settings as shared from defs import isa -isa.settings = SettingGroup('riscv') +isa.settings = SettingGroup('riscv', parent=shared.group) supports_m = BoolSetting("CPU supports the 'M' extension (mul/div)") supports_a = BoolSetting("CPU supports the 'A' extension (atomics)") supports_f = BoolSetting("CPU supports the 'F' extension (float)") supports_d = BoolSetting("CPU supports the 'D' extension (double)") +full_float = And(shared.enable_simd, supports_f, supports_d) + isa.settings.close(globals()) From 514ebc6bf9b505187c4d008ff87486e3bcc517ca Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 Aug 2016 17:38:56 -0700 Subject: [PATCH 200/968] Generate code to precompute predicates. Each ISA predicate is assigned a bit the the Flags struct, and a corresponding method is generated. --- meta/cretonne/__init__.py | 9 +++ meta/cretonne/predicates.py | 31 ++++++++++ meta/gen_settings.py | 81 +++++++++++++++++++++++---- src/libcretonne/isa/riscv/mod.rs | 2 +- src/libcretonne/isa/riscv/settings.rs | 27 ++++++++- 5 files changed, 135 insertions(+), 15 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index f576380f65..cc31f47cf5 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -63,6 +63,15 @@ class BoolSetting(Setting): else: return 0 + def rust_predicate(self, prec): + """ + Return the Rust code to compute the value of this setting. + + The emitted code assumes that the setting group exists as a local + variable. + """ + return '{}.{}()'.format(self.group.name, self.name) + class NumSetting(Setting): """ diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index 2c34e489e5..dd50613a37 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -72,23 +72,54 @@ class And(Predicate): Computed predicate that is true if all parts are true. """ + precedence = 2 + def __init__(self, *args): super(And, self).__init__(args) + def rust_predicate(self, prec): + """ + Return a Rust expression computing the value of this predicate. + + The surrounding precedence determines whether parentheses are needed: + + 0. An `if` statement. + 1. An `||` expression. + 2. An `&&` expression. + 3. A `!` expression. + """ + s = ' && '.join(p.rust_predicate(And.precedence) for p in self.parts) + if prec > And.precedence: + s = '({})'.format(s) + return s + class Or(Predicate): """ Computed predicate that is true if any parts are true. """ + precedence = 1 + def __init__(self, *args): super(Or, self).__init__(args) + def rust_predicate(self, prec): + s = ' || '.join(p.rust_predicate(Or.precedence) for p in self.parts) + if prec > Or.precedence: + s = '({})'.format(s) + return s + class Not(Predicate): """ Computed predicate that is true if its single part is false. """ + precedence = 3 + def __init__(self, part): super(Not, self).__init__((part,)) + + def rust_predicate(self, prec): + return '!' + self.parts[0].rust_predicate(Not.precedence) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index 486e71371e..85c154648d 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -12,7 +12,8 @@ def layout_group(sgrp): """ Layout the settings in sgrp, assigning byte and bit offsets. - Return the next unused byte offset. + Return the number of bytes needed for settings and the total number of + bytes needed when including predicates. """ # Byte offset where booleans are allocated. bool_byte = -1 @@ -37,7 +38,20 @@ def layout_group(sgrp): setting.byte_offset = next_byte next_byte += 1 - return next_byte + settings_size = next_byte + + # Allocate bits for all the precomputed predicates. + for pred in sgrp.predicates: + # Allocate a bit from bool_byte. + if bool_bit > 7: + bool_byte = next_byte + next_byte += 1 + bool_bit = 0 + pred.byte_offset = bool_byte + pred.bit_offset = bool_bit + bool_bit += 1 + + return (settings_size, next_byte) def gen_enum_types(sgrp, fmt): @@ -84,6 +98,18 @@ def gen_getter(setting, fmt): raise AssertionError("Unknown setting kind") +def gen_pred_getter(pred, fmt): + """ + Emit a getter for a pre-computed predicate. + """ + fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0))); + proto = 'pub fn {}(&self) -> bool'.format(pred.name) + with fmt.indented(proto + ' {', '}'): + fmt.line('(self.bytes[{}] & (1 << {})) != 0'.format( + pred.byte_offset, + pred.bit_offset)) + + def gen_getters(sgrp, fmt): """ Emit getter functions for all the settings in fmt. @@ -92,6 +118,8 @@ def gen_getters(sgrp, fmt): with fmt.indented('impl Flags {', '}'): for setting in sgrp.settings: gen_getter(setting, fmt) + for pred in sgrp.predicates: + gen_pred_getter(pred, fmt) def gen_descriptors(sgrp, fmt): @@ -147,11 +175,11 @@ def gen_descriptors(sgrp, fmt): fmt.line('{},'.format(h.descriptor_index)) -def gen_template(sgrp, byte_size, fmt): +def gen_template(sgrp, settings_size, fmt): """ Emit a Template constant. """ - v = [0] * byte_size + v = [0] * settings_size for setting in sgrp.settings: v[setting.byte_offset] |= setting.default_byte() @@ -189,50 +217,79 @@ def gen_display(sgrp, fmt): fmt.line('Ok(())') -def gen_constructor(sgrp, byte_size, parent, fmt): +def gen_constructor(sgrp, settings_size, byte_size, parent, fmt): """ Generate a Flags constructor. """ with fmt.indented('impl Flags {', '}'): - with fmt.indented('pub fn new(builder: Builder) -> Flags {', '}'): + args = 'builder: Builder' + if sgrp.parent: + p = sgrp.parent + args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args) + with fmt.indented( + 'pub fn new({}) -> Flags {{'.format(args), '}'): fmt.line('let bvec = builder.finish("{}");'.format(sgrp.name)) fmt.line('let mut bytes = [0; {}];'.format(byte_size)) - fmt.line('assert_eq!(bytes.len(), bvec.len());') + fmt.line('assert_eq!(bytes.len(), {});'.format(settings_size)) with fmt.indented( 'for (i, b) in bvec.into_iter().enumerate() {', '}'): fmt.line('bytes[i] = b;') - fmt.line('Flags { bytes: bytes }') + + # Stop here without predicates. + if len(sgrp.predicates) == 0: + fmt.line('Flags { bytes: bytes }') + return + + # Now compute the predicates. + fmt.line( + 'let mut {} = Flags {{ bytes: bytes }};' + .format(sgrp.name)) + + for pred in sgrp.predicates: + fmt.comment('Precompute: {}.'.format(pred.name)) + with fmt.indented( + 'if {} {{'.format(pred.rust_predicate(0)), + '}'): + fmt.line( + '{}.bytes[{}] |= 1 << {};' + .format( + sgrp.name, pred.byte_offset, pred.bit_offset)) + + fmt.line(sgrp.name) def gen_group(sgrp, fmt): """ Generate a Flags struct representing `sgrp`. """ - byte_size = layout_group(sgrp) + settings_size, byte_size = layout_group(sgrp) fmt.line('#[derive(Clone)]') fmt.doc_comment('Flags group `{}`.'.format(sgrp.name)) with fmt.indented('pub struct Flags {', '}'): - fmt.line('bytes: [u8; {}],'.format(byte_size)) + fmt.line('bytes: [u8; {}],'.format(settings_size)) - gen_constructor(sgrp, byte_size, None, fmt) + gen_constructor(sgrp, settings_size, byte_size, None, fmt) gen_enum_types(sgrp, fmt) gen_getters(sgrp, fmt) gen_descriptors(sgrp, fmt) - gen_template(sgrp, byte_size, fmt) + gen_template(sgrp, settings_size, fmt) gen_display(sgrp, fmt) def generate(isas, out_dir): # Generate shared settings. fmt = srcgen.Formatter() + settings.group.qual_mod = 'settings' gen_group(settings.group, fmt) fmt.update_file('settings.rs', out_dir) # Generate ISA-specific settings. for isa in isas: if isa.settings: + isa.settings.qual_mod = 'isa::{}::settings'.format( + isa.settings.name) fmt = srcgen.Formatter() gen_group(isa.settings, fmt) fmt.update_file('settings-{}.rs'.format(isa.name), out_dir) diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 641263b9f6..4938a7ad28 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -25,8 +25,8 @@ fn isa_constructor(shared_flags: shared_settings::Flags, builder: shared_settings::Builder) -> Box { Box::new(Isa { + isa_flags: settings::Flags::new(&shared_flags, builder), shared_flags: shared_flags, - isa_flags: settings::Flags::new(builder), }) } diff --git a/src/libcretonne/isa/riscv/settings.rs b/src/libcretonne/isa/riscv/settings.rs index 335314a3c9..1dd1adc405 100644 --- a/src/libcretonne/isa/riscv/settings.rs +++ b/src/libcretonne/isa/riscv/settings.rs @@ -1,6 +1,6 @@ //! RISC-V Settings. -use settings::{detail, Builder}; +use settings::{self, detail, Builder}; use std::fmt; // Include code generated by `meta/gen_settings.py`. This file contains a public `Flags` struct @@ -10,16 +10,39 @@ include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs")); #[cfg(test)] mod tests { use super::{builder, Flags}; + use settings::{self, Configurable}; #[test] fn display_default() { + let shared = settings::Flags::new(settings::builder()); let b = builder(); - let f = Flags::new(b); + let f = Flags::new(&shared, b); assert_eq!(f.to_string(), "[riscv]\n\ supports_m = false\n\ supports_a = false\n\ supports_f = false\n\ supports_d = false\n"); + // Predicates are not part of the Display output. + assert_eq!(f.full_float(), false); + } + + #[test] + fn predicates() { + let shared = settings::Flags::new(settings::builder()); + let mut b = builder(); + b.set_bool("supports_f", true).unwrap(); + b.set_bool("supports_d", true).unwrap(); + let f = Flags::new(&shared, b); + assert_eq!(f.full_float(), true); + + let mut sb = settings::builder(); + sb.set_bool("enable_simd", false).unwrap(); + let shared = settings::Flags::new(sb); + let mut b = builder(); + b.set_bool("supports_f", true).unwrap(); + b.set_bool("supports_d", true).unwrap(); + let f = Flags::new(&shared, b); + assert_eq!(f.full_float(), false); } } From 84a154a8ca4be35c7df569e24c9525284f685ad5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 Aug 2016 10:27:15 -0700 Subject: [PATCH 201/968] Move integration tests into src/tools/tests. The integration tests use both libcretonne and libreader, so moving them avoids the circular dev-dependency. Also go back to building everything under src/tools/target to avoid rebuilding the libraries when cargo is invoked in different subdirectories. This speeds up test-all.sh quite a bit. Finally, skip the pure debug build. We build "cargo test" and "cargo build --release" which should cover everything we need. --- src/libcretonne/Cargo.toml | 5 ---- .../tests/cfg_traversal.rs | 0 .../tests/dominator_tree.rs | 0 src/{libcretonne => tools}/tests/lib.rs | 0 test-all.sh | 24 +++++++++++-------- 5 files changed, 14 insertions(+), 15 deletions(-) rename src/{libcretonne => tools}/tests/cfg_traversal.rs (100%) rename src/{libcretonne => tools}/tests/dominator_tree.rs (100%) rename src/{libcretonne => tools}/tests/lib.rs (100%) diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index cfd886391c..4d9b59bb43 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -11,8 +11,3 @@ build = "build.rs" [lib] name = "cretonne" path = "lib.rs" - -[dev-dependencies] -cretonne-reader = { path = "../libreader" } - -[dependencies] diff --git a/src/libcretonne/tests/cfg_traversal.rs b/src/tools/tests/cfg_traversal.rs similarity index 100% rename from src/libcretonne/tests/cfg_traversal.rs rename to src/tools/tests/cfg_traversal.rs diff --git a/src/libcretonne/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs similarity index 100% rename from src/libcretonne/tests/dominator_tree.rs rename to src/tools/tests/dominator_tree.rs diff --git a/src/libcretonne/tests/lib.rs b/src/tools/tests/lib.rs similarity index 100% rename from src/libcretonne/tests/lib.rs rename to src/tools/tests/lib.rs diff --git a/test-all.sh b/test-all.sh index 28013ec2e3..4b65c9b56f 100755 --- a/test-all.sh +++ b/test-all.sh @@ -17,29 +17,33 @@ set -e cd $(dirname "$0") topdir=$(pwd) -PKGS="libcretonne libreader tools" -echo ====== Rust unit tests and debug builds ====== +function banner() { + echo "====== $@ ======" +} + +PKGS="cretonne cretonne-reader cretonne-tools" +cd "$topdir/src/tools" for PKG in $PKGS do - ( - cd $topdir/src/$PKG - cargo test - cargo build - ) + banner "Rust $PKG unit tests" + cargo test -p $PKG done # Build cton-util for parser testing. -echo ====== Rust release build and documentation ====== cd "$topdir/src/tools" +banner "Rust documentation" +echo "open $topdir/src/tools/target/doc/cretonne/index.html" cargo doc +banner "Rust release build" cargo build --release export CTONUTIL="$topdir/src/tools/target/release/cton-util" # Run the parser tests. -echo ====== Parser tests ====== cd "$topdir/tests" +banner "Parser tests" parser/run.sh +banner "CFG tests" cfg/run.sh -echo ====== OK ====== +banner "OK" From 9e87bffa82d28273cbc4f4827fe9f110f68d1d54 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 Aug 2016 11:03:28 -0700 Subject: [PATCH 202/968] Remove tests/lib.rs to avoid running tests twice. The 'cargo test' command simply compiles each 'tests/*.rs' and runs the enclosed tests. The 'lib.rs' source would get run as an individual test. --- src/tools/tests/lib.rs | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 src/tools/tests/lib.rs diff --git a/src/tools/tests/lib.rs b/src/tools/tests/lib.rs deleted file mode 100644 index fcd2535269..0000000000 --- a/src/tools/tests/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod cfg_traversal; -pub mod dominator_tree; From 40e0989b8b822b3e3d22d41171e84b49dc4d0bfd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 Aug 2016 16:11:38 -0700 Subject: [PATCH 203/968] Re-export common types in the cretonne::ir module. Clients should not have to navigate the ir sub-modules to find commonly used types. --- src/libcretonne/cfg.rs | 3 +-- src/libcretonne/dominator_tree.rs | 2 +- src/libcretonne/ir/dfg.rs | 9 +++++---- src/libcretonne/ir/instructions.rs | 7 ++++--- src/libcretonne/ir/jumptable.rs | 2 +- src/libcretonne/ir/layout.rs | 2 +- src/libcretonne/ir/mod.rs | 12 +++++++----- src/libcretonne/isa/mod.rs | 3 +-- src/libcretonne/isa/riscv/mod.rs | 3 +-- src/libcretonne/test_utils/make_inst.rs | 6 +++--- src/libcretonne/write.rs | 4 +--- src/libreader/lexer.rs | 4 ++-- src/libreader/parser.rs | 11 ++++++----- src/tools/tests/cfg_traversal.rs | 2 +- src/tools/tests/dominator_tree.rs | 2 +- 15 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 022c3372e4..8397ca5166 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -22,8 +22,7 @@ //! Here Ebb1 and Ebb2 would each have a single predecessor denoted as (Ebb0, `brz vx, Ebb1`) //! and (Ebb0, `jmp Ebb2`) respectively. -use ir::Function; -use ir::entities::{Inst, Ebb}; +use ir::{Function, Inst, Ebb}; use ir::instructions::BranchInfo; use entity_map::{EntityMap, Keys}; use std::collections::HashSet; diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index 8420bcf541..5909f68745 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -1,7 +1,7 @@ /// ! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. use cfg::*; -use ir::entities::Ebb; +use ir::Ebb; use entity_map::{EntityMap, Keys}; pub struct DominatorTree { diff --git a/src/libcretonne/ir/dfg.rs b/src/libcretonne/ir/dfg.rs index 4776220a6f..3fc4d596eb 100644 --- a/src/libcretonne/ir/dfg.rs +++ b/src/libcretonne/ir/dfg.rs @@ -1,9 +1,10 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use entity_map::{EntityMap, PrimaryEntityData}; -use ir::entities::{Ebb, Inst, Value, NO_VALUE, ExpandedValue}; +use ir::{Ebb, Inst, Value, Type}; +use ir::entities::{NO_VALUE, ExpandedValue}; use ir::instructions::InstructionData; -use ir::types::Type; +use entity_map::{EntityMap, PrimaryEntityData}; + use std::ops::{Index, IndexMut}; use std::u16; @@ -359,7 +360,7 @@ impl EbbData { mod tests { use super::*; use ir::types; - use ir::instructions::{Opcode, InstructionData}; + use ir::{Opcode, InstructionData}; #[test] fn make_inst() { diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 9d5c59eb7d..eee47b8d66 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -10,10 +10,11 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use ir::entities::*; -use ir::immediates::*; +use ir::{Value, Type, Ebb, JumpTable}; +use ir::entities::NO_VALUE; +use ir::immediates::{Imm64, Ieee32, Ieee64}; use ir::condcodes::*; -use ir::types::{self, Type}; +use ir::types; // Include code generated by `meta/gen_instr.py`. This file contains: // diff --git a/src/libcretonne/ir/jumptable.rs b/src/libcretonne/ir/jumptable.rs index 31ea86f643..8a2308fbe1 100644 --- a/src/libcretonne/ir/jumptable.rs +++ b/src/libcretonne/ir/jumptable.rs @@ -120,7 +120,7 @@ impl Display for JumpTableData { #[cfg(test)] mod tests { use super::JumpTableData; - use ir::entities::Ebb; + use ir::Ebb; use entity_map::EntityRef; #[test] diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index af21ee5234..a983ca178f 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -248,7 +248,7 @@ impl<'a> Iterator for Insts<'a> { mod tests { use super::Layout; use entity_map::EntityRef; - use ir::entities::{Ebb, Inst}; + use ir::{Ebb, Inst}; #[test] fn append_ebb() { diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index f0e8eda741..30fb4d9323 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -9,14 +9,16 @@ pub mod jumptable; pub mod dfg; pub mod layout; -use ir::types::{FunctionName, Signature}; -use entity_map::{EntityRef, EntityMap, PrimaryEntityData}; -use ir::entities::{StackSlot, JumpTable}; +pub use ir::types::{Type, FunctionName, Signature}; +pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; +pub use ir::instructions::{Opcode, InstructionData}; +pub use ir::dfg::DataFlowGraph; +pub use ir::layout::Layout; + use ir::jumptable::JumpTableData; -use ir::dfg::DataFlowGraph; -use ir::layout::Layout; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Index; +use entity_map::{EntityRef, EntityMap, PrimaryEntityData}; /// A function. pub struct Function { diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 43834be3ef..a84efdbc52 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -43,8 +43,7 @@ pub mod riscv; use settings; -use ir::dfg::DataFlowGraph; -use ir::entities::Inst; +use ir::{Inst, DataFlowGraph}; /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 4938a7ad28..b9ffa4925c 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -5,8 +5,7 @@ pub mod settings; use super::super::settings as shared_settings; use super::Builder as IsaBuilder; use super::{TargetIsa, Encoding}; -use ir::dfg::DataFlowGraph; -use ir::entities::Inst; +use ir::{Inst, DataFlowGraph}; #[allow(dead_code)] struct Isa { diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index adc45cdb67..eb75b2fab3 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -1,8 +1,8 @@ //! Helper functions for generating dummy instructions. -use ir::Function; -use ir::entities::{Ebb, Inst, NO_VALUE}; -use ir::instructions::{InstructionData, Opcode, VariableArgs, JumpData, BranchData}; +use ir::{Function, Ebb, Inst, Opcode}; +use ir::entities::NO_VALUE; +use ir::instructions::{InstructionData, VariableArgs, JumpData, BranchData}; use ir::types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index e7b35e806a..e21c93c554 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -4,10 +4,8 @@ //! equivalent textual representation. This textual representation can be read back by the //! `cretonne-reader` crate. +use ir::{Function, Ebb, Inst, Value, Type}; use std::io::{self, Write}; -use ir::Function; -use ir::entities::{Inst, Ebb, Value}; -use ir::types::Type; pub type Result = io::Result<()>; diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index a19c14adfd..150c38da19 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -7,7 +7,7 @@ use std::str::CharIndices; use cretonne::ir::types; -use cretonne::ir::entities::{Value, Ebb}; +use cretonne::ir::{Value, Ebb}; /// The location of a `Token` or `Error`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -374,7 +374,7 @@ impl<'a> Lexer<'a> { mod tests { use super::*; use cretonne::ir::types; - use cretonne::ir::entities::{Value, Ebb}; + use cretonne::ir::{Value, Ebb}; fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 41742b54e7..0fc0ff6ab4 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -11,12 +11,13 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; use lexer::{self, Lexer, Token}; -use cretonne::ir::types::{Type, VOID, FunctionName, Signature, ArgumentType, ArgumentExtension}; +use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlotData, + JumpTable, StackSlot}; +use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::ir::entities::*; -use cretonne::ir::instructions::{Opcode, InstructionFormat, InstructionData, VariableArgs, - JumpData, BranchData, ReturnData}; -use cretonne::ir::{Function, StackSlotData}; +use cretonne::ir::entities::{NO_EBB, NO_VALUE}; +use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, + BranchData, ReturnData}; use cretonne::ir::jumptable::JumpTableData; pub use lexer::Location; diff --git a/src/tools/tests/cfg_traversal.rs b/src/tools/tests/cfg_traversal.rs index 8908b279d8..03ed5bd148 100644 --- a/src/tools/tests/cfg_traversal.rs +++ b/src/tools/tests/cfg_traversal.rs @@ -2,7 +2,7 @@ extern crate cretonne; extern crate cton_reader; use self::cton_reader::parser::Parser; -use self::cretonne::ir::entities::Ebb; +use self::cretonne::ir::Ebb; use self::cretonne::cfg::ControlFlowGraph; use self::cretonne::entity_map::EntityMap; diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs index 8e10307a21..494414b4a9 100644 --- a/src/tools/tests/dominator_tree.rs +++ b/src/tools/tests/dominator_tree.rs @@ -2,7 +2,7 @@ extern crate cretonne; extern crate cton_reader; use self::cton_reader::parser::Parser; -use self::cretonne::ir::entities::Ebb; +use self::cretonne::ir::Ebb; use self::cretonne::cfg::ControlFlowGraph; use self::cretonne::dominator_tree::DominatorTree; From 09b7420ca9fff652d5db2f70664bb8b83c87a6e2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 Aug 2016 16:34:18 -0700 Subject: [PATCH 204/968] Use an EntityMap for stack slots. Delete the StackSlots iterator and move the remaining StackSlotData into its own module. --- src/libcretonne/ir/mod.rs | 107 +++----------------------------- src/libcretonne/ir/stackslot.rs | 45 ++++++++++++++ src/libcretonne/write.rs | 6 +- src/libreader/parser.rs | 13 ++-- 4 files changed, 63 insertions(+), 108 deletions(-) create mode 100644 src/libcretonne/ir/stackslot.rs diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 30fb4d9323..806466a90b 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -1,10 +1,11 @@ //! Representation of Cretonne IL functions. -pub mod entities; pub mod types; +pub mod entities; pub mod condcodes; pub mod immediates; pub mod instructions; +pub mod stackslot; pub mod jumptable; pub mod dfg; pub mod layout; @@ -12,13 +13,13 @@ pub mod layout; pub use ir::types::{Type, FunctionName, Signature}; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; pub use ir::instructions::{Opcode, InstructionData}; +pub use ir::stackslot::StackSlotData; +pub use ir::jumptable::JumpTableData; pub use ir::dfg::DataFlowGraph; pub use ir::layout::Layout; -use ir::jumptable::JumpTableData; -use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::Index; -use entity_map::{EntityRef, EntityMap, PrimaryEntityData}; +use std::fmt::{self, Debug, Formatter}; +use entity_map::{EntityMap, PrimaryEntityData}; /// A function. pub struct Function { @@ -29,7 +30,7 @@ pub struct Function { signature: Signature, /// Stack slots allocated in this function. - stack_slots: Vec, + pub stack_slots: EntityMap, /// Jump tables used in this function. pub jump_tables: EntityMap, @@ -41,7 +42,7 @@ pub struct Function { pub layout: Layout, } -// Tag JumpTableData as a primary entity so jump_tables above . +impl PrimaryEntityData for StackSlotData {} impl PrimaryEntityData for JumpTableData {} impl Function { @@ -50,7 +51,7 @@ impl Function { Function { name: name, signature: sig, - stack_slots: Vec::new(), + stack_slots: EntityMap::new(), jump_tables: EntityMap::new(), dfg: DataFlowGraph::new(), layout: Layout::new(), @@ -66,23 +67,6 @@ impl Function { pub fn own_signature(&self) -> &Signature { &self.signature } - - // Stack slots. - - /// Allocate a new stack slot. - pub fn make_stack_slot(&mut self, data: StackSlotData) -> StackSlot { - let ss = StackSlot::new(self.stack_slots.len()); - self.stack_slots.push(data); - ss - } - - /// Iterate over all stack slots in function. - pub fn stack_slot_iter(&self) -> StackSlotIter { - StackSlotIter { - cur: 0, - end: self.stack_slots.len(), - } - } } impl Debug for Function { @@ -91,76 +75,3 @@ impl Debug for Function { fmt.write_str(&function_to_string(self)) } } - -// ====--------------------------------------------------------------------------------------====// -// -// Stack slot implementation. -// -// ====--------------------------------------------------------------------------------------====// - -/// Contents of a stack slot. -#[derive(Debug)] -pub struct StackSlotData { - /// Size of stack slot in bytes. - pub size: u32, -} - -impl StackSlotData { - /// Create a stack slot with the specified byte size. - pub fn new(size: u32) -> StackSlotData { - StackSlotData { size: size } - } -} - -impl Display for StackSlotData { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "stack_slot {}", self.size) - } -} - -/// Allow immutable access to stack slots via function indexing. -impl Index for Function { - type Output = StackSlotData; - - fn index<'a>(&'a self, ss: StackSlot) -> &'a StackSlotData { - &self.stack_slots[ss.index()] - } -} - -/// Stack slot iterator visits all stack slots in a function, returning `StackSlot` references. -pub struct StackSlotIter { - cur: usize, - end: usize, -} - -impl Iterator for StackSlotIter { - type Item = StackSlot; - - fn next(&mut self) -> Option { - if self.cur < self.end { - let ss = StackSlot::new(self.cur); - self.cur += 1; - Some(ss) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn stack_slot() { - let mut func = Function::new(); - - let ss0 = func.make_stack_slot(StackSlotData::new(4)); - let ss1 = func.make_stack_slot(StackSlotData::new(8)); - assert_eq!(ss0.to_string(), "ss0"); - assert_eq!(ss1.to_string(), "ss1"); - - assert_eq!(func[ss0].size, 4); - assert_eq!(func[ss1].size, 8); - } -} diff --git a/src/libcretonne/ir/stackslot.rs b/src/libcretonne/ir/stackslot.rs new file mode 100644 index 0000000000..31bee66eda --- /dev/null +++ b/src/libcretonne/ir/stackslot.rs @@ -0,0 +1,45 @@ +//! Stack slots. +//! +//! The `StackSlotData` struct keeps track of a single stack slot in a function. +//! + +use std::fmt::{self, Display, Formatter}; + +/// Contents of a stack slot. +#[derive(Debug)] +pub struct StackSlotData { + /// Size of stack slot in bytes. + pub size: u32, +} + +impl StackSlotData { + /// Create a stack slot with the specified byte size. + pub fn new(size: u32) -> StackSlotData { + StackSlotData { size: size } + } +} + +impl Display for StackSlotData { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "stack_slot {}", self.size) + } +} + +#[cfg(test)] +mod tests { + use ir::Function; + use super::StackSlotData; + + #[test] + fn stack_slot() { + let mut func = Function::new(); + + let ss0 = func.stack_slots.push(StackSlotData::new(4)); + let ss1 = func.stack_slots.push(StackSlotData::new(8)); + assert_eq!(ss0.to_string(), "ss0"); + assert_eq!(ss1.to_string(), "ss1"); + + assert_eq!(func.stack_slots[ss0].size, 4); + assert_eq!(func.stack_slots[ss1].size, 8); + } +} diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index e21c93c554..a20007ed05 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -68,9 +68,9 @@ fn write_spec(w: &mut Write, func: &Function) -> Result { fn write_preamble(w: &mut Write, func: &Function) -> io::Result { let mut any = false; - for ss in func.stack_slot_iter() { + for ss in func.stack_slots.keys() { any = true; - try!(writeln!(w, " {} = {}", ss, func[ss])); + try!(writeln!(w, " {} = {}", ss, func.stack_slots[ss])); } for jt in func.jump_tables.keys() { @@ -249,7 +249,7 @@ mod tests { f.name.push_str("foo"); assert_eq!(function_to_string(&f), "function foo() {\n}\n"); - f.make_stack_slot(StackSlotData::new(4)); + f.stack_slots.push(StackSlotData::new(4)); assert_eq!(function_to_string(&f), "function foo() {\n ss0 = stack_slot 4\n}\n"); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 0fc0ff6ab4..87c336906a 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -11,14 +11,13 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; use lexer::{self, Lexer, Token}; -use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlotData, - JumpTable, StackSlot}; +use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlot, + StackSlotData, JumpTable, JumpTableData}; use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{NO_EBB, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; -use cretonne::ir::jumptable::JumpTableData; pub use lexer::Location; @@ -95,7 +94,7 @@ impl Context { // Allocate a new stack slot and add a mapping number -> StackSlot. fn add_ss(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { - if self.stack_slots.insert(number, self.function.make_stack_slot(data)).is_some() { + if self.stack_slots.insert(number, self.function.stack_slots.push(data)).is_some() { err!(loc, "duplicate stack slot: ss{}", number) } else { Ok(()) @@ -1173,13 +1172,13 @@ mod tests { .parse_function() .unwrap(); assert_eq!(func.name, "foo"); - let mut iter = func.stack_slot_iter(); + let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); assert_eq!(ss0.to_string(), "ss0"); - assert_eq!(func[ss0].size, 13); + assert_eq!(func.stack_slots[ss0].size, 13); let ss1 = iter.next().unwrap(); assert_eq!(ss1.to_string(), "ss1"); - assert_eq!(func[ss1].size, 1); + assert_eq!(func.stack_slots[ss1].size, 1); assert_eq!(iter.next(), None); // Catch duplicate definitions. From c3b7fc9a9c4dc0162293bdda514b1407cba2fba3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 Aug 2016 16:41:48 -0700 Subject: [PATCH 205/968] Move ir::Function into a sub-module. Keep the top-level ir module free of implementation details that are inadvertently exposed to sub-modules. --- src/libcretonne/ir/function.rs | 64 ++++++++++++++++++++++++++++++++++ src/libcretonne/ir/mod.rs | 60 ++----------------------------- 2 files changed, 66 insertions(+), 58 deletions(-) create mode 100644 src/libcretonne/ir/function.rs diff --git a/src/libcretonne/ir/function.rs b/src/libcretonne/ir/function.rs new file mode 100644 index 0000000000..2f8344d801 --- /dev/null +++ b/src/libcretonne/ir/function.rs @@ -0,0 +1,64 @@ +//! Intermediate representation of a function. +//! +//! The `Function` struct defined in this module owns all of its extended basic blocks and +//! instructions. + +use ir::{FunctionName, Signature, StackSlot, StackSlotData, JumpTable, JumpTableData, + DataFlowGraph, Layout}; +use entity_map::{EntityMap, PrimaryEntityData}; +use std::fmt::{self, Debug, Formatter}; + +/// A function. +pub struct Function { + /// Name of this function. Mostly used by `.cton` files. + pub name: FunctionName, + + /// Signature of this function. + signature: Signature, + + /// Stack slots allocated in this function. + pub stack_slots: EntityMap, + + /// Jump tables used in this function. + pub jump_tables: EntityMap, + + /// Data flow graph containing the primary definition of all instructions, EBBs and values. + pub dfg: DataFlowGraph, + + /// Layout of EBBs and instructions in the function body. + pub layout: Layout, +} + +impl PrimaryEntityData for StackSlotData {} +impl PrimaryEntityData for JumpTableData {} + +impl Function { + /// Create a function with the given name and signature. + pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function { + Function { + name: name, + signature: sig, + stack_slots: EntityMap::new(), + jump_tables: EntityMap::new(), + dfg: DataFlowGraph::new(), + layout: Layout::new(), + } + } + + /// Create a new empty, anomymous function. + pub fn new() -> Function { + Self::with_name_signature(FunctionName::new(), Signature::new()) + } + + /// Get the signature of this function. + pub fn own_signature(&self) -> &Signature { + &self.signature + } +} + +impl Debug for Function { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + use write::function_to_string; + fmt.write_str(&function_to_string(self)) + } +} diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 806466a90b..91f8b2516c 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -9,6 +9,7 @@ pub mod stackslot; pub mod jumptable; pub mod dfg; pub mod layout; +pub mod function; pub use ir::types::{Type, FunctionName, Signature}; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; @@ -17,61 +18,4 @@ pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; pub use ir::dfg::DataFlowGraph; pub use ir::layout::Layout; - -use std::fmt::{self, Debug, Formatter}; -use entity_map::{EntityMap, PrimaryEntityData}; - -/// A function. -pub struct Function { - /// Name of this function. Mostly used by `.cton` files. - pub name: FunctionName, - - /// Signature of this function. - signature: Signature, - - /// Stack slots allocated in this function. - pub stack_slots: EntityMap, - - /// Jump tables used in this function. - pub jump_tables: EntityMap, - - /// Data flow graph containing the primary definition of all instructions, EBBs and values. - pub dfg: DataFlowGraph, - - /// Layout of EBBs and instructions in the function body. - pub layout: Layout, -} - -impl PrimaryEntityData for StackSlotData {} -impl PrimaryEntityData for JumpTableData {} - -impl Function { - /// Create a function with the given name and signature. - pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function { - Function { - name: name, - signature: sig, - stack_slots: EntityMap::new(), - jump_tables: EntityMap::new(), - dfg: DataFlowGraph::new(), - layout: Layout::new(), - } - } - - /// Create a new empty, anomymous function. - pub fn new() -> Function { - Self::with_name_signature(FunctionName::new(), Signature::new()) - } - - /// Get the signature of this function. - pub fn own_signature(&self) -> &Signature { - &self.signature - } -} - -impl Debug for Function { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - use write::function_to_string; - fmt.write_str(&function_to_string(self)) - } -} +pub use ir::function::Function; From 15d0108e4b9c17b34ae78745efefc5554822e546 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 18 Aug 2016 12:32:46 -0700 Subject: [PATCH 206/968] Add a generic implementation of quadratic hash table probing. We have multiple pre-computed constant hash tables that all use the same quadratic probing algorithm. Add a constant_hash Rust module to match the meta/constant_hash.py module. Move the simple_hash() function into constant_hash. Its Python equivalent is in the constant_hash.py module. --- src/libcretonne/constant_hash.rs | 74 ++++++++++++++++++++++++++++++ src/libcretonne/ir/instructions.rs | 33 ++++++------- src/libcretonne/lib.rs | 2 +- src/libcretonne/settings.rs | 2 +- src/libcretonne/simple_hash.rs | 21 --------- 5 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 src/libcretonne/constant_hash.rs delete mode 100644 src/libcretonne/simple_hash.rs diff --git a/src/libcretonne/constant_hash.rs b/src/libcretonne/constant_hash.rs new file mode 100644 index 0000000000..fd8115c7fa --- /dev/null +++ b/src/libcretonne/constant_hash.rs @@ -0,0 +1,74 @@ +//! Runtime support for precomputed constant hash tables. +//! +//! The `meta/constant_hash.py` Python module can generate constant hash tables using open +//! addressing and quadratic probing. The hash tables are arrays that are guaranteed to: +//! +//! - Have a power-of-two size. +//! - Contain at least one empty slot. +//! +//! This module provides runtime support for lookups in these tables. + +/// Trait that must be implemented by the entries in a constant hash table. +pub trait Table { + /// Get the number of entries in this table which must be a power of two. + fn len(&self) -> usize; + + /// Get the key corresponding to the entry at `idx`, or `None` if the entry is empty. + /// The `idx` must be in range. + fn key(&self, idx: usize) -> Option; +} + + +/// Look for `key` in `table`. +/// +/// The provided `hash` value must have been computed from `key` using the same hash function that +/// was used to construct the table. +/// +/// Returns the table index containing the found entry, or `None` if no entry could be found. +pub fn probe + ?Sized>(table: &T, key: K, hash: usize) -> Option { + debug_assert!(table.len().is_power_of_two()); + let mask = table.len() - 1; + + let mut idx = hash; + let mut step = 0; + + loop { + idx &= mask; + + match table.key(idx) { + None => return None, + Some(k) if k == key => return Some(idx), + _ => {} + } + + // Quadratic probing. + step += 1; + // When `table.len()` is a power of two, it can be proven that `idx` will visit all + // entries. This means that this loop will always terminate if the hash table has even + // one unused entry. + debug_assert!(step < table.len()); + idx += step; + } +} + +/// A primitive hash function for matching opcodes. +/// Must match `meta/constant_hash.py`. +pub fn simple_hash(s: &str) -> usize { + let mut h: u32 = 5381; + for c in s.chars() { + h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); + } + h as usize +} + +#[cfg(test)] +mod tests { + use super::simple_hash; + + #[test] + fn basic() { + // c.f. meta/constant_hash.py tests. + assert_eq!(simple_hash("Hello"), 0x2fa70c01); + assert_eq!(simple_hash("world"), 0x5b0c31d5); + } +} diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index eee47b8d66..ca43a3dc31 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -64,30 +64,25 @@ impl FromStr for Opcode { /// Parse an Opcode name from a string. fn from_str(s: &str) -> Result { - use simple_hash::simple_hash; - let tlen = OPCODE_HASH_TABLE.len(); - assert!(tlen.is_power_of_two()); - let mut idx = simple_hash(s) as usize; - let mut step: usize = 0; - loop { - idx = idx % tlen; - let entry = OPCODE_HASH_TABLE[idx]; + use constant_hash::{Table, simple_hash, probe}; - if entry == Opcode::NotAnOpcode { - return Err("Unknown opcode"); + impl<'a> Table<&'a str> for [Opcode] { + fn len(&self) -> usize { + self.len() } - if *opcode_name(entry) == *s { - return Ok(entry); + fn key(&self, idx: usize) -> Option<&'a str> { + if self[idx] == Opcode::NotAnOpcode { + None + } else { + Some(opcode_name(self[idx])) + } } + } - // Quadratic probing. - step += 1; - // When `tlen` is a power of two, it can be proven that idx will visit all entries. - // This means that this loop will always terminate if the hash table has even one - // unused entry. - assert!(step < tlen); - idx += step; + match probe::<&str, [Opcode]>(&OPCODE_HASH_TABLE, s, simple_hash(s)) { + None => Err("Unknown opcode"), + Some(i) => Ok(OPCODE_HASH_TABLE[i]), } } } diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index a8cd09ab8d..a4ed7dfecb 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -15,6 +15,6 @@ pub mod dominator_tree; pub mod entity_map; pub mod settings; -mod simple_hash; +mod constant_hash; #[cfg(test)]pub mod test_utils; diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index c65c5eeb68..72e8f06a58 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -23,7 +23,7 @@ use std::fmt; use std::result; -use simple_hash::simple_hash; +use constant_hash::simple_hash; /// A string-based configurator for settings groups. /// diff --git a/src/libcretonne/simple_hash.rs b/src/libcretonne/simple_hash.rs deleted file mode 100644 index 99cd80015c..0000000000 --- a/src/libcretonne/simple_hash.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// A primitive hash function for matching opcodes. -/// Must match `meta/constant_hash.py`. -pub fn simple_hash(s: &str) -> u32 { - let mut h: u32 = 5381; - for c in s.chars() { - h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); - } - h -} - -#[cfg(test)] -mod tests { - use super::simple_hash; - - #[test] - fn basic() { - // c.f. meta/constant_hash.py tests. - assert_eq!(simple_hash("Hello"), 0x2fa70c01); - assert_eq!(simple_hash("world"), 0x5b0c31d5); - } -} From 55f1e69e3e5a82089985688426d651d5cc0d94d6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 18 Aug 2016 13:23:46 -0700 Subject: [PATCH 207/968] Use shared quadratic probing for settings. --- src/libcretonne/settings.rs | 44 +++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index 72e8f06a58..b3cf494954 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -23,7 +23,7 @@ use std::fmt; use std::result; -use constant_hash::simple_hash; +use constant_hash::{probe, simple_hash}; /// A string-based configurator for settings groups. /// @@ -75,27 +75,12 @@ impl Builder { /// Look up a descriptor by name. fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> { - let table = self.template.hash_table; - let descs = self.template.descriptors; - let mask = table.len() - 1; - assert!((mask + 1).is_power_of_two()); - - let mut idx = simple_hash(name) as usize; - let mut step: usize = 0; - - loop { - idx = idx & mask; - let entry = table[idx] as usize; - if entry >= descs.len() { - return Err(Error::BadName); + match probe(self.template, name, simple_hash(name)) { + None => Err(Error::BadName), + Some(entry) => { + let d = &self.template.descriptors[self.template.hash_table[entry] as usize]; + Ok((d.offset as usize, d.detail)) } - let desc = &descs[entry]; - if desc.name == name { - return Ok((desc.offset as usize, desc.detail)); - } - step += 1; - assert!(step <= mask); - idx += step; } } } @@ -167,6 +152,7 @@ pub type Result = result::Result; /// code in other modules. pub mod detail { use std::fmt; + use constant_hash; /// An instruction group template. pub struct Template { @@ -207,6 +193,22 @@ pub mod detail { } } + /// The template contains a hash table for by-name lookup. + impl<'a> constant_hash::Table<&'a str> for Template { + fn len(&self) -> usize { + self.hash_table.len() + } + + fn key(&self, idx: usize) -> Option<&'a str> { + let e = self.hash_table[idx] as usize; + if e < self.descriptors.len() { + Some(self.descriptors[e].name) + } else { + None + } + } + } + /// A setting descriptor holds the information needed to generically set and print a setting. /// /// Each settings group will be represented as a constant DESCRIPTORS array. From 8683541ed3e8a71a908b2d3beddf19ce31e2f39e Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Thu, 18 Aug 2016 14:37:32 -0700 Subject: [PATCH 208/968] Add basic block information to the dominator tree. To be complete the dominator tree must represent idoms as Ebb, Inst pairs, i.e. bais blocks. --- src/libcretonne/dominator_tree.rs | 64 ++++++++++++++++++++++--------- src/tools/tests/dominator_tree.rs | 37 ++++++++++++++++-- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index 5909f68745..7c32a8e7f4 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -2,10 +2,11 @@ use cfg::*; use ir::Ebb; +use ir::entities::NO_INST; use entity_map::{EntityMap, Keys}; pub struct DominatorTree { - data: EntityMap>, + data: EntityMap>, } impl DominatorTree { @@ -28,7 +29,7 @@ impl DominatorTree { let mut changed = false; if len > 0 { - data[ebbs[0]] = Some(ebbs[0]); + data[ebbs[0]] = Some((ebbs[0], NO_INST)); changed = true; } @@ -39,20 +40,21 @@ impl DominatorTree { let preds = cfg.get_predecessors(ebb); let mut new_idom = None; - for &(p, _) in preds { + for &(p_ebb, _) in preds { if new_idom == None { - new_idom = Some(p); + new_idom = Some((p_ebb, NO_INST)); continue; } - // If this predecessor `p` has an idom available find its common + // If this predecessor has an idom available find its common // ancestor with the current value of new_idom. - if let Some(_) = data[p] { + if let Some(_) = data[p_ebb] { new_idom = match new_idom { Some(cur_idom) => { - Some(DominatorTree::intersect(&mut data, - &postorder_map, - p, - cur_idom)) + Some((DominatorTree::intersect(&mut data, + &postorder_map, + p_ebb, + cur_idom.0), + NO_INST)) } None => panic!("A 'current idom' should have been set!"), } @@ -73,11 +75,36 @@ impl DominatorTree { } } } + + // At this point the basic blocks in the tree are incomplete + // since they have all been set with NO_INST. Here we add instructions + // by iterating through each Ebb -> BasicBlock mapping in the dominator + // tree and replacing the basic block with a corresponding predecessor + // from the Ebb (on the left hand side). + // + // The predecessor chosen should have the lowest instruction number and + // an Ebb which matches the Ebb from the dummy basic block. Because + // extended basic blocks have a single entry point this will always + // result in the correct basic block being chosen. + for lhs_ebb in ebbs { + let rhs_bb = data[lhs_ebb].unwrap(); + for pred_bb in cfg.get_predecessors(lhs_ebb) { + if rhs_bb.0 == pred_bb.0 { + // Predecessors are added in order while iterating through + // instructions from lowest to highest. Because of this, + // the first match we encounter will have the lowest instruction + // number. + data[lhs_ebb] = Some(pred_bb.clone()); + break; + } + } + } + DominatorTree { data: data } } /// Find the common dominator of two ebbs. - fn intersect(data: &EntityMap>, + fn intersect(data: &EntityMap>, ordering: &EntityMap, first: Ebb, second: Ebb) @@ -91,10 +118,10 @@ impl DominatorTree { // self.data[b] to contain non-None entries. while a != b { while ordering[a] < ordering[b] { - a = data[a].unwrap(); + a = data[a].unwrap().0; } while ordering[b] < ordering[a] { - b = data[b].unwrap(); + b = data[b].unwrap().0; } } a @@ -102,7 +129,7 @@ impl DominatorTree { /// Returns the immediate dominator of some ebb or None if the /// node is unreachable. - pub fn idom(&self, ebb: Ebb) -> Option { + pub fn idom(&self, ebb: Ebb) -> Option { self.data[ebb].clone() } @@ -116,6 +143,7 @@ impl DominatorTree { mod test { use super::*; use ir::Function; + use ir::entities::NO_INST; use cfg::ControlFlowGraph; use test_utils::make_inst; @@ -153,9 +181,9 @@ mod test { let dt = DominatorTree::new(&cfg); assert_eq!(func.layout.entry_block().unwrap(), ebb3); - assert_eq!(dt.idom(ebb3).unwrap(), ebb3); - assert_eq!(dt.idom(ebb1).unwrap(), ebb3); - assert_eq!(dt.idom(ebb2).unwrap(), ebb1); - assert_eq!(dt.idom(ebb0).unwrap(), ebb1); + assert_eq!(dt.idom(ebb3).unwrap(), (ebb3, NO_INST)); + assert_eq!(dt.idom(ebb1).unwrap(), (ebb3, jmp_ebb3_ebb1)); + assert_eq!(dt.idom(ebb2).unwrap(), (ebb1, jmp_ebb1_ebb2)); + assert_eq!(dt.idom(ebb0).unwrap(), (ebb1, br_ebb1_ebb0)); } } diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs index 494414b4a9..7642c46587 100644 --- a/src/tools/tests/dominator_tree.rs +++ b/src/tools/tests/dominator_tree.rs @@ -1,9 +1,11 @@ extern crate cretonne; extern crate cton_reader; -use self::cton_reader::parser::Parser; use self::cretonne::ir::Ebb; +use self::cton_reader::parser::Parser; +use self::cretonne::ir::entities::NO_INST; use self::cretonne::cfg::ControlFlowGraph; +use self::cretonne::ir::instructions::BranchInfo; use self::cretonne::dominator_tree::DominatorTree; fn test_dominator_tree(function_source: &str, idoms: Vec) { @@ -12,9 +14,36 @@ fn test_dominator_tree(function_source: &str, idoms: Vec) { let dtree = DominatorTree::new(&cfg); assert_eq!(dtree.ebbs().collect::>().len(), idoms.len()); for (i, j) in idoms.iter().enumerate() { - let ebb = Ebb::with_number(i.clone() as u32); - let idom = Ebb::with_number(*j); - assert_eq!(dtree.idom(ebb.unwrap()), idom); + let ebb = Ebb::with_number(i.clone() as u32).unwrap(); + let idom_ebb = Ebb::with_number(*j).unwrap(); + let mut idom_inst = NO_INST; + + // Find the first branch/jump instruction which points to the idom_ebb + // and use it to denote our idom basic block. + for inst in func.layout.ebb_insts(idom_ebb) { + match func.dfg[inst].analyze_branch() { + BranchInfo::SingleDest(dest, _) => { + if dest == ebb { + idom_inst = inst; + break; + } + } + BranchInfo::Table(jt) => { + for (_, dest) in func.jump_tables[jt].entries() { + if dest == ebb { + idom_inst = inst; + break; + } + } + // We already found our inst! + if idom_inst != NO_INST { + break; + } + } + BranchInfo::NotABranch => {} + } + } + assert_eq!(dtree.idom(ebb).unwrap(), (idom_ebb, idom_inst)); } } From da27e65c255cdd1263b3fda0e9865cb0e5490891 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 19 Aug 2016 14:27:29 -0700 Subject: [PATCH 209/968] Add rotate and shift instructions with immediate amounts. --- meta/cretonne/base.py | 45 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 1f17bab749..84d595e73b 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -444,6 +444,8 @@ bnot = Instruction( # Shift/rotate. x = Operand('x', Int, doc='Scalar or vector value to shift') y = Operand('y', iB, doc='Number of bits to shift') +Y = Operand('Y', imm64) + a = Operand('a', Int) rotl = Instruction( @@ -462,6 +464,18 @@ rotr = Instruction( """, ins=(x, y), outs=a) +rotl_imm = Instruction( + 'rotl_imm', r""" + Rotate left by immediate. + """, + ins=(x, Y), outs=a) + +rotr_imm = Instruction( + 'rotr_imm', r""" + Rotate right by immediate. + """, + ins=(x, Y), outs=a) + ishl = Instruction( 'ishl', r""" Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` @@ -474,9 +488,6 @@ ishl = Instruction( .. math:: s &:= y \pmod B, \\ a &:= x \cdot 2^s \pmod{2^B}. - - .. todo:: Add ``ishl_imm`` variant with an immediate ``y``. - """, ins=(x, y), outs=a) @@ -493,8 +504,6 @@ ushr = Instruction( .. math:: s &:= y \pmod B, \\ a &:= \lfloor x \cdot 2^{-s} \rfloor. - - .. todo:: Add ``ushr_imm`` variant with an immediate ``y``. """, ins=(x, y), outs=a) @@ -505,11 +514,33 @@ sshr = Instruction( shift*. The shift amount is masked to the size of the register. - - .. todo:: Add ``sshr_imm`` variant with an immediate ``y``. """, ins=(x, y), outs=a) +ishl_imm = Instruction( + 'ishl_imm', r""" + Integer shift left by immediate. + + The shift amount is masked to the size of ``x``. + """, + ins=(x, Y), outs=a) + +ushr_imm = Instruction( + 'ushr_imm', r""" + Unsigned shift right by immediate. + + The shift amount is masked to the size of the register. + """, + ins=(x, Y), outs=a) + +sshr_imm = Instruction( + 'sshr_imm', r""" + Signed shift right by immediate. + + The shift amount is masked to the size of the register. + """, + ins=(x, Y), outs=a) + # # Bit counting. # From 4ebad2060a342ff1a9e8b28bff84225d4c6e1885 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 19 Aug 2016 14:54:16 -0700 Subject: [PATCH 210/968] Add RISC-V encodings for imediate shifts. Also add the 32-bit shift instructions for RV64. --- meta/isa/riscv/encodings.py | 17 ++++++++++++----- meta/isa/riscv/recipes.py | 20 +++++++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/meta/isa/riscv/encodings.py b/meta/isa/riscv/encodings.py index c148247524..da5870422f 100644 --- a/meta/isa/riscv/encodings.py +++ b/meta/isa/riscv/encodings.py @@ -3,7 +3,7 @@ RISC-V Encodings. """ from cretonne import base from defs import RV32, RV64 -from recipes import OP, R +from recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt # Basic arithmetic binary instructions are encoded in an R-type instruction. for inst, f3, f7 in [ @@ -17,12 +17,19 @@ for inst, f3, f7 in [ RV64.enc(inst.i64, R, OP(f3, f7)) # Dynamic shifts have the same masking semantics as the cton base instructions -for inst, f3, f7 in [ - (base.ishl, 0b001, 0b0000000), - (base.ushr, 0b101, 0b0000000), - (base.sshr, 0b101, 0b0100000), +for inst, inst_imm, f3, f7 in [ + (base.ishl, base.ishl_imm, 0b001, 0b0000000), + (base.ushr, base.ushr_imm, 0b101, 0b0000000), + (base.sshr, base.sshr_imm, 0b101, 0b0100000), ]: RV32.enc(inst.i32.i32, R, OP(f3, f7)) RV64.enc(inst.i64.i64, R, OP(f3, f7)) + RV64.enc(inst.i32.i32, R, OP32(f3, f7)) # Allow i32 shift amounts in 64-bit shifts. RV64.enc(inst.i64.i32, R, OP(f3, f7)) + RV64.enc(inst.i32.i64, R, OP32(f3, f7)) + + # Immediate shifts. + RV32.enc(inst_imm.i32, Rshamt, OPIMM(f3, f7)) + RV64.enc(inst_imm.i64, Rshamt, OPIMM(f3, f7)) + RV64.enc(inst_imm.i32, Rshamt, OPIMM32(f3, f7)) diff --git a/meta/isa/riscv/recipes.py b/meta/isa/riscv/recipes.py index 9719a4b2ec..49352b817a 100644 --- a/meta/isa/riscv/recipes.py +++ b/meta/isa/riscv/recipes.py @@ -9,7 +9,7 @@ instruction formats described in the reference: Version 2.1 """ from cretonne import EncRecipe -from cretonne.formats import Binary +from cretonne.formats import Binary, BinaryImm # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit # instructions have 11 as the two low bits, with bits 6:2 determining the base @@ -34,9 +34,14 @@ def BRANCH(funct3): return 0b11000 | (funct3 << 5) -def OPIMM(funct3): +def OPIMM(funct3, funct7=0): assert funct3 <= 0b111 - return 0b00100 | (funct3 << 5) + return 0b00100 | (funct3 << 5) | (funct7 << 8) + + +def OPIMM32(funct3, funct7=0): + assert funct3 <= 0b111 + return 0b00110 | (funct3 << 5) | (funct7 << 8) def OP(funct3, funct7): @@ -45,6 +50,15 @@ def OP(funct3, funct7): return 0b01100 | (funct3 << 5) | (funct7 << 8) +def OP32(funct3, funct7): + assert funct3 <= 0b111 + assert funct7 <= 0b1111111 + return 0b01110 | (funct3 << 5) | (funct7 << 8) + + # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) R = EncRecipe('R', Binary) + +# R-type with an immediate shift amount instead of rs2. +Rshamt = EncRecipe('Rshamt', BinaryImm) From ddd205ff78e5a0eb9e656de63eb23a40532233c6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 11:01:08 -0700 Subject: [PATCH 211/968] Upgrade to rustfmt 0.6.0 --- src/libcretonne/ir/entities.rs | 6 +----- src/libcretonne/ir/immediates.rs | 6 +----- src/libcretonne/ir/instructions.rs | 5 +---- src/libcretonne/lib.rs | 3 ++- src/libcretonne/settings.rs | 4 +--- 5 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/libcretonne/ir/entities.rs b/src/libcretonne/ir/entities.rs index 33b41e5273..1a887452a7 100644 --- a/src/libcretonne/ir/entities.rs +++ b/src/libcretonne/ir/entities.rs @@ -42,11 +42,7 @@ impl EntityRef for Ebb { impl Ebb { /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. pub fn with_number(n: u32) -> Option { - if n < u32::MAX { - Some(Ebb(n)) - } else { - None - } + if n < u32::MAX { Some(Ebb(n)) } else { None } } } diff --git a/src/libcretonne/ir/immediates.rs b/src/libcretonne/ir/immediates.rs index a329d74f70..077ea4205f 100644 --- a/src/libcretonne/ir/immediates.rs +++ b/src/libcretonne/ir/immediates.rs @@ -56,11 +56,7 @@ impl FromStr for Imm64 { let mut value: u64 = 0; let mut digits = 0; let negative = s.starts_with('-'); - let s2 = if negative { - &s[1..] - } else { - s - }; + let s2 = if negative { &s[1..] } else { s }; if s2.starts_with("0x") { // Hexadecimal. diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index ca43a3dc31..9638d660a5 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -95,10 +95,7 @@ impl FromStr for Opcode { /// `Box` to store the additional information out of line. #[derive(Debug)] pub enum InstructionData { - Nullary { - opcode: Opcode, - ty: Type, - }, + Nullary { opcode: Opcode, ty: Type }, Unary { opcode: Opcode, ty: Type, diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index a4ed7dfecb..7bdda395ab 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -17,4 +17,5 @@ pub mod settings; mod constant_hash; -#[cfg(test)]pub mod test_utils; +#[cfg(test)] +pub mod test_utils; diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index b3cf494954..58731c40bf 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -227,9 +227,7 @@ pub mod detail { #[derive(Clone, Copy)] pub enum Detail { /// A boolean setting only uses one bit, numbered from LSB. - Bool { - bit: u8, - }, + Bool { bit: u8 }, /// A numerical setting uses the whole byte. Num, From 9165eef82301792c245aa7b7aebb54591672d019 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 23 Aug 2016 13:30:38 -0700 Subject: [PATCH 212/968] Modify the dominator tree's intersect method to interact with Basic Blocks Corresponding changes to test cases are also included. --- src/libcretonne/dominator_tree.rs | 68 ++++++++---------- src/tools/Cargo.toml | 1 + src/tools/tests/dominator_tree.rs | 112 +++++++++++++++++++----------- 3 files changed, 99 insertions(+), 82 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index 7c32a8e7f4..1b99db342d 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -10,6 +10,11 @@ pub struct DominatorTree { } impl DominatorTree { + /// Insert data directly into a dominator tree. + pub fn from_data(data: EntityMap>) -> DominatorTree { + DominatorTree { data: data } + } + /// Build a dominator tree from a control flow graph using Keith D. Cooper's /// "Simple, Fast Dominator Algorithm." pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { @@ -40,21 +45,20 @@ impl DominatorTree { let preds = cfg.get_predecessors(ebb); let mut new_idom = None; - for &(p_ebb, _) in preds { + for pred in preds { if new_idom == None { - new_idom = Some((p_ebb, NO_INST)); + new_idom = Some(pred.clone()); continue; } // If this predecessor has an idom available find its common // ancestor with the current value of new_idom. - if let Some(_) = data[p_ebb] { + if let Some(_) = data[pred.0] { new_idom = match new_idom { Some(cur_idom) => { Some((DominatorTree::intersect(&mut data, &postorder_map, - p_ebb, - cur_idom.0), - NO_INST)) + *pred, + cur_idom))) } None => panic!("A 'current idom' should have been set!"), } @@ -67,7 +71,7 @@ impl DominatorTree { } Some(idom) => { // Old idom != New idom - if idom != new_idom.unwrap() { + if idom.0 != new_idom.unwrap().0 { data[ebb] = new_idom; changed = true; } @@ -76,39 +80,15 @@ impl DominatorTree { } } - // At this point the basic blocks in the tree are incomplete - // since they have all been set with NO_INST. Here we add instructions - // by iterating through each Ebb -> BasicBlock mapping in the dominator - // tree and replacing the basic block with a corresponding predecessor - // from the Ebb (on the left hand side). - // - // The predecessor chosen should have the lowest instruction number and - // an Ebb which matches the Ebb from the dummy basic block. Because - // extended basic blocks have a single entry point this will always - // result in the correct basic block being chosen. - for lhs_ebb in ebbs { - let rhs_bb = data[lhs_ebb].unwrap(); - for pred_bb in cfg.get_predecessors(lhs_ebb) { - if rhs_bb.0 == pred_bb.0 { - // Predecessors are added in order while iterating through - // instructions from lowest to highest. Because of this, - // the first match we encounter will have the lowest instruction - // number. - data[lhs_ebb] = Some(pred_bb.clone()); - break; - } - } - } - DominatorTree { data: data } } /// Find the common dominator of two ebbs. fn intersect(data: &EntityMap>, ordering: &EntityMap, - first: Ebb, - second: Ebb) - -> Ebb { + first: BasicBlock, + second: BasicBlock) + -> BasicBlock { let mut a = first; let mut b = second; @@ -116,15 +96,23 @@ impl DominatorTree { // visitation number, to ensure that we move upward through the tree. // Walking upward means that we may always expect self.data[a] and // self.data[b] to contain non-None entries. - while a != b { - while ordering[a] < ordering[b] { - a = data[a].unwrap().0; + while a.0 != b.0 { + while ordering[a.0] < ordering[b.0] { + a = data[a.0].unwrap(); } - while ordering[b] < ordering[a] { - b = data[b].unwrap().0; + while ordering[b.0] < ordering[a.0] { + b = data[b.0].unwrap(); } } - a + + // TODO: we can't rely on instruction numbers to always be ordered + // from lowest to highest. Given that, it will be necessary to create + // an abolute mapping to determine the instruction order in the future. + if a.1 == NO_INST || a.1 < b.1 { + a + } else { + b + } } /// Returns the immediate dominator of some ebb or None if the diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index 8059002f40..b5ee718222 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -14,3 +14,4 @@ cretonne = { path = "../libcretonne" } cretonne-reader = { path = "../libreader" } docopt = "0.6.80" rustc-serialize = "0.3.19" +regex = "0.1.73" diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs index 7642c46587..1514db74fb 100644 --- a/src/tools/tests/dominator_tree.rs +++ b/src/tools/tests/dominator_tree.rs @@ -1,49 +1,77 @@ extern crate cretonne; extern crate cton_reader; +extern crate regex; +use regex::Regex; use self::cretonne::ir::Ebb; use self::cton_reader::parser::Parser; +use self::cretonne::ir::function::Function; +use self::cretonne::entity_map::EntityMap; use self::cretonne::ir::entities::NO_INST; use self::cretonne::cfg::ControlFlowGraph; -use self::cretonne::ir::instructions::BranchInfo; use self::cretonne::dominator_tree::DominatorTree; -fn test_dominator_tree(function_source: &str, idoms: Vec) { +/// Construct a dominator tree from specially formatted comments in +/// cton source. Each line with a jump/branch instruction should +/// have a comment of the format: `dominates(n, ..., N)`, where each `n` +/// is the Ebb number for which this instruction is the immediate dominator. +fn dominator_tree_from_source(func: &Function, function_source: &str) -> DominatorTree { + let ebb_re = Regex::new("^[ \t]*ebb[0-9]+.*:").unwrap(); + let dom_re = Regex::new("dominates\\(([0-9,]+)\\)").unwrap(); + let inst_re = Regex::new("^[ \t]*[a-zA-Z0-9]+[^{}]*").unwrap(); + let func_re = Regex::new("^[ \t]*function.*").unwrap(); + + let ebbs = func.layout.ebbs().collect::>(); + let mut data = EntityMap::with_capacity(ebbs.len()); + + if ebbs.len() < 1 { + return DominatorTree::from_data(data); + } + + let mut ebb_offset = 0; + let mut inst_offset = 0; + + let mut cur_ebb = ebbs[0]; + let mut insts = func.layout.ebb_insts(ebbs[ebb_offset]).collect::>(); + + for line in function_source.lines() { + if ebb_re.is_match(line) { + cur_ebb = ebbs[ebb_offset]; + insts = func.layout.ebb_insts(cur_ebb).collect::>(); + ebb_offset += 1; + inst_offset = 0; + } else if inst_re.is_match(line) && !func_re.is_match(line) { + inst_offset += 1; + } + match dom_re.captures(line) { + Some(caps) => { + for s in caps.at(1).unwrap().split(",") { + let this_ebb = Ebb::with_number(s.parse::().unwrap()).unwrap(); + let inst = if inst_offset == 0 { + NO_INST + } else { + insts[inst_offset - 1].clone() + }; + data[this_ebb] = Some((cur_ebb.clone(), inst)); + } + }, + None => continue, + }; + + } + DominatorTree::from_data(data) +} + +fn test_dominator_tree(function_source: &str) { + let func = &Parser::parse(function_source).unwrap()[0]; + let src_dtree = dominator_tree_from_source(&func, function_source); + let cfg = ControlFlowGraph::new(&func); let dtree = DominatorTree::new(&cfg); - assert_eq!(dtree.ebbs().collect::>().len(), idoms.len()); - for (i, j) in idoms.iter().enumerate() { - let ebb = Ebb::with_number(i.clone() as u32).unwrap(); - let idom_ebb = Ebb::with_number(*j).unwrap(); - let mut idom_inst = NO_INST; - // Find the first branch/jump instruction which points to the idom_ebb - // and use it to denote our idom basic block. - for inst in func.layout.ebb_insts(idom_ebb) { - match func.dfg[inst].analyze_branch() { - BranchInfo::SingleDest(dest, _) => { - if dest == ebb { - idom_inst = inst; - break; - } - } - BranchInfo::Table(jt) => { - for (_, dest) in func.jump_tables[jt].entries() { - if dest == ebb { - idom_inst = inst; - break; - } - } - // We already found our inst! - if idom_inst != NO_INST { - break; - } - } - BranchInfo::NotABranch => {} - } - } - assert_eq!(dtree.idom(ebb).unwrap(), (idom_ebb, idom_inst)); + for ebb in func.layout.ebbs() { + assert_eq!(dtree.idom(ebb), src_dtree.idom(ebb)); } } @@ -51,26 +79,26 @@ fn test_dominator_tree(function_source: &str, idoms: Vec) { fn basic() { test_dominator_tree(" function test(i32) { - ebb0(v0: i32): - jump ebb1 + ebb0(v0: i32): ; dominates(0) + jump ebb1 ; dominates(1) ebb1: - brz v0, ebb3 - jump ebb2 + brz v0, ebb3 ; dominates(3) + jump ebb2 ; dominates(2) ebb2: jump ebb3 ebb3: return } - ", vec![0, 0, 1, 1]); + "); } #[test] fn loops() { test_dominator_tree(" function test(i32) { - ebb0(v0: i32): - brz v0, ebb1 - jump ebb2 + ebb0(v0: i32): ; dominates(0) + brz v0, ebb1 ; dominates(1,3,4,5) + jump ebb2 ; dominates(2) ebb1: jump ebb3 ebb2: @@ -85,5 +113,5 @@ fn loops() { brz v0, ebb4 return } - ", vec![0, 0, 0, 0, 0, 0]); + "); } From 67fdd27d04743ffdd074c6db652e848562feade9 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 23 Aug 2016 13:33:51 -0700 Subject: [PATCH 213/968] Synchronize regex versions --- src/tools/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index b5ee718222..79e9d2685d 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -14,4 +14,4 @@ cretonne = { path = "../libcretonne" } cretonne-reader = { path = "../libreader" } docopt = "0.6.80" rustc-serialize = "0.3.19" -regex = "0.1.73" +regex = "0.1.71" From 102c0049e0011d6472f1a26600f474789b923c1d Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 23 Aug 2016 13:37:04 -0700 Subject: [PATCH 214/968] rustfmt changes --- src/libcretonne/dominator_tree.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index 1b99db342d..a3a00b2cc4 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -108,11 +108,7 @@ impl DominatorTree { // TODO: we can't rely on instruction numbers to always be ordered // from lowest to highest. Given that, it will be necessary to create // an abolute mapping to determine the instruction order in the future. - if a.1 == NO_INST || a.1 < b.1 { - a - } else { - b - } + if a.1 == NO_INST || a.1 < b.1 { a } else { b } } /// Returns the immediate dominator of some ebb or None if the From cab356bd9f338f7c9f7fd8888e6dfc7ef43ebec0 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 23 Aug 2016 15:42:31 -0700 Subject: [PATCH 215/968] Move dominator tree test cases to their own folder. --- src/tools/Cargo.lock | 23 ++++++++ src/tools/Cargo.toml | 1 + src/tools/tests/dominator_tree.rs | 53 ++++++------------- .../tests/dominator_tree_testdata/basic.cton | 11 ++++ .../tests/dominator_tree_testdata/loops.cton | 18 +++++++ 5 files changed, 69 insertions(+), 37 deletions(-) create mode 100644 src/tools/tests/dominator_tree_testdata/basic.cton create mode 100644 src/tools/tests/dominator_tree_testdata/loops.cton diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index 0b85735d6c..d7060f22e8 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -5,6 +5,8 @@ dependencies = [ "cretonne 0.0.0", "cretonne-reader 0.0.0", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -37,6 +39,11 @@ dependencies = [ "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -118,3 +125,19 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[metadata] +"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" +"checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76" +"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c96061f0c8a2dc27482e394d82e23073569de41d73cd736672ccd3e5c7471bfd" +"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e58a1b7d2bfecc0746e8587c30a53d01ea7bc0e98fac54e5aaa375b94338a0cc" +"checksum regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "baa04823ba7be7ed0bed3d0704c7b923019d9c4e4931c5af2804c7c7a0e3d00b" +"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" +"checksum strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4d73a2c36a4d095ed1a6df5cbeac159863173447f7a82b3f4757426844ab825" +"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" +"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3969e500d618a5e974917ddefd0ba152e4bcaae5eb5d9b8c1fbc008e9e28c24e" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index 79e9d2685d..8164a4e4bb 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -15,3 +15,4 @@ cretonne-reader = { path = "../libreader" } docopt = "0.6.80" rustc-serialize = "0.3.19" regex = "0.1.71" +glob = "0.2.11" diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs index 1514db74fb..c1be367137 100644 --- a/src/tools/tests/dominator_tree.rs +++ b/src/tools/tests/dominator_tree.rs @@ -1,8 +1,13 @@ extern crate cretonne; extern crate cton_reader; +extern crate glob; extern crate regex; +use std::env; +use glob::glob; use regex::Regex; +use std::fs::File; +use std::io::Read; use self::cretonne::ir::Ebb; use self::cton_reader::parser::Parser; use self::cretonne::ir::function::Function; @@ -76,42 +81,16 @@ fn test_dominator_tree(function_source: &str) { } #[test] -fn basic() { - test_dominator_tree(" - function test(i32) { - ebb0(v0: i32): ; dominates(0) - jump ebb1 ; dominates(1) - ebb1: - brz v0, ebb3 ; dominates(3) - jump ebb2 ; dominates(2) - ebb2: - jump ebb3 - ebb3: - return - } - "); -} +fn test_all() { + let testdir = format!("{}/tests/dominator_tree_testdata/*.cton", + env::current_dir().unwrap().display()); -#[test] -fn loops() { - test_dominator_tree(" - function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb1 ; dominates(1,3,4,5) - jump ebb2 ; dominates(2) - ebb1: - jump ebb3 - ebb2: - brz v0, ebb4 - jump ebb5 - ebb3: - jump ebb4 - ebb4: - brz v0, ebb3 - jump ebb5 - ebb5: - brz v0, ebb4 - return - } - "); + for entry in glob(&testdir).unwrap() { + let path = entry.unwrap(); + println!("Testing {:?}", path); + let mut file = File::open(&path).unwrap(); + let mut buffer = String::new(); + file.read_to_string(&mut buffer).unwrap(); + test_dominator_tree(&buffer); + } } diff --git a/src/tools/tests/dominator_tree_testdata/basic.cton b/src/tools/tests/dominator_tree_testdata/basic.cton new file mode 100644 index 0000000000..c274336b79 --- /dev/null +++ b/src/tools/tests/dominator_tree_testdata/basic.cton @@ -0,0 +1,11 @@ +function test(i32) { + ebb0(v0: i32): ; dominates(0) + jump ebb1 ; dominates(1) + ebb1: + brz v0, ebb3 ; dominates(3) + jump ebb2 ; dominates(2) + ebb2: + jump ebb3 + ebb3: + return +} diff --git a/src/tools/tests/dominator_tree_testdata/loops.cton b/src/tools/tests/dominator_tree_testdata/loops.cton new file mode 100644 index 0000000000..87d7780d7b --- /dev/null +++ b/src/tools/tests/dominator_tree_testdata/loops.cton @@ -0,0 +1,18 @@ +function test(i32) { + ebb0(v0: i32): ; dominates(0) + brz v0, ebb1 ; dominates(1,3,4,5) + jump ebb2 ; dominates(2) + ebb1: + jump ebb3 + ebb2: + brz v0, ebb4 + jump ebb5 + ebb3: + jump ebb4 + ebb4: + brz v0, ebb3 + jump ebb5 + ebb5: + brz v0, ebb4 + return +} From 1e1baec50ad6c262f77e597c2029d7f5b21cc006 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 15:16:40 -0700 Subject: [PATCH 216/968] Python 3 compat. Try to keep our Python sources compatible with both Python 2.7 and 3. Check with 'pylint --py3k' and 'python -3'. --- meta/build.py | 1 + meta/constant_hash.py | 1 + meta/cretonne/__init__.py | 12 ++++++------ meta/cretonne/base.py | 7 ++++--- meta/cretonne/entities.py | 2 +- meta/cretonne/formats.py | 7 +++---- meta/cretonne/immediates.py | 2 +- meta/cretonne/predicates.py | 2 ++ meta/cretonne/settings.py | 2 +- meta/cretonne/types.py | 2 +- meta/gen_build_deps.py | 6 +++--- meta/gen_instr.py | 2 +- meta/gen_settings.py | 4 ++-- meta/isa/__init__.py | 2 +- meta/isa/riscv/__init__.py | 8 ++++---- meta/isa/riscv/defs.py | 2 +- meta/isa/riscv/encodings.py | 5 +++-- meta/isa/riscv/recipes.py | 1 + meta/isa/riscv/settings.py | 4 ++-- meta/srcgen.py | 2 +- 20 files changed, 40 insertions(+), 34 deletions(-) diff --git a/meta/build.py b/meta/build.py index 6ca4425823..2806426e6d 100644 --- a/meta/build.py +++ b/meta/build.py @@ -2,6 +2,7 @@ # # This script is run from src/libcretonne/build.rs to generate Rust files. +from __future__ import absolute_import import argparse import isa import gen_instr diff --git a/meta/constant_hash.py b/meta/constant_hash.py index 27a30476a4..aa43694a50 100644 --- a/meta/constant_hash.py +++ b/meta/constant_hash.py @@ -5,6 +5,7 @@ The `constant_hash` module can generate constant pre-populated hash tables. We don't attempt parfect hashing, but simply generate an open addressed quadratically probed hash table. """ +from __future__ import absolute_import def simple_hash(s): diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index cc31f47cf5..ecfea17989 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -4,7 +4,7 @@ Cretonne meta language module. This module provides classes and functions used to describe Cretonne instructions. """ - +from __future__ import absolute_import import re import importlib from collections import namedtuple @@ -156,7 +156,7 @@ class SettingGroup(object): .format(self, SettingGroup._current)) SettingGroup._current = None if globs: - from predicates import Predicate + from .predicates import Predicate for name, obj in globs.iteritems(): if isinstance(obj, Setting): assert obj.name is None, obj.name @@ -365,7 +365,7 @@ class IntType(ScalarType): assert bits > 0, 'IntType must have positive number of bits' super(IntType, self).__init__( name='i{:d}'.format(bits), - membytes=bits/8, + membytes=bits // 8, doc="An integer type with {} bits.".format(bits)) self.bits = bits @@ -379,7 +379,7 @@ class FloatType(ScalarType): def __init__(self, bits, doc): assert bits > 0, 'FloatType must have positive number of bits' super(FloatType, self).__init__(name='f{:d}'.format(bits), - membytes=bits/8, doc=doc) + membytes=bits // 8, doc=doc) self.bits = bits def __repr__(self): @@ -393,7 +393,7 @@ class BoolType(ScalarType): assert bits > 0, 'BoolType must have positive number of bits' super(BoolType, self).__init__( name='b{:d}'.format(bits), - membytes=bits/8, + membytes=bits // 8, doc="A boolean type with {} bits.".format(bits)) self.bits = bits @@ -845,7 +845,7 @@ class BoundInstruction(object): assert len(typevars) <= 1 + len(inst.other_typevars) def __str__(self): - return '.'.join([self.inst.name, ] + map(str, self.typevars)) + return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) def bind(self, *args): """ diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 84d595e73b..2dd3c1bf95 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -4,10 +4,11 @@ Cretonne base instruction set. This module defines the basic Cretonne instruction set that all targets support. """ +from __future__ import absolute_import from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args -from types import i8, f32, f64 -from immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc -import entities +from .types import i8, f32, f64 +from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc +from . import entities instructions = InstructionGroup("base", "Shared base instruction set") diff --git a/meta/cretonne/entities.py b/meta/cretonne/entities.py index 14587d402f..bd55fa6eed 100644 --- a/meta/cretonne/entities.py +++ b/meta/cretonne/entities.py @@ -3,7 +3,7 @@ The `cretonne.entities` module predefines all the Cretonne entity reference operand types. Thee are corresponding definitions in the `cretonne.entities` Rust module. """ - +from __future__ import absolute_import from . import EntityRefKind diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 4798b9ed25..efd535b693 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -5,11 +5,10 @@ Every instruction format has a corresponding `InstructionData` variant in the Rust representation of cretonne IL, so all instruction formats must be defined in this module. """ - - +from __future__ import absolute_import from . import InstructionFormat, value, variable_args -from immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc -from entities import ebb, function, jump_table +from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc +from .entities import ebb, function, jump_table Nullary = InstructionFormat() diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index f20a6142d3..61ba418c9c 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -2,7 +2,7 @@ The `cretonne.immediates` module predefines all the Cretonne immediate operand types. """ - +from __future__ import absolute_import from . import ImmediateKind #: A 64-bit immediate integer operand. diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index dd50613a37..caedae14fb 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -21,6 +21,8 @@ All predicates have a *context* which determines where they can be evaluated. For an ISA predicate, the context is the ISA settings group. For an instruction predicate, the context is the instruction format. """ +from __future__ import absolute_import +from functools import reduce def _is_parent(a, b): diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py index 2876dfd84f..ba95bda36c 100644 --- a/meta/cretonne/settings.py +++ b/meta/cretonne/settings.py @@ -3,7 +3,7 @@ Cretonne shared settings. This module defines settings are are relevant for all code generators. """ - +from __future__ import absolute_import from . import SettingGroup, BoolSetting, EnumSetting group = SettingGroup('shared') diff --git a/meta/cretonne/types.py b/meta/cretonne/types.py index d92e6f9113..05dcb53e16 100644 --- a/meta/cretonne/types.py +++ b/meta/cretonne/types.py @@ -1,7 +1,7 @@ """ The cretonne.types module predefines all the Cretonne scalar types. """ - +from __future__ import absolute_import from . import ScalarType, IntType, FloatType, BoolType #: Boolean. diff --git a/meta/gen_build_deps.py b/meta/gen_build_deps.py index 4d0c0cb260..32ae2f7238 100644 --- a/meta/gen_build_deps.py +++ b/meta/gen_build_deps.py @@ -12,7 +12,7 @@ If the build script outputs lines of the form: cargo will rerun the build script when those files have changed since the last build. """ - +from __future__ import absolute_import, print_function import os from os.path import dirname, abspath, join @@ -30,7 +30,7 @@ def source_files(top): def generate(): - print "Dependencies from meta language directory:" + print("Dependencies from meta language directory:") meta = dirname(abspath(__file__)) for path in source_files(meta): - print "cargo:rerun-if-changed=" + path + print("cargo:rerun-if-changed=" + path) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 64c726ad9d..92808524c5 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -1,7 +1,7 @@ """ Generate sources with instruction info. """ - +from __future__ import absolute_import import srcgen import constant_hash from unique_table import UniqueTable, UniqueSeqTable diff --git a/meta/gen_settings.py b/meta/gen_settings.py index 85c154648d..65bb7c428a 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -1,7 +1,7 @@ """ Generate sources with settings. """ - +from __future__ import absolute_import import srcgen from unique_table import UniqueSeqTable import constant_hash @@ -102,7 +102,7 @@ def gen_pred_getter(pred, fmt): """ Emit a getter for a pre-computed predicate. """ - fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0))); + fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0))) proto = 'pub fn {}(&self) -> bool'.format(pred.name) with fmt.indented(proto + ' {', '}'): fmt.line('(self.bytes[{}] & (1 << {})) != 0'.format( diff --git a/meta/isa/__init__.py b/meta/isa/__init__.py index b566a521ef..addc70b8a9 100644 --- a/meta/isa/__init__.py +++ b/meta/isa/__init__.py @@ -5,7 +5,7 @@ Cretonne target ISA definitions The :py:mod:`isa` package contains sub-packages for each target instruction set architecture supported by Cretonne. """ - +from __future__ import absolute_import from . import riscv diff --git a/meta/isa/riscv/__init__.py b/meta/isa/riscv/__init__.py index ce8c47ea3d..1f9ebd3f46 100644 --- a/meta/isa/riscv/__init__.py +++ b/meta/isa/riscv/__init__.py @@ -24,10 +24,10 @@ RV32G / RV64G F, and D instruction sets listed above. """ - -import defs -import encodings -import settings +from __future__ import absolute_import +from . import defs +from . import encodings +from . import settings # Re-export the primary target ISA definition. isa = defs.isa diff --git a/meta/isa/riscv/defs.py b/meta/isa/riscv/defs.py index 6e009ccdf4..4fccbf09a6 100644 --- a/meta/isa/riscv/defs.py +++ b/meta/isa/riscv/defs.py @@ -3,7 +3,7 @@ RISC-V definitions. Commonly used definitions. """ - +from __future__ import absolute_import from cretonne import TargetISA, CPUMode import cretonne.base diff --git a/meta/isa/riscv/encodings.py b/meta/isa/riscv/encodings.py index da5870422f..3ce5102760 100644 --- a/meta/isa/riscv/encodings.py +++ b/meta/isa/riscv/encodings.py @@ -1,9 +1,10 @@ """ RISC-V Encodings. """ +from __future__ import absolute_import from cretonne import base -from defs import RV32, RV64 -from recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt +from .defs import RV32, RV64 +from .recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt # Basic arithmetic binary instructions are encoded in an R-type instruction. for inst, f3, f7 in [ diff --git a/meta/isa/riscv/recipes.py b/meta/isa/riscv/recipes.py index 49352b817a..25ea74870f 100644 --- a/meta/isa/riscv/recipes.py +++ b/meta/isa/riscv/recipes.py @@ -8,6 +8,7 @@ instruction formats described in the reference: Volume I: User-Level ISA Version 2.1 """ +from __future__ import absolute_import from cretonne import EncRecipe from cretonne.formats import Binary, BinaryImm diff --git a/meta/isa/riscv/settings.py b/meta/isa/riscv/settings.py index ade60372d6..1ab2917036 100644 --- a/meta/isa/riscv/settings.py +++ b/meta/isa/riscv/settings.py @@ -1,11 +1,11 @@ """ RISC-V settings. """ - +from __future__ import absolute_import from cretonne import SettingGroup, BoolSetting from cretonne.predicates import And import cretonne.settings as shared -from defs import isa +from .defs import isa isa.settings = SettingGroup('riscv', parent=shared.group) diff --git a/meta/srcgen.py b/meta/srcgen.py index 42f39c96de..99bb808435 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -5,7 +5,7 @@ The `srcgen` module contains generic helper routines and classes for generating source code. """ - +from __future__ import absolute_import import sys import os import re From 2dfeea67e1f86da77872cd78d86ee05f1bcc9955 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 16:54:40 -0700 Subject: [PATCH 217/968] Add script for Python 3 compat checks. --- meta/check-py3k.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 meta/check-py3k.sh diff --git a/meta/check-py3k.sh b/meta/check-py3k.sh new file mode 100755 index 0000000000..19096339bf --- /dev/null +++ b/meta/check-py3k.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Check Python sources for Python 3 compatibility using pylint. +# +# Install pylint with 'pip install pylint'. +cd $(dirname "$0") +pylint --py3k --reports=no -- *.py cretonne isa From 7ead1e3f6f2420a4c6fe91c6e1085fb8422f4beb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 11:39:47 -0700 Subject: [PATCH 218/968] Track the default member name for immediate operands. Usually an instruction firmat has only a single immediate operand called 'imm', or 'cond' if it is one of the condigtion codes. Add a 'default_member' field to ImmediateKind to keep track of this default member name in the InstructionData struct. --- meta/cretonne/__init__.py | 6 +++++- meta/cretonne/immediates.py | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index ecfea17989..c79e0f7bdc 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -237,11 +237,15 @@ variable_args = OperandKind( class ImmediateKind(OperandKind): """ The kind of an immediate instruction operand. + + :param default_member: The default member name of this kind the + `InstructionData` data structure. """ - def __init__(self, name, doc): + def __init__(self, name, doc, default_member='imm'): self.name = name self.__doc__ = doc + self.default_member = default_member def __repr__(self): return 'ImmediateKind({})'.format(self.name) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index 61ba418c9c..ca8affded0 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -34,11 +34,16 @@ immvector = ImmediateKind('immvector', 'An immediate SIMD vector.') #: #: This enumerated operand kind is used for the :cton:inst:`icmp` instruction #: and corresponds to the `condcodes::IntCC` Rust type. -intcc = ImmediateKind('intcc', 'An integer comparison condition code.') +intcc = ImmediateKind( + 'intcc', + 'An integer comparison condition code.', + default_member='cond') #: A condition code for comparing floating point values. #: #: This enumerated operand kind is used for the :cton:inst:`fcmp` instruction #: and corresponds to the `condcodes::FloatCC` Rust type. floatcc = ImmediateKind( - 'floatcc', 'A floating point comparison condition code.') + 'floatcc', + 'A floating point comparison condition code.', + default_member='cond') From fe7ad84129d8a179fa10a482698764e4de25d28f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 13:29:29 -0700 Subject: [PATCH 219/968] Create format fields for immediate operands. Each InstructionFormat instance gets data members corresponding to its immediate operands, so the can be referred to as BinaryImm.imm, for example. This will be used to construct instruction predicates. --- meta/cretonne/__init__.py | 50 +++++++++++++++++++++++++++++++++++++-- meta/cretonne/formats.py | 4 ++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index c79e0f7bdc..c6693a7c4a 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -622,9 +622,9 @@ class InstructionFormat(object): def __init__(self, *kinds, **kwargs): self.name = kwargs.get('name', None) - self.kinds = kinds self.multiple_results = kwargs.get('multiple_results', False) self.boxed_storage = kwargs.get('boxed_storage', False) + self.kinds = tuple(self._process_member_names(kinds)) # Which of self.kinds are `value`? self.value_operands = tuple( @@ -640,7 +640,7 @@ class InstructionFormat(object): self.typevar_operand = self.value_operands[0] # Compute a signature for the global registry. - sig = (self.multiple_results,) + kinds + sig = (self.multiple_results,) + self.kinds if sig in InstructionFormat._registry: raise RuntimeError( "Format '{}' has the same signature as existing format '{}'" @@ -648,6 +648,31 @@ class InstructionFormat(object): InstructionFormat._registry[sig] = self InstructionFormat.all_formats.append(self) + def _process_member_names(self, kinds): + """ + Extract names of all the immediate operands in the kinds tuple. + + Each entry is either an `OperandKind` instance, or a `(member, kind)` + pair. The member names correspond to members in the Rust + `InstructionData` data structure. + + Yields the operand kinds. + """ + for i, k in enumerate(kinds): + if isinstance(k, tuple): + member, k = k + else: + member = None + yield k + + # Create `FormatField` instances for the immediates. + if isinstance(k, ImmediateKind): + if not member: + member = k.default_member + assert not hasattr(self, member), "Duplicate member name" + field = FormatField(self, i, member) + setattr(self, member, field) + @staticmethod def lookup(ins, outs): """ @@ -680,6 +705,27 @@ class InstructionFormat(object): obj.name = name +class FormatField(object): + """ + A field in an instruction format. + + This corresponds to a single member of a variant of the `InstructionData` + data type. + + :param format: Parent `InstructionFormat`. + :param operand: Operand number in parent. + :param name: Member name in `InstructionData` variant. + """ + + def __init__(self, format, operand, name): + self.format = format + self.operand = operand + self.name = name + + def __str__(self): + return '{}.{}'.format(self.format.name, self.name) + + class Instruction(object): """ The operands to the instruction are specified as two tuples: ``ins`` and diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index efd535b693..06f4593086 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -30,8 +30,8 @@ BinaryOverflow = InstructionFormat(value, value, multiple_results=True) # The fma instruction has the same constraint on all inputs. Ternary = InstructionFormat(value, value, value, typevar_operand=1) -InsertLane = InstructionFormat(value, uimm8, value) -ExtractLane = InstructionFormat(value, uimm8) +InsertLane = InstructionFormat(value, ('lane', uimm8), value) +ExtractLane = InstructionFormat(value, ('lane', uimm8)) IntCompare = InstructionFormat(intcc, value, value) FloatCompare = InstructionFormat(floatcc, value, value) From 5a5688e446f41ce470f56cb57a669ac1f802f109 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 14:42:03 -0700 Subject: [PATCH 220/968] Add bitwise operations with an immediate operand. --- meta/cretonne/base.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 2dd3c1bf95..30d890b076 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -442,6 +442,29 @@ bnot = Instruction( """, ins=x, outs=a) +# Bitwise binary ops with immediate arg. +x = Operand('x', iB) +Y = Operand('Y', imm64) +a = Operand('a', iB) + +band_imm = Instruction( + 'band_imm', """ + Bitwise and with immediate. + """, + ins=(x, Y), outs=a) + +bor_imm = Instruction( + 'bor_imm', """ + Bitwise or with immediate. + """, + ins=(x, Y), outs=a) + +bxor_imm = Instruction( + 'bxor_imm', """ + Bitwise xor with immediate. + """, + ins=(x, Y), outs=a) + # Shift/rotate. x = Operand('x', Int, doc='Scalar or vector value to shift') y = Operand('y', iB, doc='Number of bits to shift') From 1da15a10d795d23c9d05afcc7b95f920057a264e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 23 Aug 2016 14:45:24 -0700 Subject: [PATCH 221/968] Add RISC-V arithmetic w/immediate operand encodings. Add new instruction predicates to support the 'I' encoding recipe: IsSignedInt, IsUnsignedInt used to test that an immediate operand is in the allowed range. --- meta/cretonne/__init__.py | 12 +++++- meta/cretonne/predicates.py | 76 +++++++++++++++++++++++++++++++++++++ meta/isa/riscv/encodings.py | 24 ++++++++---- meta/isa/riscv/recipes.py | 3 ++ 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index c6693a7c4a..a5e842dd8e 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -725,6 +725,12 @@ class FormatField(object): def __str__(self): return '{}.{}'.format(self.format.name, self.name) + def rust_name(self): + if self.format.boxed_storage: + return 'data.' + self.name + else: + return self.name + class Instruction(object): """ @@ -983,9 +989,13 @@ class EncRecipe(object): :py:class:`InstructionFormat`. """ - def __init__(self, name, format): + def __init__(self, name, format, instp=None, isap=None): self.name = name self.format = format + self.instp = instp + self.isap = isap + if instp: + assert instp.predicate_context() == format class Encoding(object): diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index caedae14fb..71a17eb01b 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -125,3 +125,79 @@ class Not(Predicate): def rust_predicate(self, prec): return '!' + self.parts[0].rust_predicate(Not.precedence) + + +class FieldPredicate(object): + """ + An instruction predicate that performs a test on a single `FormatField`. + + :param field: The `FormatField` to be tested. + :param method: Boolean predicate method to call. + :param args: Arguments for the predicate method. + """ + + def __init__(self, field, method, args): + self.field = field + self.method = method + self.args = args + + def predicate_context(self): + """ + This predicate can be evaluated in the context of an instruction + format. + """ + return self.field.format + + def rust_predicate(self, prec): + """ + Return a string of Rust code that evaluates this predicate. + """ + return '{}.{}({})'.format( + self.field.rust_name(), + self.method, + ', '.join(self.args)) + + +class IsSignedInt(FieldPredicate): + """ + Instruction predicate that checks if an immediate instruction format field + is representable as an n-bit two's complement integer. + + :param field: `FormatField` to be checked. + :param width: Number of bits in the allowed range. + :param scale: Number of low bits that must be 0. + + The predicate is true if the field is in the range: + `-2^(width-1) -- 2^(width-1)-1` + and a multiple of `2^scale`. + """ + + def __init__(self, field, width, scale=0): + super(IsSignedInt, self).__init__( + field, 'is_signed_int', (width, scale)) + self.width = width + self.scale = scale + assert width >= 0 and width <= 64 + assert scale >= 0 and scale < width + + +class IsUnsignedInt(FieldPredicate): + """ + Instruction predicate that checks if an immediate instruction format field + is representable as an n-bit unsigned complement integer. + + :param field: `FormatField` to be checked. + :param width: Number of bits in the allowed range. + :param scale: Number of low bits that must be 0. + + The predicate is true if the field is in the range: + `0 -- 2^width - 1` and a multiple of `2^scale`. + """ + + def __init__(self, field, width, scale=0): + super(IsUnsignedInt, self).__init__( + field, 'is_unsigned_int', (width, scale)) + self.width = width + self.scale = scale + assert width >= 0 and width <= 64 + assert scale >= 0 and scale < width diff --git a/meta/isa/riscv/encodings.py b/meta/isa/riscv/encodings.py index 3ce5102760..099b6b5630 100644 --- a/meta/isa/riscv/encodings.py +++ b/meta/isa/riscv/encodings.py @@ -4,20 +4,28 @@ RISC-V Encodings. from __future__ import absolute_import from cretonne import base from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt +from .recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt, I # Basic arithmetic binary instructions are encoded in an R-type instruction. -for inst, f3, f7 in [ - (base.iadd, 0b000, 0b0000000), - (base.isub, 0b000, 0b0100000), - (base.bxor, 0b100, 0b0000000), - (base.bor, 0b110, 0b0000000), - (base.band, 0b111, 0b0000000) +for inst, inst_imm, f3, f7 in [ + (base.iadd, base.iadd_imm, 0b000, 0b0000000), + (base.isub, None, 0b000, 0b0100000), + (base.bxor, base.bxor_imm, 0b100, 0b0000000), + (base.bor, base.bor_imm, 0b110, 0b0000000), + (base.band, base.band_imm, 0b111, 0b0000000) ]: RV32.enc(inst.i32, R, OP(f3, f7)) RV64.enc(inst.i64, R, OP(f3, f7)) -# Dynamic shifts have the same masking semantics as the cton base instructions + # Immediate versions for add/xor/or/and. + if inst_imm: + RV32.enc(inst_imm.i32, I, OPIMM(f3)) + RV64.enc(inst_imm.i64, I, OPIMM(f3)) + +# There are no andiw/oriw/xoriw variations. +RV64.enc(base.iadd_imm.i32, I, OPIMM32(0b000)) + +# Dynamic shifts have the same masking semantics as the cton base instructions. for inst, inst_imm, f3, f7 in [ (base.ishl, base.ishl_imm, 0b001, 0b0000000), (base.ushr, base.ushr_imm, 0b101, 0b0000000), diff --git a/meta/isa/riscv/recipes.py b/meta/isa/riscv/recipes.py index 25ea74870f..db6d7021c8 100644 --- a/meta/isa/riscv/recipes.py +++ b/meta/isa/riscv/recipes.py @@ -11,6 +11,7 @@ instruction formats described in the reference: from __future__ import absolute_import from cretonne import EncRecipe from cretonne.formats import Binary, BinaryImm +from cretonne.predicates import IsSignedInt # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit # instructions have 11 as the two low bits, with bits 6:2 determining the base @@ -63,3 +64,5 @@ R = EncRecipe('R', Binary) # R-type with an immediate shift amount instead of rs2. Rshamt = EncRecipe('Rshamt', BinaryImm) + +I = EncRecipe('I', BinaryImm, instp=IsSignedInt(BinaryImm.imm, 12)) From ea6eab4b3cadbb801dd088cef68aea2a79a35f75 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Wed, 24 Aug 2016 13:13:31 -0700 Subject: [PATCH 222/968] Add additional test cases --- .../dominator_tree_testdata/tall-tree.cton | 31 +++++++++++++++ .../dominator_tree_testdata/wide-tree.cton | 39 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/tools/tests/dominator_tree_testdata/tall-tree.cton create mode 100644 src/tools/tests/dominator_tree_testdata/wide-tree.cton diff --git a/src/tools/tests/dominator_tree_testdata/tall-tree.cton b/src/tools/tests/dominator_tree_testdata/tall-tree.cton new file mode 100644 index 0000000000..d2ea0f8a9e --- /dev/null +++ b/src/tools/tests/dominator_tree_testdata/tall-tree.cton @@ -0,0 +1,31 @@ +function test(i32) { + ebb0(v0: i32): ; dominates(0) + brz v0, ebb1 ; dominates(1) + brnz v0, ebb2 ; dominates(2,5) + jump ebb3 ; dominates(3) + ebb1: + jump ebb4 ; dominates(4) + ebb2: + jump ebb5 + ebb3: + jump ebb5 + ebb4: + brz v0, ebb6 ; dominates(6,10) + jump ebb7 ; dominates(7) + ebb5: + return + ebb6: + brz v0, ebb8 ; dominates(11,8) + brnz v0, ebb9 ; dominates(9) + jump ebb10 + ebb7: + jump ebb10 + ebb8: + jump ebb11 + ebb9: + jump ebb11 + ebb10: + return + ebb11: + return +} diff --git a/src/tools/tests/dominator_tree_testdata/wide-tree.cton b/src/tools/tests/dominator_tree_testdata/wide-tree.cton new file mode 100644 index 0000000000..0883ef6514 --- /dev/null +++ b/src/tools/tests/dominator_tree_testdata/wide-tree.cton @@ -0,0 +1,39 @@ +function test(i32) { + ebb0(v0: i32): ; dominates(0) + brz v0, ebb13 ; dominates(13) + jump ebb1 ; dominates(1) + ebb1: + brz v0, ebb2 ; dominates(2,7) + brnz v0, ebb3 ; dominates(3) + brz v0, ebb4 ; dominates(4) + brnz v0, ebb5 ; dominates(5) + jump ebb6 ; dominates(6) + ebb2: + jump ebb7 + ebb3: + jump ebb7 + ebb4: + jump ebb7 + ebb5: + jump ebb7 + ebb6: + jump ebb7 + ebb7: + brnz v0, ebb8 ; dominates(8,12) + brz v0, ebb9 ; dominates(9) + brnz v0, ebb10 ; dominates(10) + jump ebb11 ; dominates(11) + ebb8: + jump ebb12 + ebb9: + jump ebb12 + ebb10: + brz v0, ebb13 + jump ebb12 + ebb11: + jump ebb13 + ebb12: + return + ebb13: + return +} From cdd5872a1b93b575503faabc63aacaa83a2caac1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 24 Aug 2016 14:37:32 -0700 Subject: [PATCH 223/968] Clarify that Imm64 holds sign-extended values. When representing smaller integer types, immediate values should be sign-extended to i64. --- src/libcretonne/ir/immediates.rs | 14 +++++++++----- src/libreader/parser.rs | 7 +++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/libcretonne/ir/immediates.rs b/src/libcretonne/ir/immediates.rs index 077ea4205f..99276bfd66 100644 --- a/src/libcretonne/ir/immediates.rs +++ b/src/libcretonne/ir/immediates.rs @@ -11,16 +11,20 @@ use std::str::FromStr; /// 64-bit immediate integer operand. /// +/// An `Imm64` operand can also be used to represent immediate values of smaller integer types by +/// sign-extending to `i64`. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Imm64(i64); impl Imm64 { - pub fn from_bits(x: u64) -> Imm64 { - Imm64(x as i64) + pub fn new(x: i64) -> Imm64 { + Imm64(x) } +} - pub fn to_bits(&self) -> u64 { - self.0 as u64 +impl Into for Imm64 { + fn into(self) -> i64 { + self.0 } } @@ -115,7 +119,7 @@ impl FromStr for Imm64 { return Err("Negative number too small for Imm64"); } } - Ok(Imm64::from_bits(value)) + Ok(Imm64::new(value as i64)) } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 87c336906a..fa6a445158 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -596,8 +596,11 @@ impl<'a> Parser<'a> { try!(self.match_identifier("stack_slot", "expected 'stack_slot'")); // stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag} - let bytes = try!(self.match_imm64("expected byte-size in stack_slot decl")).to_bits(); - if bytes > u32::MAX as u64 { + let bytes: i64 = try!(self.match_imm64("expected byte-size in stack_slot decl")).into(); + if bytes < 0 { + return err!(self.loc, "negative stack slot size"); + } + if bytes > u32::MAX as i64 { return err!(self.loc, "stack slot too large"); } let data = StackSlotData::new(bytes as u32); From e812041738800c5ea256c5a033329528acdb83ad Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 24 Aug 2016 15:15:37 -0700 Subject: [PATCH 224/968] Add module with commonly used immediate predicates. --- src/libcretonne/lib.rs | 1 + src/libcretonne/predicates.rs | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/libcretonne/predicates.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 7bdda395ab..fcddbdb900 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -16,6 +16,7 @@ pub mod entity_map; pub mod settings; mod constant_hash; +mod predicates; #[cfg(test)] pub mod test_utils; diff --git a/src/libcretonne/predicates.rs b/src/libcretonne/predicates.rs new file mode 100644 index 0000000000..83676be5a0 --- /dev/null +++ b/src/libcretonne/predicates.rs @@ -0,0 +1,66 @@ +//! Predicate functions for testing instruction fields. +//! +//! This module defines functions that are used by the instruction predicates defined by +//! `meta/cretonne/predicates.py` classes. +//! +//! The predicates the operate on integer fields use `Into` as a shared trait bound. This +//! bound is implemented by all the native integer types as well as `Imm64`. +//! +//! Some of these predicates may be unused in certain ISA configurations, so we suppress the +//! dead_code warning. + +/// Check that `x` can be represented as a `wd`-bit signed integer with `sc` low zero bits. +#[allow(dead_code)] +pub fn is_signed_int>(x: T, wd: u8, sc: u8) -> bool { + let s = x.into(); + s == (s >> sc << (64 - wd + sc) >> (64 - wd)) +} + +/// Check that `x` can be represented as a `wd`-bit unsigned integer with `sc` low zero bits. +#[allow(dead_code)] +pub fn is_unsigned_int>(x: T, wd: u8, sc: u8) -> bool { + let u = x.into() as u64; + // Bitmask of the permitted bits. + let m = (1 << wd) - (1 << sc); + u == (u & m) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cvt_u32() { + let x1 = 0u32; + let x2 = 1u32; + let x3 = 0xffff_fff0u32; + + assert!(is_signed_int(x1, 1, 0)); + assert!(is_signed_int(x1, 2, 1)); + assert!(is_signed_int(x2, 2, 0)); + assert!(!is_signed_int(x2, 2, 1)); + + // u32 doesn't sign-extend when converted to i64. + assert!(!is_signed_int(x3, 8, 0)); + + assert!(is_unsigned_int(x1, 1, 0)); + assert!(is_unsigned_int(x1, 8, 4)); + assert!(is_unsigned_int(x2, 1, 0)); + assert!(!is_unsigned_int(x2, 8, 4)); + assert!(!is_unsigned_int(x3, 1, 0)); + assert!(is_unsigned_int(x3, 32, 4)); + } + + #[test] + fn cvt_imm64() { + use ir::immediates::Imm64; + + let x1 = Imm64::new(-8); + let x2 = Imm64::new(8); + + assert!(is_signed_int(x1, 16, 2)); + assert!(is_signed_int(x2, 16, 2)); + assert!(!is_signed_int(x1, 16, 4)); + assert!(!is_signed_int(x2, 16, 4)); + } +} From 9853657220cae30effdbbb265052ba67f4c52035 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 24 Aug 2016 16:02:41 -0700 Subject: [PATCH 225/968] Allow predicates on both EncRecipe and Encoding. If both specify a predicate, combine them with 'And'. --- meta/cretonne/__init__.py | 8 +++++++- meta/cretonne/predicates.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index a5e842dd8e..d265009005 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -8,6 +8,7 @@ from __future__ import absolute_import import re import importlib from collections import namedtuple +from .predicates import And camel_re = re.compile('(^|_)([a-z])') @@ -1010,9 +1011,11 @@ class Encoding(object): being encoded. :param recipe: The :py:class:`EncRecipe` to use. :param encbits: Additional encoding bits to be interpreted by `recipe`. + :param instp: Instruction predicate, or `None`. + :param isap: ISA predicate, or `None`. """ - def __init__(self, cpumode, inst, recipe, encbits): + def __init__(self, cpumode, inst, recipe, encbits, instp=None, isap=None): assert isinstance(cpumode, CPUMode) assert isinstance(recipe, EncRecipe) self.inst, self.typevars = inst.fully_bound() @@ -1022,6 +1025,9 @@ class Encoding(object): self.inst.format, recipe.format)) self.recipe = recipe self.encbits = encbits + # Combine recipe predicates with the manually specified ones. + self.instp = And.combine(recipe.instp, instp) + self.isap = And.combine(recipe.isap, instp) @staticmethod def _to_type_tuple(x): diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index 71a17eb01b..d6cb8f8b96 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -95,6 +95,22 @@ class And(Predicate): s = '({})'.format(s) return s + @staticmethod + def combine(*args): + """ + Combine a sequence of predicates, allowing for `None` members. + + Return a predicate that is true when all non-`None` arguments are true, + or `None` if all of the arguments are `None`. + """ + args = tuple(p for p in args if p) + if args == (): + return None + if len(args) == 1: + return args[0] + # We have multiple predicate args. Combine with `And`. + return And(*args) + class Or(Predicate): """ From 21ba900d19579f58780eaf1b47774832eb4a7ab7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 24 Aug 2016 16:29:54 -0700 Subject: [PATCH 226/968] Pass arguments on to rustfmt. This allows the usage: src/format-all.sh --write-mode=diff --- src/format-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/format-all.sh b/src/format-all.sh index 3569c460be..7d51b7d343 100755 --- a/src/format-all.sh +++ b/src/format-all.sh @@ -10,5 +10,5 @@ src=$(pwd) for crate in $(find "$src" -name Cargo.toml); do cd $(dirname "$crate") - cargo fmt + cargo fmt -- "$@" done From d0db39189797ca04a39dfa660d7eef3051a266f6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 24 Aug 2016 16:36:52 -0700 Subject: [PATCH 227/968] Verify Rust source code formatting as part of the unit tests. Only do this is rustfmt is installed, which likely means don't run on Travis CI. --- test-all.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-all.sh b/test-all.sh index 4b65c9b56f..e4914b9541 100755 --- a/test-all.sh +++ b/test-all.sh @@ -21,6 +21,12 @@ function banner() { echo "====== $@ ======" } +# Run rustfmt if we have it. (Travis probably won't). +if cargo install --list | grep -q '^rustfmt '; then + banner "Rust formatting" + $topdir/src/format-all.sh --write-mode=diff +fi + PKGS="cretonne cretonne-reader cretonne-tools" cd "$topdir/src/tools" for PKG in $PKGS From 4d1eb84037ee5aabf6a1b74ea2d83309e429b080 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 25 Aug 2016 09:50:23 -0700 Subject: [PATCH 228/968] Add a predicate_leafs() method. This collects all of the leaf predicates that go into a compound predicate. Current leaf predicates are: - Settings for ISA predicates, and - FieldPredicates for instruction predicates. --- meta/cretonne/__init__.py | 3 +++ meta/cretonne/predicates.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index d265009005..a286c1fb39 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -41,6 +41,9 @@ class Setting(object): """ return self.group + def predicate_leafs(self, leafs): + leafs.add(self) + class BoolSetting(Setting): """ diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index d6cb8f8b96..cd2ef242fe 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -68,6 +68,13 @@ class Predicate(object): def predicate_context(self): return self.context + def predicate_leafs(self, leafs): + """ + Collect all leaf predicates into the `leafs` set. + """ + for part in self.parts: + part.predicate_leafs(leafs) + class And(Predicate): """ @@ -164,6 +171,9 @@ class FieldPredicate(object): """ return self.field.format + def predicate_leafs(self, leafs): + leafs.add(self) + def rust_predicate(self, prec): """ Return a string of Rust code that evaluates this predicate. From c251f26d0d2c2ccf1672d72ba132a11983ba761b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 25 Aug 2016 11:36:30 -0700 Subject: [PATCH 229/968] Collect list of CPU modes in TargetISA. Fix a typo nearby. --- meta/cretonne/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index a286c1fb39..8dd0535b24 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -949,10 +949,11 @@ class TargetISA(object): relevant for this ISA. """ - def __init__(self, name, instrution_groups): + def __init__(self, name, instruction_groups): self.name = name self.settings = None - self.instruction_groups = instrution_groups + self.instruction_groups = instruction_groups + self.cpumodes = list() class CPUMode(object): @@ -970,6 +971,7 @@ class CPUMode(object): self.name = name self.isa = isa self.encodings = [] + isa.cpumodes.append(self) def enc(self, *args, **kwargs): """ @@ -1032,17 +1034,15 @@ class Encoding(object): self.instp = And.combine(recipe.instp, instp) self.isap = And.combine(recipe.isap, instp) - @staticmethod - def _to_type_tuple(x): - # Allow a single ValueType instance instead of the awkward singleton - # tuple syntax. - if isinstance(x, ValueType): - x = (x,) + def ctrl_typevar(self): + """ + Get the controlling type variable for this encoding or `None`. + """ + if self.typevars: + return self.typevars[0] else: - x = tuple(x) - for ty in x: - assert isinstance(ty, ValueType) - return x + return None + # Import the fixed instruction formats now so they can be added to the # registry. From b788ab80201bd5795ac1bf12f0eaec8f6fbf53b9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 25 Aug 2016 11:55:57 -0700 Subject: [PATCH 230/968] Fix Python3 compat in docs directory. Update copyright. --- docs/conf.py | 177 ++------------------------------------------ docs/cton_domain.py | 4 +- docs/cton_lexer.py | 21 ++++-- 3 files changed, 23 insertions(+), 179 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c4ab93eff0..5602561828 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,9 +12,10 @@ # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import absolute_import import sys import os -import shlex + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -27,9 +28,6 @@ sys.path.insert(0, os.path.abspath('../meta')) # -- General configuration ------------------------------------------------ -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. @@ -52,16 +50,13 @@ templates_path = ['_templates'] # source_suffix = ['.rst', '.md'] source_suffix = '.rst' -# The encoding of source files. -#source_encoding = 'utf-8-sig' - # The master toctree document. master_doc = 'index' # General information about the project. project = u'cretonne' -copyright = u'2016, Jakob Stoklund Olesen' -author = u'Jakob Stoklund Olesen' +copyright = u'2016, Cretonne Developers' +author = u'Cretonne Developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -79,40 +74,13 @@ release = u'0.0' # Usually you set "language" from the command line for these cases. language = None -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True @@ -123,112 +91,12 @@ todo_include_todos = True # a list of builtin themes. html_theme = 'sphinx_rtd_theme' -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - # Output file base name for HTML help builder. htmlhelp_basename = 'cretonnedoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples @@ -236,29 +104,9 @@ latex_elements = { # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'cretonne.tex', u'cretonne Documentation', - u'Jakob Stoklund Olesen', 'manual'), + author, 'manual'), ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - # -- Options for manual page output --------------------------------------- @@ -269,9 +117,6 @@ man_pages = [ [author], 1) ] -# If true, show URL addresses after external links. -#man_show_urls = False - # -- Options for Texinfo output ------------------------------------------- @@ -284,18 +129,6 @@ texinfo_documents = [ 'Miscellaneous'), ] -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False - # -- Options for Graphviz ------------------------------------------------- diff --git a/docs/cton_domain.py b/docs/cton_domain.py index aafb7724cf..ba762b4df3 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -9,6 +9,7 @@ # .. cton:inst:: v1, v2 = inst op1, op2 # Document an IR instruction. # +from __future__ import absolute_import import re @@ -133,7 +134,8 @@ class CtonInst(CtonObject): TypedField('result', label=l_('Results'), names=('out', 'result'), typerolename='type', typenames=('type',)), - GroupedField('typevar', names=('typevar',), label=l_('Type Variables')), + GroupedField( + 'typevar', names=('typevar',), label=l_('Type Variables')), GroupedField('flag', names=('flag',), label=l_('Flags')), Field('resulttype', label=l_('Result type'), has_arg=False, names=('rtype',)), diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 28a1be25b4..eaa856c444 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- # # Pygments lexer for Cretonne. +from __future__ import absolute_import from pygments.lexer import RegexLexer, bygroups, words -from pygments.token import * +from pygments.token import Comment, String, Keyword, Whitespace, Number, Name +from pygments.token import Operator, Punctuation, Text + def keywords(*args): return words(args, prefix=r'\b', suffix=r'\b') + class CretonneLexer(RegexLexer): name = 'Cretonne' aliases = ['cton'] @@ -19,7 +23,8 @@ class CretonneLexer(RegexLexer): # Strings are in double quotes, support \xx escapes only. (r'"([^"\\]+|\\[0-9a-fA-F]{2})*"', String), # A naked function name following 'function' is also a string. - (r'\b(function)([ \t]+)(\w+)\b', bygroups(Keyword, Whitespace, String.Symbol)), + (r'\b(function)([ \t]+)(\w+)\b', + bygroups(Keyword, Whitespace, String.Symbol)), # Numbers. (r'[-+]?0[xX][0-9a-fA-F]+', Number.Hex), (r'[-+]?0[xX][0-9a-fA-F]*\.[0-9a-fA-F]*([pP]\d+)?', Number.Hex), @@ -28,7 +33,8 @@ class CretonneLexer(RegexLexer): # Reserved words. (keywords('function'), Keyword), # Known attributes. - (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), Name.Attribute), + (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), + Name.Attribute), # Well known value types. (r'\b(b\d+|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value @@ -37,8 +43,10 @@ class CretonneLexer(RegexLexer): # ebb = extended basic block (r'(ebb)\d+', Name.Label), # Match instruction names in context. - (r'(=)( *)([a-z]\w*)', bygroups(Operator, Whitespace, Name.Function)), - (r'^( *)([a-z]\w*\b)(?! *[,=])', bygroups(Whitespace, Name.Function)), + (r'(=)( *)([a-z]\w*)', + bygroups(Operator, Whitespace, Name.Function)), + (r'^( *)([a-z]\w*\b)(?! *[,=])', + bygroups(Whitespace, Name.Function)), # Other names: results and arguments (r'[a-z]\w*', Name), (r'->|=|:', Operator), @@ -47,8 +55,9 @@ class CretonneLexer(RegexLexer): ] } + def setup(app): """Setup Sphinx extension.""" app.add_lexer('cton', CretonneLexer()) - return { 'version' : '0.1' } + return {'version': '0.1'} From 5f6859f0d91f391c78c4662c794a90aca2c9b01c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 25 Aug 2016 14:24:02 -0700 Subject: [PATCH 231/968] Call function in the predicates module. When generating Rust code for an instruction predicate, call the corresponding function in the predicates module, using a qualified name. We don't have methods corresponding to the predicates. --- meta/cretonne/predicates.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index cd2ef242fe..f99f4cfc5f 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -155,13 +155,13 @@ class FieldPredicate(object): An instruction predicate that performs a test on a single `FormatField`. :param field: The `FormatField` to be tested. - :param method: Boolean predicate method to call. - :param args: Arguments for the predicate method. + :param function: Boolean predicate function to call. + :param args: Additional arguments for the predicate function. """ - def __init__(self, field, method, args): + def __init__(self, field, function, args): self.field = field - self.method = method + self.function = function self.args = args def predicate_context(self): @@ -178,10 +178,9 @@ class FieldPredicate(object): """ Return a string of Rust code that evaluates this predicate. """ - return '{}.{}({})'.format( - self.field.rust_name(), - self.method, - ', '.join(self.args)) + # Prepend `field` to the predicate function arguments. + args = (self.field.rust_name(),) + tuple(map(str, self.args)) + return 'predicates::{}({})'.format(self.function, ', '.join(args)) class IsSignedInt(FieldPredicate): From 4f14d1ea32335058a5e549362663072076a35e21 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 18 Aug 2016 11:47:01 -0700 Subject: [PATCH 232/968] Generate encoding tables. (WIP). Amend build script to generate an encodings-.rs file for each target ISA. Emit a function that can evaluate instruction predicates. Describe the 3-level tables used for representing insrruction encoding tables. Add Python classes representing the tables. The generated code is incomplete and not used anywhere yet. --- meta/build.py | 2 + meta/gen_encoding.py | 212 +++++++++++++++++++++++++++++++++++++++++++ meta/srcgen.py | 11 ++- 3 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 meta/gen_encoding.py diff --git a/meta/build.py b/meta/build.py index 2806426e6d..3ccbd49c0f 100644 --- a/meta/build.py +++ b/meta/build.py @@ -8,6 +8,7 @@ import isa import gen_instr import gen_settings import gen_build_deps +import gen_encoding parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') @@ -19,4 +20,5 @@ isas = isa.all_isas() gen_instr.generate(isas, out_dir) gen_settings.generate(isas, out_dir) +gen_encoding.generate(isas, out_dir) gen_build_deps.generate() diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py new file mode 100644 index 0000000000..c1e618e046 --- /dev/null +++ b/meta/gen_encoding.py @@ -0,0 +1,212 @@ +""" +Generate sources for instruction encoding. + +The tables and functions generated here support the `TargetIsa::encode()` +function which determines if a given instruction is legal, and if so, it's +`Encoding` data which consists of a *recipe* and some *encoding* bits. + +The `encode` function doesn't actually generate the binary machine bits. Each +recipe has a corresponding hand-written function to do that after registers +are allocated. + +This is the information available to us: + +- The instruction to be encoded as an `Inst` reference. +- The data-flow graph containing the instruction, giving us access to the + `InstructionData` representation and the types of all values involved. +- A target ISA instance with shared and ISA-specific settings for evaluating + ISA predicates. +- The currently active CPU mode is determined by the ISA. + +## Level 1 table lookup + +The CPU mode provides the first table. The key is the instruction's controlling +type variable. If the instruction is not polymorphic, use `VOID` for the type +variable. The table values are level 2 tables. + +## Level 2 table lookup + +The level 2 table is keyed by the instruction's opcode. The table values are +*encoding lists*. + +The two-level table lookup allows the level 2 tables to be much smaller with +good locality. Code in any given function usually only uses a few different +types, so many of the level 2 tables will be cold. + +## Encoding lists + +An encoding list is a non-empty sequence of list entries. Each entry has +one of these forms: + +1. Instruction predicate, encoding recipe, and encoding bits. If the + instruction predicate is true, use this recipe and bits. +2. ISA predicate and skip-count. If the ISA predicate is false, skip the next + *skip-count* entries in the list. If the skip count is zero, stop + completely. +3. Stop. End of list marker. If this is reached, the instruction does not have + a legal encoding. + +The instruction predicate is also used to distinguish between polymorphic +instructions with different types for secondary type variables. +""" +from __future__ import absolute_import +import srcgen +from collections import OrderedDict + + +def emit_instp(instp, fmt): + """ + Emit code for matching an instruction predicate against an + `InstructionData` reference called `inst`. + + The generated code is a pattern match that falls through if the instruction + has an unexpected format. This should lead to a panic. + """ + iform = instp.predicate_context() + + # Which fiels do we need in the InstructionData pattern match? + if iform.boxed_storage: + fields = 'ref data' + else: + # Collect the leaf predicates + leafs = set() + instp.predicate_leafs(leafs) + # All the leafs are FieldPredicate instances. Here we just care about + # the field names. + fields = ', '.join(sorted(set(p.field.name for p in leafs))) + + with fmt.indented( + 'if let {} {{ {}, .. }} = *inst {{' + .format(iform.name, fields), '}'): + fmt.line('return {};'.format(instp.rust_predicate(0))) + + +def emit_instps(instps, fmt): + """ + Emit a function for matching instruction predicates. + """ + + with fmt.indented( + 'fn check_instp(inst: &InstructionData, instp_idx: u16) -> bool {', + '}'): + with fmt.indented('match instp_idx {', '}'): + for (instp, idx) in instps.items(): + with fmt.indented('{} => {{'.format(idx), '}'): + emit_instp(instp, fmt) + fmt.line('_ => panic!("Invalid instruction predicate")') + + # The match cases will fall through if the instruction format is wrong. + fmt.line('panic!("Bad format {}/{} for instp {}",') + fmt.line(' InstructionFormat::from(inst),') + fmt.line(' inst.opcode(),') + fmt.line(' instp_idx);') + + +def collect_instps(cpumodes): + # Map instp -> number + instps = OrderedDict() + for cpumode in cpumodes: + for enc in cpumode.encodings: + instp = enc.instp + if instp and instp not in instps: + instps[instp] = 1 + len(instps) + return instps + + +class EncList(object): + """ + List of instructions for encoding a given type + opcode pair. + + An encoding list contains a sequence of predicates and encoding recipes, + all encoded as u16 values. + + :param inst: The instruction opcode being encoded. + :param ty: Value of the controlling type variable, or `None`. + """ + + def __init__(self, inst, ty): + self.inst = inst + self.ty = ty + # List of applicable Encoding instances. + # These will have different predicates. + self.encodings = [] + + def name(self): + if self.ty: + return '{}.{}'.format(self.inst.name, self.ty.name) + else: + return self.inst.name + + +class Level2Table(object): + """ + Level 2 table mapping instruction opcodes to `EncList` objects. + + :param ty: Controlling type variable of all entries, or `None`. + """ + + def __init__(self, ty): + self.ty = ty + # Maps inst -> EncList + self.lists = OrderedDict() + + def __getitem__(self, inst): + ls = self.lists.get(inst) + if not ls: + ls = EncList(inst, self.ty) + self.lists[inst] = ls + return ls + + def __iter__(self): + return iter(self.lists.values()) + + +class Level1Table(object): + """ + Level 1 table mapping types to `Level2` objects. + """ + + def __init__(self): + self.tables = OrderedDict() + + def __getitem__(self, ty): + tbl = self.tables.get(ty) + if not tbl: + tbl = Level2Table(ty) + self.tables[ty] = tbl + return tbl + + def __iter__(self): + return iter(self.tables.values()) + + +def make_tables(cpumode): + """ + Generate tables for `cpumode` as described above. + """ + table = Level1Table() + for enc in cpumode.encodings: + ty = enc.ctrl_typevar() + inst = enc.inst + table[ty][inst].encodings.append(enc) + return table + + +def gen_isa(cpumodes, fmt): + # First assign numbers to relevant instruction predicates and generate the + # check_instp() function.. + instps = collect_instps(cpumodes) + emit_instps(instps, fmt) + + for cpumode in cpumodes: + level1 = make_tables(cpumode) + for level2 in level1: + for enclist in level2: + fmt.comment(enclist.name()) + + +def generate(isas, out_dir): + for isa in isas: + fmt = srcgen.Formatter() + gen_isa(isa.cpumodes, fmt) + fmt.update_file('encoding-{}.rs'.format(isa.name), out_dir) diff --git a/meta/srcgen.py b/meta/srcgen.py index 99bb808435..0b8a7a2973 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -51,12 +51,21 @@ class Formatter(object): self.indent = self.indent[0:-self.shiftwidth] def line(self, s=None): - """And an indented line.""" + """Add an indented line.""" if s: self.lines.append('{}{}\n'.format(self.indent, s)) else: self.lines.append('\n') + def outdented_line(self, s): + """ + Emit a line outdented one level. + + This is used for '} else {' and similar things inside a single indented + block. + """ + self.lines.append('{}{}\n'.format(self.indent[0:-self.shiftwidth], s)) + def writelines(self, f=None): """Write all lines to `f`.""" if not f: From 0b1aa7c6cd9e7ac7fc562c6e4f8e643982fd99ea Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 08:50:47 -0700 Subject: [PATCH 233/968] Add string conversions for predicates and encodings. This is just used for printing better comments in generated code. --- meta/cretonne/__init__.py | 6 ++++++ meta/cretonne/predicates.py | 12 ++++++++++++ meta/gen_encoding.py | 2 ++ 3 files changed, 20 insertions(+) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 8dd0535b24..88860286dc 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -1003,6 +1003,9 @@ class EncRecipe(object): if instp: assert instp.predicate_context() == format + def __str__(self): + return self.name + class Encoding(object): """ @@ -1034,6 +1037,9 @@ class Encoding(object): self.instp = And.combine(recipe.instp, instp) self.isap = And.combine(recipe.isap, instp) + def __str__(self): + return '[{}/{:02x}]'.format(self.recipe, self.encbits) + def ctrl_typevar(self): """ Get the controlling type variable for this encoding or `None`. diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index f99f4cfc5f..6722c820e5 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -65,6 +65,14 @@ class Predicate(object): (p.predicate_context() for p in parts)) assert self.context, "Incompatible predicate parts" + def __str__(self): + s = '{}({})'.format( + type(self).__name__, + ' ,'.join(map(str, self.parts))) + if self.name: + s = '{}={}'.format(self.name, s) + return s + def predicate_context(self): return self.context @@ -164,6 +172,10 @@ class FieldPredicate(object): self.function = function self.args = args + def __str__(self): + args = (self.field.name,) + tuple(map(str, self.args)) + return '{}({})'.format(self.function, ', '.join(args)) + def predicate_context(self): """ This predicate can be evaluated in the context of an instruction diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index c1e618e046..a7ac7e2f4a 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -203,6 +203,8 @@ def gen_isa(cpumodes, fmt): for level2 in level1: for enclist in level2: fmt.comment(enclist.name()) + for enc in enclist.encodings: + fmt.comment('{} when {}'.format(enc, enc.instp)) def generate(isas, out_dir): From c11d82ea02fdf03049464d9e4c0cd0a781616cbf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 09:31:50 -0700 Subject: [PATCH 234/968] Move predicate collection into TargetISA. Add a TargetISA.finish() method which computes derived data structures after the ISA definitions have been loaded. --- meta/cretonne/__init__.py | 31 +++++++++++++++++++++++++++++++ meta/gen_encoding.py | 34 +++++++++++----------------------- meta/isa/riscv/__init__.py | 5 ++--- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 88860286dc..bc20bcbcf3 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -955,6 +955,37 @@ class TargetISA(object): self.instruction_groups = instruction_groups self.cpumodes = list() + def finish(self): + """ + Finish the definition of a target ISA after adding all CPU modes and + settings. + + This computes some derived properties that are used in multilple + places. + + :returns self: + """ + self._collect_instruction_predicates() + return self + + def _collect_instruction_predicates(self): + """ + Collect and number all instruction predicates in use. + + Sets `instp.number` for all used instruction predicates and places them + in `self.all_instps` in numerical order. + """ + self.all_instps = list() + instps = set() + for cpumode in self.cpumodes: + for enc in cpumode.encodings: + instp = enc.instp + if instp and instp not in instps: + # assign predicate number starting from 0. + instp.number = len(instps) + instps.add(instp) + self.all_instps.append(instp) + class CPUMode(object): """ diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index a7ac7e2f4a..c10a4f8a06 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -75,10 +75,11 @@ def emit_instp(instp, fmt): # the field names. fields = ', '.join(sorted(set(p.field.name for p in leafs))) - with fmt.indented( - 'if let {} {{ {}, .. }} = *inst {{' - .format(iform.name, fields), '}'): - fmt.line('return {};'.format(instp.rust_predicate(0))) + with fmt.indented('{} => {{'.format(instp.number), '}'): + with fmt.indented( + 'if let {} {{ {}, .. }} = *inst {{' + .format(iform.name, fields), '}'): + fmt.line('return {};'.format(instp.rust_predicate(0))) def emit_instps(instps, fmt): @@ -90,9 +91,8 @@ def emit_instps(instps, fmt): 'fn check_instp(inst: &InstructionData, instp_idx: u16) -> bool {', '}'): with fmt.indented('match instp_idx {', '}'): - for (instp, idx) in instps.items(): - with fmt.indented('{} => {{'.format(idx), '}'): - emit_instp(instp, fmt) + for instp in instps: + emit_instp(instp, fmt) fmt.line('_ => panic!("Invalid instruction predicate")') # The match cases will fall through if the instruction format is wrong. @@ -102,17 +102,6 @@ def emit_instps(instps, fmt): fmt.line(' instp_idx);') -def collect_instps(cpumodes): - # Map instp -> number - instps = OrderedDict() - for cpumode in cpumodes: - for enc in cpumode.encodings: - instp = enc.instp - if instp and instp not in instps: - instps[instp] = 1 + len(instps) - return instps - - class EncList(object): """ List of instructions for encoding a given type + opcode pair. @@ -192,13 +181,12 @@ def make_tables(cpumode): return table -def gen_isa(cpumodes, fmt): +def gen_isa(isa, fmt): # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. - instps = collect_instps(cpumodes) - emit_instps(instps, fmt) + emit_instps(isa.all_instps, fmt) - for cpumode in cpumodes: + for cpumode in isa.cpumodes: level1 = make_tables(cpumode) for level2 in level1: for enclist in level2: @@ -210,5 +198,5 @@ def gen_isa(cpumodes, fmt): def generate(isas, out_dir): for isa in isas: fmt = srcgen.Formatter() - gen_isa(isa.cpumodes, fmt) + gen_isa(isa, fmt) fmt.update_file('encoding-{}.rs'.format(isa.name), out_dir) diff --git a/meta/isa/riscv/__init__.py b/meta/isa/riscv/__init__.py index 1f9ebd3f46..e187a95c0e 100644 --- a/meta/isa/riscv/__init__.py +++ b/meta/isa/riscv/__init__.py @@ -26,8 +26,7 @@ RV32G / RV64G """ from __future__ import absolute_import from . import defs -from . import encodings -from . import settings +from . import encodings, settings # noqa # Re-export the primary target ISA definition. -isa = defs.isa +isa = defs.isa.finish() From a6fd6e95d86988140da8ad19f2393dd1713543e5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 09:39:36 -0700 Subject: [PATCH 235/968] Flake8 lints. --- meta/check-py3k.sh | 1 + meta/gen_instr.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/meta/check-py3k.sh b/meta/check-py3k.sh index 19096339bf..3ac939ea15 100755 --- a/meta/check-py3k.sh +++ b/meta/check-py3k.sh @@ -5,3 +5,4 @@ # Install pylint with 'pip install pylint'. cd $(dirname "$0") pylint --py3k --reports=no -- *.py cretonne isa +flake8 . diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 92808524c5..13bcaa6842 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -71,7 +71,8 @@ def gen_instruction_data_impl(fmt): .format(f.name)) fmt.doc_comment('Mutable reference to the type of the first result.') - with fmt.indented('pub fn first_type_mut(&mut self) -> &mut Type {', '}'): + with fmt.indented( + 'pub fn first_type_mut(&mut self) -> &mut Type {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: fmt.line( @@ -152,7 +153,8 @@ def gen_instruction_data_impl(fmt): if f.boxed_storage: fmt.line( n + - ' {{ ref data, .. }} => Some(data.args[{}]),' + ' {{ ref data, .. }} => ' + + 'Some(data.args[{}]),' .format(i)) else: fmt.line( From 4b72d0e64d94058d4ad5773862b9de1f0730dc44 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Fri, 26 Aug 2016 13:39:44 -0700 Subject: [PATCH 236/968] Add another dominator tree test case --- .../tests/dominator_tree_testdata/loops2.cton | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/tools/tests/dominator_tree_testdata/loops2.cton diff --git a/src/tools/tests/dominator_tree_testdata/loops2.cton b/src/tools/tests/dominator_tree_testdata/loops2.cton new file mode 100644 index 0000000000..452641be01 --- /dev/null +++ b/src/tools/tests/dominator_tree_testdata/loops2.cton @@ -0,0 +1,29 @@ +function test(i32) { + ebb0(v0: i32): ; dominates(0) + brz v0, ebb1 ; dominates(1,6) + brnz v0, ebb2 ; dominates(2,9) + jump ebb3 ; dominates(3) + ebb1: + jump ebb6 + ebb2: + brz v0, ebb4 ; dominates(4,7,8) + jump ebb5 ; dominates(5) + ebb3: + jump ebb9 + ebb4: + brz v0, ebb4 + brnz v0, ebb6 + jump ebb7 + ebb5: + brz v0, ebb7 + brnz v0, ebb8 + jump ebb9 + ebb6: + return + ebb7: + jump ebb8 + ebb8: + return + ebb9: + return +} From 747dd508dfb97dbb48fbcaca28e19f4038c58914 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 12:43:38 -0700 Subject: [PATCH 237/968] Generate type numbers at meta-time. We need to generate hash tables keyed by types, so the Python scripts need to know the index used to represent types in Rust code. To enforce this, add a new gen_types.py script which generates constant definitions for the ir/types module. Also generate constants for common SIMD vector sizes. --- meta/build.py | 2 ++ meta/cretonne/__init__.py | 19 +++++++++++--- meta/gen_types.py | 51 +++++++++++++++++++++++++++++++++++++ src/libcretonne/ir/types.rs | 44 +++++++++----------------------- 4 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 meta/gen_types.py diff --git a/meta/build.py b/meta/build.py index 3ccbd49c0f..730d1c9b3f 100644 --- a/meta/build.py +++ b/meta/build.py @@ -5,6 +5,7 @@ from __future__ import absolute_import import argparse import isa +import gen_types import gen_instr import gen_settings import gen_build_deps @@ -18,6 +19,7 @@ out_dir = args.out_dir isas = isa.all_isas() +gen_types.generate(out_dir) gen_instr.generate(isas, out_dir) gen_settings.generate(isas, out_dir) gen_encoding.generate(isas, out_dir) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index bc20bcbcf3..324dc76b4b 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -6,6 +6,7 @@ instructions. """ from __future__ import absolute_import import re +import math import importlib from collections import namedtuple from .predicates import And @@ -279,9 +280,12 @@ class ValueType(object): or one of its subclasses. """ - # map name -> ValueType. + # Map name -> ValueType. _registry = dict() + # List of all the scalar types. + all_scalars = list() + def __init__(self, name, membytes, doc): self.name = name self.membytes = membytes @@ -321,6 +325,10 @@ class ScalarType(ValueType): def __init__(self, name, membytes, doc): super(ScalarType, self).__init__(name, membytes, doc) self._vectors = dict() + # Assign numbers starting from 1. (0 is VOID). + ValueType.all_scalars.append(self) + self.number = len(ValueType.all_scalars) + assert self.number < 16, 'Too many scalar types' def __repr__(self): return 'ScalarType({})'.format(self.name) @@ -356,10 +364,11 @@ class VectorType(ValueType): name='{}x{}'.format(base.name, lanes), membytes=lanes*base.membytes, doc=""" - A SIMD vector with {} lanes containing a {} each. + A SIMD vector with {} lanes containing a `{}` each. """.format(lanes, base.name)) self.base = base self.lanes = lanes + self.number = 16*int(math.log(lanes, 2)) + base.number def __repr__(self): return ('VectorType(base={}, lanes={})' @@ -386,8 +395,10 @@ class FloatType(ScalarType): def __init__(self, bits, doc): assert bits > 0, 'FloatType must have positive number of bits' - super(FloatType, self).__init__(name='f{:d}'.format(bits), - membytes=bits // 8, doc=doc) + super(FloatType, self).__init__( + name='f{:d}'.format(bits), + membytes=bits // 8, + doc=doc) self.bits = bits def __repr__(self): diff --git a/meta/gen_types.py b/meta/gen_types.py new file mode 100644 index 0000000000..51607a61f9 --- /dev/null +++ b/meta/gen_types.py @@ -0,0 +1,51 @@ +""" +Generate sources with type info. + +This generates a `types.rs` file which is included in +`libcretonne/ir/types/rs`. The file provides constant definitions for the most +commonly used types, including all of the scalar types. + +This ensures that Python and Rust use the same type numbering. +""" +from __future__ import absolute_import +import srcgen +from cretonne import ValueType + + +def emit_type(ty, fmt): + """ + Emit a constant definition of a single value type. + """ + name = ty.name.upper() + fmt.doc_comment(ty.__doc__) + fmt.line( + 'pub const {}: Type = Type({:#x});' + .format(name, ty.number)) + + +def emit_vectors(bits, fmt): + """ + Emit definition for all vector types with `bits` total size. + """ + size = bits // 8 + for ty in ValueType.all_scalars: + mb = ty.membytes + if mb == 0 or mb >= size: + continue + emit_type(ty.by(size // mb), fmt) + + +def emit_types(fmt): + for ty in ValueType.all_scalars: + emit_type(ty, fmt) + # Emit vector definitions for common SIMD sizes. + emit_vectors(64, fmt) + emit_vectors(128, fmt) + emit_vectors(256, fmt) + emit_vectors(512, fmt) + + +def generate(out_dir): + fmt = srcgen.Formatter() + emit_types(fmt) + fmt.update_file('types.rs', out_dir) diff --git a/src/libcretonne/ir/types.rs b/src/libcretonne/ir/types.rs index 1bc0d0a0b4..12dfc8c6ec 100644 --- a/src/libcretonne/ir/types.rs +++ b/src/libcretonne/ir/types.rs @@ -31,38 +31,9 @@ pub struct Type(u8); /// a SIMD vector. pub const VOID: Type = Type(0); -/// Integer type with 8 bits. -pub const I8: Type = Type(1); - -/// Integer type with 16 bits. -pub const I16: Type = Type(2); - -/// Integer type with 32 bits. -pub const I32: Type = Type(3); - -/// Integer type with 64 bits. -pub const I64: Type = Type(4); - -/// IEEE single precision floating point type. -pub const F32: Type = Type(5); - -/// IEEE double precision floating point type. -pub const F64: Type = Type(6); - -/// Boolean type. Can't be loaded or stored, but can be used to form SIMD vectors. -pub const B1: Type = Type(7); - -/// Boolean type using 8 bits to represent true/false. -pub const B8: Type = Type(8); - -/// Boolean type using 16 bits to represent true/false. -pub const B16: Type = Type(9); - -/// Boolean type using 32 bits to represent true/false. -pub const B32: Type = Type(10); - -/// Boolean type using 64 bits to represent true/false. -pub const B64: Type = Type(11); +// Include code generated by `meta/gen_types.py`. This file contains constant definitions for all +// the scalar types as well as common vector types for 64, 128, 256, and 512-bit SID vectors. +include!(concat!(env!("OUT_DIR"), "/types.rs")); impl Type { /// Get the lane type of this SIMD vector type. @@ -183,6 +154,11 @@ impl Type { Some(Type(self.0 - 0x10)) } } + + /// Index of this type, for use with hash tables etc. + pub fn index(self) -> usize { + self.0 as usize + } } impl Display for Type { @@ -362,6 +338,10 @@ mod tests { assert_eq!(B1.by(2).unwrap().half_vector().unwrap().to_string(), "b1"); assert_eq!(I32.half_vector(), None); assert_eq!(VOID.half_vector(), None); + + // Check that the generated constants match the computed vector types. + assert_eq!(I32.by(4), Some(I32X4)); + assert_eq!(F64.by(8), Some(F64X8)); } #[test] From a26673654f531e78111d4a111922967f501d120a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 14:48:28 -0700 Subject: [PATCH 238/968] Collect and number all active encoding recipes. The recipes are shared across CPU modes. --- meta/cretonne/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 324dc76b4b..2e316982c0 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -976,9 +976,24 @@ class TargetISA(object): :returns self: """ + self._collect_encoding_recipes() self._collect_instruction_predicates() return self + def _collect_encoding_recipes(self): + """ + Collect and number all encoding recipes in use. + """ + self.all_recipes = list() + rcps = set() + for cpumode in self.cpumodes: + for enc in cpumode.encodings: + recipe = enc.recipe + if recipe not in rcps: + recipe.number = len(rcps) + rcps.add(recipe) + self.all_recipes.append(recipe) + def _collect_instruction_predicates(self): """ Collect and number all instruction predicates in use. From 176427e22072b61b0687e5bdce9cc044028bdfa4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 14:44:03 -0700 Subject: [PATCH 239/968] Emit encoding lists (WIP). Compute the u16 representation of encoding lists and emit a big table concatenating all of them. Use the UniqueSeqTable to share some table space between CPU modes. --- meta/cretonne/__init__.py | 3 + meta/gen_encoding.py | 112 +++++++++++++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 2e316982c0..b44a238ffd 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -1030,6 +1030,9 @@ class CPUMode(object): self.encodings = [] isa.cpumodes.append(self) + def __str__(self): + return self.name + def enc(self, *args, **kwargs): """ Add a new encoding to this CPU mode. diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index c10a4f8a06..278ed66da5 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -51,7 +51,8 @@ instructions with different types for secondary type variables. """ from __future__ import absolute_import import srcgen -from collections import OrderedDict +from unique_table import UniqueSeqTable +from collections import OrderedDict, defaultdict def emit_instp(instp, fmt): @@ -102,6 +103,43 @@ def emit_instps(instps, fmt): fmt.line(' instp_idx);') +# Encoding lists are represented as u16 arrays. +CODE_BITS = 16 +PRED_BITS = 12 +PRED_MASK = (1 << PRED_BITS) - 1 + +# 0..CODE_ALWAYS means: Check instruction predicate and use the next two +# entries as a (recipe, encbits) pair if true. CODE_ALWAYS is the always-true +# predicate, smaller numbers refer to instruction predicates. +CODE_ALWAYS = PRED_MASK + +# Codes above CODE_ALWAYS indicate an ISA predicate to be tested. +# `x & PRED_MASK` is the ISA predicate number to test. +# `(x >> PRED_BITS)*3` is the number of u16 table entries to skip if the ISA +# predicate is false. (The factor of three corresponds to the (inst-pred, +# recipe, encbits) triples. +# +# Finally, CODE_FAIL indicates the end of the list. +CODE_FAIL = (1 << CODE_BITS) - 1 + + +def seq_doc(enc): + """ + Return a tuple containing u16 representations of the instruction predicate + an recipe / encbits. + + Also return a doc string. + """ + if enc.instp: + p = enc.instp.number + doc = '--> {} when {}'.format(enc, enc.instp) + else: + p = CODE_ALWAYS + doc = '--> {}'.format(enc) + assert p <= CODE_ALWAYS + return ((p, enc.recipe.number, enc.encbits), doc) + + class EncList(object): """ List of instructions for encoding a given type + opcode pair. @@ -121,10 +159,38 @@ class EncList(object): self.encodings = [] def name(self): + name = self.inst.name if self.ty: - return '{}.{}'.format(self.inst.name, self.ty.name) - else: - return self.inst.name + name = '{}.{}'.format(name, self.ty.name) + if self.encodings: + name += ' ({})'.format(self.encodings[0].cpumode) + return name + + def encode(self, seq_table, doc_table): + """ + Encode this list as a sequence of u16 numbers. + + Adds the sequence to `seq_table` and records the returned offset as + `self.offset`. + + Adds comment lines to `doc_table` keyed by seq_table offsets. + """ + words = list() + docs = list() + + for idx, enc in enumerate(self.encodings): + seq, doc = seq_doc(enc) + docs.append((len(words), doc)) + words.extend(seq) + words.append(CODE_FAIL) + + self.offset = seq_table.add(words) + + # Add doc comments. + doc_table[self.offset].append( + '{:06x}: {}'.format(self.offset, self.name())) + for pos, doc in docs: + doc_table[self.offset + pos].append(doc) class Level2Table(object): @@ -181,18 +247,46 @@ def make_tables(cpumode): return table +def encode_enclists(level1, seq_table, doc_table): + """ + Compute encodings and doc comments for encoding lists in `level1`. + """ + for level2 in level1: + for enclist in level2: + enclist.encode(seq_table, doc_table) + + +def emit_enclists(seq_table, doc_table, fmt): + with fmt.indented( + 'const ENCLISTS: [u16; {}] = ['.format(len(seq_table.table)), + '];'): + line = '' + for idx, entry in enumerate(seq_table.table): + if idx in doc_table: + if line: + fmt.line(line) + line = '' + for doc in doc_table[idx]: + fmt.comment(doc) + line += '{:#06x}, '.format(entry) + if line: + fmt.line(line) + + def gen_isa(isa, fmt): # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. emit_instps(isa.all_instps, fmt) + # Tables for enclists with comments. + seq_table = UniqueSeqTable() + doc_table = defaultdict(list) + for cpumode in isa.cpumodes: level1 = make_tables(cpumode) - for level2 in level1: - for enclist in level2: - fmt.comment(enclist.name()) - for enc in enclist.encodings: - fmt.comment('{} when {}'.format(enc, enc.instp)) + encode_enclists(level1, seq_table, doc_table) + + emit_enclists(seq_table, doc_table, fmt) def generate(isas, out_dir): From 8a1f87d32e27e189e339d79720f03ee4ed479ddb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 16:13:22 -0700 Subject: [PATCH 240/968] Add 32-bit ops to RV64. The 32-bit arithmetic instructions are encoded differently in the RISC-V 64-bit mode. --- meta/isa/riscv/encodings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/meta/isa/riscv/encodings.py b/meta/isa/riscv/encodings.py index 099b6b5630..c339859730 100644 --- a/meta/isa/riscv/encodings.py +++ b/meta/isa/riscv/encodings.py @@ -22,6 +22,9 @@ for inst, inst_imm, f3, f7 in [ RV32.enc(inst_imm.i32, I, OPIMM(f3)) RV64.enc(inst_imm.i64, I, OPIMM(f3)) +# 32-bit ops in RV64. +RV64.enc(base.iadd.i32, R, OP32(0b000, 0b0000000)) +RV64.enc(base.isub.i32, R, OP32(0b000, 0b0100000)) # There are no andiw/oriw/xoriw variations. RV64.enc(base.iadd_imm.i32, I, OPIMM32(0b000)) From 7476d996f61c392567f7c85892732459013ef83e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 26 Aug 2016 17:15:05 -0700 Subject: [PATCH 241/968] Generate level 2 hashtables. All of the level 2 hashtables are concatenated into one constant array per ISA. --- meta/gen_encoding.py | 43 +++++++++++++++++++++++++++++++++++++++++++ meta/gen_instr.py | 1 + 2 files changed, 44 insertions(+) diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index 278ed66da5..c9e0695443 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -51,6 +51,7 @@ instructions with different types for secondary type variables. """ from __future__ import absolute_import import srcgen +from constant_hash import compute_quadratic from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict @@ -215,6 +216,21 @@ class Level2Table(object): def __iter__(self): return iter(self.lists.values()) + def layout_hashtable(self, level2_hashtables): + """ + Compute the hash table mapping opcode -> enclist. + + Append the hash table to `level2_hashtables` and record the offset. + """ + hash_table = compute_quadratic( + self.lists.values(), + lambda enclist: enclist.inst.number) + + self.hash_table_offset = len(level2_hashtables) + self.hash_table_len = len(hash_table) + + level2_hashtables.extend(hash_table) + class Level1Table(object): """ @@ -273,6 +289,28 @@ def emit_enclists(seq_table, doc_table, fmt): fmt.line(line) +def encode_level2_hashtables(level1, level2_hashtables): + for level2 in level1: + level2.layout_hashtable(level2_hashtables) + + +def emit_level2_hashtables(level2_hashtables, fmt): + """ + Emit the big concatenation of level 2 hash tables. + """ + with fmt.indented( + 'const LEVEL2: [(Opcode, u32); {}] = [' + .format(len(level2_hashtables)), + '];'): + for entry in level2_hashtables: + if entry: + fmt.line( + '(Opcode::{}, {:#08x}),' + .format(entry.inst.camel_name, entry.offset)) + else: + fmt.line('(Opcode::NotAnOpcode, 0),') + + def gen_isa(isa, fmt): # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. @@ -282,11 +320,16 @@ def gen_isa(isa, fmt): seq_table = UniqueSeqTable() doc_table = defaultdict(list) + # Single table containing all the level2 hash tables. + level2_hashtables = list() + for cpumode in isa.cpumodes: level1 = make_tables(cpumode) encode_enclists(level1, seq_table, doc_table) + encode_level2_hashtables(level1, level2_hashtables) emit_enclists(seq_table, doc_table, fmt) + emit_level2_hashtables(level2_hashtables, fmt) def generate(isas, out_dir): diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 13bcaa6842..139acf59b7 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -191,6 +191,7 @@ def gen_opcodes(groups, fmt): for g in groups: for i in g.instructions: instrs.append(i) + i.number = len(instrs) # Build a doc comment. prefix = ', '.join(o.name for o in i.outs) if prefix: From 894d3796fb0e103fbcebea0442fc01e6dffc440d Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Mon, 29 Aug 2016 13:17:08 -0700 Subject: [PATCH 242/968] Rustfmt fixes --- src/libcretonne/dominator_tree.rs | 6 +++++- src/libcretonne/ir/entities.rs | 6 +++++- src/libcretonne/ir/immediates.rs | 6 +++++- src/libcretonne/ir/instructions.rs | 5 ++++- src/libcretonne/settings.rs | 4 +++- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index a3a00b2cc4..1b99db342d 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -108,7 +108,11 @@ impl DominatorTree { // TODO: we can't rely on instruction numbers to always be ordered // from lowest to highest. Given that, it will be necessary to create // an abolute mapping to determine the instruction order in the future. - if a.1 == NO_INST || a.1 < b.1 { a } else { b } + if a.1 == NO_INST || a.1 < b.1 { + a + } else { + b + } } /// Returns the immediate dominator of some ebb or None if the diff --git a/src/libcretonne/ir/entities.rs b/src/libcretonne/ir/entities.rs index 1a887452a7..33b41e5273 100644 --- a/src/libcretonne/ir/entities.rs +++ b/src/libcretonne/ir/entities.rs @@ -42,7 +42,11 @@ impl EntityRef for Ebb { impl Ebb { /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. pub fn with_number(n: u32) -> Option { - if n < u32::MAX { Some(Ebb(n)) } else { None } + if n < u32::MAX { + Some(Ebb(n)) + } else { + None + } } } diff --git a/src/libcretonne/ir/immediates.rs b/src/libcretonne/ir/immediates.rs index 99276bfd66..87433606d7 100644 --- a/src/libcretonne/ir/immediates.rs +++ b/src/libcretonne/ir/immediates.rs @@ -60,7 +60,11 @@ impl FromStr for Imm64 { let mut value: u64 = 0; let mut digits = 0; let negative = s.starts_with('-'); - let s2 = if negative { &s[1..] } else { s }; + let s2 = if negative { + &s[1..] + } else { + s + }; if s2.starts_with("0x") { // Hexadecimal. diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 9638d660a5..ca43a3dc31 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -95,7 +95,10 @@ impl FromStr for Opcode { /// `Box` to store the additional information out of line. #[derive(Debug)] pub enum InstructionData { - Nullary { opcode: Opcode, ty: Type }, + Nullary { + opcode: Opcode, + ty: Type, + }, Unary { opcode: Opcode, ty: Type, diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index 58731c40bf..b3cf494954 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -227,7 +227,9 @@ pub mod detail { #[derive(Clone, Copy)] pub enum Detail { /// A boolean setting only uses one bit, numbered from LSB. - Bool { bit: u8 }, + Bool { + bit: u8, + }, /// A numerical setting uses the whole byte. Num, From ae8eb268ab0bf2c3dd4b4436458bbc0076c868b6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 07:46:18 -0700 Subject: [PATCH 243/968] Require a specific rustfmt version in test-all script. Rustfmt is still so immature that developers can't have different versions installed, or there will be minute differences in formatting causing the tests to fail. Only run rustfmt as part of the test-all script if the expected version is available. --- test-all.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test-all.sh b/test-all.sh index e4914b9541..586f2b76f1 100755 --- a/test-all.sh +++ b/test-all.sh @@ -21,10 +21,23 @@ function banner() { echo "====== $@ ======" } -# Run rustfmt if we have it. (Travis probably won't). -if cargo install --list | grep -q '^rustfmt '; then +# Run rustfmt if we have it. +# +# Rustfmt is still immature enough that its formatting decisions can change +# between versions. This makes it difficult to enforce a certain style in a +# test script since not all developers will upgrade rustfmt at the same time. +# To work around this, we only verify formatting when a specific version of +# rustfmt is installed. +# +# This version should always be bumped to the newest version available. +RUSTFMT_VERSION="0.5.0" + +if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" $topdir/src/format-all.sh --write-mode=diff +else + echo "Please install rustfmt v$RUSTFMT_VERSION to verify formatting." + echo "If a newer version of rustfmt is available, update this script." fi PKGS="cretonne cretonne-reader cretonne-tools" From f816127dcc40d393c544f659d99c8431c46ff2f5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 29 Aug 2016 08:43:33 -0700 Subject: [PATCH 244/968] Add comments to the level2 hash tables concatenation. The large LEVEL2 table consists on multiple concatenated constant hash tables. Add comments to the generated output delineating the individual tables. --- meta/gen_encoding.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index c9e0695443..648a1289eb 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -216,7 +216,7 @@ class Level2Table(object): def __iter__(self): return iter(self.lists.values()) - def layout_hashtable(self, level2_hashtables): + def layout_hashtable(self, level2_hashtables, level2_doc): """ Compute the hash table mapping opcode -> enclist. @@ -229,6 +229,11 @@ class Level2Table(object): self.hash_table_offset = len(level2_hashtables) self.hash_table_len = len(hash_table) + level2_doc[self.hash_table_offset].append( + '{:06x}: {}, {} entries'.format( + self.hash_table_offset, + self.ty.name, + self.hash_table_len)) level2_hashtables.extend(hash_table) @@ -289,12 +294,12 @@ def emit_enclists(seq_table, doc_table, fmt): fmt.line(line) -def encode_level2_hashtables(level1, level2_hashtables): +def encode_level2_hashtables(level1, level2_hashtables, level2_doc): for level2 in level1: - level2.layout_hashtable(level2_hashtables) + level2.layout_hashtable(level2_hashtables, level2_doc) -def emit_level2_hashtables(level2_hashtables, fmt): +def emit_level2_hashtables(level2_hashtables, level2_doc, fmt): """ Emit the big concatenation of level 2 hash tables. """ @@ -302,7 +307,10 @@ def emit_level2_hashtables(level2_hashtables, fmt): 'const LEVEL2: [(Opcode, u32); {}] = [' .format(len(level2_hashtables)), '];'): - for entry in level2_hashtables: + for offset, entry in enumerate(level2_hashtables): + if offset in level2_doc: + for doc in level2_doc[offset]: + fmt.comment(doc) if entry: fmt.line( '(Opcode::{}, {:#08x}),' @@ -322,14 +330,16 @@ def gen_isa(isa, fmt): # Single table containing all the level2 hash tables. level2_hashtables = list() + level2_doc = defaultdict(list) for cpumode in isa.cpumodes: + level2_doc[len(level2_hashtables)].append(cpumode.name) level1 = make_tables(cpumode) encode_enclists(level1, seq_table, doc_table) - encode_level2_hashtables(level1, level2_hashtables) + encode_level2_hashtables(level1, level2_hashtables, level2_doc) emit_enclists(seq_table, doc_table, fmt) - emit_level2_hashtables(level2_hashtables, fmt) + emit_level2_hashtables(level2_hashtables, level2_doc, fmt) def generate(isas, out_dir): From d3faf5127e93be285555d2d805a59ab5d4a24489 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 29 Aug 2016 16:30:03 -0700 Subject: [PATCH 245/968] Add an is_64bit shared setting. Many ISAs and 64-bit and 32-bit variants. Use a shared is_64bit setting to distinguish. --- meta/cretonne/settings.py | 2 ++ src/libcretonne/settings.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py index ba95bda36c..4147d5a514 100644 --- a/meta/cretonne/settings.py +++ b/meta/cretonne/settings.py @@ -18,6 +18,8 @@ opt_level = EnumSetting( """, 'default', 'best', 'fastest') +is_64bit = BoolSetting("Enable 64-bit code generation") + enable_simd = BoolSetting( """Enable the use of SIMD instructions.""", default=True) diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index b3cf494954..186b06fe09 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -262,6 +262,7 @@ mod tests { assert_eq!(f.to_string(), "[shared]\n\ opt_level = \"default\"\n\ + is_64bit = false\n\ enable_simd = true\n"); assert_eq!(f.opt_level(), super::OptLevel::Default); assert_eq!(f.enable_simd(), true); From cdbea59269f384415635ed01b7dd6f7c929e9f54 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 29 Aug 2016 16:31:16 -0700 Subject: [PATCH 246/968] Split the Encoding data type into two u16 values. This hardcodes the division line between the recipe bits and the encoding bits. It does not seem that any ISA will need more than 16 bits for either. --- src/libcretonne/isa/mod.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index a84efdbc52..fb2a4dadff 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -100,18 +100,11 @@ pub trait TargetIsa { /// encoding *bits*. The recipe determines the native instruction format and the mapping of /// operands to encoded bits. The encoding bits provide additional information to the recipe, /// typically parts of the opcode. -pub struct Encoding(u32); +pub struct Encoding(u16, u16); impl Encoding { - /// Create a new `Encoding` containing `(recipe, bits)`. The `num_bits` parameter is the - /// ISA-dependent size of `bits`. - pub fn new(recipe: u32, bits: u32, num_bits: u8) -> Encoding { - Encoding((recipe << num_bits) | bits) - } - - /// Split the encoding into two parts: `(recipe, bits)`. Only the target ISA knows how many - /// bits are in each part. - pub fn split(&self, num_bits: u8) -> (u32, u32) { - (self.0 >> num_bits, self.0 & ((1 << num_bits) - 1)) + /// Create a new `Encoding` containing `(recipe, bits)`. + pub fn new(recipe: u16, bits: u16) -> Encoding { + Encoding(recipe, bits) } } From 38e24360741522146bc42da884ceb92977c3ad7f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 29 Aug 2016 14:00:26 -0700 Subject: [PATCH 247/968] Add an isa/encoding module. Define data types for the level 1 and level 2 hash tables. These data types are generic over the offset integer type so they can be twice as compact for typically small ISAs. Use these new types when generating encoding hash tables. Emit both level 1 and level 2 hash tables. Define generic functions that perform lookups in the encoding tables. Implement the TargetIsa::encode() method for RISC-V using these building blocks. --- meta/gen_encoding.py | 79 +++++++++++-- src/libcretonne/isa/encoding.rs | 152 ++++++++++++++++++++++++++ src/libcretonne/isa/mod.rs | 5 +- src/libcretonne/isa/riscv/encoding.rs | 14 +++ src/libcretonne/isa/riscv/mod.rs | 25 ++++- 5 files changed, 259 insertions(+), 16 deletions(-) create mode 100644 src/libcretonne/isa/encoding.rs create mode 100644 src/libcretonne/isa/riscv/encoding.rs diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index 648a1289eb..8fe8e1e304 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -54,6 +54,7 @@ import srcgen from constant_hash import compute_quadratic from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict +import math def emit_instp(instp, fmt): @@ -79,7 +80,7 @@ def emit_instp(instp, fmt): with fmt.indented('{} => {{'.format(instp.number), '}'): with fmt.indented( - 'if let {} {{ {}, .. }} = *inst {{' + 'if let InstructionData::{} {{ {}, .. }} = *inst {{' .format(iform.name, fields), '}'): fmt.line('return {};'.format(instp.rust_predicate(0))) @@ -90,15 +91,15 @@ def emit_instps(instps, fmt): """ with fmt.indented( - 'fn check_instp(inst: &InstructionData, instp_idx: u16) -> bool {', - '}'): + 'pub fn check_instp(inst: &InstructionData, instp_idx: u16) ' + + '-> bool {', '}'): with fmt.indented('match instp_idx {', '}'): for instp in instps: emit_instp(instp, fmt) fmt.line('_ => panic!("Invalid instruction predicate")') # The match cases will fall through if the instruction format is wrong. - fmt.line('panic!("Bad format {}/{} for instp {}",') + fmt.line('panic!("Bad format {:?}/{} for instp {}",') fmt.line(' InstructionFormat::from(inst),') fmt.line(' inst.opcode(),') fmt.line(' instp_idx);') @@ -279,7 +280,7 @@ def encode_enclists(level1, seq_table, doc_table): def emit_enclists(seq_table, doc_table, fmt): with fmt.indented( - 'const ENCLISTS: [u16; {}] = ['.format(len(seq_table.table)), + 'pub static ENCLISTS: [u16; {}] = ['.format(len(seq_table.table)), '];'): line = '' for idx, entry in enumerate(seq_table.table): @@ -299,13 +300,13 @@ def encode_level2_hashtables(level1, level2_hashtables, level2_doc): level2.layout_hashtable(level2_hashtables, level2_doc) -def emit_level2_hashtables(level2_hashtables, level2_doc, fmt): +def emit_level2_hashtables(level2_hashtables, offt, level2_doc, fmt): """ Emit the big concatenation of level 2 hash tables. """ with fmt.indented( - 'const LEVEL2: [(Opcode, u32); {}] = [' - .format(len(level2_hashtables)), + 'pub static LEVEL2: [Level2Entry<{}>; {}] = [' + .format(offt, len(level2_hashtables)), '];'): for offset, entry in enumerate(level2_hashtables): if offset in level2_doc: @@ -313,10 +314,54 @@ def emit_level2_hashtables(level2_hashtables, level2_doc, fmt): fmt.comment(doc) if entry: fmt.line( - '(Opcode::{}, {:#08x}),' + 'Level2Entry ' + + '{{ opcode: Opcode::{}, offset: {:#08x} }},' .format(entry.inst.camel_name, entry.offset)) else: - fmt.line('(Opcode::NotAnOpcode, 0),') + fmt.line( + 'Level2Entry ' + + '{ opcode: Opcode::NotAnOpcode, offset: 0 },') + + +def emit_level1_hashtable(cpumode, level1, offt, fmt): + """ + Emit a level 1 hash table for `cpumode`. + """ + hash_table = compute_quadratic( + level1.tables.values(), + lambda level2: level2.ty.number) + + with fmt.indented( + 'pub static LEVEL1_{}: [Level1Entry<{}>; {}] = [' + .format(cpumode.name.upper(), offt, len(hash_table)), '];'): + for level2 in hash_table: + if level2: + l2l = int(math.log(level2.hash_table_len, 2)) + assert l2l > 0, "Hash table too small" + fmt.line( + 'Level1Entry ' + + '{{ ty: types::{}, log2len: {}, offset: {:#08x} }},' + .format( + level2.ty.name.upper(), + l2l, + level2.hash_table_offset)) + else: + # Empty entry. + fmt.line( + 'Level1Entry ' + + '{ ty: types::VOID, log2len: 0, offset: 0 },') + + +def offset_type(length): + """ + Compute an appropriate Rust integer type to use for offsets into a table of + the given length. + """ + if length <= 0x10000: + return 'u16' + else: + assert length <= 0x100000000, "Table too big" + return 'u32' def gen_isa(isa, fmt): @@ -324,6 +369,9 @@ def gen_isa(isa, fmt): # check_instp() function.. emit_instps(isa.all_instps, fmt) + # Level1 tables, one per CPU mode + level1_tables = dict() + # Tables for enclists with comments. seq_table = UniqueSeqTable() doc_table = defaultdict(list) @@ -335,11 +383,20 @@ def gen_isa(isa, fmt): for cpumode in isa.cpumodes: level2_doc[len(level2_hashtables)].append(cpumode.name) level1 = make_tables(cpumode) + level1_tables[cpumode] = level1 encode_enclists(level1, seq_table, doc_table) encode_level2_hashtables(level1, level2_hashtables, level2_doc) + # Level 1 table encodes offsets into the level 2 table. + level1_offt = offset_type(len(level2_hashtables)) + # Level 2 tables encodes offsets into seq_table. + level2_offt = offset_type(len(seq_table.table)) + emit_enclists(seq_table, doc_table, fmt) - emit_level2_hashtables(level2_hashtables, level2_doc, fmt) + emit_level2_hashtables(level2_hashtables, level2_offt, level2_doc, fmt) + for cpumode in isa.cpumodes: + emit_level1_hashtable( + cpumode, level1_tables[cpumode], level1_offt, fmt) def generate(isas, out_dir): diff --git a/src/libcretonne/isa/encoding.rs b/src/libcretonne/isa/encoding.rs new file mode 100644 index 0000000000..62aa54e445 --- /dev/null +++ b/src/libcretonne/isa/encoding.rs @@ -0,0 +1,152 @@ +//! Support types for generated encoding tables. +//! +//! This module contains types and functions for working with the encoding tables generated by +//! `meta/gen_encoding.py`. +use ir::{Type, Opcode}; +use isa::Encoding; +use constant_hash::{Table, probe}; + +/// Level 1 hash table entry. +/// +/// One level 1 hash table is generated per CPU mode. This table is keyed by the controlling type +/// variable, using `VOID` for non-polymorphic instructions. +/// +/// The hash table values are references to level 2 hash tables, encoded as an offset in `LEVEL2` +/// where the table begins, and the binary logarithm of its length. All the level 2 hash tables +/// have a power-of-two size. +/// +/// Entries are generic over the offset type. It will typically be `u32` or `u16`, depending on the +/// size of the `LEVEL2` table. A `u16` offset allows entries to shrink to 32 bits each, but some +/// ISAs may have tables so large that `u32` offsets are needed. +/// +/// Empty entries are encoded with a 0 `log2len`. This is on the assumption that no level 2 tables +/// have only a single entry. +pub struct Level1Entry + Copy> { + pub ty: Type, + pub log2len: u8, + pub offset: OffT, +} + +impl + Copy> Table for [Level1Entry] { + fn len(&self) -> usize { + self.len() + } + + fn key(&self, idx: usize) -> Option { + if self[idx].log2len != 0 { + Some(self[idx].ty) + } else { + None + } + } +} + +/// Level 2 hash table entry. +/// +/// The second level hash tables are keyed by `Opcode`, and contain an offset into the `ENCLISTS` +/// table where the encoding recipes for the instrution are stored. +/// +/// Entries are generic over the offset type which depends on the size of `ENCLISTS`. A `u16` +/// offset allows the entries to be only 32 bits each. There is no benefit to dropping down to `u8` +/// for tiny ISAs. The entries won't shrink below 32 bits since the opcode is expected to be 16 +/// bits. +/// +/// Empty entries are encoded with a `NotAnOpcode` `opcode` field. +pub struct Level2Entry + Copy> { + pub opcode: Opcode, + pub offset: OffT, +} + +impl + Copy> Table for [Level2Entry] { + fn len(&self) -> usize { + self.len() + } + + fn key(&self, idx: usize) -> Option { + let opc = self[idx].opcode; + if opc != Opcode::NotAnOpcode { + Some(opc) + } else { + None + } + } +} + +/// Two-level hash table lookup. +/// +/// Given the controlling type variable and instruction opcode, find the corresponding encoding +/// list. +/// +/// Returns an offset into the ISA's `ENCLIST` table, or `None` if the opcode/type combination is +/// not legal. +pub fn lookup_enclist(ctrl_typevar: Type, + opcode: Opcode, + level1_table: &[Level1Entry], + level2_table: &[Level2Entry]) + -> Option + where OffT1: Into + Copy, + OffT2: Into + Copy +{ + probe(level1_table, ctrl_typevar, ctrl_typevar.index()).and_then(|l1idx| { + let l1ent = &level1_table[l1idx]; + let l2off = l1ent.offset.into() as usize; + let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; + probe(l2tab, opcode, opcode as usize).map(|l2idx| l2tab[l2idx].offset.into() as usize) + }) +} + +/// Encoding list entry. +/// +/// Encoding lists are represented as sequences of u16 words. +pub type EncListEntry = u16; + +/// Number of bits used to represent a predicate. c.f. `meta.gen_encoding.py`. +const PRED_BITS: u8 = 12; +const PRED_MASK: EncListEntry = (1 << PRED_BITS) - 1; + +/// The match-always instruction predicate. c.f. `meta.gen_encoding.py`. +const CODE_ALWAYS: EncListEntry = PRED_MASK; + +/// The encoding list terminator. +const CODE_FAIL: EncListEntry = 0xffff; + +/// Find the most general encoding of `inst`. +/// +/// Given an encoding list offset as returned by `lookup_enclist` above, search the encoding list +/// for the most general encoding that applies to `inst`. The encoding lists are laid out such that +/// this is the last valid entry in the list. +/// +/// This function takes two closures that are used to evaluate predicates: +/// - `instp` is passed an instruction predicate number to be evaluated on the current instruction. +/// - `isap` is passed an ISA predicate number to evaluate. +/// +/// Returns the corresponding encoding, or `None` if no list entries are satisfied by `inst`. +pub fn general_encoding(offset: usize, + enclist: &[EncListEntry], + instp: InstP, + isap: IsaP) + -> Option + where InstP: Fn(EncListEntry) -> bool, + IsaP: Fn(EncListEntry) -> bool +{ + let mut found = None; + let mut pos = offset; + while enclist[pos] != CODE_FAIL { + let pred = enclist[pos]; + if pred <= CODE_ALWAYS { + // This is an instruction predicate followed by recipe and encbits entries. + if pred == CODE_ALWAYS || instp(pred) { + found = Some(Encoding::new(enclist[pos + 1], enclist[pos + 2])) + } + pos += 3; + } else { + // This is an ISA predicate entry. + pos += 1; + if !isap(pred & PRED_MASK) { + // ISA predicate failed, skip the next N entries. + pos += 3 * (pred >> PRED_BITS) as usize; + } + } + } + found +} diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index fb2a4dadff..60028415fd 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -41,9 +41,10 @@ //! concurrent function compilations. pub mod riscv; +mod encoding; use settings; -use ir::{Inst, DataFlowGraph}; +use ir::{InstructionData, DataFlowGraph}; /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. @@ -91,7 +92,7 @@ pub trait TargetIsa { /// Otherwise, return `None`. /// /// This is also the main entry point for determining if an instruction is legal. - fn encode(&self, dfg: &DataFlowGraph, inst: &Inst) -> Option; + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Option; } /// Bits needed to encode an instruction as binary machine code. diff --git a/src/libcretonne/isa/riscv/encoding.rs b/src/libcretonne/isa/riscv/encoding.rs new file mode 100644 index 0000000000..760f4786d7 --- /dev/null +++ b/src/libcretonne/isa/riscv/encoding.rs @@ -0,0 +1,14 @@ +//! Encoding tables for RISC-V. + +use ir::{Opcode, InstructionData}; +use ir::instructions::InstructionFormat; +use ir::types; +use predicates; +use isa::encoding::{Level1Entry, Level2Entry}; + +// Include the generated encoding tables: +// - `LEVEL1_RV32` +// - `LEVEL1_RV64` +// - `LEVEL2` +// - `ENCLIST` +include!(concat!(env!("OUT_DIR"), "/encoding-riscv.rs")); diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index b9ffa4925c..0042b5e758 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -1,16 +1,19 @@ //! RISC-V Instruction Set Architecture. pub mod settings; +mod encoding; use super::super::settings as shared_settings; +use isa::encoding as shared_encoding; use super::Builder as IsaBuilder; use super::{TargetIsa, Encoding}; -use ir::{Inst, DataFlowGraph}; +use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] struct Isa { shared_flags: shared_settings::Flags, isa_flags: settings::Flags, + cpumode: &'static [shared_encoding::Level1Entry], } pub fn isa_builder() -> IsaBuilder { @@ -23,14 +26,30 @@ pub fn isa_builder() -> IsaBuilder { fn isa_constructor(shared_flags: shared_settings::Flags, builder: shared_settings::Builder) -> Box { + let level1 = if shared_flags.is_64bit() { + &encoding::LEVEL1_RV64[..] + } else { + &encoding::LEVEL1_RV32[..] + }; Box::new(Isa { isa_flags: settings::Flags::new(&shared_flags, builder), shared_flags: shared_flags, + cpumode: level1, }) } impl TargetIsa for Isa { - fn encode(&self, _: &DataFlowGraph, _: &Inst) -> Option { - unimplemented!() + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { + shared_encoding::lookup_enclist(inst.first_type(), + inst.opcode(), + self.cpumode, + &encoding::LEVEL2[..]) + .and_then(|enclist_offset| { + shared_encoding::general_encoding(enclist_offset, + &encoding::ENCLISTS[..], + |instp| encoding::check_instp(inst, instp), + // TODO: Implement ISA predicates properly. + |isap| isap != 17) + }) } } From ba26ce2e9871056f471e4fee51101b3250f1fcc5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 07:56:12 -0700 Subject: [PATCH 248/968] Upgrade to rustfmt 0.6.0. --- src/libcretonne/dominator_tree.rs | 6 +----- src/libcretonne/ir/entities.rs | 6 +----- src/libcretonne/ir/immediates.rs | 6 +----- src/libcretonne/ir/instructions.rs | 5 +---- src/libcretonne/settings.rs | 4 +--- test-all.sh | 2 +- 6 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index 1b99db342d..a3a00b2cc4 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -108,11 +108,7 @@ impl DominatorTree { // TODO: we can't rely on instruction numbers to always be ordered // from lowest to highest. Given that, it will be necessary to create // an abolute mapping to determine the instruction order in the future. - if a.1 == NO_INST || a.1 < b.1 { - a - } else { - b - } + if a.1 == NO_INST || a.1 < b.1 { a } else { b } } /// Returns the immediate dominator of some ebb or None if the diff --git a/src/libcretonne/ir/entities.rs b/src/libcretonne/ir/entities.rs index 33b41e5273..1a887452a7 100644 --- a/src/libcretonne/ir/entities.rs +++ b/src/libcretonne/ir/entities.rs @@ -42,11 +42,7 @@ impl EntityRef for Ebb { impl Ebb { /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. pub fn with_number(n: u32) -> Option { - if n < u32::MAX { - Some(Ebb(n)) - } else { - None - } + if n < u32::MAX { Some(Ebb(n)) } else { None } } } diff --git a/src/libcretonne/ir/immediates.rs b/src/libcretonne/ir/immediates.rs index 87433606d7..99276bfd66 100644 --- a/src/libcretonne/ir/immediates.rs +++ b/src/libcretonne/ir/immediates.rs @@ -60,11 +60,7 @@ impl FromStr for Imm64 { let mut value: u64 = 0; let mut digits = 0; let negative = s.starts_with('-'); - let s2 = if negative { - &s[1..] - } else { - s - }; + let s2 = if negative { &s[1..] } else { s }; if s2.starts_with("0x") { // Hexadecimal. diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index ca43a3dc31..9638d660a5 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -95,10 +95,7 @@ impl FromStr for Opcode { /// `Box` to store the additional information out of line. #[derive(Debug)] pub enum InstructionData { - Nullary { - opcode: Opcode, - ty: Type, - }, + Nullary { opcode: Opcode, ty: Type }, Unary { opcode: Opcode, ty: Type, diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index 186b06fe09..c4f42d4a04 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -227,9 +227,7 @@ pub mod detail { #[derive(Clone, Copy)] pub enum Detail { /// A boolean setting only uses one bit, numbered from LSB. - Bool { - bit: u8, - }, + Bool { bit: u8 }, /// A numerical setting uses the whole byte. Num, diff --git a/test-all.sh b/test-all.sh index 586f2b76f1..64fb83d3a8 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.5.0" +RUSTFMT_VERSION="0.6.0" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" From 1c5128584592ac26b8cfc516cd7b8f6159b32bf3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 13:50:56 -0700 Subject: [PATCH 249/968] Generate a table of encoding recipe names for each ISA. This will be used to pretty-print encodings in the textual IR. --- meta/gen_encoding.py | 15 +++++++++++++++ src/libcretonne/isa/mod.rs | 6 ++++++ src/libcretonne/isa/riscv/mod.rs | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index 8fe8e1e304..e54d15f7fb 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -364,6 +364,19 @@ def offset_type(length): return 'u32' +def emit_recipe_names(isa, fmt): + """ + Emit a table of encoding recipe names keyed by recipe number. + + This is used for pretty-printing encodings. + """ + with fmt.indented( + 'pub static RECIPE_NAMES: [&\'static str; {}] = [' + .format(len(isa.all_recipes)), '];'): + for r in isa.all_recipes: + fmt.line('"{}",'.format(r.name)) + + def gen_isa(isa, fmt): # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. @@ -398,6 +411,8 @@ def gen_isa(isa, fmt): emit_level1_hashtable( cpumode, level1_tables[cpumode], level1_offt, fmt) + emit_recipe_names(isa, fmt) + def generate(isas, out_dir): for isa in isas: diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 60028415fd..18723a16a5 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -93,6 +93,12 @@ pub trait TargetIsa { /// /// This is also the main entry point for determining if an instruction is legal. fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Option; + + /// Get a static array of names associated with encoding recipes in this ISA. Encoding recipes + /// are numbered starting from 0, corresponding to indexes into th name array. + /// + /// This is just used for printing and parsing encodings in the textual IL format. + fn recipe_names(&self) -> &'static [&'static str]; } /// Bits needed to encode an instruction as binary machine code. diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 0042b5e758..5211479f9c 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -52,4 +52,8 @@ impl TargetIsa for Isa { |isap| isap != 17) }) } + + fn recipe_names(&self) -> &'static [&'static str] { + &encoding::RECIPE_NAMES[..] + } } From 727510f97f63e778ba772454259e45cf86c18a4d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 10:44:33 -0700 Subject: [PATCH 250/968] Add an encoding test for RISC-V. Test that the generated encoding tables work as expected. Change isa::Encoding into a struct with named fields so the recipe and bits can be accessed. --- src/libcretonne/isa/mod.rs | 21 ++++++++++-- src/libcretonne/isa/riscv/mod.rs | 58 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 18723a16a5..6eaae9fc62 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -107,11 +107,28 @@ pub trait TargetIsa { /// encoding *bits*. The recipe determines the native instruction format and the mapping of /// operands to encoded bits. The encoding bits provide additional information to the recipe, /// typically parts of the opcode. -pub struct Encoding(u16, u16); +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Encoding { + recipe: u16, + bits: u16, +} impl Encoding { /// Create a new `Encoding` containing `(recipe, bits)`. pub fn new(recipe: u16, bits: u16) -> Encoding { - Encoding(recipe, bits) + Encoding { + recipe: recipe, + bits: bits, + } + } + + /// Get the recipe number in this encoding. + pub fn recipe(self) -> usize { + self.recipe as usize + } + + /// Get the recipe-specific encoding bits. + pub fn bits(self) -> u16 { + self.bits } } diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 5211479f9c..8abb53467b 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -57,3 +57,61 @@ impl TargetIsa for Isa { &encoding::RECIPE_NAMES[..] } } + +#[cfg(test)] +mod tests { + use settings::{self, Configurable}; + use isa; + use ir::{DataFlowGraph, InstructionData, Opcode}; + use ir::{types, immediates}; + + fn encstr(isa: &isa::TargetIsa, enc: isa::Encoding) -> String { + format!("{}/{:02x}", isa.recipe_names()[enc.recipe()], enc.bits()) + } + + #[test] + fn test_64bitenc() { + let mut shared_builder = settings::builder(); + shared_builder.set_bool("is_64bit", true).unwrap(); + let shared_flags = settings::Flags::new(shared_builder); + let isa = isa::lookup("riscv").unwrap().finish(shared_flags); + + let mut dfg = DataFlowGraph::new(); + let ebb = dfg.make_ebb(); + let arg64 = dfg.append_ebb_arg(ebb, types::I64); + let arg32 = dfg.append_ebb_arg(ebb, types::I32); + + // Try to encode iadd_imm.i64 vx1, -10. + let inst64 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + ty: types::I64, + arg: arg64, + imm: immediates::Imm64::new(-10), + }; + + // ADDI is I/0b00100 + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64).unwrap()), "I/04"); + + // Try to encode iadd_imm.i64 vx1, -10000. + let inst64_large = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + ty: types::I64, + arg: arg64, + imm: immediates::Imm64::new(-10000), + }; + + // Immediate is out of range for ADDI. + assert_eq!(isa.encode(&dfg, &inst64_large), None); + + // Create an iadd_imm.i32 which is encodable in RV64. + let inst32 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + ty: types::I32, + arg: arg32, + imm: immediates::Imm64::new(10), + }; + + // ADDIW is I/0b00110 + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I/06"); + } +} From 74e731ed25487482371b1c2a2f904d41b112e1ce Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 14:54:18 -0700 Subject: [PATCH 251/968] Add encoding tests for RV32. The 32-bit CPU mode uses a different encoding for iadd_imm.i32, and 64-bit instructions are not supported. --- src/libcretonne/isa/riscv/mod.rs | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 8abb53467b..ef87ade3f6 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -114,4 +114,51 @@ mod tests { // ADDIW is I/0b00110 assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I/06"); } + + // Same as above, but for RV32. + #[test] + fn test_32bitenc() { + let mut shared_builder = settings::builder(); + shared_builder.set_bool("is_64bit", false).unwrap(); + let shared_flags = settings::Flags::new(shared_builder); + let isa = isa::lookup("riscv").unwrap().finish(shared_flags); + + let mut dfg = DataFlowGraph::new(); + let ebb = dfg.make_ebb(); + let arg64 = dfg.append_ebb_arg(ebb, types::I64); + let arg32 = dfg.append_ebb_arg(ebb, types::I32); + + // Try to encode iadd_imm.i64 vx1, -10. + let inst64 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + ty: types::I64, + arg: arg64, + imm: immediates::Imm64::new(-10), + }; + + // ADDI is I/0b00100 + assert_eq!(isa.encode(&dfg, &inst64), None); + + // Try to encode iadd_imm.i64 vx1, -10000. + let inst64_large = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + ty: types::I64, + arg: arg64, + imm: immediates::Imm64::new(-10000), + }; + + // Immediate is out of range for ADDI. + assert_eq!(isa.encode(&dfg, &inst64_large), None); + + // Create an iadd_imm.i32 which is encodable in RV32. + let inst32 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + ty: types::I32, + arg: arg32, + imm: immediates::Imm64::new(10), + }; + + // ADDI is I/0b00100 + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I/04"); + } } From f18041b56cfb2ffcd215a6e892b8db90d133bc10 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 15:27:35 -0700 Subject: [PATCH 252/968] Fix settings_size vs byte_size confusion in gen_settings.py. --- meta/gen_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index 65bb7c428a..c5c48ad4b7 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -231,7 +231,7 @@ def gen_constructor(sgrp, settings_size, byte_size, parent, fmt): 'pub fn new({}) -> Flags {{'.format(args), '}'): fmt.line('let bvec = builder.finish("{}");'.format(sgrp.name)) fmt.line('let mut bytes = [0; {}];'.format(byte_size)) - fmt.line('assert_eq!(bytes.len(), {});'.format(settings_size)) + fmt.line('assert_eq!(bvec.len(), {});'.format(settings_size)) with fmt.indented( 'for (i, b) in bvec.into_iter().enumerate() {', '}'): fmt.line('bytes[i] = b;') @@ -268,7 +268,7 @@ def gen_group(sgrp, fmt): fmt.line('#[derive(Clone)]') fmt.doc_comment('Flags group `{}`.'.format(sgrp.name)) with fmt.indented('pub struct Flags {', '}'): - fmt.line('bytes: [u8; {}],'.format(settings_size)) + fmt.line('bytes: [u8; {}],'.format(byte_size)) gen_constructor(sgrp, settings_size, byte_size, None, fmt) gen_enum_types(sgrp, fmt) From c1971db0913c5c3257c94a8234d233f75d92e913 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 15:10:38 -0700 Subject: [PATCH 253/968] Add controls for enabling M, F, and D RISC-V extensions. Three predicates affect each extension: - supports_m determines whether the target CPU supports the instruction set. - enable_m determines if the instructions should be used, assuming they're available. - use_m is the predicate used to actually use the instructions. --- meta/cretonne/settings.py | 8 ++++++++ meta/isa/riscv/settings.py | 9 +++++++++ src/libcretonne/isa/riscv/settings.rs | 3 ++- src/libcretonne/settings.rs | 4 +++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py index 4147d5a514..522a649ef3 100644 --- a/meta/cretonne/settings.py +++ b/meta/cretonne/settings.py @@ -20,8 +20,16 @@ opt_level = EnumSetting( is_64bit = BoolSetting("Enable 64-bit code generation") +enable_float = BoolSetting( + """Enable the use of floating-point instructions""", + default=True) + enable_simd = BoolSetting( """Enable the use of SIMD instructions.""", default=True) +enable_atomics = BoolSetting( + """Enable the use of atomic instructions""", + default=True) + group.close(globals()) diff --git a/meta/isa/riscv/settings.py b/meta/isa/riscv/settings.py index 1ab2917036..8aae295342 100644 --- a/meta/isa/riscv/settings.py +++ b/meta/isa/riscv/settings.py @@ -14,6 +14,15 @@ supports_a = BoolSetting("CPU supports the 'A' extension (atomics)") supports_f = BoolSetting("CPU supports the 'F' extension (float)") supports_d = BoolSetting("CPU supports the 'D' extension (double)") +enable_m = BoolSetting( + "Enable the use of 'M' instructions if available", + default=True) + +use_m = And(supports_m, enable_m) +use_a = And(supports_a, shared.enable_atomics) +use_f = And(supports_f, shared.enable_float) +use_d = And(supports_d, shared.enable_float) + full_float = And(shared.enable_simd, supports_f, supports_d) isa.settings.close(globals()) diff --git a/src/libcretonne/isa/riscv/settings.rs b/src/libcretonne/isa/riscv/settings.rs index 1dd1adc405..ae765d9c15 100644 --- a/src/libcretonne/isa/riscv/settings.rs +++ b/src/libcretonne/isa/riscv/settings.rs @@ -22,7 +22,8 @@ mod tests { supports_m = false\n\ supports_a = false\n\ supports_f = false\n\ - supports_d = false\n"); + supports_d = false\n\ + enable_m = true\n"); // Predicates are not part of the Display output. assert_eq!(f.full_float(), false); } diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index c4f42d4a04..c7c0d1eb30 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -261,7 +261,9 @@ mod tests { "[shared]\n\ opt_level = \"default\"\n\ is_64bit = false\n\ - enable_simd = true\n"); + enable_float = true\n\ + enable_simd = true\n\ + enable_atomics = true\n"); assert_eq!(f.opt_level(), super::OptLevel::Default); assert_eq!(f.enable_simd(), true); } From a1cbeb7f7a03d0618ec9f60f65308168e521af18 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 30 Aug 2016 15:03:32 -0700 Subject: [PATCH 254/968] Add encodings for imul instructions to RISC-V. This is just the basic 'imul' the M instruction set also has mulh/mulhu which yield the high bits of a multiplication, and there are div/rem instructions to be implemented. These instructions are gated by the use_m predicate, but ISA predicates are not completely implemented yet. --- meta/isa/riscv/encodings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meta/isa/riscv/encodings.py b/meta/isa/riscv/encodings.py index c339859730..807fdafc7a 100644 --- a/meta/isa/riscv/encodings.py +++ b/meta/isa/riscv/encodings.py @@ -5,6 +5,7 @@ from __future__ import absolute_import from cretonne import base from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt, I +from .settings import use_m # Basic arithmetic binary instructions are encoded in an R-type instruction. for inst, inst_imm, f3, f7 in [ @@ -45,3 +46,9 @@ for inst, inst_imm, f3, f7 in [ RV32.enc(inst_imm.i32, Rshamt, OPIMM(f3, f7)) RV64.enc(inst_imm.i64, Rshamt, OPIMM(f3, f7)) RV64.enc(inst_imm.i32, Rshamt, OPIMM32(f3, f7)) + +# "M" Standard Extension for Integer Multiplication and Division. +# Gated by the `use_m` flag. +RV32.enc(base.imul.i32, R, OP(0b000, 0b0000001), isap=use_m) +RV64.enc(base.imul.i64, R, OP(0b000, 0b0000001), isap=use_m) +RV64.enc(base.imul.i32, R, OP32(0b000, 0b0000001), isap=use_m) From be8d486113f173943f18e1b87ee4cb8883635bc6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 31 Aug 2016 08:48:31 -0700 Subject: [PATCH 255/968] Fix typo in predicate combination. --- meta/cretonne/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index b44a238ffd..cc3a079e5f 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -1095,7 +1095,7 @@ class Encoding(object): self.encbits = encbits # Combine recipe predicates with the manually specified ones. self.instp = And.combine(recipe.instp, instp) - self.isap = And.combine(recipe.isap, instp) + self.isap = And.combine(recipe.isap, isap) def __str__(self): return '[{}/{:02x}]'.format(self.recipe, self.encbits) From 84b0a92326f82be295d6f52b60934476cd458cd6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 31 Aug 2016 11:53:37 -0700 Subject: [PATCH 256/968] Move byte-vector layout into SettingGroup.layout(). Move all the byte-sized settings to the front of the byte-vector, and add a mechanism for assigning numbers to predicates that have no name as well as predicates from the parent settings group. This way, all the boolean predicates that are used by a target ISA appear as a contiguous bit-vector that is a suffix of the settings byte-vector. This bit-vector can then be indexed linearly when resolving ISA predicates on encodings. Add a numbered_predicate() method to the generated Flags structs that can read a predicate by number dynamically. --- meta/cretonne/__init__.py | 116 ++++++++++++++++++++++++++++++++++++-- meta/gen_settings.py | 109 +++++++++++++---------------------- 2 files changed, 148 insertions(+), 77 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index cc3a079e5f..23ec938f61 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -8,7 +8,7 @@ from __future__ import absolute_import import re import math import importlib -from collections import namedtuple +from collections import namedtuple, OrderedDict from .predicates import And @@ -135,7 +135,17 @@ class SettingGroup(object): self.name = name self.parent = parent self.settings = [] - self.predicates = [] + # Named predicates computed from settings in this group or its + # parents. + self.named_predicates = [] + # All boolean predicates that can be accessed by number. This includes: + # - All boolean settings in this group. + # - All named predicates. + # - Added anonymous predicates, see `number_predicate()`. + # - Added parent predicates that are replicated in this group. + # Maps predicate -> number. + self.predicate_number = OrderedDict() + self.open() def open(self): @@ -169,7 +179,8 @@ class SettingGroup(object): if isinstance(obj, Predicate): assert obj.name is None obj.name = name - self.predicates.append(obj) + self.named_predicates.append(obj) + self.layout() @staticmethod def append(setting): @@ -178,6 +189,90 @@ class SettingGroup(object): g.settings.append(setting) return g + def number_predicate(self, pred): + """ + Make sure that `pred` has an assigned number, and will be included in + this group's bit vector. + + The numbered predicates include: + - `BoolSetting` settings that belong to this group. + - `Predicate` instances in `named_predicates`. + - `Predicate` instances without a name. + - Settings or computed predicates that belong to the parent group, but + need to be accessible by number in this group. + + The numbered predicates are referenced by the encoding tables as ISA + predicates. See the `isap` field on `Encoding`. + + :returns: The assigned predicate number in this group. + """ + if pred in self.predicate_number: + return self.predicate_number[pred] + else: + number = len(self.predicate_number) + self.predicate_number[pred] = number + return number + + def layout(self): + """ + Compute the layout of the byte vector used to represent this settings + group. + + The byte vector contains the following entries in order: + + 1. Byte-sized settings like `NumSetting` and `EnumSetting`. + 2. `BoolSetting` settings. + 3. Precomputed named predicates. + 4. Other numbered predicates, including anonymous predicates and parent + predicates that need to be accessible by number. + + Set `self.settings_size` to the length of the byte vector prefix that + contains the settings. All bytes after that are computed, not + configured. + + Set `self.boolean_offset` to the beginning of the numbered predicates, + 2. in the list above. + + Assign `byte_offset` and `bit_offset` fields in all settings. + + After calling this method, no more settings can be added, but + additional predicates can be made accessible with `number_predicate()`. + """ + assert len(self.predicate_number) == 0, "Too late for layout" + + # Assign the non-boolean settings. + byte_offset = 0 + for s in self.settings: + if not isinstance(s, BoolSetting): + s.byte_offset = byte_offset + byte_offset += 1 + + # Then the boolean settings. + self.boolean_offset = byte_offset + for s in self.settings: + if isinstance(s, BoolSetting): + number = self.number_predicate(s) + s.byte_offset = byte_offset + number // 8 + s.bit_offset = number % 8 + + # This is the end of the settings. Round up to a whole number of bytes. + self.boolean_settings = len(self.predicate_number) + self.settings_size = self.byte_size() + + # Now assign numbers to all our named predicates. + for p in self.named_predicates: + self.number_predicate(p) + + def byte_size(self): + """ + Compute the number of bytes required to hold all settings and + precomputed predicates. + + This is the size of the byte-sized settings plus all the numbered + predcate bits rounded up to a whole number of bytes. + """ + return self.boolean_offset + (len(self.predicate_number) + 7) // 8 + # Kinds of operands. # @@ -977,7 +1072,7 @@ class TargetISA(object): :returns self: """ self._collect_encoding_recipes() - self._collect_instruction_predicates() + self._collect_predicates() return self def _collect_encoding_recipes(self): @@ -994,12 +1089,15 @@ class TargetISA(object): rcps.add(recipe) self.all_recipes.append(recipe) - def _collect_instruction_predicates(self): + def _collect_predicates(self): """ - Collect and number all instruction predicates in use. + Collect and number all predicates in use. Sets `instp.number` for all used instruction predicates and places them in `self.all_instps` in numerical order. + + Ensures that all ISA predicates have an assigned bit number in + `self.settings`. """ self.all_instps = list() instps = set() @@ -1012,6 +1110,12 @@ class TargetISA(object): instps.add(instp) self.all_instps.append(instp) + # All referenced ISA predicates must have a number in + # `self.settings`. This may cause some parent predicates to be + # replicated here, which is OK. + if enc.isap: + self.settings.number_predicate(enc.isap) + class CPUMode(object): """ diff --git a/meta/gen_settings.py b/meta/gen_settings.py index c5c48ad4b7..588c2cf798 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -8,52 +8,6 @@ import constant_hash from cretonne import camel_case, BoolSetting, NumSetting, EnumSetting, settings -def layout_group(sgrp): - """ - Layout the settings in sgrp, assigning byte and bit offsets. - - Return the number of bytes needed for settings and the total number of - bytes needed when including predicates. - """ - # Byte offset where booleans are allocated. - bool_byte = -1 - # Next available bit number in bool_byte. - bool_bit = 10 - # Next available whole byte. - next_byte = 0 - - for setting in sgrp.settings: - if isinstance(setting, BoolSetting): - # Allocate a bit from bool_byte. - if bool_bit > 7: - bool_byte = next_byte - next_byte += 1 - bool_bit = 0 - setting.byte_offset = bool_byte - setting.bit_offset = bool_bit - bool_bit += 1 - else: - # This is a numerical or enumerated setting. Allocate a single - # byte. - setting.byte_offset = next_byte - next_byte += 1 - - settings_size = next_byte - - # Allocate bits for all the precomputed predicates. - for pred in sgrp.predicates: - # Allocate a bit from bool_byte. - if bool_bit > 7: - bool_byte = next_byte - next_byte += 1 - bool_bit = 0 - pred.byte_offset = bool_byte - pred.bit_offset = bool_bit - bool_bit += 1 - - return (settings_size, next_byte) - - def gen_enum_types(sgrp, fmt): """ Emit enum types for any enum settings. @@ -68,7 +22,7 @@ def gen_enum_types(sgrp, fmt): .format(ty, ", ".join(camel_case(v) for v in setting.values))) -def gen_getter(setting, fmt): +def gen_getter(setting, sgrp, fmt): """ Emit a getter function for `setting`. """ @@ -77,9 +31,9 @@ def gen_getter(setting, fmt): if isinstance(setting, BoolSetting): proto = 'pub fn {}(&self) -> bool'.format(setting.name) with fmt.indented(proto + ' {', '}'): - fmt.line('(self.bytes[{}] & (1 << {})) != 0'.format( - setting.byte_offset, - setting.bit_offset)) + fmt.line( + 'self.numbered_predicate({})' + .format(sgrp.predicate_number[setting])) elif isinstance(setting, NumSetting): proto = 'pub fn {}(&self) -> u8'.format(setting.name) with fmt.indented(proto + ' {', '}'): @@ -98,16 +52,16 @@ def gen_getter(setting, fmt): raise AssertionError("Unknown setting kind") -def gen_pred_getter(pred, fmt): +def gen_pred_getter(pred, sgrp, fmt): """ Emit a getter for a pre-computed predicate. """ fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0))) proto = 'pub fn {}(&self) -> bool'.format(pred.name) with fmt.indented(proto + ' {', '}'): - fmt.line('(self.bytes[{}] & (1 << {})) != 0'.format( - pred.byte_offset, - pred.bit_offset)) + fmt.line( + 'self.numbered_predicate({})' + .format(sgrp.predicate_number[pred])) def gen_getters(sgrp, fmt): @@ -116,10 +70,16 @@ def gen_getters(sgrp, fmt): """ fmt.doc_comment("User-defined settings.") with fmt.indented('impl Flags {', '}'): + # Dynamic numbered predicate getter. + with fmt.indented( + 'pub fn numbered_predicate(&self, p: usize) -> bool {', '}'): + fmt.line( + 'self.bytes[{} + p/8] & (1 << (p%8)) != 0' + .format(sgrp.boolean_offset)) for setting in sgrp.settings: - gen_getter(setting, fmt) - for pred in sgrp.predicates: - gen_pred_getter(pred, fmt) + gen_getter(setting, sgrp, fmt) + for pred in sgrp.named_predicates: + gen_pred_getter(pred, sgrp, fmt) def gen_descriptors(sgrp, fmt): @@ -175,11 +135,11 @@ def gen_descriptors(sgrp, fmt): fmt.line('{},'.format(h.descriptor_index)) -def gen_template(sgrp, settings_size, fmt): +def gen_template(sgrp, fmt): """ Emit a Template constant. """ - v = [0] * settings_size + v = [0] * sgrp.settings_size for setting in sgrp.settings: v[setting.byte_offset] |= setting.default_byte() @@ -217,7 +177,7 @@ def gen_display(sgrp, fmt): fmt.line('Ok(())') -def gen_constructor(sgrp, settings_size, byte_size, parent, fmt): +def gen_constructor(sgrp, parent, fmt): """ Generate a Flags constructor. """ @@ -230,14 +190,14 @@ def gen_constructor(sgrp, settings_size, byte_size, parent, fmt): with fmt.indented( 'pub fn new({}) -> Flags {{'.format(args), '}'): fmt.line('let bvec = builder.finish("{}");'.format(sgrp.name)) - fmt.line('let mut bytes = [0; {}];'.format(byte_size)) - fmt.line('assert_eq!(bvec.len(), {});'.format(settings_size)) + fmt.line('let mut bytes = [0; {}];'.format(sgrp.byte_size())) + fmt.line('assert_eq!(bvec.len(), {});'.format(sgrp.settings_size)) with fmt.indented( 'for (i, b) in bvec.into_iter().enumerate() {', '}'): fmt.line('bytes[i] = b;') # Stop here without predicates. - if len(sgrp.predicates) == 0: + if len(sgrp.predicate_number) == sgrp.boolean_settings: fmt.line('Flags { bytes: bytes }') return @@ -246,15 +206,24 @@ def gen_constructor(sgrp, settings_size, byte_size, parent, fmt): 'let mut {} = Flags {{ bytes: bytes }};' .format(sgrp.name)) - for pred in sgrp.predicates: - fmt.comment('Precompute: {}.'.format(pred.name)) + for pred, number in sgrp.predicate_number.items(): + # Don't compute our own settings. + if number < sgrp.boolean_settings: + continue + if pred.name: + fmt.comment( + 'Precompute #{} ({}).'.format(number, pred.name)) + else: + fmt.comment('Precompute #{}.'.format(number)) with fmt.indented( 'if {} {{'.format(pred.rust_predicate(0)), '}'): fmt.line( '{}.bytes[{}] |= 1 << {};' .format( - sgrp.name, pred.byte_offset, pred.bit_offset)) + sgrp.name, + sgrp.boolean_offset + number // 8, + number % 8)) fmt.line(sgrp.name) @@ -263,18 +232,16 @@ def gen_group(sgrp, fmt): """ Generate a Flags struct representing `sgrp`. """ - settings_size, byte_size = layout_group(sgrp) - fmt.line('#[derive(Clone)]') fmt.doc_comment('Flags group `{}`.'.format(sgrp.name)) with fmt.indented('pub struct Flags {', '}'): - fmt.line('bytes: [u8; {}],'.format(byte_size)) + fmt.line('bytes: [u8; {}],'.format(sgrp.byte_size())) - gen_constructor(sgrp, settings_size, byte_size, None, fmt) + gen_constructor(sgrp, None, fmt) gen_enum_types(sgrp, fmt) gen_getters(sgrp, fmt) gen_descriptors(sgrp, fmt) - gen_template(sgrp, settings_size, fmt) + gen_template(sgrp, fmt) gen_display(sgrp, fmt) From f305f50829cab3f469e1f1a4a5c45daaa17c235e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 31 Aug 2016 15:44:36 -0700 Subject: [PATCH 257/968] Add casual string representation of named settings and predicates. Use 'group.setting' format for named predicates, only display the expression for anonymous predicates. --- meta/cretonne/__init__.py | 3 +++ meta/cretonne/predicates.py | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 23ec938f61..82b0a02be6 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -35,6 +35,9 @@ class Setting(object): self.byte_offset = None self.group = SettingGroup.append(self) + def __str__(self): + return '{}.{}'.format(self.group.name, self.name) + def predicate_context(self): """ Return the context where this setting can be evaluated as a (leaf) diff --git a/meta/cretonne/predicates.py b/meta/cretonne/predicates.py index 6722c820e5..8a1b18807a 100644 --- a/meta/cretonne/predicates.py +++ b/meta/cretonne/predicates.py @@ -66,12 +66,12 @@ class Predicate(object): assert self.context, "Incompatible predicate parts" def __str__(self): - s = '{}({})'.format( - type(self).__name__, - ' ,'.join(map(str, self.parts))) if self.name: - s = '{}={}'.format(self.name, s) - return s + return '{}.{}'.format(self.context.name, self.name) + else: + return '{}({})'.format( + type(self).__name__, + ', '.join(map(str, self.parts))) def predicate_context(self): return self.context From 116b898da36cbbda01687663168473543e1d123c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 31 Aug 2016 10:29:53 -0700 Subject: [PATCH 258/968] Emit ISA predicates in the encoding tables. Use the new ISA predicate numbering to emit ISA predicate instructions in the encoding tables. Properly decode the ISA predicate number in RISC-V and add tests for RV32M iwth and without 'supports_m' enabled. --- meta/gen_encoding.py | 48 ++++++++++++++++++++++++----- src/libcretonne/isa/riscv/mod.rs | 53 ++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/meta/gen_encoding.py b/meta/gen_encoding.py index e54d15f7fb..e404fb79ec 100644 --- a/meta/gen_encoding.py +++ b/meta/gen_encoding.py @@ -55,6 +55,7 @@ from constant_hash import compute_quadratic from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict import math +import itertools def emit_instp(instp, fmt): @@ -168,7 +169,26 @@ class EncList(object): name += ' ({})'.format(self.encodings[0].cpumode) return name - def encode(self, seq_table, doc_table): + def by_isap(self): + """ + Group the encodings by ISA predicate without reordering them. + + Yield a sequence of `(isap, (encs...))` tuples where `isap` is the ISA + predicate or `None`, and `(encs...)` is a tuple of encodings that all + have the same ISA predicate. + """ + maxlen = CODE_FAIL >> PRED_BITS + for isap, group in itertools.groupby( + self.encodings, lambda enc: enc.isap): + group = tuple(group) + # This probably never happens, but we can't express more than + # maxlen encodings per isap. + while len(group) > maxlen: + yield (isap, group[0..maxlen]) + group = group[maxlen:] + yield (isap, group) + + def encode(self, seq_table, doc_table, isa): """ Encode this list as a sequence of u16 numbers. @@ -180,10 +200,22 @@ class EncList(object): words = list() docs = list() - for idx, enc in enumerate(self.encodings): - seq, doc = seq_doc(enc) - docs.append((len(words), doc)) - words.extend(seq) + # Group our encodings by isap. + for isap, group in self.by_isap(): + if isap: + # We have an ISA predicate covering `glen` encodings. + pnum = isa.settings.predicate_number[isap] + glen = len(group) + doc = 'skip {}x3 unless {}'.format(glen, isap) + docs.append((len(words), doc)) + words.append((glen << PRED_BITS) | pnum) + + for enc in group: + seq, doc = seq_doc(enc) + docs.append((len(words), doc)) + words.extend(seq) + + # Terminate the list. words.append(CODE_FAIL) self.offset = seq_table.add(words) @@ -269,13 +301,13 @@ def make_tables(cpumode): return table -def encode_enclists(level1, seq_table, doc_table): +def encode_enclists(level1, seq_table, doc_table, isa): """ Compute encodings and doc comments for encoding lists in `level1`. """ for level2 in level1: for enclist in level2: - enclist.encode(seq_table, doc_table) + enclist.encode(seq_table, doc_table, isa) def emit_enclists(seq_table, doc_table, fmt): @@ -397,7 +429,7 @@ def gen_isa(isa, fmt): level2_doc[len(level2_hashtables)].append(cpumode.name) level1 = make_tables(cpumode) level1_tables[cpumode] = level1 - encode_enclists(level1, seq_table, doc_table) + encode_enclists(level1, seq_table, doc_table, isa) encode_level2_hashtables(level1, level2_hashtables, level2_doc) # Level 1 table encodes offsets into the level 2 table. diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index ef87ade3f6..a4c1ce9885 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -40,16 +40,16 @@ fn isa_constructor(shared_flags: shared_settings::Flags, impl TargetIsa for Isa { fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { - shared_encoding::lookup_enclist(inst.first_type(), - inst.opcode(), - self.cpumode, - &encoding::LEVEL2[..]) + use isa::encoding::{lookup_enclist, general_encoding}; + lookup_enclist(inst.first_type(), + inst.opcode(), + self.cpumode, + &encoding::LEVEL2[..]) .and_then(|enclist_offset| { - shared_encoding::general_encoding(enclist_offset, - &encoding::ENCLISTS[..], - |instp| encoding::check_instp(inst, instp), - // TODO: Implement ISA predicates properly. - |isap| isap != 17) + general_encoding(enclist_offset, + &encoding::ENCLISTS[..], + |instp| encoding::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) }) } @@ -160,5 +160,40 @@ mod tests { // ADDI is I/0b00100 assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I/04"); + + // Create an imul.i32 which is encodable in RV32, but only when use_m is true. + let mul32 = InstructionData::Binary { + opcode: Opcode::Imul, + ty: types::I32, + args: [arg32, arg32], + }; + + assert_eq!(isa.encode(&dfg, &mul32), None); + } + + #[test] + fn test_rv32m() { + let mut shared_builder = settings::builder(); + shared_builder.set_bool("is_64bit", false).unwrap(); + let shared_flags = settings::Flags::new(shared_builder); + + // Set the supports_m stting which in turn enables the use_m predicate that unlocks + // encodings for imul. + let mut isa_builder = isa::lookup("riscv").unwrap(); + isa_builder.set_bool("supports_m", true).unwrap(); + + let isa = isa_builder.finish(shared_flags); + + let mut dfg = DataFlowGraph::new(); + let ebb = dfg.make_ebb(); + let arg32 = dfg.append_ebb_arg(ebb, types::I32); + + // Create an imul.i32 which is encodable in RV32M. + let mul32 = InstructionData::Binary { + opcode: Opcode::Imul, + ty: types::I32, + args: [arg32, arg32], + }; + assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32).unwrap()), "R/10c"); } } From de04d6d083f7094c28d95450541ed531a2da1215 Mon Sep 17 00:00:00 2001 From: Morgan Phillips Date: Tue, 6 Sep 2016 14:05:44 -0700 Subject: [PATCH 259/968] Add a verifier The current implementation only performs a few basic checks. --- src/libcretonne/Cargo.toml | 3 + src/libcretonne/ir/instructions.rs | 10 + src/libcretonne/ir/layout.rs | 7 +- src/libcretonne/ir/mod.rs | 2 +- src/libcretonne/lib.rs | 1 + src/libcretonne/test_utils/make_inst.rs | 10 +- src/libcretonne/verifier.rs | 171 ++++++++++++++++++ src/tools/Cargo.lock | 3 + src/tools/tests/verifier.rs | 66 +++++++ .../tests/verifier_testdata/bad_layout.cton | 17 ++ 10 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 src/libcretonne/verifier.rs create mode 100644 src/tools/tests/verifier.rs create mode 100644 src/tools/tests/verifier_testdata/bad_layout.cton diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index 4d9b59bb43..5d3a9511e6 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -11,3 +11,6 @@ build = "build.rs" [lib] name = "cretonne" path = "lib.rs" + +[dependencies] +regex = "0.1.71" diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 9638d660a5..a6d664170e 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -349,6 +349,16 @@ impl InstructionData { _ => BranchInfo::NotABranch, } } + + /// Return true if an instruction is terminating, or false otherwise. + pub fn is_terminating<'a>(&'a self) -> bool { + match self { + &InstructionData::Jump { .. } => true, + &InstructionData::Return { .. } => true, + &InstructionData::Nullary { .. } => true, + _ => false, + } + } } /// Information about branch and jump instructions. diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index a983ca178f..c519a1c44f 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -55,7 +55,7 @@ impl Layout { /// can only be removed from the layout when it is empty. /// /// Since every EBB must end with a terminator instruction which cannot fall through, the layout of -/// EBBs does not affect the semantics of the program. +/// EBBs do not affect the semantics of the program. /// impl Layout { /// Is `ebb` currently part of the layout? @@ -188,6 +188,11 @@ impl Layout { ebb_node.last_inst = inst; } + /// Fetch an ebb's last instruction. + pub fn last_inst(&self, ebb: Ebb) -> Inst { + self.ebbs[ebb].last_inst + } + /// Insert `inst` before the instruction `before` in the same EBB. pub fn insert_inst(&mut self, inst: Inst, before: Inst) { assert_eq!(self.inst_ebb(inst), None); diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 91f8b2516c..a26e48f15e 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -16,6 +16,6 @@ pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; pub use ir::instructions::{Opcode, InstructionData}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; -pub use ir::dfg::DataFlowGraph; +pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::Layout; pub use ir::function::Function; diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index fcddbdb900..f338aba8ac 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -14,6 +14,7 @@ pub mod cfg; pub mod dominator_tree; pub mod entity_map; pub mod settings; +pub mod verifier; mod constant_hash; mod predicates; diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index eb75b2fab3..a072b8ba8f 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -2,7 +2,7 @@ use ir::{Function, Ebb, Inst, Opcode}; use ir::entities::NO_VALUE; -use ir::instructions::{InstructionData, VariableArgs, JumpData, BranchData}; +use ir::instructions::{InstructionData, ReturnData, VariableArgs, JumpData, BranchData}; use ir::types; pub fn jump(func: &mut Function, dest: Ebb) -> Inst { @@ -27,3 +27,11 @@ pub fn branch(func: &mut Function, dest: Ebb) -> Inst { }), }) } + +pub fn ret(func: &mut Function) -> Inst { + func.dfg.make_inst(InstructionData::Return { + opcode: Opcode::Return, + ty: types::VOID, + data: Box::new(ReturnData { args: VariableArgs::new() }), + }) +} diff --git a/src/libcretonne/verifier.rs b/src/libcretonne/verifier.rs new file mode 100644 index 0000000000..e134b36285 --- /dev/null +++ b/src/libcretonne/verifier.rs @@ -0,0 +1,171 @@ +//! A verifier for ensuring that functions are well formed. +//! It verifies: +//! +//! EBB integrity +//! +//! - All instructions reached from the ebb_insts iterator must belong to +//! the EBB as reported by inst_ebb(). +//! - Every EBB must end in a terminator instruction, and no other instruction +//! can be a terminator. +//! - Every value in the ebb_args iterator belongs to the EBB as reported by value_ebb. +//! +//! Instruction integrity +//! +//! - The instruction format must match the opcode. +//! TODO: +//! - All result values must be created for multi-valued instructions. +//! - Instructions with no results must have a VOID first_type(). +//! - All referenced entities must exist. (Values, EBBs, stack slots, ...) +//! +//! SSA form +//! +//! - Values must be defined by an instruction that exists and that is inserted in +//! an EBB, or be an argument of an existing EBB. +//! - Values used by an instruction must dominate the instruction. +//! Control flow graph and dominator tree integrity: +//! +//! - All predecessors in the CFG must be branches to the EBB. +//! - All branches to an EBB must be present in the CFG. +//! - A recomputed dominator tree is identical to the existing one. +//! +//! Type checking +//! +//! - Compare input and output values against the opcode's type constraints. +//! For polymorphic opcodes, determine the controlling type variable first. +//! - Branches and jumps must pass arguments to destination EBBs that match the +//! expected types excatly. The number of arguments must match. +//! - All EBBs in a jump_table must take no arguments. +//! - Function calls are type checked against their signature. +//! - The entry block must take arguments that match the signature of the current +//! function. +//! - All return instructions must have return value operands matching the current +//! function signature. +//! +//! Ad hoc checking +//! +//! - Stack slot loads and stores must be in-bounds. +//! - Immediate constraints for certain opcodes, like udiv_imm v3, 0. +//! - Extend / truncate instructions have more type constraints: Source type can't be +//! larger / smaller than result type. +//! - Insertlane and extractlane instructions have immediate lane numbers that must be in +//! range for their polymorphic type. +//! - Swizzle and shuffle instructions take a variable number of lane arguments. The number +//! of arguments must match the destination type, and the lane indexes must be in range. + +use ir::{Function, ValueDef, Ebb, Inst}; +use ir::instructions::InstructionFormat; + +pub struct Verifier<'a> { + func: &'a Function, +} + +impl<'a> Verifier<'a> { + pub fn new(func: &'a Function) -> Verifier { + Verifier { func: func } + } + + fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<(), String> { + + let is_terminator = self.func.dfg[inst].is_terminating(); + let is_last_inst = self.func.layout.last_inst(ebb) == inst; + + if is_terminator && !is_last_inst { + // Terminating instructions only occur at the end of blocks. + return Err(format!("A terminating instruction was encountered before the \ + end of ebb {:?}!", + ebb)); + } + if is_last_inst && !is_terminator { + return Err(format!("Block {:?} does not end in a terminating instruction!", ebb)); + } + + // Instructions belong to the correct ebb. + let inst_ebb = self.func.layout.inst_ebb(inst); + if inst_ebb != Some(ebb) { + return Err(format!("{:?} should belong to {:?} not {:?}", inst, ebb, inst_ebb)); + } + + // Arguments belong to the correct ebb. + for arg in self.func.dfg.ebb_args(ebb) { + match self.func.dfg.value_def(arg) { + ValueDef::Arg(arg_ebb, _) => { + if ebb != arg_ebb { + return Err(format!("{:?} does not belong to {:?}", arg, ebb)); + } + } + _ => { + return Err("Expected an argument, found a result!".to_string()); + } + } + } + + Ok(()) + } + + fn instruction_integrity(&self, inst: Inst) -> Result<(), String> { + let inst_data = &self.func.dfg[inst]; + + // The instruction format matches the opcode + if inst_data.opcode().format() != Some(InstructionFormat::from(inst_data)) { + return Err("Instruction opcode doesn't match instruction format!".to_string()); + } + + Ok(()) + } + + pub fn run(&self) -> Result<(), String> { + for ebb in self.func.layout.ebbs() { + for inst in self.func.layout.ebb_insts(ebb) { + try!(self.ebb_integrity(ebb, inst)); + try!(self.instruction_integrity(inst)); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + extern crate regex; + + use super::*; + use ir::Function; + use ir::instructions::{InstructionData, Opcode}; + use ir::types; + use self::regex::Regex; + + macro_rules! assert_err_with_msg { + ($e:expr, $msg:expr) => ( + let err_re = Regex::new($msg).unwrap(); + match $e { + Ok(_) => { panic!("Expected an error!") }, + Err(err_msg) => { + if !err_re.is_match(&err_msg) { + panic!(format!("'{}' did not contain the pattern '{}'", err_msg, $msg)); + } + } + } + ) + } + + #[test] + fn empty() { + let func = Function::new(); + let verifier = Verifier::new(&func); + assert_eq!(verifier.run(), Ok(())); + } + + #[test] + fn bad_instruction_format() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + func.layout.append_ebb(ebb0); + let nullary_with_bad_opcode = func.dfg.make_inst(InstructionData::Nullary { + opcode: Opcode::Jump, + ty: types::VOID, + }); + func.layout.append_inst(nullary_with_bad_opcode, ebb0); + let verifier = Verifier::new(&func); + assert_err_with_msg!(verifier.run(), "instruction format"); + } +} diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index d7060f22e8..251f074c32 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -21,6 +21,9 @@ dependencies = [ [[package]] name = "cretonne" version = "0.0.0" +dependencies = [ + "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "cretonne-reader" diff --git a/src/tools/tests/verifier.rs b/src/tools/tests/verifier.rs new file mode 100644 index 0000000000..28bb1bbba5 --- /dev/null +++ b/src/tools/tests/verifier.rs @@ -0,0 +1,66 @@ +extern crate cretonne; +extern crate cton_reader; +extern crate glob; +extern crate regex; + +use std::env; +use glob::glob; +use regex::Regex; +use std::fs::File; +use std::io::Read; +use self::cton_reader::parser::Parser; +use self::cretonne::verifier::Verifier; + +/// Compile a function and run verifier tests based on specially formatted +/// comments in the [function's] source. +fn verifier_tests_from_source(function_source: &str) { + let func_re = Regex::new("^[ \t]*function.*").unwrap(); + let err_re = Regex::new(";[ \t]*Err\\((.*)+\\)").unwrap(); + + // Each entry corresponds to an optional regular expression, where + // the index corresponds to the function offset in our source code. + // If no error is expected for a given function its entry will be + // set to None. + let mut verifier_results = Vec::new(); + for line in function_source.lines() { + if func_re.is_match(line) { + match err_re.captures(line) { + Some(caps) => { + verifier_results.push(Some(Regex::new(caps.at(1).unwrap()).unwrap())); + }, + None => { + verifier_results.push(None); + }, + }; + } + } + + // Run the verifier against each function and compare the output + // with the expected result (as determined above). + for (i, func) in Parser::parse(function_source).unwrap().into_iter().enumerate() { + let result = Verifier::new(&func).run(); + match verifier_results[i] { + Some(ref re) => { + assert_eq!(re.is_match(&result.err().unwrap()), true); + }, + None => { + assert_eq!(result, Ok(())); + } + } + } +} + +#[test] +fn test_all() { + let testdir = format!("{}/tests/verifier_testdata/*.cton", + env::current_dir().unwrap().display()); + + for entry in glob(&testdir).unwrap() { + let path = entry.unwrap(); + println!("Testing {:?}", path); + let mut file = File::open(&path).unwrap(); + let mut buffer = String::new(); + file.read_to_string(&mut buffer).unwrap(); + verifier_tests_from_source(&buffer); + } +} diff --git a/src/tools/tests/verifier_testdata/bad_layout.cton b/src/tools/tests/verifier_testdata/bad_layout.cton new file mode 100644 index 0000000000..680299def7 --- /dev/null +++ b/src/tools/tests/verifier_testdata/bad_layout.cton @@ -0,0 +1,17 @@ +function test(i32) { ; Err(terminating) + ebb0(v0: i32): + jump ebb1 + return + ebb1: + jump ebb2 + brz v0, ebb3 + ebb2: + jump ebb3 + ebb3: + return +} + +function test(i32) { ; Ok + ebb0(v0: i32): + return +} From 5b22634c8d8f3945f05a4b4142c85cf9f66b5c97 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 1 Sep 2016 14:38:01 -0700 Subject: [PATCH 260/968] Add a libfilecheck crate. This library implements functionality similar to LLVM's FileCheck utility, but in library form. --- src/libfilecheck/Cargo.toml | 12 + src/libfilecheck/checker.rs | 392 ++++++++++++++++++++++++ src/libfilecheck/error.rs | 69 +++++ src/libfilecheck/lib.rs | 246 +++++++++++++++ src/libfilecheck/pattern.rs | 523 ++++++++++++++++++++++++++++++++ src/libfilecheck/tests/basic.rs | 313 +++++++++++++++++++ src/libfilecheck/variable.rs | 58 ++++ 7 files changed, 1613 insertions(+) create mode 100644 src/libfilecheck/Cargo.toml create mode 100644 src/libfilecheck/checker.rs create mode 100644 src/libfilecheck/error.rs create mode 100644 src/libfilecheck/lib.rs create mode 100644 src/libfilecheck/pattern.rs create mode 100644 src/libfilecheck/tests/basic.rs create mode 100644 src/libfilecheck/variable.rs diff --git a/src/libfilecheck/Cargo.toml b/src/libfilecheck/Cargo.toml new file mode 100644 index 0000000000..4f85778d92 --- /dev/null +++ b/src/libfilecheck/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["The Cretonne Project Developers"] +name = "filecheck" +version = "0.0.0" +publish = false + +[lib] +name = "filecheck" +path = "lib.rs" + +[dependencies] +regex = "0.1.71" diff --git a/src/libfilecheck/checker.rs b/src/libfilecheck/checker.rs new file mode 100644 index 0000000000..8abcbfdd3d --- /dev/null +++ b/src/libfilecheck/checker.rs @@ -0,0 +1,392 @@ +use error::{Error, Result}; +use variable::{VariableMap, Value, varname_prefix}; +use pattern::Pattern; +use regex::{Regex, Captures}; +use std::collections::HashMap; +use std::cmp::max; +use std::fmt::{self, Display, Formatter}; + +// The different kinds of directives we support. +enum Directive { + Check(Pattern), + SameLn(Pattern), + NextLn(Pattern), + Unordered(Pattern), + Not(Pattern), + Regex(String, String), +} + +// Regular expression matching a directive. +// The match groups are: +// +// 1. Keyword. +// 2. Rest of line / pattern. +// +const DIRECTIVE_RX: &'static str = r"\b(check|sameln|nextln|unordered|not|regex):\s+(.*)"; + +impl Directive { + /// Create a new directive from a `DIRECTIVE_RX` match. + fn new(caps: Captures) -> Result { + let cmd = caps.at(1).expect("group 1 must match"); + let rest = caps.at(2).expect("group 2 must match"); + + if cmd == "regex" { + return Directive::regex(rest); + } + + // All other commands are followed by a pattern. + let pat = try!(rest.parse()); + + match cmd { + "check" => Ok(Directive::Check(pat)), + "sameln" => Ok(Directive::SameLn(pat)), + "nextln" => Ok(Directive::NextLn(pat)), + "unordered" => Ok(Directive::Unordered(pat)), + "not" => { + if !pat.defs().is_empty() { + let msg = format!("can't define variables '$({}=...' in not: {}", + pat.defs()[0], + rest); + Err(Error::DuplicateDef(msg)) + } else { + Ok(Directive::Not(pat)) + } + } + _ => panic!("unexpected command {} in regex match", cmd), + } + } + + /// Create a `regex:` directive from a `VAR=...` string. + fn regex(rest: &str) -> Result { + let varlen = varname_prefix(rest); + if varlen == 0 { + return Err(Error::Syntax(format!("invalid variable name in regex: {}", rest))); + } + let var = rest[0..varlen].to_string(); + if !rest[varlen..].starts_with("=") { + return Err(Error::Syntax(format!("expected '=' after variable '{}' in regex: {}", + var, + rest))); + } + Ok(Directive::Regex(var, rest[varlen + 1..].to_string())) + } +} + + +/// Builder for constructing a `Checker` instance. +pub struct CheckerBuilder { + directives: Vec, + linerx: Regex, +} + +impl CheckerBuilder { + /// Create a new, blank `CheckerBuilder`. + pub fn new() -> CheckerBuilder { + CheckerBuilder { + directives: Vec::new(), + linerx: Regex::new(DIRECTIVE_RX).unwrap(), + } + } + + /// Add a potential directive line. + /// + /// Returns true if this is a a directive with one of the known prefixes. + /// Returns false if no known directive was found. + /// Returns an error if there is a problem with the directive. + pub fn directive(&mut self, l: &str) -> Result { + match self.linerx.captures(l) { + Some(caps) => { + self.directives.push(try!(Directive::new(caps))); + Ok(true) + } + None => Ok(false), + } + } + + /// Add multiple directives. + /// + /// The text is split into lines that are added individually as potential directives. + /// This method can be used to parse a whole test file containing multiple directives. + pub fn text(&mut self, t: &str) -> Result<&mut Self> { + for caps in self.linerx.captures_iter(t) { + self.directives.push(try!(Directive::new(caps))); + } + Ok(self) + } + + /// Get the finished `Checker`. + pub fn finish(&mut self) -> Checker { + // Move directives into the new checker, leaving `self.directives` empty and ready for + // building a new checker. + Checker::new(self.directives.split_off(0)) + } +} + +/// Verify a list of directives against a test input. +/// +/// Use a `CheckerBuilder` to construct a `Checker`. Then use the `test` method to verify the list +/// of directives against a test input. +pub struct Checker { + directives: Vec, +} + +impl Checker { + fn new(directives: Vec) -> Checker { + Checker { directives: directives } + } + + /// An empty checker contains no directives, and will match any input string. + pub fn is_empty(&self) -> bool { + self.directives.is_empty() + } + + /// Verify directives against the input text. + /// + /// This returns `true` if the text matches all the directives, `false` if it doesn't. + /// An error is only returned if there is a problem with the directives. + pub fn check(&self, text: &str, vars: &VariableMap) -> Result { + let mut state = State::new(text, vars); + + // For each pending `not:` check, store (begin-offset, regex). + let mut nots = Vec::new(); + + for dct in &self.directives { + let (pat, range) = match *dct { + Directive::Check(ref pat) => (pat, state.check()), + Directive::SameLn(ref pat) => (pat, state.sameln()), + Directive::NextLn(ref pat) => (pat, state.nextln()), + Directive::Unordered(ref pat) => (pat, state.unordered(pat)), + Directive::Not(ref pat) => { + // Resolve `not:` directives immediately to get the right variable values, but + // don't match it until we know the end of the range. + // + // The `not:` directives test the same range as `unordered:` directives. In + // particular, if they refer to defined variables, their range is restricted to + // the text following the match that defined the variable. + nots.push((state.unordered_begin(pat), try!(pat.resolve(&state)))); + continue; + } + Directive::Regex(ref var, ref rx) => { + state.vars.insert(var.clone(), + VarDef { + value: Value::Regex(rx.clone()), + offset: 0, + }); + continue; + } + }; + // Check if `pat` matches in `range`. + if let Some((match_begin, match_end)) = try!(state.match_positive(pat, range)) { + if let &Directive::Unordered(_) = dct { + // This was an unordered unordered match. + // Keep track of the largest matched position, but leave `last_ordered` alone. + state.max_match = max(state.max_match, match_end); + } else { + // Ordered match. + state.last_ordered = match_end; + state.max_match = match_end; + + // Verify any pending `not:` directives now that we know their range. + for (not_begin, rx) in nots.drain(..) { + if let Some(_) = rx.find(&text[not_begin..match_begin]) { + // Matched `not:` pattern. + // TODO: Use matched range for an error message. + return Ok(false); + } + } + } + } else { + // No match! + return Ok(false); + } + } + + // Verify any pending `not:` directives after the last ordered directive. + for (not_begin, rx) in nots.drain(..) { + if let Some(_) = rx.find(&text[not_begin..]) { + // Matched `not:` pattern. + // TODO: Use matched range for an error message. + return Ok(false); + } + } + + Ok(true) + } +} + +/// A local definition of a variable. +pub struct VarDef { + /// The value given to the variable. + value: Value, + /// Offset in input text from where the variable is available. + offset: usize, +} + +struct State<'a> { + env_vars: &'a VariableMap, + text: &'a str, + vars: HashMap, + // Offset after the last ordered match. This does not include recent unordered matches. + last_ordered: usize, + // Largest offset following a positive match, including unordered matches. + max_match: usize, +} + +impl<'a> State<'a> { + fn new(text: &'a str, env_vars: &'a VariableMap) -> State<'a> { + State { + text: text, + env_vars: env_vars, + vars: HashMap::new(), + last_ordered: 0, + max_match: 0, + } + } + + // Get the offset following the match that defined `var`, or 0 if var is an environment + // variable or unknown. + fn def_offset(&self, var: &str) -> usize { + self.vars.get(var).map(|&VarDef { offset, .. }| offset).unwrap_or(0) + } + + // Get the offset of the beginning of the next line after `pos`. + fn bol(&self, pos: usize) -> usize { + if let Some(offset) = self.text[pos..].find('\n') { + pos + offset + 1 + } else { + self.text.len() + } + } + + // Get the range in text to be matched by a `check:`. + fn check(&self) -> (usize, usize) { + (self.max_match, self.text.len()) + } + + // Get the range in text to be matched by a `sameln:`. + fn sameln(&self) -> (usize, usize) { + let b = self.max_match; + let e = self.bol(b); + (b, e) + } + + // Get the range in text to be matched by a `nextln:`. + fn nextln(&self) -> (usize, usize) { + let b = self.bol(self.max_match); + let e = self.bol(b); + (b, e) + } + + // Get the beginning of the range in text to be matched by a `unordered:` or `not:` directive. + // The unordered directive must match after the directives that define the variables used. + fn unordered_begin(&self, pat: &Pattern) -> usize { + let mut from = self.last_ordered; + for part in pat.parts() { + if let Some(var) = part.ref_var() { + from = max(from, self.def_offset(var)); + } + } + from + } + + // Get the range in text to be matched by a `unordered:` directive. + fn unordered(&self, pat: &Pattern) -> (usize, usize) { + (self.unordered_begin(pat), self.text.len()) + } + + // Search for `pat` in `range`, return the range matched. + // After a positive match, update variable definitions, if any. + fn match_positive(&mut self, + pat: &Pattern, + range: (usize, usize)) + -> Result> { + let rx = try!(pat.resolve(self)); + let txt = &self.text[range.0..range.1]; + let defs = pat.defs(); + let matched_range = if defs.is_empty() { + // Pattern defines no variables. Fastest search is `find`. + rx.find(txt) + } else { + // We need the captures to define variables. + rx.captures(txt).map(|caps| { + let matched_range = caps.pos(0).expect("whole expression must match"); + for var in defs { + let vardef = VarDef { + value: Value::Text(caps.name(var).unwrap_or("").to_string()), + // This offset is the end of the whole matched pattern, not just the text + // defining the variable. + offset: range.0 + matched_range.1, + }; + self.vars.insert(var.clone(), vardef); + } + matched_range + }) + }; + Ok(matched_range.map(|(b, e)| (range.0 + b, range.0 + e))) + } +} + +impl<'a> VariableMap for State<'a> { + fn lookup(&self, varname: &str) -> Option { + // First look for a local define. + if let Some(&VarDef { ref value, .. }) = self.vars.get(varname) { + Some(value.clone()) + } else { + // No local, maybe an environment variable? + self.env_vars.lookup(varname) + } + } +} + +impl Display for Directive { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use self::Directive::*; + match *self { + Check(ref pat) => writeln!(f, "check: {}", pat), + SameLn(ref pat) => writeln!(f, "sameln: {}", pat), + NextLn(ref pat) => writeln!(f, "nextln: {}", pat), + Unordered(ref pat) => writeln!(f, "unordered: {}", pat), + Not(ref pat) => writeln!(f, "not: {}", pat), + Regex(ref var, ref rx) => writeln!(f, "regex: {}={}", var, rx), + } + } +} + +impl Display for Checker { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for (idx, dir) in self.directives.iter().enumerate() { + try!(write!(f, "#{} {}", idx, dir)); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::CheckerBuilder; + use error::Error; + + fn e2s(e: Error) -> String { + e.to_string() + } + + #[test] + fn directive() { + let mut b = CheckerBuilder::new(); + + assert_eq!(b.directive("not here: more text").map_err(e2s), Ok(false)); + assert_eq!(b.directive("not here: regex: X=more text").map_err(e2s), + Ok(true)); + assert_eq!(b.directive("regex: X = tommy").map_err(e2s), + Err("expected '=' after variable 'X' in regex: X = tommy".to_string())); + assert_eq!(b.directive("[arm]not: patt $x $(y) here").map_err(e2s), + Ok(true)); + assert_eq!(b.directive("[x86]sameln: $x $(y=[^]]*) there").map_err(e2s), + Ok(true)); + + let c = b.finish(); + assert_eq!(c.to_string(), + "#0 regex: X=more text\n#1 not: patt $(x) $(y) here\n#2 sameln: $(x) \ + $(y=[^]]*) there\n"); + } +} diff --git a/src/libfilecheck/error.rs b/src/libfilecheck/error.rs new file mode 100644 index 0000000000..1cc17001fc --- /dev/null +++ b/src/libfilecheck/error.rs @@ -0,0 +1,69 @@ +use std::result; +use std::convert::From; +use std::error::Error as StdError; +use std::fmt; +use regex; + +/// A result from the filecheck library. +pub type Result = result::Result; + +/// A filecheck error. +#[derive(Debug)] +pub enum Error { + /// A syntax error in a check line. + Syntax(String), + /// A check refers to an undefined variable. + /// + /// The pattern contains `$foo` where the `foo` variable has not yet been defined. + /// Use `$$` to match a literal dollar sign. + UndefVariable(String), + /// A pattern contains a back-reference to a variable that was defined in the same pattern. + /// + /// For example, `check: Hello $(world=.*) $world`. Backreferences are not support. Often the + /// desired effect can be achieved with the `sameln` check: + /// + /// ```text + /// check: Hello $(world=[^ ]*) + /// sameln: $world + /// ``` + Backref(String), + /// A pattern contains multiple definitions of the same variable. + DuplicateDef(String), + /// An error in a regular expression. + /// + /// Use `cause()` to get the underlying `Regex` library error. + Regex(regex::Error), +} + +impl StdError for Error { + fn description(&self) -> &str { + use Error::*; + match *self { + Syntax(ref s) => s, + UndefVariable(ref s) => s, + Backref(ref s) => s, + DuplicateDef(ref s) => s, + Regex(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&StdError> { + use Error::*; + match *self { + Regex(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{}", self.description()) + } +} + +impl From for Error { + fn from(e: regex::Error) -> Error { + Error::Regex(e) + } +} diff --git a/src/libfilecheck/lib.rs b/src/libfilecheck/lib.rs new file mode 100644 index 0000000000..16d6d5028d --- /dev/null +++ b/src/libfilecheck/lib.rs @@ -0,0 +1,246 @@ +//! This crate provides a text pattern matching library with functionality similar to the LLVM +//! project's [FileCheck command](http://llvm.org/docs/CommandGuide/FileCheck.html). +//! +//! A list of directives is typically extracted from a file containing a test case. The test case +//! is then run through the program under test, and its output matched against the directives. +//! +//! See the [CheckerBuilder](struct.CheckerBuilder.html) and [Checker](struct.Checker.html) types +//! for the main library API. +//! +//! # Directives +//! +//! These are the directives recognized by *filecheck*: +//!
+//! check: <pattern>
+//! sameln: <pattern>
+//! nextln: <pattern>
+//! unordered: <pattern>
+//! not: <pattern>
+//! regex: <variable>=<regex>
+//! 
+//! Each directive is described in more detail below. +//! +//! ## Example +//! +//! The Rust program below prints the primes less than 100. It has *filecheck* directives embedded +//! in comments: +//! +//! ```rust +//! fn is_prime(x: u32) -> bool { +//! (2..x).all(|d| x % d != 0) +//! } +//! +//! // Check that we get the primes and nothing else: +//! // regex: NUM=\d+ +//! // not: $NUM +//! // check: 2 +//! // nextln: 3 +//! // check: 89 +//! // nextln: 97 +//! // not: $NUM +//! fn main() { +//! for p in (2..10).filter(|&x| is_prime(x)) { +//! println!("{}", p); +//! } +//! } +//! ``` +//! +//! A test driver compiles and runs the program, then pipes the output through *filecheck*: +//! +//! ```sh +//! $ rustc primes.rs +//! $ ./primes | cton-util filecheck -v +//! #0 regex: NUM=\d+ +//! #1 not: $NUM +//! #2 check: 2 +//! #3 nextln: 3 +//! #4 check: 89 +//! #5 nextln: 97 +//! #6 not: $NUM +//! no match #1: \d+ +//! > 2 +//! ~ +//! match #2: \b2\b +//! > 3 +//! ~ +//! match #3: \b3\b +//! > 5 +//! > 7 +//! ... +//! > 79 +//! > 83 +//! > 89 +//! ~~ +//! match #4: \b89\b +//! > 97 +//! ~~ +//! match #5: \b97\b +//! no match #6: \d+ +//! OK +//! ``` +//! +//! ## The `check:` directive +//! +//! Match patterns non-overlapping and in order: +//! +//! ```sh +//! #0 check: one +//! #1 check: two +//! ``` +//! +//! These directives will match the string `"one two"`, but not `"two one"`. The second directive +//! must match after the first one, and it can't overlap. +//! +//! ## The `sameln:` directive +//! +//! Match a pattern in the same line as the previous match. +//! +//! ```sh +//! #0 check: one +//! #1 sameln: two +//! ``` +//! +//! These directives will match the string `"one two"`, but not `"one\ntwo"`. The second match must +//! be in the same line as the first. Like the `check:` directive, the match must also follow the +//! first match, so `"two one" would not be matched. +//! +//! If there is no previous match, `sameln:` matches on the first line of the input. +//! +//! ## The `nextln:` directive +//! +//! Match a pattern in the next line after the previous match. +//! +//! ```sh +//! #0 check: one +//! #1 nextln: two +//! ``` +//! +//! These directives will match the string `"one\ntwo"`, but not `"one two"` or `"one\n\ntwo"`. +//! +//! If there is no previous match, `nextln:` matches on the second line of the input as if there +//! were a previous match on the first line. +//! +//! ## The `unordered:` directive +//! +//! Match patterns in any order, and possibly overlapping each other. +//! +//! ```sh +//! #0 unordered: one +//! #1 unordered: two +//! ``` +//! +//! These directives will match the string `"one two"` *and* the string `"two one"`. +//! +//! When a normal ordered match is inserted into a sequence of `unordered:` directives, it acts as +//! a barrier: +//! +//! ```sh +//! #0 unordered: one +//! #1 unordered: two +//! #2 check: three +//! #3 unordered: four +//! #4 unordered: five +//! ``` +//! +//! These directives will match `"two one three four five"`, but not `"two three one four five"`. +//! The `unordered:` matches are not allowed to cross the ordered `check:` directive. +//! +//! When `unordered:` matches define and use variables, a topological order is enforced. This means +//! that a match referencing a variable must follow the match where the variable was defined: +//! +//! ```sh +//! #0 regex: V=\bv\d+\b +//! #1 unordered: $(va=$V) = load +//! #2 unordered: $(vb=$V) = iadd $va +//! #3 unordered: $(vc=$V) = load +//! #4 unordered: iadd $va, $vc +//! ``` +//! +//! In the above directives, #2 must match after #1, and #4 must match after both #1 and #3, but +//! otherwise they can match in any order. +//! +//! ## The `not:` directive +//! +//! Check that a pattern *does not* appear between matches. +//! +//! ```sh +//! #0 check: one +//! #1 not: two +//! #2 check: three +//! ``` +//! +//! The directives above will match `"one five three"`, but not `"one two three"`. +//! +//! The pattern in a `not:` directive can't define any variables. Since it never matches anything, +//! the variables would not get a value. +//! +//! ## The `regex:` directive +//! +//! Define a shorthand name for a regular expression. +//! +//! ```sh +//! #0 regex: ID=\b[_a-zA-Z][_0-9a-zA-Z]*\b +//! #1 check: $ID + $ID +//! ``` +//! +//! The `regex:` directive gives a name to a regular expression which can then be used as part of a +//! pattern to match. Patterns are otherwise just plain text strings to match, so this is not +//! simple macro expansion. +//! +//! See [the Rust regex crate](../regex/index.html#syntax) for the regular expression syntax. +//! +//! # Patterns and variables +//! +//! Patterns are plain text strings to be matched in the input file. The dollar sign is used as an +//! escape character to expand variables. The following escape sequences are recognized: +//! +//!
+//! $$                Match single dollar sign.
+//! $()               Match the empty string.
+//! $(=<regex>)       Match regular expression <regex>.
+//! $<var>            Match contents of variable <var>.
+//! $(<var>)          Match contents of variable <var>.
+//! $(<var>=<regex>)  Match <regex>, then
+//!                   define <var> as the matched text.
+//! $(<var>=$<rxvar>) Match regex in <rxvar>, then
+//!                   define <var> as the matched text.
+//! 
+//! +//! Variables can contain either plain text or regular expressions. Plain text variables are +//! defined with the `$(var=...)` syntax in a previous directive. They match the same text again. +//! Backreferences within the same pattern are not allowed. When a variable is defined in a +//! pattern, it can't be referenced again in the same pattern. +//! +//! Regular expression variables are defined with the `regex:` directive. They match the regular +//! expression each time they are used, so the matches don't need to be identical. +//! +//! ## Word boundaries +//! +//! If a pattern begins or ends with a (plain text) letter or number, it will only match on a word +//! boundary. Use the `$()` empty string match to prevent this: +//! +//! ```sh +//! check: one$() +//! ``` +//! +//! This will match `"one"` and `"onetwo"`, but not `"zeroone"`. +//! +//! The empty match syntax can also be used to require leading or trailing whitespace: +//! +//! ```sh +//! check: one, $() +//! ``` +//! +//! This will match `"one, two"` , but not `"one,two"`. Without the `$()`, trailing whitespace +//! would be trimmed from the pattern. + +pub use error::{Error, Result}; +pub use variable::{VariableMap, Value, NO_VARIABLES}; +pub use checker::{Checker, CheckerBuilder}; + +extern crate regex; + +mod error; +mod variable; +mod pattern; +mod checker; diff --git a/src/libfilecheck/pattern.rs b/src/libfilecheck/pattern.rs new file mode 100644 index 0000000000..69464bd7c8 --- /dev/null +++ b/src/libfilecheck/pattern.rs @@ -0,0 +1,523 @@ +//! Pattern matching for a single directive. + +use error::{Error, Result}; +use variable::{varname_prefix, VariableMap, Value}; +use std::str::FromStr; +use std::fmt::{self, Display, Formatter, Write}; +use regex::{Regex, RegexBuilder, quote}; + +/// A pattern to match as specified in a directive. +/// +/// Each pattern is broken into a sequence of parts that must match in order. The kinds of parts +/// are: +/// +/// 1. Plain text match. +/// 2. Variable match, `$FOO` or `$(FOO)`. The variable `FOO` may expand to plain text or a regex. +/// 3. Variable definition from literal regex, `$(foo=.*)`. Match the regex and assign matching text +/// to variable `foo`. +/// 4. Variable definition from regex variable, `$(foo=$RX)`. Lookup variable `RX` which should +/// expand to a regex, match the regex, and assign matching text to variable `foo`. +/// +pub struct Pattern { + parts: Vec, + // Variables defined by this pattern. + defs: Vec, +} + +/// One atomic part of a pattern. +#[derive(Debug, PartialEq, Eq)] +pub enum Part { + /// Match a plain string. + Text(String), + /// Match a regular expression. The regex has already been wrapped in a non-capturing group if + /// necessary, so it is safe to concatenate. + Regex(String), + /// Match the contents of a variable, which can be plain text or regex. + Var(String), + /// Match literal regex, then assign match to variable. + /// The regex has already been wrapped in a named capture group. + DefLit { def: usize, regex: String }, + /// Lookup variable `var`, match resulting regex, assign matching text to variable `defs[def]`. + DefVar { def: usize, var: String }, +} + +impl Part { + /// Get the variabled referenced by this part, if any. + pub fn ref_var(&self) -> Option<&str> { + match *self { + Part::Var(ref var) => Some(var), + Part::DefVar { ref var, .. } => Some(var), + _ => None, + } + } +} + +impl Pattern { + /// Create a new blank pattern. Use the `FromStr` trait to generate Patterns with content. + fn new() -> Pattern { + Pattern { + parts: Vec::new(), + defs: Vec::new(), + } + } + + /// Check if the variable `v` is defined by this pattern. + pub fn defines_var(&self, v: &str) -> bool { + self.defs.iter().any(|d| d == v) + } + + /// Add a definition of a new variable. + /// Return the allocated def number. + fn add_def(&mut self, v: &str) -> Result { + if self.defines_var(v) { + Err(Error::DuplicateDef(format!("duplicate definition of ${} in same pattern", v))) + } else { + let idx = self.defs.len(); + self.defs.push(v.to_string()); + Ok(idx) + } + } + + /// Parse a `Part` from a prefix of `s`. + /// Return the part and the number of bytes consumed from `s`. + /// Adds defined variables to `self.defs`. + fn parse_part(&mut self, s: &str) -> Result<(Part, usize)> { + let dollar = s.find('$'); + if dollar != Some(0) { + // String doesn't begin with a dollar sign, so match plain text up to the dollar sign. + let end = dollar.unwrap_or(s.len()); + return Ok((Part::Text(s[0..end].to_string()), end)); + } + + // String starts with a dollar sign. Look for these possibilities: + // + // 1. `$$`. + // 2. `$var`. + // 3. `$(var)`. + // 4. `$(var=regex)`. Where `regex` is a regular expression possibly containing matching + // braces. + // 5. `$(var=$VAR)`. + + // A doubled dollar sign matches a single dollar sign. + if s.starts_with("$$") { + return Ok((Part::Text("$".to_string()), 2)); + } + + // Look for `$var`. + let varname_end = 1 + varname_prefix(&s[1..]); + if varname_end != 1 { + return Ok((Part::Var(s[1..varname_end].to_string()), varname_end)); + } + + // All remaining possibilities start with `$(`. + if s.len() < 2 || !s.starts_with("$(") { + return Err(Error::Syntax("pattern syntax error, use $$ to match a single $" + .to_string())); + } + + // Match the variable name, allowing for an empty varname in `$()`, or `$(=...)`. + let varname_end = 2 + varname_prefix(&s[2..]); + let varname = s[2..varname_end].to_string(); + + match s[varname_end..].chars().next() { + None => { + return Err(Error::Syntax(format!("unterminated $({}...", varname))); + } + Some(')') => { + let part = if varname.is_empty() { + // Match `$()`, turn it into an empty text match. + Part::Text(varname) + } else { + // Match `$(var)`. + Part::Var(varname) + }; + return Ok((part, varname_end + 1)); + } + Some('=') => { + // Variable definition. Fall through. + } + Some(ch) => { + return Err(Error::Syntax(format!("syntax error in $({}... '{}'", varname, ch))); + } + } + + // This is a variable definition of the form `$(var=...`. + + // Allocate a definition index. + let def = if varname.is_empty() { + None + } else { + Some(try!(self.add_def(&varname))) + }; + + // Match `$(var=$PAT)`. + if s[varname_end + 1..].starts_with('$') { + let refname_begin = varname_end + 2; + let refname_end = refname_begin + varname_prefix(&s[refname_begin..]); + if refname_begin == refname_end { + return Err(Error::Syntax(format!("expected variable name in $({}=$...", varname))); + } + if !s[refname_end..].starts_with(')') { + return Err(Error::Syntax(format!("expected ')' after $({}=${}...", + varname, + &s[refname_begin..refname_end]))); + } + let refname = s[refname_begin..refname_end].to_string(); + return if let Some(defidx) = def { + Ok((Part::DefVar { + def: defidx, + var: refname, + }, + refname_end + 1)) + } else { + Err(Error::Syntax(format!("expected variable name in $(=${})", refname))) + }; + } + + // Last case: `$(var=...)` where `...` is a regular expression, possibly containing matched + // parentheses. + let rx_begin = varname_end + 1; + let rx_end = rx_begin + regex_prefix(&s[rx_begin..]); + if s[rx_end..].starts_with(')') { + let part = if let Some(defidx) = def { + // Wrap the regex in a named capture group. + Part::DefLit { + def: defidx, + regex: format!("(?P<{}>{})", varname, &s[rx_begin..rx_end]), + } + } else { + // When the varname is empty just match the regex, don't capture any variables. + // This is `$(=[a-z])`. + // Wrap the regex in a non-capturing group to make it concatenation-safe. + Part::Regex(format!("(?:{})", &s[rx_begin..rx_end])) + }; + Ok((part, rx_end + 1)) + } else { + Err(Error::Syntax(format!("missing ')' after regex in $({}={}", + varname, + &s[rx_begin..rx_end]))) + } + } +} + +/// Compute the length of a regular expression terminated by `)` or `}`. +/// Handle nested and escaped parentheses in the rx, but don't actualy parse it. +/// Return the position of the terminating brace or the length of the string. +fn regex_prefix(s: &str) -> usize { + // The prevous char was a backslash. + let mut escape = false; + // State around parsing charsets. + enum State { + Normal, // Outside any charset. + Curly, // Inside curly braces. + CSFirst, // Immediately after opening `[`. + CSNeg, // Immediately after `[^`. + CSBody, // Inside `[...`. + } + let mut state = State::Normal; + + // Current nesting level of parens. + let mut nest = 0usize; + + for (idx, ch) in s.char_indices() { + if escape { + escape = false; + continue; + } else if ch == '\\' { + escape = true; + continue; + } + match state { + State::Normal => { + match ch { + '[' => state = State::CSFirst, + '{' => state = State::Curly, + '(' => nest += 1, + ')' if nest > 0 => nest -= 1, + ')' | '}' => return idx, + _ => {} + } + } + State::Curly => { + if ch == '}' { + state = State::Normal; + } + } + State::CSFirst => { + state = match ch { + '^' => State::CSNeg, + _ => State::CSBody, + } + } + State::CSNeg => state = State::CSBody, + State::CSBody => { + if ch == ']' { + state = State::Normal; + } + } + } + } + s.len() +} + +impl FromStr for Pattern { + type Err = Error; + + fn from_str(s: &str) -> Result { + // Always remove leading and trailing whitespace. + // Use `$()` to actually include that in a match. + let s = s.trim(); + let mut pat = Pattern::new(); + let mut pos = 0; + while pos < s.len() { + let (part, len) = try!(pat.parse_part(&s[pos..])); + if let Some(v) = part.ref_var() { + if pat.defines_var(v) { + return Err(Error::Backref(format!("unsupported back-reference to '${}' \ + defined in same pattern", + v))); + } + } + pat.parts.push(part); + pos += len; + } + Ok(pat) + } +} + +impl Pattern { + /// Get a list of parts in this pattern. + pub fn parts(&self) -> &[Part] { + &self.parts + } + + /// Get a list of variable names defined when this pattern matches. + pub fn defs(&self) -> &[String] { + &self.defs + } + + /// Resolve all variable references in this pattern, turning it into a regular expression. + pub fn resolve(&self, vmap: &VariableMap) -> Result { + let mut out = String::new(); + + // Add a word boundary check `\b` to the beginning of the regex, but only if the first part + // is a plain text match that starts with a word character. + // + // This behavior can be disabled by starting the pattern with `$()`. + if let Some(&Part::Text(ref s)) = self.parts.first() { + if s.starts_with(char::is_alphanumeric) { + out.push_str(r"\b"); + } + } + + for part in &self.parts { + match *part { + Part::Text(ref s) => { + out.push_str("e(s)); + } + Part::Regex(ref rx) => out.push_str(rx), + Part::Var(ref var) => { + // Resolve the variable. We can handle a plain text expansion. + match vmap.lookup(var) { + None => { + return Err(Error::UndefVariable(format!("undefined variable ${}", var))) + } + Some(Value::Text(s)) => out.push_str("e(&s)), + // Wrap regex in non-capturing group for safe concatenation. + Some(Value::Regex(rx)) => write!(out, "(?:{})", rx).unwrap(), + } + } + Part::DefLit { ref regex, .. } => out.push_str(regex), + Part::DefVar { def, ref var } => { + // Wrap regex in a named capture group. + write!(out, + "(?P<{}>{})", + self.defs[def], + match vmap.lookup(var) { + None => { + return Err(Error::UndefVariable(format!("undefined variable \ + ${}", + var))) + } + Some(Value::Text(s)) => quote(&s), + Some(Value::Regex(rx)) => rx, + }) + .unwrap() + } + } + + } + + // Add a word boundary check `\b` to the end of the regex, but only if the final part + // is a plain text match that ends with a word character. + // + // This behavior can be disabled by ending the pattern with `$()`. + if let Some(&Part::Text(ref s)) = self.parts.last() { + if s.ends_with(char::is_alphanumeric) { + out.push_str(r"\b"); + } + } + + Ok(try!(RegexBuilder::new(&out).multi_line(true).compile())) + } +} + +impl Display for Pattern { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for part in &self.parts { + use self::Part::*; + try!(match *part { + Text(ref txt) if txt == "" => write!(f, "$()"), + Text(ref txt) if txt == "$" => write!(f, "$$"), + Text(ref txt) => write!(f, "{}", txt), + Regex(ref rx) => write!(f, "$(={})", rx), + Var(ref var) => write!(f, "$({})", var), + DefLit { def, ref regex } => { + let defvar = &self.defs[def]; + // (?P...). + let litrx = ®ex[5 + defvar.len()..regex.len() - 1]; + write!(f, "$({}={})", defvar, litrx) + } + DefVar { def, ref var } => write!(f, "$({}=${})", self.defs[def], var), + }); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn regex() { + use super::regex_prefix; + + assert_eq!(regex_prefix(""), 0); + assert_eq!(regex_prefix(")"), 0); + assert_eq!(regex_prefix(")c"), 0); + assert_eq!(regex_prefix("x"), 1); + assert_eq!(regex_prefix("x)x"), 1); + + assert_eq!(regex_prefix("x(c))x"), 4); + assert_eq!(regex_prefix("()x(c))x"), 6); + assert_eq!(regex_prefix("()x(c)"), 6); + + assert_eq!(regex_prefix("x([)]))x"), 6); + assert_eq!(regex_prefix("x[)])x"), 4); + assert_eq!(regex_prefix("x[^)])x"), 5); + assert_eq!(regex_prefix("x[^])x"), 6); + } + + #[test] + fn part() { + use super::{Pattern, Part}; + let mut pat = Pattern::new(); + + // This is dubious, should we panic instead? + assert_eq!(pat.parse_part("").unwrap(), (Part::Text("".to_string()), 0)); + + assert_eq!(pat.parse_part("x").unwrap(), + (Part::Text("x".to_string()), 1)); + assert_eq!(pat.parse_part("x2").unwrap(), + (Part::Text("x2".to_string()), 2)); + assert_eq!(pat.parse_part("x$").unwrap(), + (Part::Text("x".to_string()), 1)); + assert_eq!(pat.parse_part("x$$").unwrap(), + (Part::Text("x".to_string()), 1)); + + assert_eq!(pat.parse_part("$").unwrap_err().to_string(), + "pattern syntax error, use $$ to match a single $"); + + assert_eq!(pat.parse_part("$$").unwrap(), + (Part::Text("$".to_string()), 2)); + assert_eq!(pat.parse_part("$$ ").unwrap(), + (Part::Text("$".to_string()), 2)); + + assert_eq!(pat.parse_part("$0").unwrap(), + (Part::Var("0".to_string()), 2)); + assert_eq!(pat.parse_part("$xx=").unwrap(), + (Part::Var("xx".to_string()), 3)); + assert_eq!(pat.parse_part("$xx$").unwrap(), + (Part::Var("xx".to_string()), 3)); + + assert_eq!(pat.parse_part("$(0)").unwrap(), + (Part::Var("0".to_string()), 4)); + assert_eq!(pat.parse_part("$()").unwrap(), + (Part::Text("".to_string()), 3)); + + assert_eq!(pat.parse_part("$(0").unwrap_err().to_string(), + ("unterminated $(0...")); + assert_eq!(pat.parse_part("$(foo:").unwrap_err().to_string(), + ("syntax error in $(foo... ':'")); + assert_eq!(pat.parse_part("$(foo =").unwrap_err().to_string(), + ("syntax error in $(foo... ' '")); + assert_eq!(pat.parse_part("$(eo0=$bar").unwrap_err().to_string(), + ("expected ')' after $(eo0=$bar...")); + assert_eq!(pat.parse_part("$(eo1=$bar}").unwrap_err().to_string(), + ("expected ')' after $(eo1=$bar...")); + assert_eq!(pat.parse_part("$(eo2=$)").unwrap_err().to_string(), + ("expected variable name in $(eo2=$...")); + assert_eq!(pat.parse_part("$(eo3=$-)").unwrap_err().to_string(), + ("expected variable name in $(eo3=$...")); + } + + #[test] + fn partdefs() { + use super::{Pattern, Part}; + let mut pat = Pattern::new(); + + assert_eq!(pat.parse_part("$(foo=$bar)").unwrap(), + (Part::DefVar { + def: 0, + var: "bar".to_string(), + }, + 11)); + assert_eq!(pat.parse_part("$(foo=$bar)").unwrap_err().to_string(), + "duplicate definition of $foo in same pattern"); + + assert_eq!(pat.parse_part("$(fxo=$bar)x").unwrap(), + (Part::DefVar { + def: 1, + var: "bar".to_string(), + }, + 11)); + + assert_eq!(pat.parse_part("$(fo2=[a-z])").unwrap(), + (Part::DefLit { + def: 2, + regex: "(?P[a-z])".to_string(), + }, + 12)); + assert_eq!(pat.parse_part("$(fo3=[a-)])").unwrap(), + (Part::DefLit { + def: 3, + regex: "(?P[a-)])".to_string(), + }, + 12)); + assert_eq!(pat.parse_part("$(fo4=)").unwrap(), + (Part::DefLit { + def: 4, + regex: "(?P)".to_string(), + }, + 7)); + + assert_eq!(pat.parse_part("$(=.*)").unwrap(), + (Part::Regex("(?:.*)".to_string()), 6)); + + assert_eq!(pat.parse_part("$(=)").unwrap(), + (Part::Regex("(?:)".to_string()), 4)); + assert_eq!(pat.parse_part("$()").unwrap(), + (Part::Text("".to_string()), 3)); + } + + #[test] + fn pattern() { + use super::Pattern; + + let p: Pattern = " Hello world! ".parse().unwrap(); + assert_eq!(format!("{:?}", p.parts), "[Text(\"Hello world!\")]"); + + let p: Pattern = " $foo=$(bar) ".parse().unwrap(); + assert_eq!(format!("{:?}", p.parts), + "[Var(\"foo\"), Text(\"=\"), Var(\"bar\")]"); + } +} diff --git a/src/libfilecheck/tests/basic.rs b/src/libfilecheck/tests/basic.rs new file mode 100644 index 0000000000..595158192c --- /dev/null +++ b/src/libfilecheck/tests/basic.rs @@ -0,0 +1,313 @@ +extern crate filecheck; + +use filecheck::{CheckerBuilder, NO_VARIABLES, Error as FcError}; + +fn e2s(e: FcError) -> String { + e.to_string() +} + +#[test] +fn empty() { + let c = CheckerBuilder::new().finish(); + assert!(c.is_empty()); + + // An empty checker matches anything. + assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn no_directives() { + let c = CheckerBuilder::new().text("nothing here").unwrap().finish(); + assert!(c.is_empty()); + + // An empty checker matches anything. + assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn no_matches() { + let c = CheckerBuilder::new().text("regex: FOO=bar").unwrap().finish(); + assert!(!c.is_empty()); + + // An empty checker matches anything. + assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn simple() { + let c = CheckerBuilder::new() + .text(" + check: one + check: two + ") + .unwrap() + .finish(); + + let t = " + zero + one + and a half + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); + + let t = " + zero + and a half + two + one + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); +} + +#[test] +fn sameln() { + let c = CheckerBuilder::new() + .text(" + check: one + sameln: two + ") + .unwrap() + .finish(); + + let t = " + zero + one + and a half + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = " + zero + one + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = " + zero + one two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn nextln() { + let c = CheckerBuilder::new() + .text(" + check: one + nextln: two + ") + .unwrap() + .finish(); + + let t = " + zero + one + and a half + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = " + zero + one + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); + + let t = " + zero + one two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = " + zero + one + two"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn leading_nextln() { + // A leading nextln directive should match from line 2. + // This is somewhat arbitrary, but consistent with a preceeding 'check: $()' directive. + let c = CheckerBuilder::new() + .text(" + nextln: one + nextln: two + ") + .unwrap() + .finish(); + + let t = "zero + one + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); + + let t = "one + two + three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); +} + +#[test] +fn leading_sameln() { + // A leading sameln directive should match from line 1. + let c = CheckerBuilder::new() + .text(" + sameln: one + sameln: two + ") + .unwrap() + .finish(); + + let t = "zero + one two three + "; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = "zero one two three"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); + + let t = "zero one + two three"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); +} + +#[test] +fn not() { + let c = CheckerBuilder::new() + .text(" + check: one$() + not: $()eat$() + check: $()two + ") + .unwrap() + .finish(); + + let t = "onetwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); + + let t = "one eat two"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = "oneeattwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = "oneatwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn notnot() { + let c = CheckerBuilder::new() + .text(" + check: one$() + not: $()eat$() + not: half + check: $()two + ") + .unwrap() + .finish(); + + let t = "onetwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); + + let t = "one eat two"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = "one half two"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = "oneeattwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + // The `not: half` pattern only matches whole words, but the bracketing matches are considered + // word boundaries, so it does match in this case. + let t = "onehalftwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); + + let t = "oneatwo"; + assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); +} + +#[test] +fn unordered() { + let c = CheckerBuilder::new() + .text(" + check: one + unordered: two + unordered: three + check: four + ") + .unwrap() + .finish(); + + assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), Ok(true)); + + assert_eq!(c.check("one two four three four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three four two four", NO_VARIABLES).map_err(e2s), Ok(true)); + + assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), Ok(false)); + assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), Ok(false)); +} + +#[test] +fn leading_unordered() { + let c = CheckerBuilder::new() + .text(" + unordered: two + unordered: three + check: four + ") + .unwrap() + .finish(); + + assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), Ok(true)); + + assert_eq!(c.check("one two four three four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three four two four", NO_VARIABLES).map_err(e2s), Ok(true)); + + assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), Ok(false)); + assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), Ok(false)); +} + +#[test] +fn trailing_unordered() { + let c = CheckerBuilder::new() + .text(" + check: one + unordered: two + unordered: three + ") + .unwrap() + .finish(); + + assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), Ok(true)); + + assert_eq!(c.check("one two four three four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three four two four", NO_VARIABLES).map_err(e2s), Ok(true)); + + assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), Ok(true)); +} diff --git a/src/libfilecheck/variable.rs b/src/libfilecheck/variable.rs new file mode 100644 index 0000000000..7238249146 --- /dev/null +++ b/src/libfilecheck/variable.rs @@ -0,0 +1,58 @@ +/// A variable name is one or more ASCII alphanumerical characters, including underscore. +/// Note that numerical variable names like `$45` are allowed too. +/// +/// Try to parse a variable name from the begining of `s`. +/// Return the index of the character following the varname. +/// This returns 0 if `s` doesn't have a prefix that is a variable name. +pub fn varname_prefix(s: &str) -> usize { + for (idx, ch) in s.char_indices() { + match ch { + 'a'...'z' | 'A'...'Z' | '0'...'9' | '_' => {} + _ => return idx, + } + } + s.len() +} + +/// A variable can contain either a regular expression or plain text. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Value { + Text(String), + Regex(String), +} + +/// Resolve variables by name. +pub trait VariableMap { + /// Get the value of the variable `varname`, or return `None` for an unknown variable name. + fn lookup(&self, varname: &str) -> Option; +} + +impl VariableMap for () { + fn lookup(&self, _: &str) -> Option { + None + } +} + +/// An empty variable map. +pub const NO_VARIABLES: &'static VariableMap = &(); + +#[cfg(test)] +mod tests { + #[test] + fn varname() { + use super::varname_prefix; + + assert_eq!(varname_prefix(""), 0); + assert_eq!(varname_prefix("\0"), 0); + assert_eq!(varname_prefix("_"), 1); + assert_eq!(varname_prefix("0"), 1); + assert_eq!(varname_prefix("01"), 2); + assert_eq!(varname_prefix("b"), 1); + assert_eq!(varname_prefix("C"), 1); + assert_eq!(varname_prefix("."), 0); + assert_eq!(varname_prefix(".s"), 0); + assert_eq!(varname_prefix("0."), 1); + assert_eq!(varname_prefix("01="), 2); + assert_eq!(varname_prefix("0a)"), 2); + } +} From c95c23dbbee2b3c8324119717205cbde02c6b834 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 8 Sep 2016 16:20:06 -0700 Subject: [PATCH 261/968] Add a cton-util filecheck sub-command. --- src/tools/Cargo.lock | 8 ++++++++ src/tools/Cargo.toml | 1 + src/tools/main.rs | 12 ++++++++++-- src/tools/rsfilecheck.rs | 40 ++++++++++++++++++++++++++++++++++++++++ test-all.sh | 2 +- 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/tools/rsfilecheck.rs diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index 251f074c32..2aca3c591b 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -5,6 +5,7 @@ dependencies = [ "cretonne 0.0.0", "cretonne-reader 0.0.0", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "filecheck 0.0.0", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -42,6 +43,13 @@ dependencies = [ "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "filecheck" +version = "0.0.0" +dependencies = [ + "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.2.11" diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index 8164a4e4bb..ba465f7d0e 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -12,6 +12,7 @@ path = "main.rs" [dependencies] cretonne = { path = "../libcretonne" } cretonne-reader = { path = "../libreader" } +filecheck = { path = "../libfilecheck" } docopt = "0.6.80" rustc-serialize = "0.3.19" regex = "0.1.71" diff --git a/src/tools/main.rs b/src/tools/main.rs index abad701af6..bd0999cd4e 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -3,6 +3,7 @@ extern crate cretonne; extern crate cton_reader; extern crate docopt; extern crate rustc_serialize; +extern crate filecheck; use cretonne::VERSION; use docopt::Docopt; @@ -12,26 +13,31 @@ use std::process; mod cat; mod print_cfg; +mod rsfilecheck; const USAGE: &'static str = " Cretonne code generator utility Usage: cton-util cat ... + cton-util filecheck [-v] cton-util print-cfg ... cton-util --help | --version Options: - -h, --help print this help message - --version print the Cretonne version + -v, --verbose be more verbose + -h, --help print this help message + --version print the Cretonne version "; #[derive(RustcDecodable, Debug)] struct Args { cmd_cat: bool, + cmd_filecheck: bool, cmd_print_cfg: bool, arg_file: Vec, + flag_verbose: bool, } /// A command either succeeds or fails with an error message. @@ -51,6 +57,8 @@ fn cton_util() -> CommandResult { // Find the sub-command to execute. if args.cmd_cat { cat::run(args.arg_file) + } else if args.cmd_filecheck { + rsfilecheck::run(args.arg_file, args.flag_verbose) } else if args.cmd_print_cfg { print_cfg::run(args.arg_file) } else { diff --git a/src/tools/rsfilecheck.rs b/src/tools/rsfilecheck.rs new file mode 100644 index 0000000000..bce661282c --- /dev/null +++ b/src/tools/rsfilecheck.rs @@ -0,0 +1,40 @@ +use CommandResult; +use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; +use std::fs::File; +use std::io::{self, Read}; + +pub fn run(files: Vec, verbose: bool) -> CommandResult { + if files.is_empty() { + return Err("No check files".to_string()); + } + let checker = try!(read_checkfile(&files[0])); + if checker.is_empty() { + return Err(format!("{}: no filecheck directives found", files[0])); + } + + // Print out the directives under --verbose. + if verbose { + println!("{}", checker); + } + + let mut buffer = String::new(); + try!(io::stdin().read_to_string(&mut buffer).map_err(|e| format!("stdin: {}", e))); + + if try!(checker.check(&buffer, NO_VARIABLES).map_err(|e| e.to_string())) { + Ok(()) + } else { + // TODO: We need to do better than this. + Err("Check failed".to_string()) + } +} + +fn read_checkfile(filename: &str) -> Result { + let mut file = try!(File::open(&filename).map_err(|e| format!("{}: {}", filename, e))); + let mut buffer = String::new(); + try!(file.read_to_string(&mut buffer) + .map_err(|e| format!("Couldn't read {}: {}", filename, e))); + + let mut builder = CheckerBuilder::new(); + try!(builder.text(&buffer).map_err(|e| format!("{}: {}", filename, e))); + Ok(builder.finish()) +} diff --git a/test-all.sh b/test-all.sh index 64fb83d3a8..cd3916d8c3 100755 --- a/test-all.sh +++ b/test-all.sh @@ -40,7 +40,7 @@ else echo "If a newer version of rustfmt is available, update this script." fi -PKGS="cretonne cretonne-reader cretonne-tools" +PKGS="cretonne cretonne-reader cretonne-tools filecheck" cd "$topdir/src/tools" for PKG in $PKGS do From e41853cab0f46749719d2d1a44cd304fce00ff8d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 9 Sep 2016 11:09:54 -0700 Subject: [PATCH 262/968] Convert the DFG tests to use filecheck. --- tests/cfg/loop.cton | 7 ++++--- tests/cfg/run.sh | 4 +--- tests/cfg/traps_early.cton | 7 ++++--- tests/cfg/unused_node.cton | 8 ++++++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/cfg/loop.cton b/tests/cfg/loop.cton index 73ad1c4b6a..07d93926d8 100644 --- a/tests/cfg/loop.cton +++ b/tests/cfg/loop.cton @@ -1,12 +1,13 @@ ; For testing cfg generation. This code is nonsense. function nonsense(i32, i32) -> f32 { +; check: digraph nonsense { ebb0(v1: i32, v2: i32): v3 = f64const 0x0.0 - brz v2, ebb2 ;;;; ebb0:inst1 -> ebb2 + brz v2, ebb2 ; unordered: ebb0:inst1 -> ebb2 v4 = iconst.i32 0 - jump ebb1(v4) ;;;; ebb0:inst3 -> ebb1 + jump ebb1(v4) ; unordered: ebb0:inst3 -> ebb1 ebb1(v5: i32): v6 = imul_imm v5, 4 @@ -17,7 +18,7 @@ ebb1(v5: i32): v11 = fadd v9, v10 v12 = iadd_imm v5, 1 v13 = icmp ult, v12, v2 - brnz v13, ebb1(v12) ;;;; ebb1:inst12 -> ebb1 + brnz v13, ebb1(v12) ; unordered: ebb1:inst12 -> ebb1 v14 = f64const 0.0 v15 = f64const 0.0 v16 = fdiv v14, v15 diff --git a/tests/cfg/run.sh b/tests/cfg/run.sh index 2230d4c1ca..cd324cc73f 100755 --- a/tests/cfg/run.sh +++ b/tests/cfg/run.sh @@ -16,9 +16,7 @@ fi declare -a fails for testcase in $(find cfg -name '*.cton'); do - annotations=$(cat $testcase | awk /';;;;'/ | awk -F ";;;;" '{print $2}' | sort) - connections=$("${CTONUTIL}" print-cfg "$testcase" | awk /"->"/ | sort) - if diff -u <(echo $annotations) <(echo $connections); then + if "${CTONUTIL}" print-cfg "$testcase" | "${CTONUTIL}" filecheck "$testcase"; then echo OK $testcase else fails=(${fails[@]} "$testcase") diff --git a/tests/cfg/traps_early.cton b/tests/cfg/traps_early.cton index 6993cc128e..71070f4b15 100644 --- a/tests/cfg/traps_early.cton +++ b/tests/cfg/traps_early.cton @@ -2,16 +2,17 @@ ; a terminating instruction before any connections have been made. function nonsense(i32) { +; check: digraph nonsense { ebb0(v1: i32): trap - brnz v1, ebb2 ;;;; ebb0:inst1 -> ebb2 - jump ebb1 ;;;; ebb0:inst2 -> ebb1 + brnz v1, ebb2 ; unordered: ebb0:inst1 -> ebb2 + jump ebb1 ; unordered: ebb0:inst2 -> ebb1 ebb1: v2 = iconst.i32 0 v3 = iadd v1, v3 - jump ebb0(v3) ;;;; ebb1:inst5 -> ebb0 + jump ebb0(v3) ; unordered: ebb1:inst5 -> ebb0 ebb2: return v1 diff --git a/tests/cfg/unused_node.cton b/tests/cfg/unused_node.cton index 3120ffbe68..693196ccde 100644 --- a/tests/cfg/unused_node.cton +++ b/tests/cfg/unused_node.cton @@ -1,15 +1,19 @@ ; For testing cfg generation where some block is never reached. function not_reached(i32) -> i32 { +; check: digraph not_reached { +; check: ebb0 [shape=record, label="{ebb0 | brnz ebb2}"] +; check: ebb1 [shape=record, label="{ebb1 | jump ebb0}"] +; check: ebb2 [shape=record, label="{ebb2}"] ebb0(v0: i32): - brnz v0, ebb2 ;;;; ebb0:inst0 -> ebb2 + brnz v0, ebb2 ; unordered: ebb0:inst0 -> ebb2 trap ebb1: v1 = iconst.i32 1 v2 = iadd v0, v1 - jump ebb0(v2) ;;;; ebb1:inst4 -> ebb0 + jump ebb0(v2) ; unordered: ebb1:inst4 -> ebb0 ebb2: return v0 From 7317775052c612e74c87d074af0f02651e275bc0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 9 Sep 2016 14:11:56 -0700 Subject: [PATCH 263/968] Add a MatchRange type alias. The regex library also uses (usize, usize) for ranges. The type alias is just to make it clearer what the tuple means. --- src/libfilecheck/checker.rs | 14 ++++++-------- src/libfilecheck/lib.rs | 3 +++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libfilecheck/checker.rs b/src/libfilecheck/checker.rs index 8abcbfdd3d..ea23f9e0d9 100644 --- a/src/libfilecheck/checker.rs +++ b/src/libfilecheck/checker.rs @@ -5,6 +5,7 @@ use regex::{Regex, Captures}; use std::collections::HashMap; use std::cmp::max; use std::fmt::{self, Display, Formatter}; +use MatchRange; // The different kinds of directives we support. enum Directive { @@ -259,19 +260,19 @@ impl<'a> State<'a> { } // Get the range in text to be matched by a `check:`. - fn check(&self) -> (usize, usize) { + fn check(&self) -> MatchRange { (self.max_match, self.text.len()) } // Get the range in text to be matched by a `sameln:`. - fn sameln(&self) -> (usize, usize) { + fn sameln(&self) -> MatchRange { let b = self.max_match; let e = self.bol(b); (b, e) } // Get the range in text to be matched by a `nextln:`. - fn nextln(&self) -> (usize, usize) { + fn nextln(&self) -> MatchRange { let b = self.bol(self.max_match); let e = self.bol(b); (b, e) @@ -290,16 +291,13 @@ impl<'a> State<'a> { } // Get the range in text to be matched by a `unordered:` directive. - fn unordered(&self, pat: &Pattern) -> (usize, usize) { + fn unordered(&self, pat: &Pattern) -> MatchRange { (self.unordered_begin(pat), self.text.len()) } // Search for `pat` in `range`, return the range matched. // After a positive match, update variable definitions, if any. - fn match_positive(&mut self, - pat: &Pattern, - range: (usize, usize)) - -> Result> { + fn match_positive(&mut self, pat: &Pattern, range: MatchRange) -> Result> { let rx = try!(pat.resolve(self)); let txt = &self.text[range.0..range.1]; let defs = pat.defs(); diff --git a/src/libfilecheck/lib.rs b/src/libfilecheck/lib.rs index 16d6d5028d..c38efe0890 100644 --- a/src/libfilecheck/lib.rs +++ b/src/libfilecheck/lib.rs @@ -244,3 +244,6 @@ mod error; mod variable; mod pattern; mod checker; + +/// The range of a match in the input text. +type MatchRange = (usize, usize); From b9d6ff2b51d56dc30e9abb2fcca3e3110bf7b733 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 9 Sep 2016 14:32:07 -0700 Subject: [PATCH 264/968] pub --- src/libfilecheck/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libfilecheck/lib.rs b/src/libfilecheck/lib.rs index c38efe0890..8d61a1dfbf 100644 --- a/src/libfilecheck/lib.rs +++ b/src/libfilecheck/lib.rs @@ -246,4 +246,4 @@ mod pattern; mod checker; /// The range of a match in the input text. -type MatchRange = (usize, usize); +pub type MatchRange = (usize, usize); From 8483dc10850954933470b829fccf6d97f6999225 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 9 Sep 2016 17:08:29 -0700 Subject: [PATCH 265/968] Add an explainer mode to filecheck. The -c flag to 'cton-util filecheck' will now print out a description of how the directives are matching the input. This explanation is also printed when a match fails. --- src/libfilecheck/checker.rs | 52 ++++++++-- src/libfilecheck/explain.rs | 196 ++++++++++++++++++++++++++++++++++++ src/libfilecheck/lib.rs | 1 + src/tools/rsfilecheck.rs | 15 ++- tests/cfg/loop.cton | 6 +- 5 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 src/libfilecheck/explain.rs diff --git a/src/libfilecheck/checker.rs b/src/libfilecheck/checker.rs index ea23f9e0d9..eba5da78a7 100644 --- a/src/libfilecheck/checker.rs +++ b/src/libfilecheck/checker.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use std::cmp::max; use std::fmt::{self, Display, Formatter}; use MatchRange; +use explain::{Recorder, Explainer}; // The different kinds of directives we support. enum Directive { @@ -146,12 +147,24 @@ impl Checker { /// This returns `true` if the text matches all the directives, `false` if it doesn't. /// An error is only returned if there is a problem with the directives. pub fn check(&self, text: &str, vars: &VariableMap) -> Result { - let mut state = State::new(text, vars); + self.run(text, vars, &mut ()) + } + + /// Explain how directives are matched against the input text. + pub fn explain(&self, text: &str, vars: &VariableMap) -> Result<(bool, String)> { + let mut expl = Explainer::new(text); + let success = try!(self.run(text, vars, &mut expl)); + expl.finish(); + Ok((success, expl.to_string())) + } + + fn run(&self, text: &str, vars: &VariableMap, recorder: &mut Recorder) -> Result { + let mut state = State::new(text, vars, recorder); // For each pending `not:` check, store (begin-offset, regex). let mut nots = Vec::new(); - for dct in &self.directives { + for (dct_idx, dct) in self.directives.iter().enumerate() { let (pat, range) = match *dct { Directive::Check(ref pat) => (pat, state.check()), Directive::SameLn(ref pat) => (pat, state.sameln()), @@ -164,7 +177,7 @@ impl Checker { // The `not:` directives test the same range as `unordered:` directives. In // particular, if they refer to defined variables, their range is restricted to // the text following the match that defined the variable. - nots.push((state.unordered_begin(pat), try!(pat.resolve(&state)))); + nots.push((dct_idx, state.unordered_begin(pat), try!(pat.resolve(&state)))); continue; } Directive::Regex(ref var, ref rx) => { @@ -177,6 +190,7 @@ impl Checker { } }; // Check if `pat` matches in `range`. + state.recorder.directive(dct_idx); if let Some((match_begin, match_end)) = try!(state.match_positive(pat, range)) { if let &Directive::Unordered(_) = dct { // This was an unordered unordered match. @@ -188,11 +202,14 @@ impl Checker { state.max_match = match_end; // Verify any pending `not:` directives now that we know their range. - for (not_begin, rx) in nots.drain(..) { - if let Some(_) = rx.find(&text[not_begin..match_begin]) { + for (not_idx, not_begin, rx) in nots.drain(..) { + state.recorder.directive(not_idx); + if let Some((s, e)) = rx.find(&text[not_begin..match_begin]) { // Matched `not:` pattern. - // TODO: Use matched range for an error message. + state.recorder.matched_not(rx.as_str(), (not_begin + s, not_begin + e)); return Ok(false); + } else { + state.recorder.missed_not(rx.as_str(), (not_begin, match_begin)); } } } @@ -203,7 +220,8 @@ impl Checker { } // Verify any pending `not:` directives after the last ordered directive. - for (not_begin, rx) in nots.drain(..) { + for (not_idx, not_begin, rx) in nots.drain(..) { + state.recorder.directive(not_idx); if let Some(_) = rx.find(&text[not_begin..]) { // Matched `not:` pattern. // TODO: Use matched range for an error message. @@ -224,8 +242,10 @@ pub struct VarDef { } struct State<'a> { - env_vars: &'a VariableMap, text: &'a str, + env_vars: &'a VariableMap, + recorder: &'a mut Recorder, + vars: HashMap, // Offset after the last ordered match. This does not include recent unordered matches. last_ordered: usize, @@ -234,10 +254,11 @@ struct State<'a> { } impl<'a> State<'a> { - fn new(text: &'a str, env_vars: &'a VariableMap) -> State<'a> { + fn new(text: &'a str, env_vars: &'a VariableMap, recorder: &'a mut Recorder) -> State<'a> { State { text: text, env_vars: env_vars, + recorder: recorder, vars: HashMap::new(), last_ordered: 0, max_match: 0, @@ -309,8 +330,10 @@ impl<'a> State<'a> { rx.captures(txt).map(|caps| { let matched_range = caps.pos(0).expect("whole expression must match"); for var in defs { + let txtval = caps.name(var).unwrap_or(""); + self.recorder.defined_var(var, txtval); let vardef = VarDef { - value: Value::Text(caps.name(var).unwrap_or("").to_string()), + value: Value::Text(txtval.to_string()), // This offset is the end of the whole matched pattern, not just the text // defining the variable. offset: range.0 + matched_range.1, @@ -320,7 +343,14 @@ impl<'a> State<'a> { matched_range }) }; - Ok(matched_range.map(|(b, e)| (range.0 + b, range.0 + e))) + Ok(if let Some((b, e)) = matched_range { + let r = (range.0 + b, range.0 + e); + self.recorder.matched_check(rx.as_str(), r); + Some(r) + } else { + self.recorder.missed_check(rx.as_str(), range); + None + }) } } diff --git a/src/libfilecheck/explain.rs b/src/libfilecheck/explain.rs new file mode 100644 index 0000000000..53dd4003a7 --- /dev/null +++ b/src/libfilecheck/explain.rs @@ -0,0 +1,196 @@ +//! Explaining how *filecheck* matched or failed to match a file. + +use MatchRange; +use std::fmt::{self, Display, Formatter}; +use std::cmp::min; + +/// Record events during matching. +pub trait Recorder { + /// Set the directive we're talking about now. + fn directive(&mut self, dct: usize); + + /// Matched a positive check directive (check/sameln/nextln/unordered). + fn matched_check(&mut self, regex: &str, matched: MatchRange); + + /// Matched a `not:` directive. This means the match will fail. + fn matched_not(&mut self, regex: &str, matched: MatchRange); + + /// Missed a positive check directive. The range given is the range searched for a match. + fn missed_check(&mut self, regex: &str, searched: MatchRange); + + /// Missed `not:` directive (as intended). + fn missed_not(&mut self, regex: &str, searched: MatchRange); + + /// The directive defined a variable. + fn defined_var(&mut self, varname: &str, value: &str); +} + +/// The null recorder just doesn't listen to anything you say. +impl Recorder for () { + fn directive(&mut self, _: usize) {} + fn matched_check(&mut self, _: &str, _: MatchRange) {} + fn matched_not(&mut self, _: &str, _: MatchRange) {} + fn defined_var(&mut self, _: &str, _: &str) {} + fn missed_check(&mut self, _: &str, _: MatchRange) {} + fn missed_not(&mut self, _: &str, _: MatchRange) {} +} + +struct Match { + directive: usize, + is_match: bool, + is_not: bool, + regex: String, + range: MatchRange, +} + +struct VarDef { + directive: usize, + varname: String, + value: String, +} + +/// Record an explanation for the matching process, success or failure. +pub struct Explainer<'a> { + text: &'a str, + directive: usize, + matches: Vec, + vardefs: Vec, +} + +impl<'a> Explainer<'a> { + pub fn new(text: &'a str) -> Explainer { + Explainer { + text: text, + directive: 0, + matches: Vec::new(), + vardefs: Vec::new(), + } + } + + /// Finish up after recording all events in a match. + pub fn finish(&mut self) { + self.matches.sort_by_key(|m| (m.range, m.directive)); + self.vardefs.sort_by_key(|v| v.directive); + } +} + +impl<'a> Display for Explainer<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // Offset of beginning of the last line printed. + let mut curln = 0; + // Offset of beginning of the next line to be printed. + let mut nextln = 0; + + for m in &self.matches { + // Emit lines until m.range.0 is visible. + while nextln <= m.range.0 && nextln < self.text.len() { + let newln = self.text[nextln..] + .find('\n') + .map(|d| nextln + d + 1) + .unwrap_or(self.text.len()); + assert!(newln > nextln); + try!(writeln!(f, "> {}", &self.text[nextln..newln - 1])); + curln = nextln; + nextln = newln; + } + + // Emit ~~~ under the part of the match in curln. + if m.is_match { + try!(write!(f, " ")); + let mend = min(m.range.1, nextln - 1); + for pos in curln..mend { + try!(if pos < m.range.0 { + write!(f, " ") + } else if pos == m.range.0 { + write!(f, "^") + } else { + write!(f, "~") + }); + } + try!(writeln!(f, "")); + } + + // Emit the match message itself. + try!(writeln!(f, + "{} #{}{}: {}", + if m.is_match { "Matched" } else { "Missed" }, + m.directive, + if m.is_not { " not" } else { "" }, + m.regex)); + + // Emit any variable definitions. + if let Ok(found) = self.vardefs.binary_search_by_key(&m.directive, |v| v.directive) { + let mut first = found; + while first > 0 && self.vardefs[first - 1].directive == m.directive { + first -= 1; + } + for d in &self.vardefs[first..] { + if d.directive != m.directive { + break; + } + try!(writeln!(f, "Define {}={}", d.varname, d.value)); + } + } + } + + // Emit trailing lines. + for line in self.text[nextln..].lines() { + try!(writeln!(f, "> {}", line)); + } + Ok(()) + } +} + +impl<'a> Recorder for Explainer<'a> { + fn directive(&mut self, dct: usize) { + self.directive = dct; + } + + fn matched_check(&mut self, regex: &str, matched: MatchRange) { + self.matches.push(Match { + directive: self.directive, + is_match: true, + is_not: false, + regex: regex.to_owned(), + range: matched, + }); + } + + fn matched_not(&mut self, regex: &str, matched: MatchRange) { + self.matches.push(Match { + directive: self.directive, + is_match: true, + is_not: true, + regex: regex.to_owned(), + range: matched, + }); + } + + fn missed_check(&mut self, regex: &str, searched: MatchRange) { + self.matches.push(Match { + directive: self.directive, + is_match: false, + is_not: false, + regex: regex.to_owned(), + range: searched, + }); + } + + fn missed_not(&mut self, regex: &str, searched: MatchRange) { + self.matches.push(Match { + directive: self.directive, + is_match: false, + is_not: true, + regex: regex.to_owned(), + range: searched, + }); + } + + fn defined_var(&mut self, varname: &str, value: &str) { + self.vardefs.push(VarDef { + directive: self.directive, + varname: varname.to_owned(), + value: value.to_owned(), + }); + } +} diff --git a/src/libfilecheck/lib.rs b/src/libfilecheck/lib.rs index 8d61a1dfbf..f6c53c5dcf 100644 --- a/src/libfilecheck/lib.rs +++ b/src/libfilecheck/lib.rs @@ -244,6 +244,7 @@ mod error; mod variable; mod pattern; mod checker; +mod explain; /// The range of a match in the input text. pub type MatchRange = (usize, usize); diff --git a/src/tools/rsfilecheck.rs b/src/tools/rsfilecheck.rs index bce661282c..b64a86283e 100644 --- a/src/tools/rsfilecheck.rs +++ b/src/tools/rsfilecheck.rs @@ -20,10 +20,21 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { let mut buffer = String::new(); try!(io::stdin().read_to_string(&mut buffer).map_err(|e| format!("stdin: {}", e))); - if try!(checker.check(&buffer, NO_VARIABLES).map_err(|e| e.to_string())) { + if verbose { + let (success, explain) = try!(checker.explain(&buffer, NO_VARIABLES) + .map_err(|e| e.to_string())); + print!("{}", explain); + if success { + println!("OK"); + Ok(()) + } else { + Err("Check failed".to_string()) + } + } else if try!(checker.check(&buffer, NO_VARIABLES).map_err(|e| e.to_string())) { Ok(()) } else { - // TODO: We need to do better than this. + let (_, explain) = try!(checker.explain(&buffer, NO_VARIABLES).map_err(|e| e.to_string())); + print!("{}", explain); Err("Check failed".to_string()) } } diff --git a/tests/cfg/loop.cton b/tests/cfg/loop.cton index 07d93926d8..fd6e4fa6b9 100644 --- a/tests/cfg/loop.cton +++ b/tests/cfg/loop.cton @@ -2,12 +2,14 @@ function nonsense(i32, i32) -> f32 { ; check: digraph nonsense { +; regex: I=\binst\d+\b +; check: label="{ebb0 | <$(BRZ=$I)>brz ebb2 | <$(JUMP=$I)>jump ebb1}"] ebb0(v1: i32, v2: i32): v3 = f64const 0x0.0 - brz v2, ebb2 ; unordered: ebb0:inst1 -> ebb2 + brz v2, ebb2 ; unordered: ebb0:$BRZ -> ebb2 v4 = iconst.i32 0 - jump ebb1(v4) ; unordered: ebb0:inst3 -> ebb1 + jump ebb1(v4) ; unordered: ebb0:$JUMP -> ebb1 ebb1(v5: i32): v6 = imul_imm v5, 4 From 0eea144fefad18690c86712d931af3ec8ca54499 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 10 Sep 2016 12:33:58 -0700 Subject: [PATCH 266/968] Idiomatic impl of unordered_begin. --- src/libfilecheck/checker.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libfilecheck/checker.rs b/src/libfilecheck/checker.rs index eba5da78a7..6bb458b55e 100644 --- a/src/libfilecheck/checker.rs +++ b/src/libfilecheck/checker.rs @@ -302,13 +302,11 @@ impl<'a> State<'a> { // Get the beginning of the range in text to be matched by a `unordered:` or `not:` directive. // The unordered directive must match after the directives that define the variables used. fn unordered_begin(&self, pat: &Pattern) -> usize { - let mut from = self.last_ordered; - for part in pat.parts() { - if let Some(var) = part.ref_var() { - from = max(from, self.def_offset(var)); - } - } - from + pat.parts() + .iter() + .filter_map(|part| part.ref_var()) + .map(|var| self.def_offset(var)) + .fold(self.last_ordered, max) } // Get the range in text to be matched by a `unordered:` directive. From eeb8f5e4e41b8efc05e0dc2181a044756efbbd0a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 12 Sep 2016 14:14:41 -0700 Subject: [PATCH 267/968] Add an AnyEntity enum type. This type can reference any type of entity in a function. It will be used for the location of verifier error messages and other annotations. --- src/libcretonne/ir/entities.rs | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/libcretonne/ir/entities.rs b/src/libcretonne/ir/entities.rs index 1a887452a7..5672e5b3d3 100644 --- a/src/libcretonne/ir/entities.rs +++ b/src/libcretonne/ir/entities.rs @@ -245,6 +245,61 @@ impl Default for JumpTable { } } +/// A reference to any of the entities defined in this module. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum AnyEntity { + /// The whole function. + Function, + Ebb(Ebb), + Inst(Inst), + Value(Value), + StackSlot(StackSlot), + JumpTable(JumpTable), +} + +impl Display for AnyEntity { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + match *self { + AnyEntity::Function => write!(fmt, "function"), + AnyEntity::Ebb(r) => r.fmt(fmt), + AnyEntity::Inst(r) => r.fmt(fmt), + AnyEntity::Value(r) => r.fmt(fmt), + AnyEntity::StackSlot(r) => r.fmt(fmt), + AnyEntity::JumpTable(r) => r.fmt(fmt), + } + } +} + +impl From for AnyEntity { + fn from(r: Ebb) -> AnyEntity { + AnyEntity::Ebb(r) + } +} + +impl From for AnyEntity { + fn from(r: Inst) -> AnyEntity { + AnyEntity::Inst(r) + } +} + +impl From for AnyEntity { + fn from(r: Value) -> AnyEntity { + AnyEntity::Value(r) + } +} + +impl From for AnyEntity { + fn from(r: StackSlot) -> AnyEntity { + AnyEntity::StackSlot(r) + } +} + +impl From for AnyEntity { + fn from(r: JumpTable) -> AnyEntity { + AnyEntity::JumpTable(r) + } +} + #[cfg(test)] mod tests { use super::*; From d0fb647357c5500a32fd60bd907b20cecc61a649 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Sep 2016 10:51:15 -0700 Subject: [PATCH 268/968] Add a data structure representing a parsed test command. It's not used for anything yet. --- src/libreader/lib.rs | 18 +++----- src/libreader/testcommand.rs | 88 ++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 src/libreader/testcommand.rs diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index 2d63e68264..4c53c79fa0 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -1,16 +1,12 @@ - -// ====------------------------------------------------------------------------------------==== // -// -// Cretonne file reader library. -// -// ====------------------------------------------------------------------------------------==== // -// -// The cton_reader library supports reading and writing .cton files. This functionality is needed -// for testing Cretonne, but is not essential for a JIT compiler. -// -// ====------------------------------------------------------------------------------------==== // +//! Cretonne file reader library. +//! +//! The cton_reader library supports reading .cton files. This functionality is needed for testing +//! Cretonne, but is not essential for a JIT compiler. extern crate cretonne; +pub use testcommand::{TestCommand, TestOption}; + pub mod lexer; pub mod parser; +mod testcommand; diff --git a/src/libreader/testcommand.rs b/src/libreader/testcommand.rs new file mode 100644 index 0000000000..3eba0d0a53 --- /dev/null +++ b/src/libreader/testcommand.rs @@ -0,0 +1,88 @@ +//! Test commands. +//! +//! A `.cton` file can begin with one or more *test commands* which specify what is to be tested. +//! The general syntax is: +//! +//!
+//! test <command> [options]...
+//! 
+//! +//! The options are either a single identifier flag, or setting values like `identifier=value`. +//! +//! The parser does not understand the test commands or which options are alid. It simply parses +//! the general format into a `TestCommand` data structure. + +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct TestCommand<'a> { + pub command: &'a str, + pub options: Vec>, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TestOption<'a> { + Flag(&'a str), + Value(&'a str, &'a str), +} + +impl<'a> TestCommand<'a> { + pub fn new(s: &'a str) -> TestCommand<'a> { + let mut parts = s.split_whitespace(); + let cmd = parts.next().unwrap_or(""); + TestCommand { + command: cmd, + options: parts.filter(|s| !s.is_empty()).map(TestOption::new).collect(), + } + } +} + +impl<'a> Display for TestCommand<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + try!(write!(f, "{}", self.command)); + for opt in &self.options { + try!(write!(f, " {}", opt)); + } + writeln!(f, "") + } +} + +impl<'a> TestOption<'a> { + pub fn new(s: &'a str) -> TestOption<'a> { + match s.find('=') { + None => TestOption::Flag(s), + Some(p) => TestOption::Value(&s[0..p], &s[p + 1..]), + } + } +} + +impl<'a> Display for TestOption<'a> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + TestOption::Flag(s) => write!(f, "{}", s), + TestOption::Value(s, v) => write!(f, "{}={}", s, v), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_option() { + assert_eq!(TestOption::new(""), TestOption::Flag("")); + assert_eq!(TestOption::new("foo"), TestOption::Flag("foo")); + assert_eq!(TestOption::new("foo=bar"), TestOption::Value("foo", "bar")); + } + + #[test] + fn parse_command() { + assert_eq!(&TestCommand::new("").to_string(), "\n"); + assert_eq!(&TestCommand::new("cat").to_string(), "cat\n"); + assert_eq!(&TestCommand::new("cat ").to_string(), "cat\n"); + assert_eq!(&TestCommand::new("cat 1 ").to_string(), "cat 1\n"); + assert_eq!(&TestCommand::new("cat one=4 two t").to_string(), + "cat one=4 two t\n"); + } +} From 169a2f75426e854338b596aa441e8562fde339bf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Sep 2016 11:01:21 -0700 Subject: [PATCH 269/968] Simplify the interface to cretonne-reader. Export a single function: parse_functions() which results a vector of functions parsed from the source string. Hide the parser and lexer modules. They are not useful to external clients. --- src/libreader/lib.rs | 5 +++-- src/libreader/parser.rs | 12 +++++++----- src/tools/cat.rs | 4 ++-- src/tools/print_cfg.rs | 4 ++-- src/tools/tests/cfg_traversal.rs | 4 ++-- src/tools/tests/dominator_tree.rs | 4 ++-- src/tools/tests/verifier.rs | 4 ++-- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index 4c53c79fa0..519fee7eed 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -5,8 +5,9 @@ extern crate cretonne; +pub use parser::{Result, parse_functions}; pub use testcommand::{TestCommand, TestOption}; -pub mod lexer; -pub mod parser; +mod lexer; +mod parser; mod testcommand; diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index fa6a445158..2ae1629c80 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -21,6 +21,13 @@ use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArg pub use lexer::Location; +/// Parse the entire `text` into a list of functions. +/// +/// Any test commands or ISA declarations are ignored. +pub fn parse_functions(text: &str) -> Result> { + Parser::new(text).parse_function_list() +} + /// A parse error is returned when the parse failed. #[derive(Debug)] pub struct Error { @@ -256,11 +263,6 @@ impl<'a> Parser<'a> { } } - /// Parse the entire string into a list of functions. - pub fn parse(text: &'a str) -> Result> { - Self::new(text).parse_function_list() - } - // Consume the current lookahead token and return it. fn consume(&mut self) -> Token<'a> { self.lookahead.take().expect("No token to consume") diff --git a/src/tools/cat.rs b/src/tools/cat.rs index 6d740da701..a41b5b5f27 100644 --- a/src/tools/cat.rs +++ b/src/tools/cat.rs @@ -8,7 +8,7 @@ use std::io::{self, Read}; use CommandResult; -use cton_reader::parser::Parser; +use cton_reader::parse_functions; use cretonne::write::write_function; pub fn run(files: Vec) -> CommandResult { @@ -26,7 +26,7 @@ fn cat_one(filename: String) -> CommandResult { let mut buffer = String::new(); try!(file.read_to_string(&mut buffer) .map_err(|e| format!("Couldn't read {}: {}", filename, e))); - let items = try!(Parser::parse(&buffer).map_err(|e| format!("{}: {}", filename, e))); + let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); for (idx, func) in items.into_iter().enumerate() { if idx != 0 { diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index dd1aa3de18..649d87fb9f 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -9,7 +9,7 @@ use CommandResult; use cretonne::ir::Function; use cretonne::cfg::ControlFlowGraph; use cretonne::ir::instructions::InstructionData; -use cton_reader::parser::Parser; +use cton_reader::parse_functions; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { @@ -160,7 +160,7 @@ fn print_cfg(filename: String) -> CommandResult { let mut buffer = String::new(); try!(file.read_to_string(&mut buffer) .map_err(|e| format!("Couldn't read {}: {}", filename, e))); - let items = try!(Parser::parse(&buffer).map_err(|e| format!("{}: {}", filename, e))); + let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); let mut cfg_printer = CFGPrinter::new(stdout()); for (idx, func) in items.into_iter().enumerate() { diff --git a/src/tools/tests/cfg_traversal.rs b/src/tools/tests/cfg_traversal.rs index 03ed5bd148..b766f9d96c 100644 --- a/src/tools/tests/cfg_traversal.rs +++ b/src/tools/tests/cfg_traversal.rs @@ -1,13 +1,13 @@ extern crate cretonne; extern crate cton_reader; -use self::cton_reader::parser::Parser; +use self::cton_reader::parse_functions; use self::cretonne::ir::Ebb; use self::cretonne::cfg::ControlFlowGraph; use self::cretonne::entity_map::EntityMap; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { - let func = &Parser::parse(function_source).unwrap()[0]; + let func = &parse_functions(function_source).unwrap()[0]; let cfg = ControlFlowGraph::new(&func); let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()) .collect::>(); diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs index c1be367137..71c02dc652 100644 --- a/src/tools/tests/dominator_tree.rs +++ b/src/tools/tests/dominator_tree.rs @@ -9,7 +9,7 @@ use regex::Regex; use std::fs::File; use std::io::Read; use self::cretonne::ir::Ebb; -use self::cton_reader::parser::Parser; +use self::cton_reader::parse_functions; use self::cretonne::ir::function::Function; use self::cretonne::entity_map::EntityMap; use self::cretonne::ir::entities::NO_INST; @@ -69,7 +69,7 @@ fn dominator_tree_from_source(func: &Function, function_source: &str) -> Dominat fn test_dominator_tree(function_source: &str) { - let func = &Parser::parse(function_source).unwrap()[0]; + let func = &parse_functions(function_source).unwrap()[0]; let src_dtree = dominator_tree_from_source(&func, function_source); let cfg = ControlFlowGraph::new(&func); diff --git a/src/tools/tests/verifier.rs b/src/tools/tests/verifier.rs index 28bb1bbba5..c26056b8ba 100644 --- a/src/tools/tests/verifier.rs +++ b/src/tools/tests/verifier.rs @@ -8,7 +8,7 @@ use glob::glob; use regex::Regex; use std::fs::File; use std::io::Read; -use self::cton_reader::parser::Parser; +use self::cton_reader::parse_functions; use self::cretonne::verifier::Verifier; /// Compile a function and run verifier tests based on specially formatted @@ -37,7 +37,7 @@ fn verifier_tests_from_source(function_source: &str) { // Run the verifier against each function and compare the output // with the expected result (as determined above). - for (i, func) in Parser::parse(function_source).unwrap().into_iter().enumerate() { + for (i, func) in parse_functions(function_source).unwrap().into_iter().enumerate() { let result = Verifier::new(&func).run(); match verifier_results[i] { Some(ref re) => { From 4fd15f98eda4771bdcfbd5a406c9b1cd23daee02 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Sep 2016 12:18:36 -0700 Subject: [PATCH 270/968] Add a representation of a parsed test case file. The new exported function `parse_test()` will produce it eventually. --- src/libreader/lib.rs | 4 +++- src/libreader/parser.rs | 9 +++++++++ src/libreader/testfile.rs | 40 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/libreader/testfile.rs diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index 519fee7eed..d435b4ac3b 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -5,9 +5,11 @@ extern crate cretonne; -pub use parser::{Result, parse_functions}; +pub use parser::{Result, parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; +pub use testfile::TestFile; mod lexer; mod parser; mod testcommand; +mod testfile; diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 2ae1629c80..e9d77c67d7 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -18,6 +18,7 @@ use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{NO_EBB, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; +use testfile::TestFile; pub use lexer::Location; @@ -28,6 +29,14 @@ pub fn parse_functions(text: &str) -> Result> { Parser::new(text).parse_function_list() } +/// Parse the entire `text` as a test case file. +/// +/// The returned `TestFile` contains direct references to substrings of `text`. +pub fn parse_test<'a>(text: &'a str) -> Result> { + Parser::new(text); + unimplemented!() +} + /// A parse error is returned when the parse failed. #[derive(Debug)] pub struct Error { diff --git a/src/libreader/testfile.rs b/src/libreader/testfile.rs new file mode 100644 index 0000000000..34eddb4c6b --- /dev/null +++ b/src/libreader/testfile.rs @@ -0,0 +1,40 @@ +//! Data structures representing a parsed test file. +//! +//! A test file is a `.cton` file which contains test commands and settings for running a +//! file-based test case. +//! + +use cretonne::ir::Function; +use cretonne::ir::entities::AnyEntity; +use testcommand::TestCommand; + +/// A parsed test case. +/// +/// This is the result of parsing a `.cton` file which contains a number of test commands followed +/// by the functions that should be tested. +#[derive(Debug)] +pub struct TestFile<'a> { + pub commands: Vec>, + pub functions: Vec>, +} + +/// A function parsed from a text string along with other details that are useful for running +/// tests. +#[derive(Debug)] +pub struct DetailedFunction<'a> { + pub function: Function, + pub comments: Vec>, +} + +/// A comment in a parsed function. +/// +/// The comment belongs to the immediately preceeding entity, whether that is an EBB header, and +/// instruction, or one of the preamble declarations. +/// +/// Comments appearing inside the function but before the preamble, as well as comments appearing +/// after the function are tagged as `AnyEntity::Function`. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Comment<'a> { + pub entity: AnyEntity, + pub text: &'a str, +} From 525c69bbe81edb352c9e87c4581c537dcb0ad91c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Sep 2016 16:14:46 -0700 Subject: [PATCH 271/968] Add a next_key() method to primary entity maps. It is sometimes useful to know the entity reference number that will be assigned to the next thing added to a map. --- src/libcretonne/entity_map.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 91ff7505a3..df486c48ed 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -94,9 +94,14 @@ impl EntityMap where K: EntityRef, V: PrimaryEntityData { + /// Get the key that will be assigned to the next pushed value. + pub fn next_key(&self) -> K { + K::new(self.elems.len()) + } + /// Append `v` to the mapping, assigning a new key which is returned. pub fn push(&mut self, v: V) -> K { - let k = K::new(self.elems.len()); + let k = self.next_key(); self.elems.push(v); k } From dedc44be698eae574627e7e45dd367f159ada000 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Sep 2016 16:15:42 -0700 Subject: [PATCH 272/968] Collect comments while parsing functions. The result from parsing a function is now a DetailedFunction which includes all comments that can be associated with an entity. Comments before the first function are ignored, everything else is associated with the preceeding entity. The parse_functions() function still returns plain functions. --- src/libreader/parser.rs | 119 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 10 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index e9d77c67d7..5e18af59be 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -10,15 +10,16 @@ use std::result; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; +use std::mem; use lexer::{self, Lexer, Token}; use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlot, StackSlotData, JumpTable, JumpTableData}; use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::ir::entities::{NO_EBB, NO_VALUE}; +use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; -use testfile::TestFile; +use testfile::{TestFile, DetailedFunction, Comment}; pub use lexer::Location; @@ -26,7 +27,9 @@ pub use lexer::Location; /// /// Any test commands or ISA declarations are ignored. pub fn parse_functions(text: &str) -> Result> { - Parser::new(text).parse_function_list() + Parser::new(text) + .parse_function_list() + .map(|list| list.into_iter().map(|dfunc| dfunc.function).collect()) } /// Parse the entire `text` as a test case file. @@ -79,6 +82,13 @@ pub struct Parser<'a> { // Location of lookahead. loc: Location, + + // The currently active entity that should be associated with collected comments, or `None` if + // comments are ignored. + comment_entity: Option, + + // Comments collected so far. + comments: Vec>, } // Context for resolving references when parsing a single function. @@ -269,6 +279,8 @@ impl<'a> Parser<'a> { lex_error: None, lookahead: None, loc: Location { line_number: 0 }, + comment_entity: None, + comments: Vec::new(), } } @@ -283,8 +295,14 @@ impl<'a> Parser<'a> { match self.lex.next() { Some(Ok(lexer::LocatedToken { token, location })) => { match token { - Token::Comment(_) => { - // Ignore comments. + Token::Comment(text) => { + // Gather comments, associate them with `comment_entity`. + if let Some(entity) = self.comment_entity { + self.comments.push(Comment { + entity: entity, + text: text, + }); + } } _ => self.lookahead = Some(token), } @@ -301,6 +319,22 @@ impl<'a> Parser<'a> { return self.lookahead; } + // Begin gathering comments associated with `entity`. + fn gather_comments>(&mut self, entity: E) { + self.comment_entity = Some(entity.into()); + } + + // Rewrite the entity of the last added comments from `old` to `new`. + // Also switch to collecting future comments for `new`. + fn rewrite_last_comment_entities>(&mut self, old: E, new: E) { + let old = old.into(); + let new = new.into(); + for comment in (&mut self.comments).into_iter().rev().take_while(|c| c.entity == old) { + comment.entity = new; + } + self.comment_entity = Some(new); + } + // Match and consume a token without payload. fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result> { if self.token() == Some(want) { @@ -450,7 +484,7 @@ impl<'a> Parser<'a> { /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. - pub fn parse_function_list(&mut self) -> Result> { + pub fn parse_function_list(&mut self) -> Result>> { let mut list = Vec::new(); while self.token().is_some() { list.push(try!(self.parse_function())); @@ -462,7 +496,13 @@ impl<'a> Parser<'a> { // // function ::= * function-spec "{" preamble function-body "}" // - fn parse_function(&mut self) -> Result { + fn parse_function(&mut self) -> Result> { + // Begin gathering comments. + // Make sure we don't include any comments before the `function` keyword. + self.token(); + self.comments.clear(); + self.gather_comments(AnyEntity::Function); + let (name, sig) = try!(self.parse_function_spec()); let mut ctx = Context::new(Function::with_name_signature(name, sig)); @@ -475,11 +515,19 @@ impl<'a> Parser<'a> { // function ::= function-spec "{" preamble function-body * "}" try!(self.match_token(Token::RBrace, "expected '}' after function body")); + // Collect any comments following the end of the function, then stop gathering comments. + self.gather_comments(AnyEntity::Function); + self.token(); + self.comment_entity = None; + // Rewrite references to values and EBBs after parsing everuthing to allow forward // references. try!(ctx.rewrite_references()); - Ok(ctx.function) + Ok(DetailedFunction { + function: ctx.function, + comments: mem::replace(&mut self.comments, Vec::new()), + }) } // Parse a function spec. @@ -585,10 +633,12 @@ impl<'a> Parser<'a> { loop { try!(match self.token() { Some(Token::StackSlot(..)) => { + self.gather_comments(ctx.function.stack_slots.next_key()); self.parse_stack_slot_decl() .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) } Some(Token::JumpTable(..)) => { + self.gather_comments(ctx.function.jump_tables.next_key()); self.parse_jump_table_decl() .and_then(|(num, dat)| ctx.add_jt(num, dat, &self.loc)) } @@ -681,6 +731,7 @@ impl<'a> Parser<'a> { fn parse_extended_basic_block(&mut self, ctx: &mut Context) -> Result<()> { let ebb_num = try!(self.match_ebb("expected EBB header")); let ebb = try!(ctx.add_ebb(ebb_num, &self.loc)); + self.gather_comments(ebb); if !self.optional(Token::Colon) { // ebb-header ::= Ebb(ebb) [ * ebb-args ] ":" @@ -746,6 +797,10 @@ impl<'a> Parser<'a> { // inst-results ::= Value(v) { "," Value(vx) } // fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { + // Collect comments for `NO_INST` while parsing the instruction, then rewrite after we + // allocate an instruction number. + self.gather_comments(NO_INST); + // Result value numbers. let mut results = Vec::new(); @@ -805,6 +860,10 @@ impl<'a> Parser<'a> { results.len()); } + // If we saw any comments while parsing the instruction, they will have been recorded as + // belonging to `NO_INST`. + self.rewrite_last_comment_entities(NO_INST, inst); + // Now map the source result values to the just created instruction results. // Pass a reference to `ctx.values` instead of `ctx` itself since the `Values` iterator // holds a reference to `ctx.function`. @@ -1140,6 +1199,8 @@ impl<'a> Parser<'a> { mod tests { use super::*; use cretonne::ir::types::{self, ArgumentType, ArgumentExtension}; + use cretonne::ir::entities::AnyEntity; + use testfile::Comment; #[test] fn argument_type() { @@ -1184,7 +1245,8 @@ mod tests { ss1 = stack_slot 1 }") .parse_function() - .unwrap(); + .unwrap() + .function; assert_eq!(func.name, "foo"); let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); @@ -1213,7 +1275,8 @@ mod tests { ebb4(vx3: i32): }") .parse_function() - .unwrap(); + .unwrap() + .function; assert_eq!(func.name, "ebbs"); let mut ebbs = func.layout.ebbs(); @@ -1227,4 +1290,40 @@ mod tests { assert_eq!(func.dfg.value_type(arg0), types::I32); assert_eq!(ebb4_args.next(), None); } + + #[test] + fn comments() { + let dfunc = Parser::new("; before + function comment() { ; decl + ss10 = stack_slot 13 ; stackslot. + ; Still stackslot. + jt10 = jump_table ebb0 + ; Jumptable + ebb0: ; Basic block + trap ; Instruction + } ; Trailing. + ; More trailing.") + .parse_function() + .unwrap(); + assert_eq!(&dfunc.function.name, "comment"); + assert_eq!(dfunc.comments.len(), 8); // no 'before' comment. + assert_eq!(dfunc.comments[0], + Comment { + entity: AnyEntity::Function, + text: "; decl", + }); + assert_eq!(dfunc.comments[1].entity.to_string(), "ss0"); + assert_eq!(dfunc.comments[2].entity.to_string(), "ss0"); + assert_eq!(dfunc.comments[2].text, "; Still stackslot."); + assert_eq!(dfunc.comments[3].entity.to_string(), "jt0"); + assert_eq!(dfunc.comments[3].text, "; Jumptable"); + assert_eq!(dfunc.comments[4].entity.to_string(), "ebb0"); + assert_eq!(dfunc.comments[4].text, "; Basic block"); + + assert_eq!(dfunc.comments[5].entity.to_string(), "inst0"); + assert_eq!(dfunc.comments[5].text, "; Instruction"); + + assert_eq!(dfunc.comments[6].entity, AnyEntity::Function); + assert_eq!(dfunc.comments[7].entity, AnyEntity::Function); + } } From 55a89c21675141caacbce2e0e1a23ef43e7cebcf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Sep 2016 16:50:10 -0700 Subject: [PATCH 273/968] Add a public rest_of_line() function to lexer. This is used to grap the tail of a 'test' line which doesn't use the same tokens as a normal .cton file. --- src/libreader/lexer.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 150c38da19..6b5e881a1f 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -166,21 +166,25 @@ impl<'a> Lexer<'a> { token(tok, loc) } - // Scan a comment extending to the end of the current line. - fn scan_comment(&mut self) -> Result, LocatedError> { + /// Get the rest of the current line. + /// The next token returned by `next()` will be from the following lines. + pub fn rest_of_line(&mut self) -> &'a str { let begin = self.pos; - let loc = self.loc(); loop { match self.next_ch() { - None | Some('\n') => { - let text = &self.source[begin..self.pos]; - return token(Token::Comment(text), loc); - } + None | Some('\n') => return &self.source[begin..self.pos], _ => {} } } } + // Scan a comment extending to the end of the current line. + fn scan_comment(&mut self) -> Result, LocatedError> { + let loc = self.loc(); + let text = self.rest_of_line(); + return token(Token::Comment(text), loc); + } + // Scan a number token which can represent either an integer or floating point number. // // Accept the following forms: From a544c0e1719a3529d76558e50286780f8daf1f6e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Sep 2016 09:10:32 -0700 Subject: [PATCH 274/968] Parse test commands in a .cton file. The top-level parse_test() function now parses test commands in the preamble of a .cton file. --- src/libreader/parser.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 5e18af59be..7f8cc543ad 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -20,6 +20,7 @@ use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; use testfile::{TestFile, DetailedFunction, Comment}; +use testcommand::TestCommand; pub use lexer::Location; @@ -36,8 +37,11 @@ pub fn parse_functions(text: &str) -> Result> { /// /// The returned `TestFile` contains direct references to substrings of `text`. pub fn parse_test<'a>(text: &'a str) -> Result> { - Parser::new(text); - unimplemented!() + let mut parser = Parser::new(text); + Ok(TestFile { + commands: parser.parse_test_commands(), + functions: try!(parser.parse_function_list()), + }) } /// A parse error is returned when the parse failed. @@ -289,6 +293,14 @@ impl<'a> Parser<'a> { self.lookahead.take().expect("No token to consume") } + // Consume the whole line following the current lookahead token. + // Return the text of the line tail. + fn consume_line(&mut self) -> &'a str { + let rest = self.lex.rest_of_line(); + self.consume(); + rest + } + // Get the current lookahead token, after making sure there is one. fn token(&mut self) -> Option> { while self.lookahead == None { @@ -481,6 +493,15 @@ impl<'a> Parser<'a> { } } + /// Parse a list of test commands. + pub fn parse_test_commands(&mut self) -> Vec> { + let mut list = Vec::new(); + while self.token() == Some(Token::Identifier("test")) { + list.push(TestCommand::new(self.consume_line())); + } + list + } + /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. @@ -1326,4 +1347,18 @@ mod tests { assert_eq!(dfunc.comments[6].entity, AnyEntity::Function); assert_eq!(dfunc.comments[7].entity, AnyEntity::Function); } + + #[test] + fn test_file() { + let tf = parse_test("; before + test cfg option=5 + test verify + function comment() {}") + .unwrap(); + assert_eq!(tf.commands.len(), 2); + assert_eq!(tf.commands[0].command, "cfg"); + assert_eq!(tf.commands[1].command, "verify"); + assert_eq!(tf.functions.len(), 1); + assert_eq!(tf.functions[0].function.name, "comment"); + } } From 4521afd4744a414a9e11dee92844e0620a9f7e00 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Sep 2016 12:20:41 -0700 Subject: [PATCH 275/968] Add scaffolding for a 'cton-util test' command. This command accepts files and directories containing test cases to run. Recursively searches for test files in any directory it is passed. Actually running tests is not yet implemented. --- src/tools/filetest/mod.rs | 33 +++++++++++ src/tools/filetest/runner.rs | 112 +++++++++++++++++++++++++++++++++++ src/tools/main.rs | 8 ++- 3 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 src/tools/filetest/mod.rs create mode 100644 src/tools/filetest/runner.rs diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs new file mode 100644 index 0000000000..078e287dde --- /dev/null +++ b/src/tools/filetest/mod.rs @@ -0,0 +1,33 @@ +//! File tests. +//! +//! This module contains the main driver for `cton-util test` as well as implementations of the +//! available test commands. + +use std::path::Path; +use CommandResult; +use filetest::runner::TestRunner; + +mod runner; + +/// Main entry point for `cton-util test`. +/// +/// Take a list of filenames which can be either `.cton` files or directories. +/// +/// Files are interpreted as test cases and executed immediately. +/// +/// Directories are scanned recursively for test cases ending in `.cton`. These test cases are +/// executed on background threads. +/// +pub fn run(files: Vec) -> CommandResult { + let mut runner = TestRunner::new(); + + for path in files.iter().map(Path::new) { + if path.is_file() { + runner.push_test(path); + } else { + runner.push_dir(path); + } + } + + runner.run() +} diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs new file mode 100644 index 0000000000..7f39ec6490 --- /dev/null +++ b/src/tools/filetest/runner.rs @@ -0,0 +1,112 @@ +//! Test runner. +//! +//! This module implements the `TestRunner` struct which manages executing tests as well as +//! scanning directories for tests. + +use std::ffi::OsStr; +use std::path::PathBuf; +use std::error::Error; +use CommandResult; + +pub struct TestRunner { + // Directories that have not yet been scanned. + dir_stack: Vec, + + // Filenames of tests to run. + test_list: Vec, + + errors: usize, +} + +impl TestRunner { + /// Create a new blank TrstRunner. + pub fn new() -> TestRunner { + TestRunner { + dir_stack: Vec::new(), + test_list: Vec::new(), + errors: 0, + } + } + + /// Add a directory path to be scanned later. + /// + /// If `dir` turns out to be a regular file, it is silently ignored. + /// Otherwise, any problems reading the directory are reported. + pub fn push_dir>(&mut self, dir: P) { + self.dir_stack.push(dir.into()); + } + + /// Add a test to be executed later. + /// + /// Any problems reading `file` as a test case file will be reported as a test failure. + pub fn push_test>(&mut self, file: P) { + self.test_list.push(file.into()); + } + + /// Scan any directories pushed so far. + /// Push any potential test cases found. + pub fn scan_dirs(&mut self) { + // This recursive search tries to minimize statting in a directory hierarchy containing + // mostly test cases. + // + // - Directory entries with a "cton" extension are presumed to be test case files. + // - Directory entries with no extension are presumed to be subdirectories. + // - Anything else is ignored. + // + while let Some(dir) = self.dir_stack.pop() { + match dir.read_dir() { + Err(err) => { + // Fail silently if `dir` was actually a regular file. + // This lets us skip spurious extensionless files without statting everything + // needlessly. + if !dir.is_file() { + self.path_error(dir, err); + } + } + Ok(entries) => { + // Read all directory entries. Avoid statting. + for entry_result in entries { + match entry_result { + Err(err) => { + // Not sure why this would happen. `read_dir` succeeds, but there's + // a problem with an entry. I/O error during a getdirentries + // syscall seems to be the reason. The implementation in + // libstd/sys/unix/fs.rs seems to suggest that breaking now would + // be a good idea, or the iterator could keep returning the same + // error forever. + self.path_error(dir, err); + break; + } + Ok(entry) => { + let path = entry.path(); + // Recognize directories and tests by extension. + // Yes, this means we ignore directories with '.' in their name. + match path.extension().and_then(OsStr::to_str) { + Some("cton") => self.push_test(path), + Some(_) => {} + None => self.push_dir(path), + } + } + } + } + } + } + } + } + + /// Report an error related to a path. + fn path_error(&mut self, path: PathBuf, err: E) { + self.errors += 1; + println!("path-error {}: {}", path.to_string_lossy(), err); + } + + /// Scan pushed directories for tests and run them. + pub fn run(&mut self) -> CommandResult { + self.scan_dirs(); + match self.errors { + 0 => Ok(()), + 1 => Err("1 failure".to_string()), + n => Err(format!("{} failures", n)), + } + } +} diff --git a/src/tools/main.rs b/src/tools/main.rs index bd0999cd4e..5abfa89462 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -10,7 +10,7 @@ use docopt::Docopt; use std::io::{self, Write}; use std::process; - +mod filetest; mod cat; mod print_cfg; mod rsfilecheck; @@ -19,6 +19,7 @@ const USAGE: &'static str = " Cretonne code generator utility Usage: + cton-util test ... cton-util cat ... cton-util filecheck [-v] cton-util print-cfg ... @@ -33,6 +34,7 @@ Options: #[derive(RustcDecodable, Debug)] struct Args { + cmd_test: bool, cmd_cat: bool, cmd_filecheck: bool, cmd_print_cfg: bool, @@ -55,7 +57,9 @@ fn cton_util() -> CommandResult { .unwrap_or_else(|e| e.exit()); // Find the sub-command to execute. - if args.cmd_cat { + if args.cmd_test { + filetest::run(args.arg_file) + } else if args.cmd_cat { cat::run(args.arg_file) } else if args.cmd_filecheck { rsfilecheck::run(args.arg_file, args.flag_verbose) From 480054a094d0b71fb0984e7c87f97d39c55d46e7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Sep 2016 13:14:43 -0700 Subject: [PATCH 276/968] Add a utility read_to_string() function. --- src/tools/cat.rs | 11 +++-------- src/tools/main.rs | 1 + src/tools/print_cfg.rs | 9 +++------ src/tools/rsfilecheck.rs | 8 ++------ src/tools/utils.rs | 13 +++++++++++++ 5 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 src/tools/utils.rs diff --git a/src/tools/cat.rs b/src/tools/cat.rs index a41b5b5f27..566997c403 100644 --- a/src/tools/cat.rs +++ b/src/tools/cat.rs @@ -3,11 +3,9 @@ //! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of //! normalizing formatting and removing comments. -use std::fs::File; -use std::io::{self, Read}; - +use std::io; use CommandResult; - +use utils::read_to_string; use cton_reader::parse_functions; use cretonne::write::write_function; @@ -22,10 +20,7 @@ pub fn run(files: Vec) -> CommandResult { } fn cat_one(filename: String) -> CommandResult { - let mut file = try!(File::open(&filename).map_err(|e| format!("{}: {}", filename, e))); - let mut buffer = String::new(); - try!(file.read_to_string(&mut buffer) - .map_err(|e| format!("Couldn't read {}: {}", filename, e))); + let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); for (idx, func) in items.into_iter().enumerate() { diff --git a/src/tools/main.rs b/src/tools/main.rs index 5abfa89462..af7a092394 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -10,6 +10,7 @@ use docopt::Docopt; use std::io::{self, Write}; use std::process; +mod utils; mod filetest; mod cat; mod print_cfg; diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 649d87fb9f..1c57710b29 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -2,10 +2,10 @@ //! //! Read a series of Cretonne IL files and print their control flow graphs //! in graphviz format. -use std::fs::File; -use std::io::{Read, Write, stdout}; +use std::io::{Write, stdout}; use CommandResult; +use utils::read_to_string; use cretonne::ir::Function; use cretonne::cfg::ControlFlowGraph; use cretonne::ir::instructions::InstructionData; @@ -156,10 +156,7 @@ impl CFGPrinter { } fn print_cfg(filename: String) -> CommandResult { - let mut file = try!(File::open(&filename).map_err(|e| format!("{}: {}", filename, e))); - let mut buffer = String::new(); - try!(file.read_to_string(&mut buffer) - .map_err(|e| format!("Couldn't read {}: {}", filename, e))); + let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); let mut cfg_printer = CFGPrinter::new(stdout()); diff --git a/src/tools/rsfilecheck.rs b/src/tools/rsfilecheck.rs index b64a86283e..5ff6c596d1 100644 --- a/src/tools/rsfilecheck.rs +++ b/src/tools/rsfilecheck.rs @@ -1,6 +1,6 @@ use CommandResult; +use utils::read_to_string; use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; -use std::fs::File; use std::io::{self, Read}; pub fn run(files: Vec, verbose: bool) -> CommandResult { @@ -40,11 +40,7 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } fn read_checkfile(filename: &str) -> Result { - let mut file = try!(File::open(&filename).map_err(|e| format!("{}: {}", filename, e))); - let mut buffer = String::new(); - try!(file.read_to_string(&mut buffer) - .map_err(|e| format!("Couldn't read {}: {}", filename, e))); - + let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); let mut builder = CheckerBuilder::new(); try!(builder.text(&buffer).map_err(|e| format!("{}: {}", filename, e))); Ok(builder.finish()) diff --git a/src/tools/utils.rs b/src/tools/utils.rs new file mode 100644 index 0000000000..5973f25814 --- /dev/null +++ b/src/tools/utils.rs @@ -0,0 +1,13 @@ +//! Utility functions. + +use std::path::Path; +use std::fs::File; +use std::io::{Result, Read}; + +/// Read an entire file into a string. +pub fn read_to_string>(path: P) -> Result { + let mut file = try!(File::open(path)); + let mut buffer = String::new(); + try!(file.read_to_string(&mut buffer)); + Ok(buffer) +} From a24ca56c0d67fed324315f7213fbc6cfc8df6f1f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Sep 2016 15:15:02 -0700 Subject: [PATCH 277/968] Add a job queue and begin loading test files. This code looks overly complicated because it is anticipating running jobs in a thread pool. The test files are loaded and parsed, but not actually executed yet. Errors are reported back to the test runner. --- src/tools/filetest/runner.rs | 118 +++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 5 deletions(-) diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 7f39ec6490..e7e33b735b 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -4,16 +4,36 @@ //! scanning directories for tests. use std::ffi::OsStr; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::error::Error; +use std::mem; +use std::panic::catch_unwind; +use std::time; use CommandResult; +use utils::read_to_string; +use cton_reader::parse_test; + +type TestResult = Result; + +#[derive(PartialEq, Eq, Debug)] +enum QueueEntry { + New(PathBuf), + Running, + Done(PathBuf, TestResult), +} pub struct TestRunner { // Directories that have not yet been scanned. dir_stack: Vec, // Filenames of tests to run. - test_list: Vec, + tests: Vec, + + // Pointer into `tests` where the `New` entries begin. + new_tests: usize, + + // Number of contiguous finished tests at the front of `tests`. + finished_tests: usize, errors: usize, } @@ -23,7 +43,9 @@ impl TestRunner { pub fn new() -> TestRunner { TestRunner { dir_stack: Vec::new(), - test_list: Vec::new(), + tests: Vec::new(), + new_tests: 0, + finished_tests: 0, errors: 0, } } @@ -40,7 +62,40 @@ impl TestRunner { /// /// Any problems reading `file` as a test case file will be reported as a test failure. pub fn push_test>(&mut self, file: P) { - self.test_list.push(file.into()); + self.tests.push(QueueEntry::New(file.into())); + } + + /// Take a new test for running as a job. + /// Leaves the queue entry marked as `Runnning`. + fn take_job(&mut self) -> Option { + let index = self.new_tests; + if index == self.tests.len() { + return None; + } + self.new_tests += 1; + + let entry = mem::replace(&mut self.tests[index], QueueEntry::Running); + if let QueueEntry::New(path) = entry { + Some(Job::new(index, path)) + } else { + // Oh, sorry about that. Put the entry back. + self.tests[index] = entry; + None + } + } + + /// Report the end of a job. + fn finish_job(&mut self, job: Job, result: TestResult) { + assert_eq!(self.tests[job.index], QueueEntry::Running); + if let Err(ref e) = result { + self.job_error(&job.path, e); + } + self.tests[job.index] = QueueEntry::Done(job.path, result); + if job.index == self.finished_tests { + while let Some(&QueueEntry::Done(_, _)) = self.tests.get(self.finished_tests) { + self.finished_tests += 1; + } + } } /// Scan any directories pushed so far. @@ -91,18 +146,35 @@ impl TestRunner { } } } + // Get the new jobs running before moving on to the next directory. + self.schedule_jobs(); } } /// Report an error related to a path. fn path_error(&mut self, path: PathBuf, err: E) { self.errors += 1; - println!("path-error {}: {}", path.to_string_lossy(), err); + println!("{}: {}", path.to_string_lossy(), err); + } + + /// Report an error related to a job. + fn job_error(&mut self, path: &Path, err: &str) { + self.errors += 1; + println!("FAIL {}: {}", path.to_string_lossy(), err); + } + + /// Schedule and new jobs to run. + fn schedule_jobs(&mut self) { + while let Some(job) = self.take_job() { + let result = job.run(); + self.finish_job(job, result); + } } /// Scan pushed directories for tests and run them. pub fn run(&mut self) -> CommandResult { self.scan_dirs(); + self.schedule_jobs(); match self.errors { 0 => Ok(()), 1 => Err("1 failure".to_string()), @@ -110,3 +182,39 @@ impl TestRunner { } } } + +/// A test file waiting to be run. +struct Job { + index: usize, + path: PathBuf, +} + +impl Job { + pub fn new(index: usize, path: PathBuf) -> Job { + Job { + index: index, + path: path, + } + } + + pub fn run(&self) -> TestResult { + match catch_unwind(|| self.run_or_panic()) { + Err(msg) => Err(format!("panic: {:?}", msg)), + Ok(result) => result, + } + } + + fn run_or_panic(&self) -> TestResult { + let started = time::Instant::now(); + let buffer = try!(read_to_string(&self.path).map_err(|e| e.to_string())); + let testfile = try!(parse_test(&buffer).map_err(|e| e.to_string())); + if testfile.commands.is_empty() { + return Err("no test commands found".to_string()); + } + if testfile.functions.is_empty() { + return Err("no functions found".to_string()); + } + // TODO: Actually run the tests. + Ok(started.elapsed()) + } +} From 2901a85a36b82714648398deb1a7befdb481b26f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 08:32:03 -0700 Subject: [PATCH 278/968] Break DetailedFunction into a tuple. Use (Function, Details) in place of the aggregrate DetailedFunction. It turns out that some tests want to clone and manipulate the function while the details never change. --- src/libreader/lib.rs | 2 +- src/libreader/parser.rs | 92 +++++++++++++++++++-------------------- src/libreader/testfile.rs | 10 ++--- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index d435b4ac3b..94436e12f1 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -7,7 +7,7 @@ extern crate cretonne; pub use parser::{Result, parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; -pub use testfile::TestFile; +pub use testfile::{TestFile, Details}; mod lexer; mod parser; diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 7f8cc543ad..4a10f1cec7 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -19,7 +19,7 @@ use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; -use testfile::{TestFile, DetailedFunction, Comment}; +use testfile::{TestFile, Details, Comment}; use testcommand::TestCommand; pub use lexer::Location; @@ -30,7 +30,7 @@ pub use lexer::Location; pub fn parse_functions(text: &str) -> Result> { Parser::new(text) .parse_function_list() - .map(|list| list.into_iter().map(|dfunc| dfunc.function).collect()) + .map(|list| list.into_iter().map(|(func, _)| func).collect()) } /// Parse the entire `text` as a test case file. @@ -505,7 +505,7 @@ impl<'a> Parser<'a> { /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. - pub fn parse_function_list(&mut self) -> Result>> { + pub fn parse_function_list(&mut self) -> Result)>> { let mut list = Vec::new(); while self.token().is_some() { list.push(try!(self.parse_function())); @@ -517,7 +517,7 @@ impl<'a> Parser<'a> { // // function ::= * function-spec "{" preamble function-body "}" // - fn parse_function(&mut self) -> Result> { + fn parse_function(&mut self) -> Result<(Function, Details<'a>)> { // Begin gathering comments. // Make sure we don't include any comments before the `function` keyword. self.token(); @@ -545,10 +545,7 @@ impl<'a> Parser<'a> { // references. try!(ctx.rewrite_references()); - Ok(DetailedFunction { - function: ctx.function, - comments: mem::replace(&mut self.comments, Vec::new()), - }) + Ok((ctx.function, Details { comments: mem::replace(&mut self.comments, Vec::new()) })) } // Parse a function spec. @@ -1221,7 +1218,7 @@ mod tests { use super::*; use cretonne::ir::types::{self, ArgumentType, ArgumentExtension}; use cretonne::ir::entities::AnyEntity; - use testfile::Comment; + use testfile::{Details, Comment}; #[test] fn argument_type() { @@ -1261,13 +1258,12 @@ mod tests { #[test] fn stack_slot_decl() { - let func = Parser::new("function foo() { - ss3 = stack_slot 13 - ss1 = stack_slot 1 - }") + let (func, _) = Parser::new("function foo() { + ss3 = stack_slot 13 + ss1 = stack_slot 1 + }") .parse_function() - .unwrap() - .function; + .unwrap(); assert_eq!(func.name, "foo"); let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); @@ -1291,13 +1287,12 @@ mod tests { #[test] fn ebb_header() { - let func = Parser::new("function ebbs() { - ebb0: - ebb4(vx3: i32): - }") + let (func, _) = Parser::new("function ebbs() { + ebb0: + ebb4(vx3: i32): + }") .parse_function() - .unwrap() - .function; + .unwrap(); assert_eq!(func.name, "ebbs"); let mut ebbs = func.layout.ebbs(); @@ -1314,38 +1309,39 @@ mod tests { #[test] fn comments() { - let dfunc = Parser::new("; before - function comment() { ; decl - ss10 = stack_slot 13 ; stackslot. - ; Still stackslot. - jt10 = jump_table ebb0 - ; Jumptable - ebb0: ; Basic block - trap ; Instruction - } ; Trailing. - ; More trailing.") - .parse_function() - .unwrap(); - assert_eq!(&dfunc.function.name, "comment"); - assert_eq!(dfunc.comments.len(), 8); // no 'before' comment. - assert_eq!(dfunc.comments[0], + let (func, Details { comments }) = + Parser::new("; before + function comment() { ; decl + ss10 = stack_slot 13 ; stackslot. + ; Still stackslot. + jt10 = jump_table ebb0 + ; Jumptable + ebb0: ; Basic block + trap ; Instruction + } ; Trailing. + ; More trailing.") + .parse_function() + .unwrap(); + assert_eq!(&func.name, "comment"); + assert_eq!(comments.len(), 8); // no 'before' comment. + assert_eq!(comments[0], Comment { entity: AnyEntity::Function, text: "; decl", }); - assert_eq!(dfunc.comments[1].entity.to_string(), "ss0"); - assert_eq!(dfunc.comments[2].entity.to_string(), "ss0"); - assert_eq!(dfunc.comments[2].text, "; Still stackslot."); - assert_eq!(dfunc.comments[3].entity.to_string(), "jt0"); - assert_eq!(dfunc.comments[3].text, "; Jumptable"); - assert_eq!(dfunc.comments[4].entity.to_string(), "ebb0"); - assert_eq!(dfunc.comments[4].text, "; Basic block"); + assert_eq!(comments[1].entity.to_string(), "ss0"); + assert_eq!(comments[2].entity.to_string(), "ss0"); + assert_eq!(comments[2].text, "; Still stackslot."); + assert_eq!(comments[3].entity.to_string(), "jt0"); + assert_eq!(comments[3].text, "; Jumptable"); + assert_eq!(comments[4].entity.to_string(), "ebb0"); + assert_eq!(comments[4].text, "; Basic block"); - assert_eq!(dfunc.comments[5].entity.to_string(), "inst0"); - assert_eq!(dfunc.comments[5].text, "; Instruction"); + assert_eq!(comments[5].entity.to_string(), "inst0"); + assert_eq!(comments[5].text, "; Instruction"); - assert_eq!(dfunc.comments[6].entity, AnyEntity::Function); - assert_eq!(dfunc.comments[7].entity, AnyEntity::Function); + assert_eq!(comments[6].entity, AnyEntity::Function); + assert_eq!(comments[7].entity, AnyEntity::Function); } #[test] @@ -1359,6 +1355,6 @@ mod tests { assert_eq!(tf.commands[0].command, "cfg"); assert_eq!(tf.commands[1].command, "verify"); assert_eq!(tf.functions.len(), 1); - assert_eq!(tf.functions[0].function.name, "comment"); + assert_eq!(tf.functions[0].0.name, "comment"); } } diff --git a/src/libreader/testfile.rs b/src/libreader/testfile.rs index 34eddb4c6b..3b4f3d4e16 100644 --- a/src/libreader/testfile.rs +++ b/src/libreader/testfile.rs @@ -15,14 +15,14 @@ use testcommand::TestCommand; #[derive(Debug)] pub struct TestFile<'a> { pub commands: Vec>, - pub functions: Vec>, + pub functions: Vec<(Function, Details<'a>)>, } -/// A function parsed from a text string along with other details that are useful for running -/// tests. +/// Additional details about a function parsed from a text string. +/// These are useful for detecting test commands embedded in comments etc. +/// The details to not affect the semantics of the function. #[derive(Debug)] -pub struct DetailedFunction<'a> { - pub function: Function, +pub struct Details<'a> { pub comments: Vec>, } From 6ffca9ec99399d204486a2ae03abb4cc40a6a1ca Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 08:41:19 -0700 Subject: [PATCH 279/968] Make functions cloneable for testing. It's not super fast to clone a function, but it is faster than re-parsing the test case file it came from. Some tests want to mutate the function, and there may be other tests in the same script that need the original function. --- src/libcretonne/entity_map.rs | 2 +- src/libcretonne/ir/dfg.rs | 3 +++ src/libcretonne/ir/function.rs | 4 ++++ src/libcretonne/ir/instructions.rs | 12 ++++++------ src/libcretonne/ir/jumptable.rs | 1 + src/libcretonne/ir/layout.rs | 1 + src/libcretonne/ir/stackslot.rs | 2 +- 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index df486c48ed..2a76e3cce9 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -45,7 +45,7 @@ pub trait EntityRef: Copy + Eq { } /// A mapping `K -> V` for densely indexed entity references. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EntityMap where K: EntityRef { diff --git a/src/libcretonne/ir/dfg.rs b/src/libcretonne/ir/dfg.rs index 3fc4d596eb..35bb9c8f7e 100644 --- a/src/libcretonne/ir/dfg.rs +++ b/src/libcretonne/ir/dfg.rs @@ -15,6 +15,7 @@ use std::u16; /// The layout of EBBs in the function and of instructions in each EBB is recorded by the /// `FunctionLayout` data structure which form the other half of the function representation. /// +#[derive(Clone)] pub struct DataFlowGraph { /// Data about all of the instructions in the function, including opcodes and operands. /// The instructions in this map are not in program order. That is tracked by `Layout`, along @@ -119,6 +120,7 @@ pub enum ValueDef { } // Internal table storage for extended values. +#[derive(Clone)] enum ValueData { // Value is defined by an instruction, but it is not the first result. Inst { @@ -336,6 +338,7 @@ impl DataFlowGraph { // Arguments for an extended basic block are values that dominate everything in the EBB. All // branches to this EBB must provide matching arguments, and the arguments to the entry EBB must // match the function arguments. +#[derive(Clone)] struct EbbData { // First argument to this EBB, or `NO_VALUE` if the block has no arguments. // diff --git a/src/libcretonne/ir/function.rs b/src/libcretonne/ir/function.rs index 2f8344d801..d6a44edfa6 100644 --- a/src/libcretonne/ir/function.rs +++ b/src/libcretonne/ir/function.rs @@ -9,6 +9,10 @@ use entity_map::{EntityMap, PrimaryEntityData}; use std::fmt::{self, Debug, Formatter}; /// A function. +/// +/// Functions can be cloned, but it is not a very fast operation. +/// The clone will have all the same entity numbers as the original. +#[derive(Clone)] pub struct Function { /// Name of this function. Mostly used by `.cton` files. pub name: FunctionName, diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index a6d664170e..c8228d1f43 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -93,7 +93,7 @@ impl FromStr for Opcode { /// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a /// `Box` to store the additional information out of line. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum InstructionData { Nullary { opcode: Opcode, ty: Type }, Unary { @@ -203,7 +203,7 @@ pub enum InstructionData { /// A variable list of `Value` operands used for function call arguments and passing arguments to /// basic blocks. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct VariableArgs(Vec); impl VariableArgs { @@ -256,7 +256,7 @@ impl Default for VariableArgs { /// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit /// in the allowed InstructionData size. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct JumpData { pub destination: Ebb, pub arguments: VariableArgs, @@ -274,7 +274,7 @@ impl Display for JumpData { /// Payload data for branch instructions. These need to carry lists of EBB arguments that won't fit /// in the allowed InstructionData size. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct BranchData { pub arg: Value, pub destination: Ebb, @@ -292,7 +292,7 @@ impl Display for BranchData { } /// Payload of a call instruction. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct CallData { /// Second result value for a call producing multiple return values. second_result: Value, @@ -308,7 +308,7 @@ impl Display for CallData { } /// Payload of a return instruction. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ReturnData { // Dynamically sized array containing return values. pub args: VariableArgs, diff --git a/src/libcretonne/ir/jumptable.rs b/src/libcretonne/ir/jumptable.rs index 8a2308fbe1..cbd8630e46 100644 --- a/src/libcretonne/ir/jumptable.rs +++ b/src/libcretonne/ir/jumptable.rs @@ -12,6 +12,7 @@ use std::fmt::{self, Display, Formatter}; /// /// All jump tables use 0-based indexing and are expected to be densely populated. They don't need /// to be completely populated, though. Individual entries can be missing. +#[derive(Clone)] pub struct JumpTableData { // Table entries, using NO_EBB as a placeholder for missing entries. table: Vec, diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index c519a1c44f..a872efa5d8 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -20,6 +20,7 @@ use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; /// While data dependencies are not recorded, instruction ordering does affect control /// dependencies, so part of the semantics of the program are determined by the layout. /// +#[derive(Clone)] pub struct Layout { // Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in // both ends by NO_EBB. diff --git a/src/libcretonne/ir/stackslot.rs b/src/libcretonne/ir/stackslot.rs index 31bee66eda..b22b82919c 100644 --- a/src/libcretonne/ir/stackslot.rs +++ b/src/libcretonne/ir/stackslot.rs @@ -6,7 +6,7 @@ use std::fmt::{self, Display, Formatter}; /// Contents of a stack slot. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct StackSlotData { /// Size of stack slot in bytes. pub size: u32, From d16b57d54056d92fa423778c6b4f8b6f887d3e7a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 13:56:42 -0700 Subject: [PATCH 280/968] Use fmt::Write instead of io::Write in write.rs. It is common to represent a function as a String, and previously that required re-validating the UTF-8 in a Vec. The fmt::Write trait writes UTF-8 directly into a String, so no extra checking is required. This also means we can implement Display for Function which gives it a to_string() method. This makes the function_to_string() method redundant, so delete it. The functions in the write module are no longer generally useful, so make the module private. The Display trait on Function is all we need. --- src/libcretonne/ir/function.rs | 14 ++++++++++---- src/libcretonne/lib.rs | 2 +- src/libcretonne/write.rs | 29 +++++++++-------------------- src/tools/cat.rs | 6 +----- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/libcretonne/ir/function.rs b/src/libcretonne/ir/function.rs index d6a44edfa6..415e8de9cc 100644 --- a/src/libcretonne/ir/function.rs +++ b/src/libcretonne/ir/function.rs @@ -6,7 +6,8 @@ use ir::{FunctionName, Signature, StackSlot, StackSlotData, JumpTable, JumpTableData, DataFlowGraph, Layout}; use entity_map::{EntityMap, PrimaryEntityData}; -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Display, Debug, Formatter}; +use write::write_function; /// A function. /// @@ -60,9 +61,14 @@ impl Function { } } -impl Debug for Function { +impl Display for Function { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - use write::function_to_string; - fmt.write_str(&function_to_string(self)) + write_function(fmt, self) + } +} + +impl Debug for Function { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write_function(fmt, self) } } diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index f338aba8ac..7b90a902c3 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -9,13 +9,13 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod ir; pub mod isa; -pub mod write; pub mod cfg; pub mod dominator_tree; pub mod entity_map; pub mod settings; pub mod verifier; +mod write; mod constant_hash; mod predicates; diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index a20007ed05..aa5076f5d5 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -5,9 +5,8 @@ //! `cretonne-reader` crate. use ir::{Function, Ebb, Inst, Value, Type}; -use std::io::{self, Write}; - -pub type Result = io::Result<()>; +use std::fmt::{Result, Error, Write}; +use std::result; /// Write `func` to `w` as equivalent text. pub fn write_function(w: &mut Write, func: &Function) -> Result { @@ -24,15 +23,6 @@ pub fn write_function(w: &mut Write, func: &Function) -> Result { writeln!(w, "}}") } -/// Convert `func` to a string. -pub fn function_to_string(func: &Function) -> String { - let mut buffer: Vec = Vec::new(); - // Any errors here would be out-of-memory, which should not happen with normal functions. - write_function(&mut buffer, func).unwrap(); - // A UTF-8 conversion error is a real bug. - String::from_utf8(buffer).unwrap() -} - // ====--------------------------------------------------------------------------------------====// // // Function spec. @@ -65,7 +55,7 @@ fn write_spec(w: &mut Write, func: &Function) -> Result { } } -fn write_preamble(w: &mut Write, func: &Function) -> io::Result { +fn write_preamble(w: &mut Write, func: &Function) -> result::Result { let mut any = false; for ss in func.stack_slots.keys() { @@ -218,7 +208,6 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { #[cfg(test)] mod tests { - use super::*; use super::{needs_quotes, escaped}; use ir::{Function, StackSlotData}; use ir::types; @@ -244,26 +233,26 @@ mod tests { #[test] fn basic() { let mut f = Function::new(); - assert_eq!(function_to_string(&f), "function \"\"() {\n}\n"); + assert_eq!(f.to_string(), "function \"\"() {\n}\n"); f.name.push_str("foo"); - assert_eq!(function_to_string(&f), "function foo() {\n}\n"); + assert_eq!(f.to_string(), "function foo() {\n}\n"); f.stack_slots.push(StackSlotData::new(4)); - assert_eq!(function_to_string(&f), + assert_eq!(f.to_string(), "function foo() {\n ss0 = stack_slot 4\n}\n"); let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); - assert_eq!(function_to_string(&f), + assert_eq!(f.to_string(), "function foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); f.dfg.append_ebb_arg(ebb, types::I8); - assert_eq!(function_to_string(&f), + assert_eq!(f.to_string(), "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8):\n}\n"); f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); - assert_eq!(function_to_string(&f), + assert_eq!(f.to_string(), "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8, vx1: f32x4):\n}\n"); } } diff --git a/src/tools/cat.rs b/src/tools/cat.rs index 566997c403..0db69b48db 100644 --- a/src/tools/cat.rs +++ b/src/tools/cat.rs @@ -3,11 +3,9 @@ //! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of //! normalizing formatting and removing comments. -use std::io; use CommandResult; use utils::read_to_string; use cton_reader::parse_functions; -use cretonne::write::write_function; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { @@ -27,9 +25,7 @@ fn cat_one(filename: String) -> CommandResult { if idx != 0 { println!(""); } - let stdout = io::stdout(); - let mut handle = stdout.lock(); - try!(write_function(&mut handle, &func).map_err(|e| format!("{}: {}", filename, e))); + print!("{}", func); } Ok(()) From d221249e7f3c2d8dd3dba942da99f16ef334a33d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 08:17:50 -0700 Subject: [PATCH 281/968] Add a SubTest trait and filecheck utilities. This trait serves as a shared interface for the different kinds of test commands the 'cton-util test' understands. Many tests produce output that is run through filecheck for validation. Provide a simple run_filecheck() function to help with this. Implement the 'test cat' sub-test which is probably the simplest possible. --- src/libcretonne/lib.rs | 2 + src/libcretonne/verifier.rs | 4 ++ src/tools/cat.rs | 36 +++++++++++++++- src/tools/filetest/mod.rs | 1 + src/tools/filetest/runner.rs | 53 ++++++++++++++++++++++-- src/tools/filetest/subtest.rs | 77 +++++++++++++++++++++++++++++++++++ 6 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/tools/filetest/subtest.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 7b90a902c3..de6cf109b5 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -5,6 +5,8 @@ // // ====------------------------------------------------------------------------------------==== // +pub use verifier::verify_function; + pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod ir; diff --git a/src/libcretonne/verifier.rs b/src/libcretonne/verifier.rs index e134b36285..91131ee6ef 100644 --- a/src/libcretonne/verifier.rs +++ b/src/libcretonne/verifier.rs @@ -55,6 +55,10 @@ use ir::{Function, ValueDef, Ebb, Inst}; use ir::instructions::InstructionFormat; +pub fn verify_function(func: &Function) -> Result<(), String> { + Verifier::new(func).run() +} + pub struct Verifier<'a> { func: &'a Function, } diff --git a/src/tools/cat.rs b/src/tools/cat.rs index 0db69b48db..14871b07c2 100644 --- a/src/tools/cat.rs +++ b/src/tools/cat.rs @@ -3,9 +3,12 @@ //! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of //! normalizing formatting and removing comments. +use std::borrow::Cow; +use cretonne::ir::Function; +use cton_reader::{parse_functions, TestCommand}; use CommandResult; use utils::read_to_string; -use cton_reader::parse_functions; +use filetest::subtest::{self, SubTest, Context, Result as STResult}; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { @@ -30,3 +33,34 @@ fn cat_one(filename: String) -> CommandResult { Ok(()) } + +/// Object implementing the `test cat` sub-test. +/// +/// This command is used for testing the parser and function printer. It simply parses a function +/// and prints it out again. +/// +/// The result is verified by filecheck. +struct TestCat; + +pub fn subtest(parsed: &TestCommand) -> STResult> { + assert_eq!(parsed.command, "cat"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestCat)) + } +} + +impl SubTest for TestCat { + fn name(&self) -> Cow { + Cow::from("cat") + } + + fn needs_verifier(&self) -> bool { + false + } + + fn run(&self, func: Cow, context: &Context) -> STResult<()> { + subtest::run_filecheck(&func.to_string(), context) + } +} diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index 078e287dde..c1019446ca 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -7,6 +7,7 @@ use std::path::Path; use CommandResult; use filetest::runner::TestRunner; +pub mod subtest; mod runner; /// Main entry point for `cton-util test`. diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index e7e33b735b..1f7ac7ee88 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -7,11 +7,15 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::error::Error; use std::mem; +use std::borrow::{Borrow, Cow}; use std::panic::catch_unwind; use std::time; use CommandResult; use utils::read_to_string; use cton_reader::parse_test; +use cretonne::ir::Function; +use cretonne::verify_function; +use filetest::subtest::{self, SubTest, Context}; type TestResult = Result; @@ -208,13 +212,56 @@ impl Job { let started = time::Instant::now(); let buffer = try!(read_to_string(&self.path).map_err(|e| e.to_string())); let testfile = try!(parse_test(&buffer).map_err(|e| e.to_string())); - if testfile.commands.is_empty() { - return Err("no test commands found".to_string()); - } if testfile.functions.is_empty() { return Err("no functions found".to_string()); } + // Parse the test commands. + let mut tests = + try!(testfile.commands.iter().map(subtest::new).collect::>>()); + + // Sort the tests so the mutators are at the end, and those that + // don't need the verifier are at the front + tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); + + // Isolate the last test in the hope that this is the only mutating test. + // If so, we can completely avoid cloning functions. + let last_test = match tests.pop() { + None => return Err("no test commands found".to_string()), + Some(t) => t, + }; + + for (func, details) in testfile.functions { + let mut context = subtest::Context { + details: details, + verified: false, + }; + + for test in &tests { + try!(self.run_one_test(test.borrow(), Cow::Borrowed(&func), &mut context)); + } + // Run the last test with an owned function which means it won't need to clone it + // before mutating. + try!(self.run_one_test(last_test.borrow(), Cow::Owned(func), &mut context)); + } + + // TODO: Actually run the tests. Ok(started.elapsed()) } + + fn run_one_test(&self, + test: &SubTest, + func: Cow, + context: &mut Context) + -> subtest::Result<()> { + let name = format!("{}({})", test.name(), func.name); + + // Should we run the verifier before this test? + if !context.verified && test.needs_verifier() { + try!(verify_function(&func)); + context.verified = true; + } + + test.run(func, context).map_err(|e| format!("{}: {}", name, e)) + } } diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs new file mode 100644 index 0000000000..69ca47aa64 --- /dev/null +++ b/src/tools/filetest/subtest.rs @@ -0,0 +1,77 @@ +//! SubTest trait. + +use std::result; +use std::borrow::Cow; +use cretonne::ir::Function; +use cton_reader::{TestCommand, Details}; +use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; + +pub type Result = result::Result; + +/// Create a new subcommand trait object to match `parsed.command`. +pub fn new(parsed: &TestCommand) -> Result> { + use cat; + match parsed.command { + "cat" => cat::subtest(parsed), + _ => Err(format!("unknown test command '{}'", parsed.command)), + } +} + +/// Context for running a a test on a single function. +pub struct Context<'a> { + /// Additional details about the function from the parser. + pub details: Details<'a>, + + /// Was the function verified before running this test? + pub verified: bool, +} + +/// Common interface for implementations of test commands. +/// +/// Each `.cton` test file may contain multiple test commands, each represented by a `SubTest` +/// trait object. +pub trait SubTest { + /// Name identifying this subtest. Typically the same as the test command. + fn name(&self) -> Cow; + + /// Should the verifier be run on the function before running the test? + fn needs_verifier(&self) -> bool { + true + } + + /// Does this test mutate the function when it runs? + /// This is used as a hint to avoid cloning the function needlessly. + fn is_mutating(&self) -> bool { + false + } + + /// Run this test on `func`. + fn run(&self, func: Cow, context: &Context) -> Result<()>; +} + +/// Run filecheck on `text`, using directives extracted from `context`. +pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { + let checker = try!(build_filechecker(&context.details)); + if try!(checker.check(&text, NO_VARIABLES).map_err(|e| format!("filecheck: {}", e))) { + Ok(()) + } else { + // Filecheck mismatch. Emit an explanation as output. + let (_, explain) = try!(checker.explain(&text, NO_VARIABLES) + .map_err(|e| format!("explain: {}", e))); + Err(format!("filecheck failed:\n{}{}", checker, explain)) + } +} + +/// Build a filechecker using the directives in the function's comments. +pub fn build_filechecker(details: &Details) -> Result { + let mut builder = CheckerBuilder::new(); + for comment in &details.comments { + try!(builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))); + } + let checker = builder.finish(); + if checker.is_empty() { + Err("no filecheck directives in function".to_string()) + } else { + Ok(checker) + } +} From 0b7f87b14cddec5c85df747530a9b562fc86810c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 15:27:37 -0700 Subject: [PATCH 282/968] Convert parser tests to filetests. Create a new directory hierarchy under 'filetests' for all the tests that are run by 'cton-util test'. Convert the parser tests under 'tests/parser' to use 'test cat' and filecheck directives. --- filetests/parser/branch.cton | 113 +++++++++++++++++++++++ filetests/parser/call.cton | 24 +++++ {tests => filetests}/parser/rewrite.cton | 13 +++ {tests => filetests}/parser/tiny.cton | 39 ++++++++ test-all.sh | 6 +- tests/parser/README.rst | 9 -- tests/parser/branch.cton | 60 ------------ tests/parser/branch.cton.ref | 57 ------------ tests/parser/call.cton | 13 --- tests/parser/call.cton.ref | 11 --- tests/parser/rewrite.cton.ref | 13 --- tests/parser/run.sh | 40 -------- tests/parser/tiny.cton.ref | 43 --------- 13 files changed, 193 insertions(+), 248 deletions(-) create mode 100644 filetests/parser/branch.cton create mode 100644 filetests/parser/call.cton rename {tests => filetests}/parser/rewrite.cton (68%) rename {tests => filetests}/parser/tiny.cton (50%) delete mode 100644 tests/parser/README.rst delete mode 100644 tests/parser/branch.cton delete mode 100644 tests/parser/branch.cton.ref delete mode 100644 tests/parser/call.cton delete mode 100644 tests/parser/call.cton.ref delete mode 100644 tests/parser/rewrite.cton.ref delete mode 100755 tests/parser/run.sh delete mode 100644 tests/parser/tiny.cton.ref diff --git a/filetests/parser/branch.cton b/filetests/parser/branch.cton new file mode 100644 index 0000000000..46612973af --- /dev/null +++ b/filetests/parser/branch.cton @@ -0,0 +1,113 @@ +; Parsing branches and jumps. +test cat + +; Jumps with no arguments. The '()' empty argument list is optional. +function minimal() { +ebb0: + jump ebb1 + +ebb1: + jump ebb0() +} +; sameln: function minimal() { +; nextln: ebb0: +; nextln: jump ebb1 +; nextln: +; nextln: ebb1: +; nextln: jump ebb0 +; nextln: } + +; Jumps with 1 arg. +function onearg(i32) { +ebb0(vx0: i32): + jump ebb1(vx0) + +ebb1(vx1: i32): + jump ebb0(vx1) +} +; sameln: function onearg(i32) { +; nextln: ebb0(vx0: i32): +; nextln: jump ebb1(vx0) +; nextln: +; nextln: ebb1(vx1: i32): +; nextln: jump ebb0(vx1) +; nextln: } + +; Jumps with 2 args. +function twoargs(i32, f32) { +ebb0(vx0: i32, vx1: f32): + jump ebb1(vx0, vx1) + +ebb1(vx2: i32, vx3: f32): + jump ebb0(vx2, vx3) +} +; sameln: function twoargs(i32, f32) { +; nextln: ebb0(vx0: i32, vx1: f32): +; nextln: jump ebb1(vx0, vx1) +; nextln: +; nextln: ebb1(vx2: i32, vx3: f32): +; nextln: jump ebb0(vx2, vx3) +; nextln: } + +; Branches with no arguments. The '()' empty argument list is optional. +function minimal(i32) { +ebb0(vx0: i32): + brz vx0, ebb1 + +ebb1: + brnz vx0, ebb1() +} +; sameln: function minimal(i32) { +; nextln: ebb0(vx0: i32): +; nextln: brz vx0, ebb1 +; nextln: +; nextln: ebb1: +; nextln: brnz vx0, ebb1 +; nextln: } + +function twoargs(i32, f32) { +ebb0(vx0: i32, vx1: f32): + brz vx0, ebb1(vx0, vx1) + +ebb1(vx2: i32, vx3: f32): + brnz vx0, ebb0(vx2, vx3) +} +; sameln: function twoargs(i32, f32) { +; nextln: ebb0(vx0: i32, vx1: f32): +; nextln: brz vx0, ebb1(vx0, vx1) +; nextln: +; nextln: ebb1(vx2: i32, vx3: f32): +; nextln: brnz vx0, ebb0(vx2, vx3) +; nextln: } + +function jumptable(i32) { + jt200 = jump_table 0, 0 + jt2 = jump_table 0, 0, ebb10, ebb40, ebb20, ebb30 + +ebb10(v3: i32): + br_table v3, jt2 + trap +ebb20: + trap +ebb30: + trap +ebb40: + trap +} +; sameln: function jumptable(i32) { +; nextln: jt0 = jump_table 0 +; nextln: jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 +; nextln: +; nextln: ebb0(vx0: i32): +; nextln: br_table vx0, jt1 +; nextln: trap +; nextln: +; nextln: ebb1: +; nextln: trap +; nextln: +; nextln: ebb2: +; nextln: trap +; nextln: +; nextln: ebb3: +; nextln: trap +; nextln: } diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton new file mode 100644 index 0000000000..71c7c1efb0 --- /dev/null +++ b/filetests/parser/call.cton @@ -0,0 +1,24 @@ +; Parser tests for call and return syntax. +test cat + +function mini() { +ebb1: + return +} +; sameln: function mini() { +; nextln: ebb0: +; nextln: return +; nextln: } + +function r1() -> i32, f32 { +ebb1: + v1 = iconst.i32 3 + v2 = f32const 0.0 + return v1, v2 +} +; sameln: function r1() -> i32, f32 { +; nextln: ebb0: +; nextln: v0 = iconst.i32 3 +; nextln: v1 = f32const 0.0 +; nextln: return v0, v1 +; nextln: } diff --git a/tests/parser/rewrite.cton b/filetests/parser/rewrite.cton similarity index 68% rename from tests/parser/rewrite.cton rename to filetests/parser/rewrite.cton index 33e5277db6..969572bdbf 100644 --- a/tests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -6,6 +6,7 @@ ; It is possible to refer to instructions and EBBs that have not yet been ; defined in the lexical order, so the parser needs to rewrite these references ; after the fact. +test cat ; Check that defining numbers are rewritten. function defs() { @@ -14,6 +15,12 @@ ebb100(v20: i32): vx200 = f64const 0x4.0p0 trap } +; sameln: function defs() { +; nextln: ebb0(vx0: i32): +; nextln: v0 = iconst.i32x8 5 +; nextln: v1 = f64const 0x1.0000000000000p2 +; nextln: trap +; nextln: } ; Using values. function use_value() { @@ -22,3 +29,9 @@ ebb100(v20: i32): vx200 = iadd v20, v1000 jump ebb100(v1000) } +; sameln: function "use_value"() { +; nextln: ebb0(vx0: i32): +; nextln: v0 = iadd_imm vx0, 5 +; nextln: v1 = iadd vx0, v0 +; nextln: jump ebb0(v0) +; nextln: } diff --git a/tests/parser/tiny.cton b/filetests/parser/tiny.cton similarity index 50% rename from tests/parser/tiny.cton rename to filetests/parser/tiny.cton index ae3466b618..588223c521 100644 --- a/tests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -1,8 +1,14 @@ +test cat + ; The smallest possible function. function minimal() { ebb0: trap } +; sameln: function minimal() { +; nextln: ebb0: +; nextln: trap +; nextln: } ; Create and use values. ; Polymorphic instructions with type suffix. @@ -12,12 +18,22 @@ ebb0: v1 = iconst.i8 6 v2 = ishl v0, v1 } +; sameln: function ivalues() { +; nextln: ebb0: +; nextln: v0 = iconst.i32 2 +; nextln: v1 = iconst.i8 6 +; nextln: v2 = ishl v0, v1 +; nextln: } ; Polymorphic istruction controlled by second operand. function select() { ebb0(vx0: i32, vx1: i32, vx2: b1): v0 = select vx2, vx0, vx1 } +; sameln: function select() { +; nextln: ebb0(vx0: i32, vx1: i32, vx2: b1): +; nextln: v0 = select vx2, vx0, vx1 +; nextln: } ; Lane indexes. function lanes() { @@ -26,6 +42,12 @@ ebb0: v1 = extractlane v0, 3 v2 = insertlane v0, 1, v1 } +; sameln: function lanes() { +; nextln: ebb0: +; nextln: v0 = iconst.i32x4 2 +; nextln: v1 = extractlane v0, 3 +; nextln: v2 = insertlane v0, 1, v1 +; nextln: } ; Integer condition codes. function icmp(i32, i32) { @@ -34,6 +56,12 @@ ebb0(vx0: i32, vx1: i32): v1 = icmp ult, vx0, vx1 v2 = icmp sge, vx0, vx1 } +; sameln: function icmp(i32, i32) { +; nextln: ebb0(vx0: i32, vx1: i32): +; nextln: v0 = icmp eq, vx0, vx1 +; nextln: v1 = icmp ult, vx0, vx1 +; nextln: v2 = icmp sge, vx0, vx1 +; nextln: } ; Floating condition codes. function fcmp(f32, f32) { @@ -42,6 +70,12 @@ ebb0(vx0: f32, vx1: f32): v1 = fcmp uno, vx0, vx1 v2 = fcmp lt, vx0, vx1 } +; sameln: function fcmp(f32, f32) { +; nextln: ebb0(vx0: f32, vx1: f32): +; nextln: v0 = fcmp eq, vx0, vx1 +; nextln: v1 = fcmp uno, vx0, vx1 +; nextln: v2 = fcmp lt, vx0, vx1 +; nextln: } ; The bitcast instruction has two type variables: The controlling type variable ; controls the outout type, and the input type is a free variable. @@ -50,3 +84,8 @@ ebb0(vx0: i32, vx1: f32): v0 = bitcast.i8x4 vx0 v1 = bitcast.i32 vx1 } +; sameln: function bitcast(i32, f32) { +; nextln: ebb0(vx0: i32, vx1: f32): +; nextln: v0 = bitcast.i8x4 vx0 +; nextln: v1 = bitcast.i32 vx1 +; nextln: } diff --git a/test-all.sh b/test-all.sh index cd3916d8c3..6c8406493a 100755 --- a/test-all.sh +++ b/test-all.sh @@ -58,10 +58,12 @@ cargo build --release export CTONUTIL="$topdir/src/tools/target/release/cton-util" +cd "$topdir" +banner "File tests" +"$CTONUTIL" test filetests + # Run the parser tests. cd "$topdir/tests" -banner "Parser tests" -parser/run.sh banner "CFG tests" cfg/run.sh diff --git a/tests/parser/README.rst b/tests/parser/README.rst deleted file mode 100644 index 9ac4658d13..0000000000 --- a/tests/parser/README.rst +++ /dev/null @@ -1,9 +0,0 @@ -Parser tests -============ - -This directory contains test cases for the Cretonne IL parser. - -Each test case consists of a `foo.cton` input file and a `foo.ref` reference -output file. Each input file is run through the `cton-util cat` command, and the -output is compared against the reference file. If the two are identical, the -test passes. diff --git a/tests/parser/branch.cton b/tests/parser/branch.cton deleted file mode 100644 index 05526fd910..0000000000 --- a/tests/parser/branch.cton +++ /dev/null @@ -1,60 +0,0 @@ -; Parsing branches and jumps. - -; Jumps with no arguments. The '()' empty argument list is optional. -function minimal() { -ebb0: - jump ebb1 - -ebb1: - jump ebb0() -} - -; Jumps with 1 arg. -function onearg(i32) { -ebb0(vx0: i32): - jump ebb1(vx0) - -ebb1(vx1: i32): - jump ebb0(vx1) -} - -; Jumps with 2 args. -function twoargs(i32, f32) { -ebb0(vx0: i32, vx1: f32): - jump ebb1(vx0, vx1) - -ebb1(vx2: i32, vx3: f32): - jump ebb0(vx2, vx3) -} - -; Branches with no arguments. The '()' empty argument list is optional. -function minimal(i32) { -ebb0(vx0: i32): - brz vx0, ebb1 - -ebb1: - brnz vx0, ebb1() -} - -function twoargs(i32, f32) { -ebb0(vx0: i32, vx1: f32): - brz vx0, ebb1(vx0, vx1) - -ebb1(vx2: i32, vx3: f32): - brnz vx0, ebb0(vx2, vx3) -} - -function jumptable(i32) { - jt200 = jump_table 0, 0 - jt2 = jump_table 0, 0, ebb10, ebb40, ebb20, ebb30 - -ebb10(v3: i32): - br_table v3, jt2 - trap -ebb20: - trap -ebb30: - trap -ebb40: - trap -} diff --git a/tests/parser/branch.cton.ref b/tests/parser/branch.cton.ref deleted file mode 100644 index 8f395c885c..0000000000 --- a/tests/parser/branch.cton.ref +++ /dev/null @@ -1,57 +0,0 @@ -function minimal() { -ebb0: - jump ebb1 - -ebb1: - jump ebb0 -} - -function onearg(i32) { -ebb0(vx0: i32): - jump ebb1(vx0) - -ebb1(vx1: i32): - jump ebb0(vx1) -} - -function twoargs(i32, f32) { -ebb0(vx0: i32, vx1: f32): - jump ebb1(vx0, vx1) - -ebb1(vx2: i32, vx3: f32): - jump ebb0(vx2, vx3) -} - -function minimal(i32) { -ebb0(vx0: i32): - brz vx0, ebb1 - -ebb1: - brnz vx0, ebb1 -} - -function twoargs(i32, f32) { -ebb0(vx0: i32, vx1: f32): - brz vx0, ebb1(vx0, vx1) - -ebb1(vx2: i32, vx3: f32): - brnz vx0, ebb0(vx2, vx3) -} - -function jumptable(i32) { - jt0 = jump_table 0 - jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 - -ebb0(vx0: i32): - br_table vx0, jt1 - trap - -ebb1: - trap - -ebb2: - trap - -ebb3: - trap -} diff --git a/tests/parser/call.cton b/tests/parser/call.cton deleted file mode 100644 index 12221c3ff1..0000000000 --- a/tests/parser/call.cton +++ /dev/null @@ -1,13 +0,0 @@ -; Parser tests for call and return syntax. - -function mini() { -ebb1: - return -} - -function r1() -> i32, f32 { -ebb1: - v1 = iconst.i32 3 - v2 = f32const 0.0 - return v1, v2 -} diff --git a/tests/parser/call.cton.ref b/tests/parser/call.cton.ref deleted file mode 100644 index a6d51166f4..0000000000 --- a/tests/parser/call.cton.ref +++ /dev/null @@ -1,11 +0,0 @@ -function mini() { -ebb0: - return -} - -function r1() -> i32, f32 { -ebb0: - v0 = iconst.i32 3 - v1 = f32const 0.0 - return v0, v1 -} diff --git a/tests/parser/rewrite.cton.ref b/tests/parser/rewrite.cton.ref deleted file mode 100644 index 8f379abd64..0000000000 --- a/tests/parser/rewrite.cton.ref +++ /dev/null @@ -1,13 +0,0 @@ -function defs() { -ebb0(vx0: i32): - v0 = iconst.i32x8 5 - v1 = f64const 0x1.0000000000000p2 - trap -} - -function "use_value"() { -ebb0(vx0: i32): - v0 = iadd_imm vx0, 5 - v1 = iadd vx0, v0 - jump ebb0(v0) -} diff --git a/tests/parser/run.sh b/tests/parser/run.sh deleted file mode 100755 index e5795df1da..0000000000 --- a/tests/parser/run.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# Go to tests directory. -cd $(dirname "$0")/.. - -# The path to cton-util should be in $CTONUTIL. -if [ -z "$CTONUTIL" ]; then - CTONUTIL=../src/tools/target/debug/cton-util -fi - -if [ ! -x "$CTONUTIL" ]; then - echo "Can't fund executable cton-util: $CTONUTIL" 1>&2 - exit 1 -fi - -declare -a fails - -for testcase in $(find parser -name '*.cton'); do - ref="${testcase}.ref" - if [ ! -r "$ref" ]; then - fails=(${fails[@]} "$testcase") - echo MISSING: $ref - elif diff -u "$ref" <("$CTONUTIL" cat "$testcase"); then - echo OK $testcase - else - fails=(${fails[@]} "$testcase") - echo FAIL $testcase - fi -done - -if [ ${#fails[@]} -ne 0 ]; then - echo - echo Failures: - for f in "${fails[@]}"; do - echo " $f" - done - exit 1 -else - echo "All passed" -fi diff --git a/tests/parser/tiny.cton.ref b/tests/parser/tiny.cton.ref deleted file mode 100644 index f2404bbf94..0000000000 --- a/tests/parser/tiny.cton.ref +++ /dev/null @@ -1,43 +0,0 @@ -function minimal() { -ebb0: - trap -} - -function ivalues() { -ebb0: - v0 = iconst.i32 2 - v1 = iconst.i8 6 - v2 = ishl v0, v1 -} - -function select() { -ebb0(vx0: i32, vx1: i32, vx2: b1): - v0 = select vx2, vx0, vx1 -} - -function lanes() { -ebb0: - v0 = iconst.i32x4 2 - v1 = extractlane v0, 3 - v2 = insertlane v0, 1, v1 -} - -function icmp(i32, i32) { -ebb0(vx0: i32, vx1: i32): - v0 = icmp eq, vx0, vx1 - v1 = icmp ult, vx0, vx1 - v2 = icmp sge, vx0, vx1 -} - -function fcmp(f32, f32) { -ebb0(vx0: f32, vx1: f32): - v0 = fcmp eq, vx0, vx1 - v1 = fcmp uno, vx0, vx1 - v2 = fcmp lt, vx0, vx1 -} - -function bitcast(i32, f32) { -ebb0(vx0: i32, vx1: f32): - v0 = bitcast.i8x4 vx0 - v1 = bitcast.i32 vx1 -} From 5ade8dc36a6f5779a556fa77c8a650551445b245 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 16:07:00 -0700 Subject: [PATCH 283/968] Ignore test commands in parse_functions(). No point in returning a syntax error if the file contains test commands. --- src/libreader/parser.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 4a10f1cec7..63a635b3f7 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -28,9 +28,7 @@ pub use lexer::Location; /// /// Any test commands or ISA declarations are ignored. pub fn parse_functions(text: &str) -> Result> { - Parser::new(text) - .parse_function_list() - .map(|list| list.into_iter().map(|(func, _)| func).collect()) + parse_test(text).map(|file| file.functions.into_iter().map(|(func, _)| func).collect()) } /// Parse the entire `text` as a test case file. From 1ed8e1206df391c5aa767e4c7806e813ac76635a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 17:10:21 -0700 Subject: [PATCH 284/968] Also use fmt::Write for the print-cfg command. This prepares use for implementing a 'test print-cfg' sub-test. --- src/tools/print_cfg.rs | 166 ++++++++++++----------------------------- 1 file changed, 48 insertions(+), 118 deletions(-) diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index 1c57710b29..d4d6ccf79f 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -2,13 +2,13 @@ //! //! Read a series of Cretonne IL files and print their control flow graphs //! in graphviz format. -use std::io::{Write, stdout}; +use std::fmt::{Result, Write, Display, Formatter}; use CommandResult; use utils::read_to_string; use cretonne::ir::Function; use cretonne::cfg::ControlFlowGraph; -use cretonne::ir::instructions::InstructionData; +use cretonne::ir::instructions::BranchInfo; use cton_reader::parse_functions; pub fn run(files: Vec) -> CommandResult { @@ -21,137 +21,69 @@ pub fn run(files: Vec) -> CommandResult { Ok(()) } -struct CFGPrinter { - level: usize, - writer: T, - buffer: String, +struct CFGPrinter<'a> { + func: &'a Function, + cfg: ControlFlowGraph, } -impl CFGPrinter { - pub fn new(writer: T) -> CFGPrinter { +impl<'a> CFGPrinter<'a> { + pub fn new(func: &'a Function) -> CFGPrinter<'a> { CFGPrinter { - level: 0, - writer: writer, - buffer: String::new(), + func: func, + cfg: ControlFlowGraph::new(func), } } - pub fn print(&mut self, func: &Function) -> Result<(), String> { - self.level = 0; - self.header(func); - self.push_indent(); - self.ebb_subgraphs(func); - let cfg = ControlFlowGraph::new(func); - self.cfg_connections(func, &cfg); - self.pop_indent(); - self.footer(); - self.write() + /// Write the CFG for this function to `w`. + pub fn write(&self, w: &mut Write) -> Result { + try!(self.header(w)); + try!(self.ebb_nodes(w)); + try!(self.cfg_connections(w)); + writeln!(w, "}}") } - fn write(&mut self) -> Result<(), String> { - match self.writer.write(self.buffer.as_bytes()) { - Err(_) => return Err("Write failed!".to_string()), - _ => (), - }; - match self.writer.flush() { - Err(_) => return Err("Flush failed!".to_string()), - _ => (), - }; + fn header(&self, w: &mut Write) -> Result { + try!(writeln!(w, "digraph {} {{", self.func.name)); + if let Some(entry) = self.func.layout.entry_block() { + try!(writeln!(w, " {{rank=min; {}}}", entry)); + } Ok(()) } - fn append(&mut self, s: &str) { - let mut indent = String::new(); - for _ in 0..self.level { - indent = indent + " "; - } - self.buffer.push_str(&(indent + s)); - } - - fn push_indent(&mut self) { - self.level += 1; - } - - fn pop_indent(&mut self) { - if self.level > 0 { - self.level -= 1; - } - } - - fn open_paren(&mut self) { - self.append("{"); - } - - fn close_paren(&mut self) { - self.append("}"); - } - - fn newline(&mut self) { - self.append("\n"); - } - - fn header(&mut self, func: &Function) { - self.append(&format!("digraph {} ", func.name)); - self.open_paren(); - self.newline(); - self.push_indent(); - self.append("{rank=min; ebb0}"); - self.pop_indent(); - self.newline(); - } - - fn footer(&mut self) { - self.close_paren(); - self.newline(); - } - - fn ebb_subgraphs(&mut self, func: &Function) { - for ebb in &func.layout { - let inst_data = func.layout - .ebb_insts(ebb) - .filter(|inst| { - match func.dfg[*inst] { - InstructionData::Branch { ty: _, opcode: _, data: _ } => true, - InstructionData::Jump { ty: _, opcode: _, data: _ } => true, - _ => false, + fn ebb_nodes(&self, w: &mut Write) -> Result { + for ebb in &self.func.layout { + try!(write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)); + // Add all outgoing branch instructions to the label. + for inst in self.func.layout.ebb_insts(ebb) { + let idata = &self.func.dfg[inst]; + match idata.analyze_branch() { + BranchInfo::SingleDest(dest, _) => { + try!(write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)) } - }) - .map(|inst| { - let op = match func.dfg[inst] { - InstructionData::Branch { ty: _, opcode, ref data } => { - Some((opcode, data.destination)) - } - InstructionData::Jump { ty: _, opcode, ref data } => { - Some((opcode, data.destination)) - } - _ => None, - }; - (inst, op) - }) - .collect::>(); - - let mut insts = vec![format!("{}", ebb)]; - for (inst, data) in inst_data { - let (op, dest) = data.unwrap(); - insts.push(format!("<{}>{} {}", inst, op, dest)); + BranchInfo::Table(table) => { + try!(write!(w, " | <{}>{} {}", inst, idata.opcode(), table)) + } + BranchInfo::NotABranch => {} + } } - - self.append(&format!("{} [shape=record, label=\"{}{}{}\"]", - ebb, - "{", - insts.join(" | "), - "}")); - self.newline(); + try!(writeln!(w, "}}\"]")) } + Ok(()) } - fn cfg_connections(&mut self, func: &Function, cfg: &ControlFlowGraph) { - for ebb in &func.layout { - for &(parent, inst) in cfg.get_predecessors(ebb) { - self.append(&format!("{}:{} -> {}", parent, inst, ebb)); - self.newline(); + fn cfg_connections(&self, w: &mut Write) -> Result { + for ebb in &self.func.layout { + for &(parent, inst) in self.cfg.get_predecessors(ebb) { + try!(writeln!(w, " {}:{} -> {}", parent, inst, ebb)); } } + Ok(()) + } +} + +impl<'a> Display for CFGPrinter<'a> { + fn fmt(&self, f: &mut Formatter) -> Result { + self.write(f) } } @@ -159,13 +91,11 @@ fn print_cfg(filename: String) -> CommandResult { let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); - let mut cfg_printer = CFGPrinter::new(stdout()); for (idx, func) in items.into_iter().enumerate() { if idx != 0 { println!(""); } - - try!(cfg_printer.print(&func)); + print!("{}", CFGPrinter::new(&func)); } Ok(()) From 78a2e47d9587f69ffbab7e8b70478d48233f4207 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 17:20:39 -0700 Subject: [PATCH 285/968] Implement the 'test print-cfg' sub-test. Move the CFG tests into the filetests directory. Remove the tests directory, there are no more shell-driven tests left. --- {tests => filetests}/cfg/loop.cton | 1 + {tests => filetests}/cfg/traps_early.cton | 1 + {tests => filetests}/cfg/unused_node.cton | 1 + src/tools/filetest/subtest.rs | 2 ++ src/tools/print_cfg.rs | 31 ++++++++++++++++++- test-all.sh | 5 ---- tests/cfg/README.rst | 14 --------- tests/cfg/run.sh | 36 ----------------------- 8 files changed, 35 insertions(+), 56 deletions(-) rename {tests => filetests}/cfg/loop.cton (98%) rename {tests => filetests}/cfg/traps_early.cton (96%) rename {tests => filetests}/cfg/unused_node.cton (97%) delete mode 100644 tests/cfg/README.rst delete mode 100755 tests/cfg/run.sh diff --git a/tests/cfg/loop.cton b/filetests/cfg/loop.cton similarity index 98% rename from tests/cfg/loop.cton rename to filetests/cfg/loop.cton index fd6e4fa6b9..5732ac593a 100644 --- a/tests/cfg/loop.cton +++ b/filetests/cfg/loop.cton @@ -1,4 +1,5 @@ ; For testing cfg generation. This code is nonsense. +test print-cfg function nonsense(i32, i32) -> f32 { ; check: digraph nonsense { diff --git a/tests/cfg/traps_early.cton b/filetests/cfg/traps_early.cton similarity index 96% rename from tests/cfg/traps_early.cton rename to filetests/cfg/traps_early.cton index 71070f4b15..a52cac1ba4 100644 --- a/tests/cfg/traps_early.cton +++ b/filetests/cfg/traps_early.cton @@ -1,5 +1,6 @@ ; For testing cfg generation. This code explores the implications of encountering ; a terminating instruction before any connections have been made. +test print-cfg function nonsense(i32) { ; check: digraph nonsense { diff --git a/tests/cfg/unused_node.cton b/filetests/cfg/unused_node.cton similarity index 97% rename from tests/cfg/unused_node.cton rename to filetests/cfg/unused_node.cton index 693196ccde..d4be2deddd 100644 --- a/tests/cfg/unused_node.cton +++ b/filetests/cfg/unused_node.cton @@ -1,4 +1,5 @@ ; For testing cfg generation where some block is never reached. +test print-cfg function not_reached(i32) -> i32 { ; check: digraph not_reached { diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index 69ca47aa64..f87eda9b98 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -11,8 +11,10 @@ pub type Result = result::Result; /// Create a new subcommand trait object to match `parsed.command`. pub fn new(parsed: &TestCommand) -> Result> { use cat; + use print_cfg; match parsed.command { "cat" => cat::subtest(parsed), + "print-cfg" => print_cfg::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/tools/print_cfg.rs b/src/tools/print_cfg.rs index d4d6ccf79f..858ef4de23 100644 --- a/src/tools/print_cfg.rs +++ b/src/tools/print_cfg.rs @@ -2,14 +2,17 @@ //! //! Read a series of Cretonne IL files and print their control flow graphs //! in graphviz format. + +use std::borrow::Cow; use std::fmt::{Result, Write, Display, Formatter}; use CommandResult; use utils::read_to_string; +use filetest::subtest::{self, SubTest, Context, Result as STResult}; use cretonne::ir::Function; use cretonne::cfg::ControlFlowGraph; use cretonne::ir::instructions::BranchInfo; -use cton_reader::parse_functions; +use cton_reader::{parse_functions, TestCommand}; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { @@ -100,3 +103,29 @@ fn print_cfg(filename: String) -> CommandResult { Ok(()) } + +/// Object implementing the `test print-cfg` sub-test. +struct TestPrintCfg; + +pub fn subtest(parsed: &TestCommand) -> STResult> { + assert_eq!(parsed.command, "print-cfg"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestPrintCfg)) + } +} + +impl SubTest for TestPrintCfg { + fn name(&self) -> Cow { + Cow::from("print-cfg") + } + + fn needs_verifier(&self) -> bool { + false + } + + fn run(&self, func: Cow, context: &Context) -> STResult<()> { + subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context) + } +} diff --git a/test-all.sh b/test-all.sh index 6c8406493a..87aea4573a 100755 --- a/test-all.sh +++ b/test-all.sh @@ -62,9 +62,4 @@ cd "$topdir" banner "File tests" "$CTONUTIL" test filetests -# Run the parser tests. -cd "$topdir/tests" -banner "CFG tests" -cfg/run.sh - banner "OK" diff --git a/tests/cfg/README.rst b/tests/cfg/README.rst deleted file mode 100644 index c1ee4b1fa3..0000000000 --- a/tests/cfg/README.rst +++ /dev/null @@ -1,14 +0,0 @@ -CFG tests -============ - -This directory contains test cases for the Cretonne cfg printer. - -Each test case consists of a `foo.cton` input file annotated with its expected connections. -Annotations are comments of the form: `ebbx:insty -> ebbz` where ebbx is connected to ebbz via -a branch or jump instruction at line y. Instructions are labeled by line number starting from zero: `inst0` .. `instn`. - - -Each input file is run through the `cton-util print-cfg` command and the -output is compared against the specially formatted comments to ensure that -expected connections exist. This scheme allows for changes to graph style -without the need to update tests. diff --git a/tests/cfg/run.sh b/tests/cfg/run.sh deleted file mode 100755 index cd324cc73f..0000000000 --- a/tests/cfg/run.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Go to tests directory. -cd $(dirname "$0")/.. - -# The path to cton-util should be in $CTONUTIL. -if [ -z "$CTONUTIL" ]; then - CTONUTIL=../src/tools/target/debug/cton-util -fi - -if [ ! -x "$CTONUTIL" ]; then - echo "Can't fund executable cton-util: $CTONUTIL" 1>&2 - exit 1 -fi - -declare -a fails - -for testcase in $(find cfg -name '*.cton'); do - if "${CTONUTIL}" print-cfg "$testcase" | "${CTONUTIL}" filecheck "$testcase"; then - echo OK $testcase - else - fails=(${fails[@]} "$testcase") - echo FAIL $testcase - fi -done - -if [ ${#fails[@]} -ne 0 ]; then - echo - echo Failures: - for f in "${fails[@]}"; do - echo " $f" - done - exit 1 -else - echo "All passed" -fi From 8a9b34411aaca59ed94e1b87ce2ec0079203e9b5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 15 Sep 2016 17:28:09 -0700 Subject: [PATCH 286/968] Print the number of tests run. Don't let 'cton-util test' finish without printing anything. --- src/tools/filetest/runner.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 1f7ac7ee88..ceb1e34c62 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -179,6 +179,7 @@ impl TestRunner { pub fn run(&mut self) -> CommandResult { self.scan_dirs(); self.schedule_jobs(); + println!("{} tests", self.tests.len()); match self.errors { 0 => Ok(()), 1 => Err("1 failure".to_string()), From ea748f77180c8c7f3c1fd1ad513d4b69d22e0ae1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 09:36:34 -0700 Subject: [PATCH 287/968] Use Cow for the values of filecheck variables. Often, an implementation of VariableMap can return references to internal strings, and Cow::Borrow() allows that without making any copies. We still want to allow VariableMap implementations to return owned strings in case they have to manufacture variable values on demand. The Cow::Owned() variant does exactly that. Switch the internal VariableMap implementations over to Cows. It turns out they can simply store references to substrings of the input test, completely avoiding string allocation for script-defined variables. --- src/libfilecheck/checker.rs | 11 ++++++----- src/libfilecheck/pattern.rs | 21 ++++++++------------- src/libfilecheck/variable.rs | 8 +++++--- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/libfilecheck/checker.rs b/src/libfilecheck/checker.rs index 6bb458b55e..a2af0815e7 100644 --- a/src/libfilecheck/checker.rs +++ b/src/libfilecheck/checker.rs @@ -2,6 +2,7 @@ use error::{Error, Result}; use variable::{VariableMap, Value, varname_prefix}; use pattern::Pattern; use regex::{Regex, Captures}; +use std::borrow::Cow; use std::collections::HashMap; use std::cmp::max; use std::fmt::{self, Display, Formatter}; @@ -183,7 +184,7 @@ impl Checker { Directive::Regex(ref var, ref rx) => { state.vars.insert(var.clone(), VarDef { - value: Value::Regex(rx.clone()), + value: Value::Regex(Cow::Borrowed(rx)), offset: 0, }); continue; @@ -234,9 +235,9 @@ impl Checker { } /// A local definition of a variable. -pub struct VarDef { +pub struct VarDef<'a> { /// The value given to the variable. - value: Value, + value: Value<'a>, /// Offset in input text from where the variable is available. offset: usize, } @@ -246,7 +247,7 @@ struct State<'a> { env_vars: &'a VariableMap, recorder: &'a mut Recorder, - vars: HashMap, + vars: HashMap>, // Offset after the last ordered match. This does not include recent unordered matches. last_ordered: usize, // Largest offset following a positive match, including unordered matches. @@ -331,7 +332,7 @@ impl<'a> State<'a> { let txtval = caps.name(var).unwrap_or(""); self.recorder.defined_var(var, txtval); let vardef = VarDef { - value: Value::Text(txtval.to_string()), + value: Value::Text(Cow::Borrowed(txtval)), // This offset is the end of the whole matched pattern, not just the text // defining the variable. offset: range.0 + matched_range.1, diff --git a/src/libfilecheck/pattern.rs b/src/libfilecheck/pattern.rs index 69464bd7c8..88c47011f9 100644 --- a/src/libfilecheck/pattern.rs +++ b/src/libfilecheck/pattern.rs @@ -330,19 +330,14 @@ impl Pattern { Part::DefLit { ref regex, .. } => out.push_str(regex), Part::DefVar { def, ref var } => { // Wrap regex in a named capture group. - write!(out, - "(?P<{}>{})", - self.defs[def], - match vmap.lookup(var) { - None => { - return Err(Error::UndefVariable(format!("undefined variable \ - ${}", - var))) - } - Some(Value::Text(s)) => quote(&s), - Some(Value::Regex(rx)) => rx, - }) - .unwrap() + write!(out, "(?P<{}>", self.defs[def]).unwrap(); + match vmap.lookup(var) { + None => { + return Err(Error::UndefVariable(format!("undefined variable ${}", var))) + } + Some(Value::Text(s)) => write!(out, "{})", quote(&s[..])).unwrap(), + Some(Value::Regex(rx)) => write!(out, "{})", rx).unwrap(), + } } } diff --git a/src/libfilecheck/variable.rs b/src/libfilecheck/variable.rs index 7238249146..51bc38fc07 100644 --- a/src/libfilecheck/variable.rs +++ b/src/libfilecheck/variable.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + /// A variable name is one or more ASCII alphanumerical characters, including underscore. /// Note that numerical variable names like `$45` are allowed too. /// @@ -16,9 +18,9 @@ pub fn varname_prefix(s: &str) -> usize { /// A variable can contain either a regular expression or plain text. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Value { - Text(String), - Regex(String), +pub enum Value<'a> { + Text(Cow<'a, str>), + Regex(Cow<'a, str>), } /// Resolve variables by name. From 77264ead08b67fad905f647f2e2e9d4a00995180 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 12:14:23 -0700 Subject: [PATCH 288/968] Add a SourceMap to libreader. Give crate clients the possiblility of mapping source-level entity names to proper entity references that are valid in the parsed function. --- src/libreader/lib.rs | 2 + src/libreader/parser.rs | 12 +++- src/libreader/sourcemap.rs | 133 +++++++++++++++++++++++++++++++++++++ src/libreader/testfile.rs | 2 + 4 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/libreader/sourcemap.rs diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index 94436e12f1..79f9b23521 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -8,8 +8,10 @@ extern crate cretonne; pub use parser::{Result, parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; pub use testfile::{TestFile, Details}; +pub use sourcemap::SourceMap; mod lexer; mod parser; mod testcommand; mod testfile; +mod sourcemap; diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 63a635b3f7..54a53310ee 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -21,6 +21,7 @@ use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArg BranchData, ReturnData}; use testfile::{TestFile, Details, Comment}; use testcommand::TestCommand; +use sourcemap; pub use lexer::Location; @@ -539,11 +540,16 @@ impl<'a> Parser<'a> { self.token(); self.comment_entity = None; - // Rewrite references to values and EBBs after parsing everuthing to allow forward + // Rewrite references to values and EBBs after parsing everything to allow forward // references. try!(ctx.rewrite_references()); - Ok((ctx.function, Details { comments: mem::replace(&mut self.comments, Vec::new()) })) + let details = Details { + comments: mem::replace(&mut self.comments, Vec::new()), + map: sourcemap::new(ctx.values, ctx.ebbs, ctx.stack_slots, ctx.jump_tables), + }; + + Ok((ctx.function, details)) } // Parse a function spec. @@ -1307,7 +1313,7 @@ mod tests { #[test] fn comments() { - let (func, Details { comments }) = + let (func, Details { comments, .. }) = Parser::new("; before function comment() { ; decl ss10 = stack_slot 13 ; stackslot. diff --git a/src/libreader/sourcemap.rs b/src/libreader/sourcemap.rs new file mode 100644 index 0000000000..9a601a3e39 --- /dev/null +++ b/src/libreader/sourcemap.rs @@ -0,0 +1,133 @@ +//! Source map for translating source entity names to parsed entities. +//! +//! When the parser reads in a source file, entities like instructions, EBBs, and values get new +//! entity numbers. The parser maintains a mapping from the entity names in the source to the final +//! entity references. +//! +//! The `SourceMap` struct defined in this module makes the same mapping available to parser +//! clients. + +use std::collections::HashMap; +use cretonne::ir::{StackSlot, JumpTable, Ebb, Value}; +use cretonne::ir::entities::AnyEntity; + +/// Mapping from source entity names to entity references that are valid in the parsed function. +#[derive(Debug)] +pub struct SourceMap { + values: HashMap, // vNN, vxNN + ebbs: HashMap, // ebbNN + stack_slots: HashMap, // ssNN + jump_tables: HashMap, // jtNN +} + +/// Read-only interface which is exposed outside the parser crate. +impl SourceMap { + /// Look up an entity by source name. + /// Returns the entity reference corresponding to `name`, if it exists. + pub fn lookup_str(&self, name: &str) -> Option { + split_entity_name(name).and_then(|(ent, num)| { + match ent { + "v" => { + Value::direct_with_number(num) + .and_then(|v| self.values.get(&v).cloned()) + .map(AnyEntity::Value) + } + "vx" => { + Value::table_with_number(num) + .and_then(|v| self.values.get(&v).cloned()) + .map(AnyEntity::Value) + } + "ebb" => { + Ebb::with_number(num) + .and_then(|e| self.ebbs.get(&e).cloned()) + .map(AnyEntity::Ebb) + } + "ss" => self.stack_slots.get(&num).cloned().map(AnyEntity::StackSlot), + "jt" => self.jump_tables.get(&num).cloned().map(AnyEntity::JumpTable), + _ => None, + } + }) + } +} + +/// Get the number of decimal digits at the end of `s`. +fn trailing_digits(s: &str) -> usize { + // It's faster to iterate backwards over bytes, and we're only counting ASCII digits. + s.as_bytes().iter().rev().cloned().take_while(|&b| b'0' <= b && b <= b'9').count() +} + +/// Pre-parse a supposed entity name by splitting it into two parts: A head of lowercase ASCII +/// letters and numeric tail. +fn split_entity_name(name: &str) -> Option<(&str, u32)> { + let (head, tail) = name.split_at(name.len() - trailing_digits(name)); + if tail.len() > 1 && tail.starts_with('0') { + None + } else { + tail.parse().ok().map(|n| (head, n)) + } +} + +/// Create a new SourceMap from all the individual mappings. +pub fn new(values: HashMap, + ebbs: HashMap, + stack_slots: HashMap, + jump_tables: HashMap) + -> SourceMap { + SourceMap { + values: values, + ebbs: ebbs, + stack_slots: stack_slots, + jump_tables: jump_tables, + } +} + +#[cfg(test)] +mod tests { + use super::{trailing_digits, split_entity_name}; + use parse_test; + + #[test] + fn digits() { + assert_eq!(trailing_digits(""), 0); + assert_eq!(trailing_digits("x"), 0); + assert_eq!(trailing_digits("0x"), 0); + assert_eq!(trailing_digits("x1"), 1); + assert_eq!(trailing_digits("1x1"), 1); + assert_eq!(trailing_digits("1x01"), 2); + } + + #[test] + fn entity_name() { + assert_eq!(split_entity_name(""), None); + assert_eq!(split_entity_name("x"), None); + assert_eq!(split_entity_name("x+"), None); + assert_eq!(split_entity_name("x+1"), Some(("x+", 1))); + assert_eq!(split_entity_name("x-1"), Some(("x-", 1))); + assert_eq!(split_entity_name("1"), Some(("", 1))); + assert_eq!(split_entity_name("x1"), Some(("x", 1))); + assert_eq!(split_entity_name("xy0"), Some(("xy", 0))); + // Reject this non-canonical form. + assert_eq!(split_entity_name("inst01"), None); + } + + #[test] + fn details() { + let tf = parse_test("function detail() { + ss10 = stack_slot 13 + jt10 = jump_table ebb0 + ebb0(v4: i32, vx7: i32): + v10 = iadd v4, vx7 + }") + .unwrap(); + let map = &tf.functions[0].1.map; + + assert_eq!(map.lookup_str("v0"), None); + assert_eq!(map.lookup_str("ss1"), None); + assert_eq!(map.lookup_str("ss10").unwrap().to_string(), "ss0"); + assert_eq!(map.lookup_str("jt10").unwrap().to_string(), "jt0"); + assert_eq!(map.lookup_str("ebb0").unwrap().to_string(), "ebb0"); + assert_eq!(map.lookup_str("v4").unwrap().to_string(), "vx0"); + assert_eq!(map.lookup_str("vx7").unwrap().to_string(), "vx1"); + assert_eq!(map.lookup_str("v10").unwrap().to_string(), "v0"); + } +} diff --git a/src/libreader/testfile.rs b/src/libreader/testfile.rs index 3b4f3d4e16..34cfabce83 100644 --- a/src/libreader/testfile.rs +++ b/src/libreader/testfile.rs @@ -7,6 +7,7 @@ use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; use testcommand::TestCommand; +use sourcemap::SourceMap; /// A parsed test case. /// @@ -24,6 +25,7 @@ pub struct TestFile<'a> { #[derive(Debug)] pub struct Details<'a> { pub comments: Vec>, + pub map: SourceMap, } /// A comment in a parsed function. From 88218440a3d9504b6899d896c1c69ef0b94fffdb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 12:49:49 -0700 Subject: [PATCH 289/968] Make the source map available as filecheck variables. This makes it possible to refer to entities defined in the source file, using the source names prefixed with $. For example, $v20 refers to the value by that name in the sources, even if it was renumbered to 'vx0' in the parsed file. --- filetests/parser/rewrite.cton | 6 +++--- src/tools/filetest/subtest.rs | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton index 969572bdbf..48ad84869c 100644 --- a/filetests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -16,9 +16,9 @@ ebb100(v20: i32): trap } ; sameln: function defs() { -; nextln: ebb0(vx0: i32): -; nextln: v0 = iconst.i32x8 5 -; nextln: v1 = f64const 0x1.0000000000000p2 +; nextln: $ebb100($v20: i32): +; nextln: $v1000 = iconst.i32x8 5 +; nextln: $vx200 = f64const 0x1.0000000000000p2 ; nextln: trap ; nextln: } diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index f87eda9b98..89a8b830e1 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -4,7 +4,7 @@ use std::result; use std::borrow::Cow; use cretonne::ir::Function; use cton_reader::{TestCommand, Details}; -use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; +use filecheck::{self, CheckerBuilder, Checker, Value as FCValue}; pub type Result = result::Result; @@ -51,14 +51,27 @@ pub trait SubTest { fn run(&self, func: Cow, context: &Context) -> Result<()>; } +/// Make the parser's source map available as filecheck variables. +/// +/// This means that the filecheck directives can refer to entities like `jump $ebb3`, where `$ebb3` +/// will expand to the EBB number that was assigned to `ebb3` in the input source. +/// +/// The expanded entity names are wrapped in word boundary regex guards so that 'inst1' doesn't +/// match 'inst10'. +impl<'a> filecheck::VariableMap for Context<'a> { + fn lookup(&self, varname: &str) -> Option { + self.details.map.lookup_str(varname).map(|e| FCValue::Regex(format!(r"\b{}\b", e).into())) + } +} + /// Run filecheck on `text`, using directives extracted from `context`. pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { let checker = try!(build_filechecker(&context.details)); - if try!(checker.check(&text, NO_VARIABLES).map_err(|e| format!("filecheck: {}", e))) { + if try!(checker.check(&text, context).map_err(|e| format!("filecheck: {}", e))) { Ok(()) } else { // Filecheck mismatch. Emit an explanation as output. - let (_, explain) = try!(checker.explain(&text, NO_VARIABLES) + let (_, explain) = try!(checker.explain(&text, context) .map_err(|e| format!("explain: {}", e))); Err(format!("filecheck failed:\n{}{}", checker, explain)) } From 4198b2dde57b723e669d92e5aa27577e7d833fae Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 14:47:47 -0700 Subject: [PATCH 290/968] Implement a domtree sub-test. This test verifies the computed dominator tree against annotations. Move the existing testcases into filetests/ with the new syntax. --- filetests/domtree/basic.cton | 13 +++ .../domtree}/loops.cton | 8 +- .../domtree}/loops2.cton | 14 ++- filetests/domtree/tall-tree.cton | 33 ++++++ filetests/domtree/wide-tree.cton | 41 +++++++ src/tools/filetest/domtree.rs | 103 ++++++++++++++++++ src/tools/filetest/mod.rs | 1 + src/tools/filetest/subtest.rs | 2 + src/tools/tests/dominator_tree.rs | 96 ---------------- .../tests/dominator_tree_testdata/basic.cton | 11 -- .../dominator_tree_testdata/tall-tree.cton | 31 ------ .../dominator_tree_testdata/wide-tree.cton | 39 ------- src/tools/utils.rs | 26 +++++ 13 files changed, 232 insertions(+), 186 deletions(-) create mode 100644 filetests/domtree/basic.cton rename {src/tools/tests/dominator_tree_testdata => filetests/domtree}/loops.cton (63%) rename {src/tools/tests/dominator_tree_testdata => filetests/domtree}/loops2.cton (55%) create mode 100644 filetests/domtree/tall-tree.cton create mode 100644 filetests/domtree/wide-tree.cton create mode 100644 src/tools/filetest/domtree.rs delete mode 100644 src/tools/tests/dominator_tree.rs delete mode 100644 src/tools/tests/dominator_tree_testdata/basic.cton delete mode 100644 src/tools/tests/dominator_tree_testdata/tall-tree.cton delete mode 100644 src/tools/tests/dominator_tree_testdata/wide-tree.cton diff --git a/filetests/domtree/basic.cton b/filetests/domtree/basic.cton new file mode 100644 index 0000000000..0b8536effd --- /dev/null +++ b/filetests/domtree/basic.cton @@ -0,0 +1,13 @@ +test domtree + +function test(i32) { + ebb0(v0: i32): + jump ebb1 ; dominates: ebb1 + ebb1: + brz v0, ebb3 ; dominates: ebb3 + jump ebb2 ; dominates: ebb2 + ebb2: + jump ebb3 + ebb3: + return +} diff --git a/src/tools/tests/dominator_tree_testdata/loops.cton b/filetests/domtree/loops.cton similarity index 63% rename from src/tools/tests/dominator_tree_testdata/loops.cton rename to filetests/domtree/loops.cton index 87d7780d7b..cdc88544cc 100644 --- a/src/tools/tests/dominator_tree_testdata/loops.cton +++ b/filetests/domtree/loops.cton @@ -1,7 +1,9 @@ +test domtree + function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb1 ; dominates(1,3,4,5) - jump ebb2 ; dominates(2) + ebb0(v0: i32): + brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 + jump ebb2 ; dominates: ebb2 ebb1: jump ebb3 ebb2: diff --git a/src/tools/tests/dominator_tree_testdata/loops2.cton b/filetests/domtree/loops2.cton similarity index 55% rename from src/tools/tests/dominator_tree_testdata/loops2.cton rename to filetests/domtree/loops2.cton index 452641be01..d2e3ba4f2c 100644 --- a/src/tools/tests/dominator_tree_testdata/loops2.cton +++ b/filetests/domtree/loops2.cton @@ -1,13 +1,15 @@ +test domtree + function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb1 ; dominates(1,6) - brnz v0, ebb2 ; dominates(2,9) - jump ebb3 ; dominates(3) + ebb0(v0: i32): + brz v0, ebb1 ; dominates: ebb1 ebb6 + brnz v0, ebb2 ; dominates: ebb2 ebb9 + jump ebb3 ; dominates: ebb3 ebb1: jump ebb6 ebb2: - brz v0, ebb4 ; dominates(4,7,8) - jump ebb5 ; dominates(5) + brz v0, ebb4 ; dominates: ebb4 ebb7 ebb8 + jump ebb5 ; dominates: ebb5 ebb3: jump ebb9 ebb4: diff --git a/filetests/domtree/tall-tree.cton b/filetests/domtree/tall-tree.cton new file mode 100644 index 0000000000..3dd6f51da6 --- /dev/null +++ b/filetests/domtree/tall-tree.cton @@ -0,0 +1,33 @@ +test domtree + +function test(i32) { + ebb0(v0: i32): + brz v0, ebb1 ; dominates: ebb1 + brnz v0, ebb2 ; dominates: ebb2 ebb5 + jump ebb3 ; dominates: ebb3 + ebb1: + jump ebb4 ; dominates: ebb4 + ebb2: + jump ebb5 + ebb3: + jump ebb5 + ebb4: + brz v0, ebb6 ; dominates: ebb6 ebb10 + jump ebb7 ; dominates: ebb7 + ebb5: + return + ebb6: + brz v0, ebb8 ; dominates: ebb11 ebb8 + brnz v0, ebb9 ; dominates: ebb9 + jump ebb10 + ebb7: + jump ebb10 + ebb8: + jump ebb11 + ebb9: + jump ebb11 + ebb10: + return + ebb11: + return +} diff --git a/filetests/domtree/wide-tree.cton b/filetests/domtree/wide-tree.cton new file mode 100644 index 0000000000..f4e822cd81 --- /dev/null +++ b/filetests/domtree/wide-tree.cton @@ -0,0 +1,41 @@ +test domtree + +function test(i32) { + ebb0(v0: i32): + brz v0, ebb13 ; dominates: ebb13 + jump ebb1 ; dominates: ebb1 + ebb1: + brz v0, ebb2 ; dominates: ebb2 ebb7 + brnz v0, ebb3 ; dominates: ebb3 + brz v0, ebb4 ; dominates: ebb4 + brnz v0, ebb5 ; dominates: ebb5 + jump ebb6 ; dominates: ebb6 + ebb2: + jump ebb7 + ebb3: + jump ebb7 + ebb4: + jump ebb7 + ebb5: + jump ebb7 + ebb6: + jump ebb7 + ebb7: + brnz v0, ebb8 ; dominates: ebb8 ebb12 + brz v0, ebb9 ; dominates: ebb9 + brnz v0, ebb10 ; dominates: ebb10 + jump ebb11 ; dominates: ebb11 + ebb8: + jump ebb12 + ebb9: + jump ebb12 + ebb10: + brz v0, ebb13 + jump ebb12 + ebb11: + jump ebb13 + ebb12: + return + ebb13: + return +} diff --git a/src/tools/filetest/domtree.rs b/src/tools/filetest/domtree.rs new file mode 100644 index 0000000000..306fc433e9 --- /dev/null +++ b/src/tools/filetest/domtree.rs @@ -0,0 +1,103 @@ + +//! Test command for verifying dominator trees. +//! +//! The `test domtree` test command looks for annotations on instructions like this: +//! +//! jump ebb3 ; dominates: ebb3 +//! +//! This annotation means that the jump instruction is expected to be the immediate dominator of +//! `ebb3`. +//! +//! We verify that the dominator tree annotations are complete and correct. +//! + +use std::collections::HashMap; +use std::borrow::{Borrow, Cow}; +use cretonne::ir::Function; +use cretonne::ir::entities::AnyEntity; +use cretonne::cfg::ControlFlowGraph; +use cretonne::dominator_tree::DominatorTree; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result}; +use utils::match_directive; + +struct TestDomtree; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "domtree"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestDomtree)) + } +} + +impl SubTest for TestDomtree { + fn name(&self) -> Cow { + Cow::from("domtree") + } + + // Extract our own dominator tree from + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let func = func.borrow(); + let cfg = ControlFlowGraph::new(func); + let domtree = DominatorTree::new(&cfg); + + // Build an expected domtree from the source annotations. + let mut expected = HashMap::new(); + for comment in &context.details.comments { + if let Some(tail) = match_directive(comment.text, "dominates:") { + let inst = match comment.entity { + AnyEntity::Inst(inst) => inst, + _ => { + return Err(format!("annotation on non-inst {}: {}", + comment.entity, + comment.text)) + } + }; + for src_ebb in tail.split_whitespace() { + let ebb = match context.details.map.lookup_str(src_ebb) { + Some(AnyEntity::Ebb(ebb)) => ebb, + _ => return Err(format!("expected EBB: {}", src_ebb)), + }; + + // Annotations say that `inst` is the idom of `ebb`. + if expected.insert(ebb, inst).is_some() { + return Err(format!("multiple dominators for {}", src_ebb)); + } + + // Compare to computed domtree. + match domtree.idom(ebb) { + Some((_, got_inst)) if got_inst != inst => { + return Err(format!("mismatching idoms for {}:\n\ + want: {}, got: {}", + src_ebb, + inst, + got_inst)); + } + None => { + return Err(format!("mismatching idoms for {}:\n\ + want: {}, got: unreachable", + src_ebb, + inst)); + } + _ => {} + } + } + } + } + + // Now we know that everything in `expected` is consistent with `domtree`. + // All other EBB's should be either unreachable or the entry block. + for ebb in func.layout.ebbs().skip(1).filter(|ebb| !expected.contains_key(&ebb)) { + if let Some((_, got_inst)) = domtree.idom(ebb) { + return Err(format!("mismatching idoms for renumbered {}:\n\ + want: unrechable, got: {}", + ebb, + got_inst)); + } + } + + Ok(()) + } +} diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index c1019446ca..0a7bdf0bab 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -9,6 +9,7 @@ use filetest::runner::TestRunner; pub mod subtest; mod runner; +mod domtree; /// Main entry point for `cton-util test`. /// diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index 89a8b830e1..0359265d3b 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -12,9 +12,11 @@ pub type Result = result::Result; pub fn new(parsed: &TestCommand) -> Result> { use cat; use print_cfg; + use filetest::domtree; match parsed.command { "cat" => cat::subtest(parsed), "print-cfg" => print_cfg::subtest(parsed), + "domtree" => domtree::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs deleted file mode 100644 index 71c02dc652..0000000000 --- a/src/tools/tests/dominator_tree.rs +++ /dev/null @@ -1,96 +0,0 @@ -extern crate cretonne; -extern crate cton_reader; -extern crate glob; -extern crate regex; - -use std::env; -use glob::glob; -use regex::Regex; -use std::fs::File; -use std::io::Read; -use self::cretonne::ir::Ebb; -use self::cton_reader::parse_functions; -use self::cretonne::ir::function::Function; -use self::cretonne::entity_map::EntityMap; -use self::cretonne::ir::entities::NO_INST; -use self::cretonne::cfg::ControlFlowGraph; -use self::cretonne::dominator_tree::DominatorTree; - -/// Construct a dominator tree from specially formatted comments in -/// cton source. Each line with a jump/branch instruction should -/// have a comment of the format: `dominates(n, ..., N)`, where each `n` -/// is the Ebb number for which this instruction is the immediate dominator. -fn dominator_tree_from_source(func: &Function, function_source: &str) -> DominatorTree { - let ebb_re = Regex::new("^[ \t]*ebb[0-9]+.*:").unwrap(); - let dom_re = Regex::new("dominates\\(([0-9,]+)\\)").unwrap(); - let inst_re = Regex::new("^[ \t]*[a-zA-Z0-9]+[^{}]*").unwrap(); - let func_re = Regex::new("^[ \t]*function.*").unwrap(); - - let ebbs = func.layout.ebbs().collect::>(); - let mut data = EntityMap::with_capacity(ebbs.len()); - - if ebbs.len() < 1 { - return DominatorTree::from_data(data); - } - - let mut ebb_offset = 0; - let mut inst_offset = 0; - - let mut cur_ebb = ebbs[0]; - let mut insts = func.layout.ebb_insts(ebbs[ebb_offset]).collect::>(); - - for line in function_source.lines() { - if ebb_re.is_match(line) { - cur_ebb = ebbs[ebb_offset]; - insts = func.layout.ebb_insts(cur_ebb).collect::>(); - ebb_offset += 1; - inst_offset = 0; - } else if inst_re.is_match(line) && !func_re.is_match(line) { - inst_offset += 1; - } - match dom_re.captures(line) { - Some(caps) => { - for s in caps.at(1).unwrap().split(",") { - let this_ebb = Ebb::with_number(s.parse::().unwrap()).unwrap(); - let inst = if inst_offset == 0 { - NO_INST - } else { - insts[inst_offset - 1].clone() - }; - data[this_ebb] = Some((cur_ebb.clone(), inst)); - } - }, - None => continue, - }; - - } - DominatorTree::from_data(data) -} - -fn test_dominator_tree(function_source: &str) { - - let func = &parse_functions(function_source).unwrap()[0]; - let src_dtree = dominator_tree_from_source(&func, function_source); - - let cfg = ControlFlowGraph::new(&func); - let dtree = DominatorTree::new(&cfg); - - for ebb in func.layout.ebbs() { - assert_eq!(dtree.idom(ebb), src_dtree.idom(ebb)); - } -} - -#[test] -fn test_all() { - let testdir = format!("{}/tests/dominator_tree_testdata/*.cton", - env::current_dir().unwrap().display()); - - for entry in glob(&testdir).unwrap() { - let path = entry.unwrap(); - println!("Testing {:?}", path); - let mut file = File::open(&path).unwrap(); - let mut buffer = String::new(); - file.read_to_string(&mut buffer).unwrap(); - test_dominator_tree(&buffer); - } -} diff --git a/src/tools/tests/dominator_tree_testdata/basic.cton b/src/tools/tests/dominator_tree_testdata/basic.cton deleted file mode 100644 index c274336b79..0000000000 --- a/src/tools/tests/dominator_tree_testdata/basic.cton +++ /dev/null @@ -1,11 +0,0 @@ -function test(i32) { - ebb0(v0: i32): ; dominates(0) - jump ebb1 ; dominates(1) - ebb1: - brz v0, ebb3 ; dominates(3) - jump ebb2 ; dominates(2) - ebb2: - jump ebb3 - ebb3: - return -} diff --git a/src/tools/tests/dominator_tree_testdata/tall-tree.cton b/src/tools/tests/dominator_tree_testdata/tall-tree.cton deleted file mode 100644 index d2ea0f8a9e..0000000000 --- a/src/tools/tests/dominator_tree_testdata/tall-tree.cton +++ /dev/null @@ -1,31 +0,0 @@ -function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb1 ; dominates(1) - brnz v0, ebb2 ; dominates(2,5) - jump ebb3 ; dominates(3) - ebb1: - jump ebb4 ; dominates(4) - ebb2: - jump ebb5 - ebb3: - jump ebb5 - ebb4: - brz v0, ebb6 ; dominates(6,10) - jump ebb7 ; dominates(7) - ebb5: - return - ebb6: - brz v0, ebb8 ; dominates(11,8) - brnz v0, ebb9 ; dominates(9) - jump ebb10 - ebb7: - jump ebb10 - ebb8: - jump ebb11 - ebb9: - jump ebb11 - ebb10: - return - ebb11: - return -} diff --git a/src/tools/tests/dominator_tree_testdata/wide-tree.cton b/src/tools/tests/dominator_tree_testdata/wide-tree.cton deleted file mode 100644 index 0883ef6514..0000000000 --- a/src/tools/tests/dominator_tree_testdata/wide-tree.cton +++ /dev/null @@ -1,39 +0,0 @@ -function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb13 ; dominates(13) - jump ebb1 ; dominates(1) - ebb1: - brz v0, ebb2 ; dominates(2,7) - brnz v0, ebb3 ; dominates(3) - brz v0, ebb4 ; dominates(4) - brnz v0, ebb5 ; dominates(5) - jump ebb6 ; dominates(6) - ebb2: - jump ebb7 - ebb3: - jump ebb7 - ebb4: - jump ebb7 - ebb5: - jump ebb7 - ebb6: - jump ebb7 - ebb7: - brnz v0, ebb8 ; dominates(8,12) - brz v0, ebb9 ; dominates(9) - brnz v0, ebb10 ; dominates(10) - jump ebb11 ; dominates(11) - ebb8: - jump ebb12 - ebb9: - jump ebb12 - ebb10: - brz v0, ebb13 - jump ebb12 - ebb11: - jump ebb13 - ebb12: - return - ebb13: - return -} diff --git a/src/tools/utils.rs b/src/tools/utils.rs index 5973f25814..f89bc46b43 100644 --- a/src/tools/utils.rs +++ b/src/tools/utils.rs @@ -11,3 +11,29 @@ pub fn read_to_string>(path: P) -> Result { try!(file.read_to_string(&mut buffer)); Ok(buffer) } + +/// Look for a directive in a comment string. +/// The directive is of the form "foo:" and should follow the leading `;` in the comment: +/// +/// ; dominates: ebb3 ebb4 +/// +/// Return the comment text following the directive. +pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> { + assert!(directive.ends_with(':'), + "Directive must include trailing colon"); + let text = comment.trim_left_matches(';').trim_left(); + if text.starts_with(directive) { + Some(text[directive.len()..].trim()) + } else { + None + } +} + +#[test] +fn test_match_directive() { + assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar")); + assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar")); + assert_eq!(match_directive("foo:bar", "foo:"), Some("bar")); + assert_eq!(match_directive(";x foo: bar", "foo:"), None); + assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar")); +} From 52aca982a1bcb73cf9c56a551bf990459cc50406 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 15:17:19 -0700 Subject: [PATCH 291/968] Remove dead public functions from DominatorTree. --- src/libcretonne/dominator_tree.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index a3a00b2cc4..c741b5992d 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -3,18 +3,13 @@ use cfg::*; use ir::Ebb; use ir::entities::NO_INST; -use entity_map::{EntityMap, Keys}; +use entity_map::EntityMap; pub struct DominatorTree { data: EntityMap>, } impl DominatorTree { - /// Insert data directly into a dominator tree. - pub fn from_data(data: EntityMap>) -> DominatorTree { - DominatorTree { data: data } - } - /// Build a dominator tree from a control flow graph using Keith D. Cooper's /// "Simple, Fast Dominator Algorithm." pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { @@ -116,11 +111,6 @@ impl DominatorTree { pub fn idom(&self, ebb: Ebb) -> Option { self.data[ebb].clone() } - - /// An iterator across all of the ebbs stored in the tree. - pub fn ebbs(&self) -> Keys { - self.data.keys() - } } #[cfg(test)] @@ -136,7 +126,7 @@ mod test { let func = Function::new(); let cfg = ControlFlowGraph::new(&func); let dtree = DominatorTree::new(&cfg); - assert_eq!(None, dtree.ebbs().next()); + assert_eq!(0, dtree.data.keys().count()); } #[test] From c3afc1f2bec3dad60da8c0fb60cb61457e1e500a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 12 Sep 2016 14:17:54 -0700 Subject: [PATCH 292/968] Add a location to verifier error messages. The verifier reports the 'location' of an error message as a reference to the entity that has a problem. This uses the 'AnyEntity' type to refer to instructions/values/ebbs etc. Also add an err! macro similar to the one used by the parser. --- src/libcretonne/verifier.rs | 65 ++++++++++++++----- src/tools/filetest/runner.rs | 2 +- src/tools/tests/verifier.rs | 2 +- .../tests/verifier_testdata/bad_layout.cton | 2 +- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/libcretonne/verifier.rs b/src/libcretonne/verifier.rs index 91131ee6ef..d58283e3ae 100644 --- a/src/libcretonne/verifier.rs +++ b/src/libcretonne/verifier.rs @@ -54,8 +54,43 @@ use ir::{Function, ValueDef, Ebb, Inst}; use ir::instructions::InstructionFormat; +use ir::entities::AnyEntity; +use std::fmt::{self, Display, Formatter}; +use std::result; -pub fn verify_function(func: &Function) -> Result<(), String> { +/// A verifier error. +#[derive(Debug, PartialEq, Eq)] +pub struct Error { + pub location: AnyEntity, + pub message: String, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}: {}", self.location, self.message) + } +} + +pub type Result = result::Result; + +// Create an `Err` variant of `Result` from a location and `format!` args. +macro_rules! err { + ( $loc:expr, $msg:expr ) => { + Err(Error { + location: $loc.into(), + message: String::from($msg), + }) + }; + + ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => { + Err(Error { + location: $loc.into(), + message: format!( $fmt, $( $arg ),+ ), + }) + }; +} + +pub fn verify_function(func: &Function) -> Result<()> { Verifier::new(func).run() } @@ -68,25 +103,25 @@ impl<'a> Verifier<'a> { Verifier { func: func } } - fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<(), String> { + fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<()> { let is_terminator = self.func.dfg[inst].is_terminating(); let is_last_inst = self.func.layout.last_inst(ebb) == inst; if is_terminator && !is_last_inst { // Terminating instructions only occur at the end of blocks. - return Err(format!("A terminating instruction was encountered before the \ - end of ebb {:?}!", - ebb)); + return err!(inst, + "a terminator instruction was encountered before the end of {}", + ebb); } if is_last_inst && !is_terminator { - return Err(format!("Block {:?} does not end in a terminating instruction!", ebb)); + return err!(ebb, "block does not end in a terminator instruction!"); } // Instructions belong to the correct ebb. let inst_ebb = self.func.layout.inst_ebb(inst); if inst_ebb != Some(ebb) { - return Err(format!("{:?} should belong to {:?} not {:?}", inst, ebb, inst_ebb)); + return err!(inst, "should belong to {} not {:?}", ebb, inst_ebb); } // Arguments belong to the correct ebb. @@ -94,11 +129,11 @@ impl<'a> Verifier<'a> { match self.func.dfg.value_def(arg) { ValueDef::Arg(arg_ebb, _) => { if ebb != arg_ebb { - return Err(format!("{:?} does not belong to {:?}", arg, ebb)); + return err!(arg, "does not belong to {}", ebb); } } _ => { - return Err("Expected an argument, found a result!".to_string()); + return err!(arg, "expected an argument, found a result"); } } } @@ -106,18 +141,18 @@ impl<'a> Verifier<'a> { Ok(()) } - fn instruction_integrity(&self, inst: Inst) -> Result<(), String> { + fn instruction_integrity(&self, inst: Inst) -> Result<()> { let inst_data = &self.func.dfg[inst]; // The instruction format matches the opcode if inst_data.opcode().format() != Some(InstructionFormat::from(inst_data)) { - return Err("Instruction opcode doesn't match instruction format!".to_string()); + return err!(inst, "instruction opcode doesn't match instruction format"); } Ok(()) } - pub fn run(&self) -> Result<(), String> { + pub fn run(&self) -> Result<()> { for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { try!(self.ebb_integrity(ebb, inst)); @@ -143,9 +178,9 @@ mod tests { let err_re = Regex::new($msg).unwrap(); match $e { Ok(_) => { panic!("Expected an error!") }, - Err(err_msg) => { - if !err_re.is_match(&err_msg) { - panic!(format!("'{}' did not contain the pattern '{}'", err_msg, $msg)); + Err(Error { location, message } ) => { + if !err_re.is_match(&message) { + panic!(format!("'{}' did not contain the pattern '{}'", message, $msg)); } } } diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index ceb1e34c62..5f9fac9713 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -259,7 +259,7 @@ impl Job { // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { - try!(verify_function(&func)); + try!(verify_function(&func).map_err(|e| e.to_string())); context.verified = true; } diff --git a/src/tools/tests/verifier.rs b/src/tools/tests/verifier.rs index c26056b8ba..15fcf05ec8 100644 --- a/src/tools/tests/verifier.rs +++ b/src/tools/tests/verifier.rs @@ -41,7 +41,7 @@ fn verifier_tests_from_source(function_source: &str) { let result = Verifier::new(&func).run(); match verifier_results[i] { Some(ref re) => { - assert_eq!(re.is_match(&result.err().unwrap()), true); + assert_eq!(re.is_match(&result.err().unwrap().message), true); }, None => { assert_eq!(result, Ok(())); diff --git a/src/tools/tests/verifier_testdata/bad_layout.cton b/src/tools/tests/verifier_testdata/bad_layout.cton index 680299def7..a99b9b5fe4 100644 --- a/src/tools/tests/verifier_testdata/bad_layout.cton +++ b/src/tools/tests/verifier_testdata/bad_layout.cton @@ -1,4 +1,4 @@ -function test(i32) { ; Err(terminating) +function test(i32) { ; Err(terminator) ebb0(v0: i32): jump ebb1 return From b5b1ee23b5726f11dbab9e15bdbacc4c37cb7cc9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 16:19:47 -0700 Subject: [PATCH 293/968] Add a 'test verifier' sub-test. This test runs the verifier on each function and matches the resulting verifier error against the "error:" annotation. Move the existing verifier test into filetests/verifier/ and use the new syntex. --- filetests/cfg/loop.cton | 1 + filetests/cfg/traps_early.cton | 3 +- .../verifier}/bad_layout.cton | 6 +- src/tools/filetest/mod.rs | 1 + src/tools/filetest/subtest.rs | 2 + src/tools/filetest/verifier.rs | 78 +++++++++++++++++++ src/tools/tests/verifier.rs | 66 ---------------- 7 files changed, 88 insertions(+), 69 deletions(-) rename {src/tools/tests/verifier_testdata => filetests/verifier}/bad_layout.cton (71%) create mode 100644 src/tools/filetest/verifier.rs delete mode 100644 src/tools/tests/verifier.rs diff --git a/filetests/cfg/loop.cton b/filetests/cfg/loop.cton index 5732ac593a..9a1ca6d7e3 100644 --- a/filetests/cfg/loop.cton +++ b/filetests/cfg/loop.cton @@ -1,5 +1,6 @@ ; For testing cfg generation. This code is nonsense. test print-cfg +test verifier function nonsense(i32, i32) -> f32 { ; check: digraph nonsense { diff --git a/filetests/cfg/traps_early.cton b/filetests/cfg/traps_early.cton index a52cac1ba4..9648190bc5 100644 --- a/filetests/cfg/traps_early.cton +++ b/filetests/cfg/traps_early.cton @@ -1,12 +1,13 @@ ; For testing cfg generation. This code explores the implications of encountering ; a terminating instruction before any connections have been made. test print-cfg +test verifier function nonsense(i32) { ; check: digraph nonsense { ebb0(v1: i32): - trap + trap ; error: terminator instruction was encountered before the end brnz v1, ebb2 ; unordered: ebb0:inst1 -> ebb2 jump ebb1 ; unordered: ebb0:inst2 -> ebb1 diff --git a/src/tools/tests/verifier_testdata/bad_layout.cton b/filetests/verifier/bad_layout.cton similarity index 71% rename from src/tools/tests/verifier_testdata/bad_layout.cton rename to filetests/verifier/bad_layout.cton index a99b9b5fe4..ac29452958 100644 --- a/src/tools/tests/verifier_testdata/bad_layout.cton +++ b/filetests/verifier/bad_layout.cton @@ -1,6 +1,8 @@ -function test(i32) { ; Err(terminator) +test verifier + +function test(i32) { ebb0(v0: i32): - jump ebb1 + jump ebb1 ; error: terminator return ebb1: jump ebb2 diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index 0a7bdf0bab..b368959125 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -10,6 +10,7 @@ use filetest::runner::TestRunner; pub mod subtest; mod runner; mod domtree; +mod verifier; /// Main entry point for `cton-util test`. /// diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index 0359265d3b..cfc5cec5bf 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -13,10 +13,12 @@ pub fn new(parsed: &TestCommand) -> Result> { use cat; use print_cfg; use filetest::domtree; + use filetest::verifier; match parsed.command { "cat" => cat::subtest(parsed), "print-cfg" => print_cfg::subtest(parsed), "domtree" => domtree::subtest(parsed), + "verifier" => verifier::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/tools/filetest/verifier.rs b/src/tools/filetest/verifier.rs new file mode 100644 index 0000000000..5812dc9db0 --- /dev/null +++ b/src/tools/filetest/verifier.rs @@ -0,0 +1,78 @@ +//! Test command for checking the IL verifier. +//! +//! The `test verifier` test command looks for annotations on instructions like this: +//! +//! jump ebb3 ; error: jump to non-existent EBB +//! +//! This annotation means that the verifier is expected to given an error for the jump instruction +//! containing the substring "jump to non-existent EBB". + +use std::borrow::{Borrow, Cow}; +use cretonne::verify_function; +use cretonne::ir::Function; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result}; +use utils::match_directive; + +struct TestVerifier; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "verifier"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestVerifier)) + } +} + +impl SubTest for TestVerifier { + fn name(&self) -> Cow { + Cow::from("verifier") + } + + fn needs_verifier(&self) -> bool { + // Running the verifier before this test would defeat its purpose. + false + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let func = func.borrow(); + + // Scan source annotations for "error:" directives. + let mut expected = None; + for comment in &context.details.comments { + if let Some(tail) = match_directive(comment.text, "error:") { + // Currently, the verifier can only report one problem at a time. + // Reject more than one `error:` directives. + if expected.is_some() { + return Err("cannot handle multiple error: directives".to_string()); + } + expected = Some((comment.entity, tail)); + } + } + + match verify_function(func) { + Ok(_) => { + match expected { + None => Ok(()), + Some((_, msg)) => Err(format!("passed, expected error: {}", msg)), + } + } + Err(got) => { + match expected { + None => Err(format!("verifier pass, got {}", got)), + Some((want_loc, want_msg)) if got.message.contains(want_msg) => { + if want_loc == got.location { + Ok(()) + } else { + Err(format!("correct error reported on {}, but wanted {}", + got.location, + want_loc)) + } + } + Some(_) => Err(format!("mismatching error: {}", got)), + } + } + } + } +} diff --git a/src/tools/tests/verifier.rs b/src/tools/tests/verifier.rs deleted file mode 100644 index 15fcf05ec8..0000000000 --- a/src/tools/tests/verifier.rs +++ /dev/null @@ -1,66 +0,0 @@ -extern crate cretonne; -extern crate cton_reader; -extern crate glob; -extern crate regex; - -use std::env; -use glob::glob; -use regex::Regex; -use std::fs::File; -use std::io::Read; -use self::cton_reader::parse_functions; -use self::cretonne::verifier::Verifier; - -/// Compile a function and run verifier tests based on specially formatted -/// comments in the [function's] source. -fn verifier_tests_from_source(function_source: &str) { - let func_re = Regex::new("^[ \t]*function.*").unwrap(); - let err_re = Regex::new(";[ \t]*Err\\((.*)+\\)").unwrap(); - - // Each entry corresponds to an optional regular expression, where - // the index corresponds to the function offset in our source code. - // If no error is expected for a given function its entry will be - // set to None. - let mut verifier_results = Vec::new(); - for line in function_source.lines() { - if func_re.is_match(line) { - match err_re.captures(line) { - Some(caps) => { - verifier_results.push(Some(Regex::new(caps.at(1).unwrap()).unwrap())); - }, - None => { - verifier_results.push(None); - }, - }; - } - } - - // Run the verifier against each function and compare the output - // with the expected result (as determined above). - for (i, func) in parse_functions(function_source).unwrap().into_iter().enumerate() { - let result = Verifier::new(&func).run(); - match verifier_results[i] { - Some(ref re) => { - assert_eq!(re.is_match(&result.err().unwrap().message), true); - }, - None => { - assert_eq!(result, Ok(())); - } - } - } -} - -#[test] -fn test_all() { - let testdir = format!("{}/tests/verifier_testdata/*.cton", - env::current_dir().unwrap().display()); - - for entry in glob(&testdir).unwrap() { - let path = entry.unwrap(); - println!("Testing {:?}", path); - let mut file = File::open(&path).unwrap(); - let mut buffer = String::new(); - file.read_to_string(&mut buffer).unwrap(); - verifier_tests_from_source(&buffer); - } -} From d2e5059ab90e1e83f5214bc0ec09ead6a257ffb8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 16:34:50 -0700 Subject: [PATCH 294/968] Remove unnecessary external dependencies. The main libcretonne crate should not have any external dependencies if at all possible. Use simple substring matching instead of regular expressions in the verifier tests to achieve this. The tools crate no longer depends directly on glob and regex. It still has an indirect dependency on regex through libfilecheck. --- src/libcretonne/Cargo.toml | 5 ++++- src/libcretonne/verifier.rs | 10 +++------- src/tools/Cargo.lock | 10 ---------- src/tools/Cargo.toml | 2 -- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/libcretonne/Cargo.toml b/src/libcretonne/Cargo.toml index 5d3a9511e6..174fb2e0bf 100644 --- a/src/libcretonne/Cargo.toml +++ b/src/libcretonne/Cargo.toml @@ -13,4 +13,7 @@ name = "cretonne" path = "lib.rs" [dependencies] -regex = "0.1.71" +# It is a goal of the cretonne crate to have minimal external dependencies. +# Please don't add any unless they are essential to the task of creating binary +# machine code. Integration tests that need external dependencies can be +# accomodated in src/tools/tests. diff --git a/src/libcretonne/verifier.rs b/src/libcretonne/verifier.rs index d58283e3ae..2b544967e2 100644 --- a/src/libcretonne/verifier.rs +++ b/src/libcretonne/verifier.rs @@ -165,22 +165,18 @@ impl<'a> Verifier<'a> { #[cfg(test)] mod tests { - extern crate regex; - use super::*; use ir::Function; use ir::instructions::{InstructionData, Opcode}; use ir::types; - use self::regex::Regex; macro_rules! assert_err_with_msg { ($e:expr, $msg:expr) => ( - let err_re = Regex::new($msg).unwrap(); match $e { Ok(_) => { panic!("Expected an error!") }, - Err(Error { location, message } ) => { - if !err_re.is_match(&message) { - panic!(format!("'{}' did not contain the pattern '{}'", message, $msg)); + Err(Error { message, .. } ) => { + if !message.contains($msg) { + panic!(format!("'{}' did not contain the substring '{}'", message, $msg)); } } } diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index 2aca3c591b..50576fd02b 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -6,8 +6,6 @@ dependencies = [ "cretonne-reader 0.0.0", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "filecheck 0.0.0", - "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -22,9 +20,6 @@ dependencies = [ [[package]] name = "cretonne" version = "0.0.0" -dependencies = [ - "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "cretonne-reader" @@ -50,11 +45,6 @@ dependencies = [ "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "glob" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "kernel32-sys" version = "0.2.2" diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index ba465f7d0e..b4572f9f49 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -15,5 +15,3 @@ cretonne-reader = { path = "../libreader" } filecheck = { path = "../libfilecheck" } docopt = "0.6.80" rustc-serialize = "0.3.19" -regex = "0.1.71" -glob = "0.2.11" From 7f1f4175ac9a1f515ed5336704f1a8dc378b224b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 16:41:04 -0700 Subject: [PATCH 295/968] cargo update. --- src/tools/Cargo.lock | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index 50576fd02b..ca97206cc4 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -4,14 +4,14 @@ version = "0.0.0" dependencies = [ "cretonne 0.0.0", "cretonne-reader 0.0.0", - "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", + "docopt 0.6.83 (registry+https://github.com/rust-lang/crates.io-index)", "filecheck 0.0.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "aho-corasick" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -30,19 +30,20 @@ dependencies = [ [[package]] name = "docopt" -version = "0.6.80" +version = "0.6.83" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "filecheck" version = "0.0.0" dependencies = [ - "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -50,13 +51,18 @@ name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lazy_static" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -64,24 +70,24 @@ name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "0.1.71" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -91,7 +97,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.3.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -100,7 +106,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -118,7 +124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] From 30009356a65350a3f71cd1e26727b9ed113b422f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 17:00:55 -0700 Subject: [PATCH 296/968] Move subtest::new() into the parent module. This saves on importing of all the sub-modules that implement new test commands, and it provides a nice overview of what 'cton-util test' can do. --- src/tools/filetest/mod.rs | 17 +++++++++++++++++ src/tools/filetest/runner.rs | 3 ++- src/tools/filetest/subtest.rs | 17 +---------------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index b368959125..9126efbfc0 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -4,7 +4,10 @@ //! available test commands. use std::path::Path; +use cton_reader::TestCommand; use CommandResult; +use cat; +use print_cfg; use filetest::runner::TestRunner; pub mod subtest; @@ -34,3 +37,17 @@ pub fn run(files: Vec) -> CommandResult { runner.run() } + +/// Create a new subcommand trait object to match `parsed.command`. +/// +/// This function knows how to create all of the possible `test ` commands that can appear in +/// a .cton test file. +fn new_subtest(parsed: &TestCommand) -> subtest::Result> { + match parsed.command { + "cat" => cat::subtest(parsed), + "print-cfg" => print_cfg::subtest(parsed), + "domtree" => domtree::subtest(parsed), + "verifier" => verifier::subtest(parsed), + _ => Err(format!("unknown test command '{}'", parsed.command)), + } +} diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 5f9fac9713..8c9ea87810 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -15,6 +15,7 @@ use utils::read_to_string; use cton_reader::parse_test; use cretonne::ir::Function; use cretonne::verify_function; +use filetest::new_subtest; use filetest::subtest::{self, SubTest, Context}; type TestResult = Result; @@ -218,7 +219,7 @@ impl Job { } // Parse the test commands. let mut tests = - try!(testfile.commands.iter().map(subtest::new).collect::>>()); + try!(testfile.commands.iter().map(new_subtest).collect::>>()); // Sort the tests so the mutators are at the end, and those that // don't need the verifier are at the front diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index cfc5cec5bf..b958193cd8 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -3,26 +3,11 @@ use std::result; use std::borrow::Cow; use cretonne::ir::Function; -use cton_reader::{TestCommand, Details}; +use cton_reader::Details; use filecheck::{self, CheckerBuilder, Checker, Value as FCValue}; pub type Result = result::Result; -/// Create a new subcommand trait object to match `parsed.command`. -pub fn new(parsed: &TestCommand) -> Result> { - use cat; - use print_cfg; - use filetest::domtree; - use filetest::verifier; - match parsed.command { - "cat" => cat::subtest(parsed), - "print-cfg" => print_cfg::subtest(parsed), - "domtree" => domtree::subtest(parsed), - "verifier" => verifier::subtest(parsed), - _ => Err(format!("unknown test command '{}'", parsed.command)), - } -} - /// Context for running a a test on a single function. pub struct Context<'a> { /// Additional details about the function from the parser. From c04b2fa7938036d09695ef2ef14ebe0a33578d0c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 21:06:50 -0700 Subject: [PATCH 297/968] Move the code to run test into its own module. Loading a file and running the test in it can be separated from the mechanics of running multiple tests and scanning directories for test files. --- src/tools/filetest/mod.rs | 5 +++ src/tools/filetest/runner.rs | 74 ++---------------------------------- src/tools/filetest/runone.rs | 66 ++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 70 deletions(-) create mode 100644 src/tools/filetest/runone.rs diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index 9126efbfc0..dec7ff6016 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -4,6 +4,7 @@ //! available test commands. use std::path::Path; +use std::time; use cton_reader::TestCommand; use CommandResult; use cat; @@ -12,9 +13,13 @@ use filetest::runner::TestRunner; pub mod subtest; mod runner; +mod runone; mod domtree; mod verifier; +/// The result of running the test in a file. +pub type TestResult = Result; + /// Main entry point for `cton-util test`. /// /// Take a list of filenames which can be either `.cton` files or directories. diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 8c9ea87810..7de2829296 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -3,22 +3,13 @@ //! This module implements the `TestRunner` struct which manages executing tests as well as //! scanning directories for tests. -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; use std::error::Error; +use std::ffi::OsStr; use std::mem; -use std::borrow::{Borrow, Cow}; use std::panic::catch_unwind; -use std::time; +use std::path::{Path, PathBuf}; +use filetest::{TestResult, runone}; use CommandResult; -use utils::read_to_string; -use cton_reader::parse_test; -use cretonne::ir::Function; -use cretonne::verify_function; -use filetest::new_subtest; -use filetest::subtest::{self, SubTest, Context}; - -type TestResult = Result; #[derive(PartialEq, Eq, Debug)] enum QueueEntry { @@ -204,66 +195,9 @@ impl Job { } pub fn run(&self) -> TestResult { - match catch_unwind(|| self.run_or_panic()) { + match catch_unwind(|| runone::run(self.path.as_path())) { Err(msg) => Err(format!("panic: {:?}", msg)), Ok(result) => result, } } - - fn run_or_panic(&self) -> TestResult { - let started = time::Instant::now(); - let buffer = try!(read_to_string(&self.path).map_err(|e| e.to_string())); - let testfile = try!(parse_test(&buffer).map_err(|e| e.to_string())); - if testfile.functions.is_empty() { - return Err("no functions found".to_string()); - } - // Parse the test commands. - let mut tests = - try!(testfile.commands.iter().map(new_subtest).collect::>>()); - - // Sort the tests so the mutators are at the end, and those that - // don't need the verifier are at the front - tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); - - // Isolate the last test in the hope that this is the only mutating test. - // If so, we can completely avoid cloning functions. - let last_test = match tests.pop() { - None => return Err("no test commands found".to_string()), - Some(t) => t, - }; - - for (func, details) in testfile.functions { - let mut context = subtest::Context { - details: details, - verified: false, - }; - - for test in &tests { - try!(self.run_one_test(test.borrow(), Cow::Borrowed(&func), &mut context)); - } - // Run the last test with an owned function which means it won't need to clone it - // before mutating. - try!(self.run_one_test(last_test.borrow(), Cow::Owned(func), &mut context)); - } - - - // TODO: Actually run the tests. - Ok(started.elapsed()) - } - - fn run_one_test(&self, - test: &SubTest, - func: Cow, - context: &mut Context) - -> subtest::Result<()> { - let name = format!("{}({})", test.name(), func.name); - - // Should we run the verifier before this test? - if !context.verified && test.needs_verifier() { - try!(verify_function(&func).map_err(|e| e.to_string())); - context.verified = true; - } - - test.run(func, context).map_err(|e| format!("{}: {}", name, e)) - } } diff --git a/src/tools/filetest/runone.rs b/src/tools/filetest/runone.rs new file mode 100644 index 0000000000..e0d1e9d17b --- /dev/null +++ b/src/tools/filetest/runone.rs @@ -0,0 +1,66 @@ +//! Run the tests in a single test file. + +use std::borrow::{Borrow, Cow}; +use std::path::Path; +use std::time; +use cretonne::ir::Function; +use cretonne::verify_function; +use cton_reader::parse_test; +use utils::read_to_string; +use filetest::{TestResult, new_subtest}; +use filetest::subtest::{SubTest, Context, Result}; + +/// Load `path` and run the test in it. +/// +/// If running this test causes a panic, it will propagate as normal. +pub fn run(path: &Path) -> TestResult { + let started = time::Instant::now(); + let buffer = try!(read_to_string(path).map_err(|e| e.to_string())); + let testfile = try!(parse_test(&buffer).map_err(|e| e.to_string())); + if testfile.functions.is_empty() { + return Err("no functions found".to_string()); + } + // Parse the test commands. + let mut tests = try!(testfile.commands.iter().map(new_subtest).collect::>>()); + + // Sort the tests so the mutators are at the end, and those that don't need the verifier are at + // the front. + tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); + + // Isolate the last test in the hope that this is the only mutating test. + // If so, we can completely avoid cloning functions. + let last_test = match tests.pop() { + None => return Err("no test commands found".to_string()), + Some(t) => t, + }; + + for (func, details) in testfile.functions { + let mut context = Context { + details: details, + verified: false, + }; + + for test in &tests { + try!(run_one_test(test.borrow(), Cow::Borrowed(&func), &mut context)); + } + // Run the last test with an owned function which means it won't need to clone it before + // mutating. + try!(run_one_test(last_test.borrow(), Cow::Owned(func), &mut context)); + } + + + // TODO: Actually run the tests. + Ok(started.elapsed()) +} + +fn run_one_test(test: &SubTest, func: Cow, context: &mut Context) -> Result<()> { + let name = format!("{}({})", test.name(), func.name); + + // Should we run the verifier before this test? + if !context.verified && test.needs_verifier() { + try!(verify_function(&func).map_err(|e| e.to_string())); + context.verified = true; + } + + test.run(func, context).map_err(|e| format!("{}: {}", name, e)) +} From 356e05d2255e72d5bf30519e3fbc66d4b53aa7ef Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Sep 2016 21:48:20 -0700 Subject: [PATCH 298/968] Simplify job queue. Always keep PathBufs for every entry in the test list. When concurrent testing is enabled, we'll want to clone the path for the worker threads. Remove the Job struct for the same reason. --- src/tools/filetest/runner.rs | 87 +++++++++++++++--------------------- 1 file changed, 36 insertions(+), 51 deletions(-) diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 7de2829296..44501489dd 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -5,17 +5,26 @@ use std::error::Error; use std::ffi::OsStr; -use std::mem; -use std::panic::catch_unwind; use std::path::{Path, PathBuf}; use filetest::{TestResult, runone}; use CommandResult; +struct QueueEntry { + path: PathBuf, + state: State, +} + #[derive(PartialEq, Eq, Debug)] -enum QueueEntry { - New(PathBuf), +enum State { + New, Running, - Done(PathBuf, TestResult), + Done(TestResult), +} + +impl QueueEntry { + pub fn path(&self) -> &Path { + self.path.as_path() + } } pub struct TestRunner { @@ -58,37 +67,35 @@ impl TestRunner { /// /// Any problems reading `file` as a test case file will be reported as a test failure. pub fn push_test>(&mut self, file: P) { - self.tests.push(QueueEntry::New(file.into())); + self.tests.push(QueueEntry { + path: file.into(), + state: State::New, + }); } /// Take a new test for running as a job. /// Leaves the queue entry marked as `Runnning`. - fn take_job(&mut self) -> Option { - let index = self.new_tests; - if index == self.tests.len() { + fn take_job(&mut self) -> Option { + let jobid = self.new_tests; + if jobid == self.tests.len() { return None; } self.new_tests += 1; - - let entry = mem::replace(&mut self.tests[index], QueueEntry::Running); - if let QueueEntry::New(path) = entry { - Some(Job::new(index, path)) - } else { - // Oh, sorry about that. Put the entry back. - self.tests[index] = entry; - None - } + assert_eq!(self.tests[jobid].state, State::New); + self.tests[jobid].state = State::Running; + Some(jobid) } /// Report the end of a job. - fn finish_job(&mut self, job: Job, result: TestResult) { - assert_eq!(self.tests[job.index], QueueEntry::Running); + fn finish_job(&mut self, jobid: usize, result: TestResult) { + assert_eq!(self.tests[jobid].state, State::Running); if let Err(ref e) = result { - self.job_error(&job.path, e); + self.job_error(jobid, e); } - self.tests[job.index] = QueueEntry::Done(job.path, result); - if job.index == self.finished_tests { - while let Some(&QueueEntry::Done(_, _)) = self.tests.get(self.finished_tests) { + self.tests[jobid].state = State::Done(result); + if jobid == self.finished_tests { + while let Some(&QueueEntry { state: State::Done(_), .. }) = self.tests + .get(self.finished_tests) { self.finished_tests += 1; } } @@ -154,16 +161,16 @@ impl TestRunner { } /// Report an error related to a job. - fn job_error(&mut self, path: &Path, err: &str) { + fn job_error(&mut self, jobid: usize, err: &str) { self.errors += 1; - println!("FAIL {}: {}", path.to_string_lossy(), err); + println!("FAIL {}: {}", self.tests[jobid].path.to_string_lossy(), err); } /// Schedule and new jobs to run. fn schedule_jobs(&mut self) { - while let Some(job) = self.take_job() { - let result = job.run(); - self.finish_job(job, result); + while let Some(jobid) = self.take_job() { + let result = runone::run(self.tests[jobid].path()); + self.finish_job(jobid, result); } } @@ -179,25 +186,3 @@ impl TestRunner { } } } - -/// A test file waiting to be run. -struct Job { - index: usize, - path: PathBuf, -} - -impl Job { - pub fn new(index: usize, path: PathBuf) -> Job { - Job { - index: index, - path: path, - } - } - - pub fn run(&self) -> TestResult { - match catch_unwind(|| runone::run(self.path.as_path())) { - Err(msg) => Err(format!("panic: {:?}", msg)), - Ok(result) => result, - } - } -} From 1c1ae524aaf55aa6a8ab37669a99b047453c824d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 17 Sep 2016 09:51:06 -0700 Subject: [PATCH 299/968] Run tests concurrently. Spin up one worker thread per cpu, and run filetests on all of them. Use a reorder buffer in Runner to make sure results are still reported in order. Individual test files given as command line arguments are still run synchronously for easier debugging. Only directories are run on worker threads. The recursive directory traversal is still happening on the main thread. Use a heartbeat thread to send ticks on the reply channel every second, and use the ticks to detect tests that are stuck. When Receiver::recv_timeout() is stabilized, we can probably get rid of the heartbeat thread. Catch panics on the worker threads and report them as test failures. --- src/tools/Cargo.lock | 9 ++ src/tools/Cargo.toml | 1 + src/tools/filetest/concurrent.rs | 153 +++++++++++++++++++++++++++++++ src/tools/filetest/mod.rs | 2 + src/tools/filetest/runner.rs | 131 ++++++++++++++++++++------ src/tools/main.rs | 1 + 6 files changed, 267 insertions(+), 30 deletions(-) create mode 100644 src/tools/filetest/concurrent.rs diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock index ca97206cc4..28d0f79f5b 100644 --- a/src/tools/Cargo.lock +++ b/src/tools/Cargo.lock @@ -6,6 +6,7 @@ dependencies = [ "cretonne-reader 0.0.0", "docopt 0.6.83 (registry+https://github.com/rust-lang/crates.io-index)", "filecheck 0.0.0", + "num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -73,6 +74,14 @@ dependencies = [ "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num_cpus" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex" version = "0.1.77" diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index b4572f9f49..3a1a8979a7 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -15,3 +15,4 @@ cretonne-reader = { path = "../libreader" } filecheck = { path = "../libfilecheck" } docopt = "0.6.80" rustc-serialize = "0.3.19" +num_cpus = "1.1.0" diff --git a/src/tools/filetest/concurrent.rs b/src/tools/filetest/concurrent.rs new file mode 100644 index 0000000000..3123124086 --- /dev/null +++ b/src/tools/filetest/concurrent.rs @@ -0,0 +1,153 @@ +//! Run tests concurrently. +//! +//! This module provides the `ConcurrentRunner` struct which uses a pool of threads to run tests +//! concurrently. + +use std::panic::catch_unwind; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::{channel, Sender, Receiver}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; +use num_cpus; +use filetest::{TestResult, runone}; + +// Request sent to worker threads contains jobid and path. +struct Request(usize, PathBuf); + +/// Reply from worker thread, +pub enum Reply { + Starting { jobid: usize, thread_num: usize }, + Done { jobid: usize, result: TestResult }, + Tick, +} + +/// Manage threads that run test jobs concurrently. +pub struct ConcurrentRunner { + // Channel for sending requests to the worker threads. + // The workers are sharing the receiver with an `Arc>`. + // This is `None` when shutting down. + request_tx: Option>, + + // Channel for receiving replies from the workers. + // Workers have their own `Sender`. + reply_rx: Receiver, + + handles: Vec>, +} + +impl ConcurrentRunner { + /// Create a new `ConcurrentRunner` with threads spun up. + pub fn new() -> ConcurrentRunner { + let (request_tx, request_rx) = channel(); + let request_mutex = Arc::new(Mutex::new(request_rx)); + let (reply_tx, reply_rx) = channel(); + + heartbeat_thread(reply_tx.clone()); + + let handles = (0..num_cpus::get()) + .map(|num| worker_thread(num, request_mutex.clone(), reply_tx.clone())) + .collect(); + + ConcurrentRunner { + request_tx: Some(request_tx), + reply_rx: reply_rx, + handles: handles, + } + } + + /// Shut down worker threads orderly. They will finish any queued jobs first. + pub fn shutdown(&mut self) { + self.request_tx = None; + } + + /// Join all the worker threads. + pub fn join(&mut self) { + assert!(self.request_tx.is_none(), "must shutdown before join"); + for h in self.handles.drain(..) { + if let Err(e) = h.join() { + println!("worker panicked: {:?}", e); + } + } + } + + /// Add a new job to the queues. + pub fn put(&mut self, jobid: usize, path: &Path) { + self.request_tx + .as_ref() + .expect("cannot push after shutdown") + .send(Request(jobid, path.to_owned())) + .expect("all the worker threads are gone"); + } + + /// Get a job reply without blocking. + pub fn try_get(&mut self) -> Option { + self.reply_rx.try_recv().ok() + } + + /// Get a job reply, blocking until one is available. + pub fn get(&mut self) -> Option { + self.reply_rx.recv().ok() + } +} + +/// Spawn a heartbeat thread which sends ticks down the reply channel every second. +/// This lets us implement timeouts without the not yet stable `recv_timeout`. +fn heartbeat_thread(replies: Sender) -> thread::JoinHandle<()> { + thread::Builder::new() + .name("heartbeat".to_string()) + .spawn(move || { + while replies.send(Reply::Tick).is_ok() { + thread::sleep(Duration::from_secs(1)); + } + }) + .unwrap() +} + +/// Spawn a worker thread running tests. +fn worker_thread(thread_num: usize, + requests: Arc>>, + replies: Sender) + -> thread::JoinHandle<()> { + thread::Builder::new() + .name(format!("worker #{}", thread_num)) + .spawn(move || { + loop { + // Lock the mutex only long enough to extract a request. + let Request(jobid, path) = match requests.lock().unwrap().recv() { + Err(..) => break, // TX end shuit down. exit thread. + Ok(req) => req, + }; + + // Tell them we're starting this job. + // The receiver should always be present for this as long as we have jobs. + replies.send(Reply::Starting { + jobid: jobid, + thread_num: thread_num, + }) + .unwrap(); + + let result = match catch_unwind(|| runone::run(path.as_path())) { + Ok(r) => r, + Err(e) => { + // The test panicked, leaving us a `Box`. + // Panics are usually strings. + if let Some(msg) = e.downcast_ref::() { + Err(format!("panicked in worker #{}: {}", thread_num, msg)) + } else if let Some(msg) = e.downcast_ref::<&'static str>() { + Err(format!("panicked in worker #{}: {}", thread_num, msg)) + } else { + Err(format!("panicked in worker #{}", thread_num)) + } + } + }; + + replies.send(Reply::Done { + jobid: jobid, + result: result, + }) + .unwrap(); + } + }) + .unwrap() +} diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index dec7ff6016..60b6180c05 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -14,6 +14,7 @@ use filetest::runner::TestRunner; pub mod subtest; mod runner; mod runone; +mod concurrent; mod domtree; mod verifier; @@ -40,6 +41,7 @@ pub fn run(files: Vec) -> CommandResult { } } + runner.start_threads(); runner.run() } diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 44501489dd..c4cfca1ec3 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -7,8 +7,15 @@ use std::error::Error; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use filetest::{TestResult, runone}; +use filetest::concurrent::{ConcurrentRunner, Reply}; use CommandResult; +// Timeout in seconds when we're not making progress. +const TIMEOUT_PANIC: usize = 10; + +// Timeout for reporting slow tests without panicking. +const TIMEOUT_SLOW: usize = 3; + struct QueueEntry { path: PathBuf, state: State, @@ -17,6 +24,7 @@ struct QueueEntry { #[derive(PartialEq, Eq, Debug)] enum State { New, + Queued, Running, Done(TestResult), } @@ -40,7 +48,13 @@ pub struct TestRunner { // Number of contiguous finished tests at the front of `tests`. finished_tests: usize, + // Number of errors seen so far. errors: usize, + + // Number of ticks received since we saw any progress. + ticks_since_progress: usize, + + threads: Option, } impl TestRunner { @@ -52,6 +66,8 @@ impl TestRunner { new_tests: 0, finished_tests: 0, errors: 0, + ticks_since_progress: 0, + threads: None, } } @@ -73,32 +89,10 @@ impl TestRunner { }); } - /// Take a new test for running as a job. - /// Leaves the queue entry marked as `Runnning`. - fn take_job(&mut self) -> Option { - let jobid = self.new_tests; - if jobid == self.tests.len() { - return None; - } - self.new_tests += 1; - assert_eq!(self.tests[jobid].state, State::New); - self.tests[jobid].state = State::Running; - Some(jobid) - } - - /// Report the end of a job. - fn finish_job(&mut self, jobid: usize, result: TestResult) { - assert_eq!(self.tests[jobid].state, State::Running); - if let Err(ref e) = result { - self.job_error(jobid, e); - } - self.tests[jobid].state = State::Done(result); - if jobid == self.finished_tests { - while let Some(&QueueEntry { state: State::Done(_), .. }) = self.tests - .get(self.finished_tests) { - self.finished_tests += 1; - } - } + /// Begin running tests concurrently. + pub fn start_threads(&mut self) { + assert!(self.threads.is_none()); + self.threads = Some(ConcurrentRunner::new()); } /// Scan any directories pushed so far. @@ -166,11 +160,87 @@ impl TestRunner { println!("FAIL {}: {}", self.tests[jobid].path.to_string_lossy(), err); } - /// Schedule and new jobs to run. + /// Schedule any new jobs to run. fn schedule_jobs(&mut self) { - while let Some(jobid) = self.take_job() { - let result = runone::run(self.tests[jobid].path()); - self.finish_job(jobid, result); + for jobid in self.new_tests..self.tests.len() { + assert_eq!(self.tests[jobid].state, State::New); + if let Some(ref mut conc) = self.threads { + // Queue test for concurrent execution. + self.tests[jobid].state = State::Queued; + conc.put(jobid, self.tests[jobid].path()); + } else { + // Run test synchronously. + self.tests[jobid].state = State::Running; + let result = runone::run(self.tests[jobid].path()); + self.finish_job(jobid, result); + } + self.new_tests = jobid + 1; + } + + // Check for any asynchronous replies without blocking. + while let Some(reply) = self.threads.as_mut().and_then(ConcurrentRunner::try_get) { + self.handle_reply(reply); + } + } + + /// Report the end of a job. + fn finish_job(&mut self, jobid: usize, result: TestResult) { + assert_eq!(self.tests[jobid].state, State::Running); + if let Err(ref e) = result { + self.job_error(jobid, e); + } + self.tests[jobid].state = State::Done(result); + if jobid == self.finished_tests { + while let Some(&QueueEntry { state: State::Done(_), .. }) = self.tests + .get(self.finished_tests) { + self.finished_tests += 1; + } + } + } + + /// Handle a reply from the async threads. + fn handle_reply(&mut self, reply: Reply) { + match reply { + Reply::Starting { jobid, .. } => { + assert_eq!(self.tests[jobid].state, State::Queued); + self.tests[jobid].state = State::Running; + } + Reply::Done { jobid, result } => { + self.ticks_since_progress = 0; + self.finish_job(jobid, result) + } + Reply::Tick => { + self.ticks_since_progress += 1; + if self.ticks_since_progress == TIMEOUT_SLOW { + println!("STALLED for {} seconds with {}/{} tests finished", + self.ticks_since_progress, + self.finished_tests, + self.tests.len()); + for jobid in self.finished_tests..self.tests.len() { + if self.tests[jobid].state == State::Running { + println!("slow: {}", self.tests[jobid].path.to_string_lossy()); + } + } + } + if self.ticks_since_progress >= TIMEOUT_PANIC { + panic!("worker threads stalled for {} seconds.", + self.ticks_since_progress); + } + } + } + } + + /// Drain the async jobs and shut down the threads. + fn drain_threads(&mut self) { + if let Some(mut conc) = self.threads.take() { + conc.shutdown(); + while self.finished_tests < self.tests.len() { + match conc.get() { + Some(reply) => self.handle_reply(reply), + None => break, + } + } + conc.join(); } } @@ -178,6 +248,7 @@ impl TestRunner { pub fn run(&mut self) -> CommandResult { self.scan_dirs(); self.schedule_jobs(); + self.drain_threads(); println!("{} tests", self.tests.len()); match self.errors { 0 => Ok(()), diff --git a/src/tools/main.rs b/src/tools/main.rs index af7a092394..436b07bb05 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -4,6 +4,7 @@ extern crate cton_reader; extern crate docopt; extern crate rustc_serialize; extern crate filecheck; +extern crate num_cpus; use cretonne::VERSION; use docopt::Docopt; From 951ff3e6fc0b35c842e266e802acd1ab95c74730 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 17 Sep 2016 11:29:52 -0700 Subject: [PATCH 300/968] Tell Travis to cache Cargo intermediate build products. The CI builds were using a lot of time downloading and building crates. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 567d824194..33eed41b71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,7 @@ rust: - beta - nightly script: ./test-all.sh +cache: + - cargo + - directories: + - src/tools/target From aa1da4d87163f9bb4cb66a23d6625d3800138d34 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 17 Sep 2016 11:42:08 -0700 Subject: [PATCH 301/968] Record the location of parsed functions. Remember the location of the 'function' keyword that starts every function. This can be useful for reporting test failures etc. --- src/libreader/parser.rs | 8 +++++--- src/libreader/testfile.rs | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 54a53310ee..6fea02b22b 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -523,7 +523,7 @@ impl<'a> Parser<'a> { self.comments.clear(); self.gather_comments(AnyEntity::Function); - let (name, sig) = try!(self.parse_function_spec()); + let (location, name, sig) = try!(self.parse_function_spec()); let mut ctx = Context::new(Function::with_name_signature(name, sig)); // function ::= function-spec * "{" preamble function-body "}" @@ -545,6 +545,7 @@ impl<'a> Parser<'a> { try!(ctx.rewrite_references()); let details = Details { + location: location, comments: mem::replace(&mut self.comments, Vec::new()), map: sourcemap::new(ctx.values, ctx.ebbs, ctx.stack_slots, ctx.jump_tables), }; @@ -556,8 +557,9 @@ impl<'a> Parser<'a> { // // function-spec ::= * "function" name signature // - fn parse_function_spec(&mut self) -> Result<(FunctionName, Signature)> { + fn parse_function_spec(&mut self) -> Result<(Location, FunctionName, Signature)> { try!(self.match_token(Token::Function, "expected 'function' keyword")); + let location = self.loc; // function-spec ::= "function" * name signature let name = try!(self.parse_function_name()); @@ -565,7 +567,7 @@ impl<'a> Parser<'a> { // function-spec ::= "function" name * signature let sig = try!(self.parse_signature()); - Ok((name, sig)) + Ok((location, name, sig)) } // Parse a function name. diff --git a/src/libreader/testfile.rs b/src/libreader/testfile.rs index 34cfabce83..8a9f8d3451 100644 --- a/src/libreader/testfile.rs +++ b/src/libreader/testfile.rs @@ -8,6 +8,7 @@ use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; use testcommand::TestCommand; use sourcemap::SourceMap; +use parser::Location; /// A parsed test case. /// @@ -24,6 +25,7 @@ pub struct TestFile<'a> { /// The details to not affect the semantics of the function. #[derive(Debug)] pub struct Details<'a> { + pub location: Location, pub comments: Vec>, pub map: SourceMap, } From b1468ee0bcf7e3e94a91b9557ead2501e9171e30 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 17 Sep 2016 12:36:35 -0700 Subject: [PATCH 302/968] Simplify with unwrap_or_else(). --- src/tools/filetest/concurrent.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/tools/filetest/concurrent.rs b/src/tools/filetest/concurrent.rs index 3123124086..30abb844d4 100644 --- a/src/tools/filetest/concurrent.rs +++ b/src/tools/filetest/concurrent.rs @@ -127,20 +127,17 @@ fn worker_thread(thread_num: usize, }) .unwrap(); - let result = match catch_unwind(|| runone::run(path.as_path())) { - Ok(r) => r, - Err(e) => { - // The test panicked, leaving us a `Box`. - // Panics are usually strings. - if let Some(msg) = e.downcast_ref::() { - Err(format!("panicked in worker #{}: {}", thread_num, msg)) - } else if let Some(msg) = e.downcast_ref::<&'static str>() { - Err(format!("panicked in worker #{}: {}", thread_num, msg)) - } else { - Err(format!("panicked in worker #{}", thread_num)) - } + let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| { + // The test panicked, leaving us a `Box`. + // Panics are usually strings. + if let Some(msg) = e.downcast_ref::() { + Err(format!("panicked in worker #{}: {}", thread_num, msg)) + } else if let Some(msg) = e.downcast_ref::<&'static str>() { + Err(format!("panicked in worker #{}: {}", thread_num, msg)) + } else { + Err(format!("panicked in worker #{}", thread_num)) } - }; + }); replies.send(Reply::Done { jobid: jobid, From 5cb5110330f6eb6993681902f94919dd41d0d035 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 17 Sep 2016 17:11:17 -0700 Subject: [PATCH 303/968] Add --verbose flag to cton-util test. --- src/tools/filetest/mod.rs | 4 +-- src/tools/filetest/runner.rs | 65 +++++++++++++++++++++++++----------- src/tools/main.rs | 4 +-- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index 60b6180c05..a924cb04eb 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -30,8 +30,8 @@ pub type TestResult = Result; /// Directories are scanned recursively for test cases ending in `.cton`. These test cases are /// executed on background threads. /// -pub fn run(files: Vec) -> CommandResult { - let mut runner = TestRunner::new(); +pub fn run(verbose: bool, files: Vec) -> CommandResult { + let mut runner = TestRunner::new(verbose); for path in files.iter().map(Path::new) { if path.is_file() { diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index c4cfca1ec3..11a98fb43a 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -4,6 +4,7 @@ //! scanning directories for tests. use std::error::Error; +use std::fmt::{self, Display}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use filetest::{TestResult, runone}; @@ -35,7 +36,26 @@ impl QueueEntry { } } +impl Display for QueueEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let p = self.path.to_string_lossy(); + match self.state { + State::Done(Ok(dur)) => { + write!(f, + "{}.{:03} {}", + dur.as_secs(), + dur.subsec_nanos() / 1000000, + p) + } + State::Done(Err(ref e)) => write!(f, "FAIL {}: {}", p, e), + _ => write!(f, "{}", p), + } + } +} + pub struct TestRunner { + verbose: bool, + // Directories that have not yet been scanned. dir_stack: Vec, @@ -45,8 +65,8 @@ pub struct TestRunner { // Pointer into `tests` where the `New` entries begin. new_tests: usize, - // Number of contiguous finished tests at the front of `tests`. - finished_tests: usize, + // Number of contiguous reported tests at the front of `tests`. + reported_tests: usize, // Number of errors seen so far. errors: usize, @@ -59,12 +79,13 @@ pub struct TestRunner { impl TestRunner { /// Create a new blank TrstRunner. - pub fn new() -> TestRunner { + pub fn new(verbose: bool) -> TestRunner { TestRunner { + verbose: verbose, dir_stack: Vec::new(), tests: Vec::new(), new_tests: 0, - finished_tests: 0, + reported_tests: 0, errors: 0, ticks_since_progress: 0, threads: None, @@ -154,10 +175,17 @@ impl TestRunner { println!("{}: {}", path.to_string_lossy(), err); } - /// Report an error related to a job. - fn job_error(&mut self, jobid: usize, err: &str) { - self.errors += 1; - println!("FAIL {}: {}", self.tests[jobid].path.to_string_lossy(), err); + /// Report on the next in-order job, if it's done. + fn report_job(&self) -> bool { + let jobid = self.reported_tests; + if let Some(&QueueEntry { state: State::Done(ref result), .. }) = self.tests.get(jobid) { + if self.verbose || result.is_err() { + println!("{}", self.tests[jobid]); + } + true + } else { + false + } } /// Schedule any new jobs to run. @@ -186,15 +214,14 @@ impl TestRunner { /// Report the end of a job. fn finish_job(&mut self, jobid: usize, result: TestResult) { assert_eq!(self.tests[jobid].state, State::Running); - if let Err(ref e) = result { - self.job_error(jobid, e); + if result.is_err() { + self.errors += 1; } self.tests[jobid].state = State::Done(result); - if jobid == self.finished_tests { - while let Some(&QueueEntry { state: State::Done(_), .. }) = self.tests - .get(self.finished_tests) { - self.finished_tests += 1; - } + + // Rports jobs in order. + while self.report_job() { + self.reported_tests += 1; } } @@ -214,11 +241,11 @@ impl TestRunner { if self.ticks_since_progress == TIMEOUT_SLOW { println!("STALLED for {} seconds with {}/{} tests finished", self.ticks_since_progress, - self.finished_tests, + self.reported_tests, self.tests.len()); - for jobid in self.finished_tests..self.tests.len() { + for jobid in self.reported_tests..self.tests.len() { if self.tests[jobid].state == State::Running { - println!("slow: {}", self.tests[jobid].path.to_string_lossy()); + println!("slow: {}", self.tests[jobid]); } } } @@ -234,7 +261,7 @@ impl TestRunner { fn drain_threads(&mut self) { if let Some(mut conc) = self.threads.take() { conc.shutdown(); - while self.finished_tests < self.tests.len() { + while self.reported_tests < self.tests.len() { match conc.get() { Some(reply) => self.handle_reply(reply), None => break, diff --git a/src/tools/main.rs b/src/tools/main.rs index 436b07bb05..35a5c8a564 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -21,7 +21,7 @@ const USAGE: &'static str = " Cretonne code generator utility Usage: - cton-util test ... + cton-util test [-v] ... cton-util cat ... cton-util filecheck [-v] cton-util print-cfg ... @@ -60,7 +60,7 @@ fn cton_util() -> CommandResult { // Find the sub-command to execute. if args.cmd_test { - filetest::run(args.arg_file) + filetest::run(args.flag_verbose, args.arg_file) } else if args.cmd_cat { cat::run(args.arg_file) } else if args.cmd_filecheck { From 17c2b5213a34cd71fb98abc7d8e2029e3addf261 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sun, 18 Sep 2016 11:35:10 -0700 Subject: [PATCH 304/968] Print out a report of slow-running tests. The slow tests are computed as those that would be printed as outliers on a boxplot of all the test runtimes. These are more than 1.5 inter-quartile range away from the 75% quartile. --- src/tools/filetest/runner.rs | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/tools/filetest/runner.rs b/src/tools/filetest/runner.rs index 11a98fb43a..8794de0004 100644 --- a/src/tools/filetest/runner.rs +++ b/src/tools/filetest/runner.rs @@ -271,11 +271,55 @@ impl TestRunner { } } + /// Print out a report of slow tests. + fn report_slow_tests(&self) { + // Collect runtimes of succeeded tests. + let mut times = self.tests + .iter() + .filter_map(|entry| match *entry { + QueueEntry { state: State::Done(Ok(dur)), .. } => Some(dur), + _ => None, + }) + .collect::>(); + + // Get me some real data, kid. + let len = times.len(); + if len < 4 { + return; + } + + // Compute quartiles. + times.sort(); + let qlen = len / 4; + let q1 = times[qlen]; + let q3 = times[len - 1 - qlen]; + // Inter-quartile range. + let iqr = q3 - q1; + + // Cut-off for what we consider a 'slow' test: 1.5 IQR from the 75% quartile. + // These are the data points that would be plotted as outliers outside a box plot. + let cut = q3 + iqr * 2 / 3; + if cut > *times.last().unwrap() { + return; + } + + for t in self.tests + .iter() + .filter(|entry| match **entry { + QueueEntry { state: State::Done(Ok(dur)), .. } => dur > cut, + _ => false, + }) { + println!("slow: {}", t) + } + + } + /// Scan pushed directories for tests and run them. pub fn run(&mut self) -> CommandResult { self.scan_dirs(); self.schedule_jobs(); self.drain_threads(); + self.report_slow_tests(); println!("{} tests", self.tests.len()); match self.errors { 0 => Ok(()), From feef2ecf3f556c88f50acf0341d34499ed9d7c4c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 10:08:21 -0700 Subject: [PATCH 305/968] Extract Result and Error into their own module. Also include the err! macro and make it usable outside the module. --- src/libreader/error.rs | 44 +++++++++++++++++++++++++++++++++++++++ src/libreader/lexer.rs | 8 ++----- src/libreader/lib.rs | 4 +++- src/libreader/parser.rs | 40 +++-------------------------------- src/libreader/testfile.rs | 2 +- 5 files changed, 53 insertions(+), 45 deletions(-) create mode 100644 src/libreader/error.rs diff --git a/src/libreader/error.rs b/src/libreader/error.rs new file mode 100644 index 0000000000..edc6e40649 --- /dev/null +++ b/src/libreader/error.rs @@ -0,0 +1,44 @@ +//! Define the `Location`, `Error`, and `Result` types. + +#![macro_use] + +use std::fmt; +use std::result; + +/// The location of a `Token` or `Error`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Location { + pub line_number: usize, +} + +/// A parse error is returned when the parse failed. +#[derive(Debug)] +pub struct Error { + pub location: Location, + pub message: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.location.line_number, self.message) + } +} + +pub type Result = result::Result; + +// Create an `Err` variant of `Result` from a location and `format!` args. +macro_rules! err { + ( $loc:expr, $msg:expr ) => { + Err($crate::Error { + location: $loc.clone(), + message: String::from($msg), + }) + }; + + ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => { + Err($crate::Error { + location: $loc.clone(), + message: format!( $fmt, $( $arg ),+ ), + }) + }; +} diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 6b5e881a1f..7631e306ad 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -8,12 +8,7 @@ use std::str::CharIndices; use cretonne::ir::types; use cretonne::ir::{Value, Ebb}; - -/// The location of a `Token` or `Error`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Location { - pub line_number: usize, -} +use error::Location; /// A Token returned from the `Lexer`. /// @@ -379,6 +374,7 @@ mod tests { use super::*; use cretonne::ir::types; use cretonne::ir::{Value, Ebb}; + use error::Location; fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index 79f9b23521..f0f04fa381 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -5,11 +5,13 @@ extern crate cretonne; -pub use parser::{Result, parse_functions, parse_test}; +pub use error::{Location, Result, Error}; +pub use parser::{parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; pub use testfile::{TestFile, Details}; pub use sourcemap::SourceMap; +mod error; mod lexer; mod parser; mod testcommand; diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 6fea02b22b..68a5e72a4f 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -6,12 +6,9 @@ // ====--------------------------------------------------------------------------------------====// use std::collections::HashMap; -use std::result; -use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::u32; use std::mem; -use lexer::{self, Lexer, Token}; use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlot, StackSlotData, JumpTable, JumpTableData}; use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; @@ -20,11 +17,11 @@ use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; use testfile::{TestFile, Details, Comment}; +use error::{Location, Error, Result}; +use lexer::{self, Lexer, Token}; use testcommand::TestCommand; use sourcemap; -pub use lexer::Location; - /// Parse the entire `text` into a list of functions. /// /// Any test commands or ISA declarations are ignored. @@ -43,38 +40,6 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { }) } -/// A parse error is returned when the parse failed. -#[derive(Debug)] -pub struct Error { - pub location: Location, - pub message: String, -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}: {}", self.location.line_number, self.message) - } -} - -pub type Result = result::Result; - -// Create an `Err` variant of `Result` from a location and `format!` args. -macro_rules! err { - ( $loc:expr, $msg:expr ) => { - Err(Error { - location: $loc.clone(), - message: String::from($msg), - }) - }; - - ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => { - Err(Error { - location: $loc.clone(), - message: format!( $fmt, $( $arg ),+ ), - }) - }; -} - pub struct Parser<'a> { lex: Lexer<'a>, @@ -1225,6 +1190,7 @@ mod tests { use cretonne::ir::types::{self, ArgumentType, ArgumentExtension}; use cretonne::ir::entities::AnyEntity; use testfile::{Details, Comment}; + use error::Error; #[test] fn argument_type() { diff --git a/src/libreader/testfile.rs b/src/libreader/testfile.rs index 8a9f8d3451..d63b1f9f9d 100644 --- a/src/libreader/testfile.rs +++ b/src/libreader/testfile.rs @@ -8,7 +8,7 @@ use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; use testcommand::TestCommand; use sourcemap::SourceMap; -use parser::Location; +use error::Location; /// A parsed test case. /// From dd8e7df8ba5712c31ab257be538ff974d84913cc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 11:11:30 -0700 Subject: [PATCH 306/968] Add an internal MutableSourceMap trait. Use the SourceMap for mapping during parsing too. --- src/libreader/parser.rs | 125 ++++++++--------------------------- src/libreader/sourcemap.rs | 130 +++++++++++++++++++++++++++++++------ 2 files changed, 138 insertions(+), 117 deletions(-) diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 68a5e72a4f..b2c9f40d6f 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -5,12 +5,11 @@ // // ====--------------------------------------------------------------------------------------====// -use std::collections::HashMap; use std::str::FromStr; use std::u32; use std::mem; -use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlot, - StackSlotData, JumpTable, JumpTableData}; +use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlotData, + JumpTable, JumpTableData}; use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; @@ -20,7 +19,7 @@ use testfile::{TestFile, Details, Comment}; use error::{Location, Error, Result}; use lexer::{self, Lexer, Token}; use testcommand::TestCommand; -use sourcemap; +use sourcemap::{SourceMap, MutableSourceMap}; /// Parse the entire `text` into a list of functions. /// @@ -65,10 +64,7 @@ pub struct Parser<'a> { // file by number. We need to map these numbers to real references. struct Context { function: Function, - stack_slots: HashMap, // ssNN - jump_tables: HashMap, // jtNN - ebbs: HashMap, // ebbNN - values: HashMap, // vNN, vxNN + map: SourceMap, // Remember the location of every instruction. inst_locs: Vec<(Inst, Location)>, @@ -78,36 +74,25 @@ impl Context { fn new(f: Function) -> Context { Context { function: f, - stack_slots: HashMap::new(), - jump_tables: HashMap::new(), - ebbs: HashMap::new(), - values: HashMap::new(), + map: SourceMap::new(), inst_locs: Vec::new(), } } // Allocate a new stack slot and add a mapping number -> StackSlot. fn add_ss(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { - if self.stack_slots.insert(number, self.function.stack_slots.push(data)).is_some() { - err!(loc, "duplicate stack slot: ss{}", number) - } else { - Ok(()) - } + self.map.def_ss(number, self.function.stack_slots.push(data), loc) } // Allocate a new jump table and add a mapping number -> JumpTable. fn add_jt(&mut self, number: u32, data: JumpTableData, loc: &Location) -> Result<()> { - if self.jump_tables.insert(number, self.function.jump_tables.push(data)).is_some() { - err!(loc, "duplicate jump table: jt{}", number) - } else { - Ok(()) - } + self.map.def_jt(number, self.function.jump_tables.push(data), loc) } // Resolve a reference to a jump table. fn get_jt(&self, number: u32, loc: &Location) -> Result { - match self.jump_tables.get(&number) { - Some(&jt) => Ok(jt), + match self.map.get_jt(number) { + Some(jt) => Ok(jt), None => err!(loc, "undefined jump table jt{}", number), } } @@ -116,20 +101,7 @@ impl Context { fn add_ebb(&mut self, src_ebb: Ebb, loc: &Location) -> Result { let ebb = self.function.dfg.make_ebb(); self.function.layout.append_ebb(ebb); - if self.ebbs.insert(src_ebb, ebb).is_some() { - err!(loc, "duplicate EBB: {}", src_ebb) - } else { - Ok(ebb) - } - } - - // Add a value mapping src_val -> data. - fn add_value(&mut self, src_val: Value, data: Value, loc: &Location) -> Result<()> { - if self.values.insert(src_val, data).is_some() { - err!(loc, "duplicate value: {}", src_val) - } else { - Ok(()) - } + self.map.def_ebb(src_ebb, ebb, loc).and(Ok(ebb)) } // Record the location of an instuction. @@ -140,41 +112,6 @@ impl Context { // The parser creates all instructions with Ebb and Value references using the source file // numbering. These references need to be rewritten after parsing is complete since forward // references are allowed. - - // Rewrite an Ebb reference. - fn rewrite_ebb(map: &HashMap, ebb: &mut Ebb, loc: &Location) -> Result<()> { - match map.get(ebb) { - Some(&new) => { - *ebb = new; - Ok(()) - } - None => err!(loc, "undefined reference: {}", ebb), - } - } - - // Rewrite a value reference. - fn rewrite_value(map: &HashMap, val: &mut Value, loc: &Location) -> Result<()> { - match map.get(val) { - Some(&new) => { - *val = new; - Ok(()) - } - None => err!(loc, "undefined reference: {}", val), - } - } - - // Rewrite a slice of value references. - fn rewrite_values(map: &HashMap, - vals: &mut [Value], - loc: &Location) - -> Result<()> { - for val in vals { - try!(Self::rewrite_value(map, val, loc)); - } - Ok(()) - } - - // Rewrite all EBB and value references in the function. fn rewrite_references(&mut self) -> Result<()> { for &(inst, loc) in &self.inst_locs { match self.function.dfg[inst] { @@ -189,7 +126,7 @@ impl Context { InstructionData::BinaryImmRev { ref mut arg, .. } | InstructionData::ExtractLane { ref mut arg, .. } | InstructionData::BranchTable { ref mut arg, .. } => { - try!(Self::rewrite_value(&self.values, arg, &loc)); + try!(self.map.rewrite_value(arg, &loc)); } InstructionData::Binary { ref mut args, .. } | @@ -197,30 +134,30 @@ impl Context { InstructionData::InsertLane { ref mut args, .. } | InstructionData::IntCompare { ref mut args, .. } | InstructionData::FloatCompare { ref mut args, .. } => { - try!(Self::rewrite_values(&self.values, args, &loc)); + try!(self.map.rewrite_values(args, &loc)); } InstructionData::Ternary { ref mut args, .. } => { - try!(Self::rewrite_values(&self.values, args, &loc)); + try!(self.map.rewrite_values(args, &loc)); } InstructionData::Jump { ref mut data, .. } => { - try!(Self::rewrite_ebb(&self.ebbs, &mut data.destination, &loc)); - try!(Self::rewrite_values(&self.values, &mut data.arguments, &loc)); + try!(self.map.rewrite_ebb(&mut data.destination, &loc)); + try!(self.map.rewrite_values(&mut data.arguments, &loc)); } InstructionData::Branch { ref mut data, .. } => { - try!(Self::rewrite_value(&self.values, &mut data.arg, &loc)); - try!(Self::rewrite_ebb(&self.ebbs, &mut data.destination, &loc)); - try!(Self::rewrite_values(&self.values, &mut data.arguments, &loc)); + try!(self.map.rewrite_value(&mut data.arg, &loc)); + try!(self.map.rewrite_ebb(&mut data.destination, &loc)); + try!(self.map.rewrite_values(&mut data.arguments, &loc)); } InstructionData::Call { ref mut data, .. } => { - try!(Self::rewrite_values(&self.values, &mut data.args, &loc)); + try!(self.map.rewrite_values(&mut data.args, &loc)); } InstructionData::Return { ref mut data, .. } => { - try!(Self::rewrite_values(&self.values, &mut data.args, &loc)); + try!(self.map.rewrite_values(&mut data.args, &loc)); } } } @@ -230,7 +167,7 @@ impl Context { for jt in self.function.jump_tables.keys() { for ebb in self.function.jump_tables[jt].as_mut_slice() { if *ebb != NO_EBB { - try!(Self::rewrite_ebb(&self.ebbs, ebb, &loc)); + try!(self.map.rewrite_ebb(ebb, &loc)); } } } @@ -512,7 +449,7 @@ impl<'a> Parser<'a> { let details = Details { location: location, comments: mem::replace(&mut self.comments, Vec::new()), - map: sourcemap::new(ctx.values, ctx.ebbs, ctx.stack_slots, ctx.jump_tables), + map: ctx.map, }; Ok((ctx.function, details)) @@ -777,7 +714,7 @@ impl<'a> Parser<'a> { let t = try!(self.match_type("expected EBB argument type")); // Allocate the EBB argument and add the mapping. let value = ctx.function.dfg.append_ebb_arg(ebb, t); - ctx.add_value(vx, value, &vx_location) + ctx.map.def_value(vx, value, &vx_location) } // Parse an instruction, append it to `ebb`. @@ -856,7 +793,7 @@ impl<'a> Parser<'a> { // Now map the source result values to the just created instruction results. // Pass a reference to `ctx.values` instead of `ctx` itself since the `Values` iterator // holds a reference to `ctx.function`. - self.add_values(&mut ctx.values, + self.add_values(&mut ctx.map, results.into_iter(), ctx.function.dfg.inst_results(inst)) } @@ -888,8 +825,8 @@ impl<'a> Parser<'a> { // layout of the blocks. let ctrl_src_value = inst_data.typevar_operand() .expect("Constraints <-> Format inconsistency"); - ctx.function.dfg.value_type(match ctx.values.get(&ctrl_src_value) { - Some(&v) => v, + ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { + Some(v) => v, None => { return err!(self.loc, "cannot determine type of operand {}", @@ -932,18 +869,12 @@ impl<'a> Parser<'a> { } // Add mappings for a list of source values to their corresponding new values. - fn add_values(&self, - values: &mut HashMap, - results: S, - new_results: V) - -> Result<()> + fn add_values(&self, map: &mut SourceMap, results: S, new_results: V) -> Result<()> where S: Iterator, V: Iterator { for (src, val) in results.zip(new_results) { - if values.insert(src, val).is_some() { - return err!(self.loc, "duplicate result value: {}", src); - } + try!(map.def_value(src, val, &self.loc)); } Ok(()) } diff --git a/src/libreader/sourcemap.rs b/src/libreader/sourcemap.rs index 9a601a3e39..090e438d54 100644 --- a/src/libreader/sourcemap.rs +++ b/src/libreader/sourcemap.rs @@ -10,6 +10,7 @@ use std::collections::HashMap; use cretonne::ir::{StackSlot, JumpTable, Ebb, Value}; use cretonne::ir::entities::AnyEntity; +use error::{Result, Location}; /// Mapping from source entity names to entity references that are valid in the parsed function. #[derive(Debug)] @@ -22,6 +23,26 @@ pub struct SourceMap { /// Read-only interface which is exposed outside the parser crate. impl SourceMap { + /// Look up a value entity by its source number. + pub fn get_value(&self, src: Value) -> Option { + self.values.get(&src).cloned() + } + + /// Look up a EBB entity by its source number. + pub fn get_ebb(&self, src: Ebb) -> Option { + self.ebbs.get(&src).cloned() + } + + /// Look up a stack slot entity by its source number. + pub fn get_ss(&self, src_num: u32) -> Option { + self.stack_slots.get(&src_num).cloned() + } + + /// Look up a jump table entity by its source number. + pub fn get_jt(&self, src_num: u32) -> Option { + self.jump_tables.get(&src_num).cloned() + } + /// Look up an entity by source name. /// Returns the entity reference corresponding to `name`, if it exists. pub fn lookup_str(&self, name: &str) -> Option { @@ -29,25 +50,51 @@ impl SourceMap { match ent { "v" => { Value::direct_with_number(num) - .and_then(|v| self.values.get(&v).cloned()) + .and_then(|v| self.get_value(v)) .map(AnyEntity::Value) } "vx" => { Value::table_with_number(num) - .and_then(|v| self.values.get(&v).cloned()) + .and_then(|v| self.get_value(v)) .map(AnyEntity::Value) } - "ebb" => { - Ebb::with_number(num) - .and_then(|e| self.ebbs.get(&e).cloned()) - .map(AnyEntity::Ebb) - } - "ss" => self.stack_slots.get(&num).cloned().map(AnyEntity::StackSlot), - "jt" => self.jump_tables.get(&num).cloned().map(AnyEntity::JumpTable), + "ebb" => Ebb::with_number(num).and_then(|e| self.get_ebb(e)).map(AnyEntity::Ebb), + "ss" => self.get_ss(num).map(AnyEntity::StackSlot), + "jt" => self.get_jt(num).map(AnyEntity::JumpTable), _ => None, } }) } + + /// Rewrite an Ebb reference. + pub fn rewrite_ebb(&self, ebb: &mut Ebb, loc: &Location) -> Result<()> { + match self.get_ebb(*ebb) { + Some(new) => { + *ebb = new; + Ok(()) + } + None => err!(loc, "undefined reference: {}", ebb), + } + } + + /// Rewrite a value reference. + pub fn rewrite_value(&self, val: &mut Value, loc: &Location) -> Result<()> { + match self.get_value(*val) { + Some(new) => { + *val = new; + Ok(()) + } + None => err!(loc, "undefined reference: {}", val), + } + } + + /// Rewrite a slice of value references. + pub fn rewrite_values(&self, vals: &mut [Value], loc: &Location) -> Result<()> { + for val in vals { + try!(self.rewrite_value(val, loc)); + } + Ok(()) + } } /// Get the number of decimal digits at the end of `s`. @@ -67,17 +114,60 @@ fn split_entity_name(name: &str) -> Option<(&str, u32)> { } } -/// Create a new SourceMap from all the individual mappings. -pub fn new(values: HashMap, - ebbs: HashMap, - stack_slots: HashMap, - jump_tables: HashMap) - -> SourceMap { - SourceMap { - values: values, - ebbs: ebbs, - stack_slots: stack_slots, - jump_tables: jump_tables, + +/// Interface for mutating a source map. +/// +/// This interface is provided for the parser itself, it is not made available outside the crate. +pub trait MutableSourceMap { + fn new() -> Self; + + /// Define a value mapping from the source name `src` to the final `entity`. + fn def_value(&mut self, src: Value, entity: Value, loc: &Location) -> Result<()>; + fn def_ebb(&mut self, src: Ebb, entity: Ebb, loc: &Location) -> Result<()>; + fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()>; + fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()>; +} + +impl MutableSourceMap for SourceMap { + fn new() -> SourceMap { + SourceMap { + values: HashMap::new(), + ebbs: HashMap::new(), + stack_slots: HashMap::new(), + jump_tables: HashMap::new(), + } + } + + fn def_value(&mut self, src: Value, entity: Value, loc: &Location) -> Result<()> { + if self.values.insert(src, entity).is_some() { + err!(loc, "duplicate value: {}", src) + } else { + Ok(()) + } + } + + fn def_ebb(&mut self, src: Ebb, entity: Ebb, loc: &Location) -> Result<()> { + if self.ebbs.insert(src, entity).is_some() { + err!(loc, "duplicate EBB: {}", src) + } else { + Ok(()) + } + } + + fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()> { + if self.stack_slots.insert(src_num, entity).is_some() { + err!(loc, "duplicate stack slot: ss{}", src_num) + } else { + Ok(()) + } + } + + fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()> { + if self.jump_tables.insert(src_num, entity).is_some() { + err!(loc, "duplicate jump table: jt{}", src_num) + } else { + Ok(()) + } } } From d0f9f92317dedb5ba8b90f62eeb40d1ac1a86d1f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 11:29:55 -0700 Subject: [PATCH 307/968] Also record locations for tracked entities. --- src/libreader/sourcemap.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/libreader/sourcemap.rs b/src/libreader/sourcemap.rs index 090e438d54..e7828a1803 100644 --- a/src/libreader/sourcemap.rs +++ b/src/libreader/sourcemap.rs @@ -8,7 +8,7 @@ //! clients. use std::collections::HashMap; -use cretonne::ir::{StackSlot, JumpTable, Ebb, Value}; +use cretonne::ir::{StackSlot, JumpTable, Ebb, Value, Inst}; use cretonne::ir::entities::AnyEntity; use error::{Result, Location}; @@ -19,6 +19,9 @@ pub struct SourceMap { ebbs: HashMap, // ebbNN stack_slots: HashMap, // ssNN jump_tables: HashMap, // jtNN + + // Store locations for entities, including instructions. + locations: HashMap, } /// Read-only interface which is exposed outside the parser crate. @@ -66,6 +69,12 @@ impl SourceMap { }) } + /// Get the source location where an entity was defined. + /// This looks up entities in the parsed function, not the source entity numbers. + pub fn location(&self, entity: AnyEntity) -> Option { + self.locations.get(&entity).cloned() + } + /// Rewrite an Ebb reference. pub fn rewrite_ebb(&self, ebb: &mut Ebb, loc: &Location) -> Result<()> { match self.get_ebb(*ebb) { @@ -126,6 +135,10 @@ pub trait MutableSourceMap { fn def_ebb(&mut self, src: Ebb, entity: Ebb, loc: &Location) -> Result<()>; fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()>; fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()>; + + /// Define an instruction. Since instruction numbers never appear in source, only the location + /// is recorded. + fn def_inst(&mut self, entity: Inst, loc: &Location) -> Result<()>; } impl MutableSourceMap for SourceMap { @@ -135,12 +148,15 @@ impl MutableSourceMap for SourceMap { ebbs: HashMap::new(), stack_slots: HashMap::new(), jump_tables: HashMap::new(), + locations: HashMap::new(), } } fn def_value(&mut self, src: Value, entity: Value, loc: &Location) -> Result<()> { if self.values.insert(src, entity).is_some() { err!(loc, "duplicate value: {}", src) + } else if self.locations.insert(entity.into(), loc.clone()).is_some() { + err!(loc, "duplicate entity: {}", entity) } else { Ok(()) } @@ -149,6 +165,8 @@ impl MutableSourceMap for SourceMap { fn def_ebb(&mut self, src: Ebb, entity: Ebb, loc: &Location) -> Result<()> { if self.ebbs.insert(src, entity).is_some() { err!(loc, "duplicate EBB: {}", src) + } else if self.locations.insert(entity.into(), loc.clone()).is_some() { + err!(loc, "duplicate entity: {}", entity) } else { Ok(()) } @@ -157,6 +175,8 @@ impl MutableSourceMap for SourceMap { fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()> { if self.stack_slots.insert(src_num, entity).is_some() { err!(loc, "duplicate stack slot: ss{}", src_num) + } else if self.locations.insert(entity.into(), loc.clone()).is_some() { + err!(loc, "duplicate entity: {}", entity) } else { Ok(()) } @@ -165,6 +185,16 @@ impl MutableSourceMap for SourceMap { fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()> { if self.jump_tables.insert(src_num, entity).is_some() { err!(loc, "duplicate jump table: jt{}", src_num) + } else if self.locations.insert(entity.into(), loc.clone()).is_some() { + err!(loc, "duplicate entity: {}", entity) + } else { + Ok(()) + } + } + + fn def_inst(&mut self, entity: Inst, loc: &Location) -> Result<()> { + if self.locations.insert(entity.into(), loc.clone()).is_some() { + err!(loc, "duplicate entity: {}", entity) } else { Ok(()) } From 622006ecc54b8e8150020b14ca81d19c2f435c78 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 11:42:56 -0700 Subject: [PATCH 308/968] Remove the inst_locs vector in the parser. Use the source map to track instruction locations instead. The rewrite methods now take an AnyEntity argument as the location to use for errors. This means that bad EBB references in jump tables are now reported correctly. --- src/libreader/error.rs | 2 +- src/libreader/parser.rs | 98 ++++++++++++++++++-------------------- src/libreader/sourcemap.rs | 18 +++++-- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/libreader/error.rs b/src/libreader/error.rs index edc6e40649..cf8bd12f08 100644 --- a/src/libreader/error.rs +++ b/src/libreader/error.rs @@ -6,7 +6,7 @@ use std::fmt; use std::result; /// The location of a `Token` or `Error`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Location { pub line_number: usize, } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index b2c9f40d6f..d76392d0f2 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -8,8 +8,8 @@ use std::str::FromStr; use std::u32; use std::mem; -use cretonne::ir::{Function, Ebb, Inst, Opcode, Value, Type, FunctionName, StackSlotData, - JumpTable, JumpTableData}; +use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, + JumpTableData}; use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; @@ -65,9 +65,6 @@ pub struct Parser<'a> { struct Context { function: Function, map: SourceMap, - - // Remember the location of every instruction. - inst_locs: Vec<(Inst, Location)>, } impl Context { @@ -75,7 +72,6 @@ impl Context { Context { function: f, map: SourceMap::new(), - inst_locs: Vec::new(), } } @@ -104,70 +100,68 @@ impl Context { self.map.def_ebb(src_ebb, ebb, loc).and(Ok(ebb)) } - // Record the location of an instuction. - fn add_inst_loc(&mut self, inst: Inst, loc: &Location) { - self.inst_locs.push((inst, *loc)); - } - // The parser creates all instructions with Ebb and Value references using the source file // numbering. These references need to be rewritten after parsing is complete since forward // references are allowed. fn rewrite_references(&mut self) -> Result<()> { - for &(inst, loc) in &self.inst_locs { - match self.function.dfg[inst] { - InstructionData::Nullary { .. } | - InstructionData::UnaryImm { .. } | - InstructionData::UnaryIeee32 { .. } | - InstructionData::UnaryIeee64 { .. } | - InstructionData::UnaryImmVector { .. } => {} + for ebb in self.function.layout.ebbs() { + for inst in self.function.layout.ebb_insts(ebb) { + let loc = inst.into(); + match self.function.dfg[inst] { + InstructionData::Nullary { .. } | + InstructionData::UnaryImm { .. } | + InstructionData::UnaryIeee32 { .. } | + InstructionData::UnaryIeee64 { .. } | + InstructionData::UnaryImmVector { .. } => {} - InstructionData::Unary { ref mut arg, .. } | - InstructionData::BinaryImm { ref mut arg, .. } | - InstructionData::BinaryImmRev { ref mut arg, .. } | - InstructionData::ExtractLane { ref mut arg, .. } | - InstructionData::BranchTable { ref mut arg, .. } => { - try!(self.map.rewrite_value(arg, &loc)); - } + InstructionData::Unary { ref mut arg, .. } | + InstructionData::BinaryImm { ref mut arg, .. } | + InstructionData::BinaryImmRev { ref mut arg, .. } | + InstructionData::ExtractLane { ref mut arg, .. } | + InstructionData::BranchTable { ref mut arg, .. } => { + try!(self.map.rewrite_value(arg, loc)); + } - InstructionData::Binary { ref mut args, .. } | - InstructionData::BinaryOverflow { ref mut args, .. } | - InstructionData::InsertLane { ref mut args, .. } | - InstructionData::IntCompare { ref mut args, .. } | - InstructionData::FloatCompare { ref mut args, .. } => { - try!(self.map.rewrite_values(args, &loc)); - } + InstructionData::Binary { ref mut args, .. } | + InstructionData::BinaryOverflow { ref mut args, .. } | + InstructionData::InsertLane { ref mut args, .. } | + InstructionData::IntCompare { ref mut args, .. } | + InstructionData::FloatCompare { ref mut args, .. } => { + try!(self.map.rewrite_values(args, loc)); + } - InstructionData::Ternary { ref mut args, .. } => { - try!(self.map.rewrite_values(args, &loc)); - } + InstructionData::Ternary { ref mut args, .. } => { + try!(self.map.rewrite_values(args, loc)); + } - InstructionData::Jump { ref mut data, .. } => { - try!(self.map.rewrite_ebb(&mut data.destination, &loc)); - try!(self.map.rewrite_values(&mut data.arguments, &loc)); - } + InstructionData::Jump { ref mut data, .. } => { + try!(self.map.rewrite_ebb(&mut data.destination, loc)); + try!(self.map.rewrite_values(&mut data.arguments, loc)); + } - InstructionData::Branch { ref mut data, .. } => { - try!(self.map.rewrite_value(&mut data.arg, &loc)); - try!(self.map.rewrite_ebb(&mut data.destination, &loc)); - try!(self.map.rewrite_values(&mut data.arguments, &loc)); - } + InstructionData::Branch { ref mut data, .. } => { + try!(self.map.rewrite_value(&mut data.arg, loc)); + try!(self.map.rewrite_ebb(&mut data.destination, loc)); + try!(self.map.rewrite_values(&mut data.arguments, loc)); + } - InstructionData::Call { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.args, &loc)); - } + InstructionData::Call { ref mut data, .. } => { + try!(self.map.rewrite_values(&mut data.args, loc)); + } - InstructionData::Return { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.args, &loc)); + InstructionData::Return { ref mut data, .. } => { + try!(self.map.rewrite_values(&mut data.args, loc)); + } } } } // Rewrite EBB references in jump tables. - let loc = Location { line_number: 0 }; for jt in self.function.jump_tables.keys() { + let loc = jt.into(); for ebb in self.function.jump_tables[jt].as_mut_slice() { if *ebb != NO_EBB { - try!(self.map.rewrite_ebb(ebb, &loc)); + try!(self.map.rewrite_ebb(ebb, loc)); } } } @@ -777,7 +771,7 @@ impl<'a> Parser<'a> { let inst = ctx.function.dfg.make_inst(inst_data); let num_results = ctx.function.dfg.make_inst_results(inst, ctrl_typevar); ctx.function.layout.append_inst(inst, ebb); - ctx.add_inst_loc(inst, &opcode_loc); + ctx.map.def_inst(inst, &opcode_loc).expect("duplicate inst references created"); if results.len() != num_results { return err!(self.loc, diff --git a/src/libreader/sourcemap.rs b/src/libreader/sourcemap.rs index e7828a1803..82f5ece750 100644 --- a/src/libreader/sourcemap.rs +++ b/src/libreader/sourcemap.rs @@ -76,29 +76,37 @@ impl SourceMap { } /// Rewrite an Ebb reference. - pub fn rewrite_ebb(&self, ebb: &mut Ebb, loc: &Location) -> Result<()> { + pub fn rewrite_ebb(&self, ebb: &mut Ebb, loc: AnyEntity) -> Result<()> { match self.get_ebb(*ebb) { Some(new) => { *ebb = new; Ok(()) } - None => err!(loc, "undefined reference: {}", ebb), + None => { + err!(self.location(loc).unwrap_or_default(), + "undefined reference: {}", + ebb) + } } } /// Rewrite a value reference. - pub fn rewrite_value(&self, val: &mut Value, loc: &Location) -> Result<()> { + pub fn rewrite_value(&self, val: &mut Value, loc: AnyEntity) -> Result<()> { match self.get_value(*val) { Some(new) => { *val = new; Ok(()) } - None => err!(loc, "undefined reference: {}", val), + None => { + err!(self.location(loc).unwrap_or_default(), + "undefined reference: {}", + val) + } } } /// Rewrite a slice of value references. - pub fn rewrite_values(&self, vals: &mut [Value], loc: &Location) -> Result<()> { + pub fn rewrite_values(&self, vals: &mut [Value], loc: AnyEntity) -> Result<()> { for val in vals { try!(self.rewrite_value(val, loc)); } From ce6a463267526b26094cc0f237991fd655cfcdf6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 13:00:16 -0700 Subject: [PATCH 309/968] Rename 'encoding' modules to 'enc_tables'. These modules contain encoding tables, not the 'Encoding' struct. --- .../isa/{encoding.rs => enc_tables.rs} | 0 src/libcretonne/isa/mod.rs | 2 +- .../isa/riscv/{encoding.rs => enc_tables.rs} | 2 +- src/libcretonne/isa/riscv/mod.rs | 23 +++++++++---------- 4 files changed, 13 insertions(+), 14 deletions(-) rename src/libcretonne/isa/{encoding.rs => enc_tables.rs} (100%) rename src/libcretonne/isa/riscv/{encoding.rs => enc_tables.rs} (86%) diff --git a/src/libcretonne/isa/encoding.rs b/src/libcretonne/isa/enc_tables.rs similarity index 100% rename from src/libcretonne/isa/encoding.rs rename to src/libcretonne/isa/enc_tables.rs diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 6eaae9fc62..74e1590fea 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -41,7 +41,7 @@ //! concurrent function compilations. pub mod riscv; -mod encoding; +mod enc_tables; use settings; use ir::{InstructionData, DataFlowGraph}; diff --git a/src/libcretonne/isa/riscv/encoding.rs b/src/libcretonne/isa/riscv/enc_tables.rs similarity index 86% rename from src/libcretonne/isa/riscv/encoding.rs rename to src/libcretonne/isa/riscv/enc_tables.rs index 760f4786d7..f32a313c92 100644 --- a/src/libcretonne/isa/riscv/encoding.rs +++ b/src/libcretonne/isa/riscv/enc_tables.rs @@ -4,7 +4,7 @@ use ir::{Opcode, InstructionData}; use ir::instructions::InstructionFormat; use ir::types; use predicates; -use isa::encoding::{Level1Entry, Level2Entry}; +use isa::enc_tables::{Level1Entry, Level2Entry}; // Include the generated encoding tables: // - `LEVEL1_RV32` diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index a4c1ce9885..0f8ec7c71b 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -1,19 +1,19 @@ //! RISC-V Instruction Set Architecture. pub mod settings; -mod encoding; +mod enc_tables; use super::super::settings as shared_settings; -use isa::encoding as shared_encoding; -use super::Builder as IsaBuilder; -use super::{TargetIsa, Encoding}; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; +use isa::Builder as IsaBuilder; +use isa::{TargetIsa, Encoding}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] struct Isa { shared_flags: shared_settings::Flags, isa_flags: settings::Flags, - cpumode: &'static [shared_encoding::Level1Entry], + cpumode: &'static [shared_enc_tables::Level1Entry], } pub fn isa_builder() -> IsaBuilder { @@ -27,9 +27,9 @@ fn isa_constructor(shared_flags: shared_settings::Flags, builder: shared_settings::Builder) -> Box { let level1 = if shared_flags.is_64bit() { - &encoding::LEVEL1_RV64[..] + &enc_tables::LEVEL1_RV64[..] } else { - &encoding::LEVEL1_RV32[..] + &enc_tables::LEVEL1_RV32[..] }; Box::new(Isa { isa_flags: settings::Flags::new(&shared_flags, builder), @@ -40,21 +40,20 @@ fn isa_constructor(shared_flags: shared_settings::Flags, impl TargetIsa for Isa { fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { - use isa::encoding::{lookup_enclist, general_encoding}; lookup_enclist(inst.first_type(), inst.opcode(), self.cpumode, - &encoding::LEVEL2[..]) + &enc_tables::LEVEL2[..]) .and_then(|enclist_offset| { general_encoding(enclist_offset, - &encoding::ENCLISTS[..], - |instp| encoding::check_instp(inst, instp), + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), |isap| self.isa_flags.numbered_predicate(isap as usize)) }) } fn recipe_names(&self) -> &'static [&'static str] { - &encoding::RECIPE_NAMES[..] + &enc_tables::RECIPE_NAMES[..] } } From 59c404ed298bad14980b503fc385e47f0e408f77 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 13:07:58 -0700 Subject: [PATCH 310/968] Move 'Encoding' into its own module. --- src/libcretonne/isa/encoding.rs | 33 +++++++++++++++++++++++++++ src/libcretonne/isa/mod.rs | 40 +++++---------------------------- 2 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 src/libcretonne/isa/encoding.rs diff --git a/src/libcretonne/isa/encoding.rs b/src/libcretonne/isa/encoding.rs new file mode 100644 index 0000000000..1b26e2bab9 --- /dev/null +++ b/src/libcretonne/isa/encoding.rs @@ -0,0 +1,33 @@ +//! The `Encoding` struct. + +/// Bits needed to encode an instruction as binary machine code. +/// +/// The encoding consists of two parts, both specific to the target ISA: An encoding *recipe*, and +/// encoding *bits*. The recipe determines the native instruction format and the mapping of +/// operands to encoded bits. The encoding bits provide additional information to the recipe, +/// typically parts of the opcode. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Encoding { + recipe: u16, + bits: u16, +} + +impl Encoding { + /// Create a new `Encoding` containing `(recipe, bits)`. + pub fn new(recipe: u16, bits: u16) -> Encoding { + Encoding { + recipe: recipe, + bits: bits, + } + } + + /// Get the recipe number in this encoding. + pub fn recipe(self) -> usize { + self.recipe as usize + } + + /// Get the recipe-specific encoding bits. + pub fn bits(self) -> u16 { + self.bits + } +} diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 74e1590fea..445893ee15 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -40,12 +40,14 @@ //! The configured target ISA trait object is a `Box` which can be used for multiple //! concurrent function compilations. -pub mod riscv; -mod enc_tables; - +pub use isa::encoding::Encoding; use settings; use ir::{InstructionData, DataFlowGraph}; +pub mod riscv; +mod encoding; +mod enc_tables; + /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. pub fn lookup(name: &str) -> Option { @@ -100,35 +102,3 @@ pub trait TargetIsa { /// This is just used for printing and parsing encodings in the textual IL format. fn recipe_names(&self) -> &'static [&'static str]; } - -/// Bits needed to encode an instruction as binary machine code. -/// -/// The encoding consists of two parts, both specific to the target ISA: An encoding *recipe*, and -/// encoding *bits*. The recipe determines the native instruction format and the mapping of -/// operands to encoded bits. The encoding bits provide additional information to the recipe, -/// typically parts of the opcode. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Encoding { - recipe: u16, - bits: u16, -} - -impl Encoding { - /// Create a new `Encoding` containing `(recipe, bits)`. - pub fn new(recipe: u16, bits: u16) -> Encoding { - Encoding { - recipe: recipe, - bits: bits, - } - } - - /// Get the recipe number in this encoding. - pub fn recipe(self) -> usize { - self.recipe as usize - } - - /// Get the recipe-specific encoding bits. - pub fn bits(self) -> u16 { - self.bits - } -} From 43aa6f66d9f714af739dc378b0fd5e87b44e834c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 13:45:22 -0700 Subject: [PATCH 311/968] Add a TargetIsa::display_enc() method. The interpretation of an encoding depends on the target ISA, and so does the proper printing of the encoding. --- src/libcretonne/isa/encoding.rs | 45 ++++++++++++++++++++++++++++++++ src/libcretonne/isa/mod.rs | 8 ++++++ src/libcretonne/isa/riscv/mod.rs | 2 +- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/libcretonne/isa/encoding.rs b/src/libcretonne/isa/encoding.rs index 1b26e2bab9..0bb1f6cc62 100644 --- a/src/libcretonne/isa/encoding.rs +++ b/src/libcretonne/isa/encoding.rs @@ -1,5 +1,7 @@ //! The `Encoding` struct. +use std::fmt; + /// Bits needed to encode an instruction as binary machine code. /// /// The encoding consists of two parts, both specific to the target ISA: An encoding *recipe*, and @@ -30,4 +32,47 @@ impl Encoding { pub fn bits(self) -> u16 { self.bits } + + /// Is this a legal encoding, or the default placeholder? + pub fn is_legal(self) -> bool { + self != Self::default() + } +} + +/// The default encoding is the illegal one. +impl Default for Encoding { + fn default() -> Self { + Self::new(0xffff, 0xffff) + } +} + +/// ISA-independent display of an encoding. +impl fmt::Display for Encoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_legal() { + write!(f, "#{}/{:02x}", self.recipe, self.bits) + } else { + write!(f, "-") + } + } +} + +/// Temporary object that holds enough context to properly display an encoding. +/// This is meant to be created by `TargetIsa::display_enc()`. +pub struct DisplayEncoding { + pub encoding: Encoding, + pub recipe_names: &'static [&'static str], +} + +impl fmt::Display for DisplayEncoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.encoding.is_legal() { + write!(f, + "{}/{:02x}", + self.recipe_names[self.encoding.recipe()], + self.encoding.bits) + } else { + write!(f, "-") + } + } } diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 445893ee15..1d788ffff1 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -101,4 +101,12 @@ pub trait TargetIsa { /// /// This is just used for printing and parsing encodings in the textual IL format. fn recipe_names(&self) -> &'static [&'static str]; + + /// Create an object that can display an ISA-dependent encoding properly. + fn display_enc(&self, enc: Encoding) -> encoding::DisplayEncoding { + encoding::DisplayEncoding { + encoding: enc, + recipe_names: self.recipe_names(), + } + } } diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 0f8ec7c71b..b1ee92a4a9 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -65,7 +65,7 @@ mod tests { use ir::{types, immediates}; fn encstr(isa: &isa::TargetIsa, enc: isa::Encoding) -> String { - format!("{}/{:02x}", isa.recipe_names()[enc.recipe()], enc.bits()) + isa.display_enc(enc).to_string() } #[test] From af29fee5d2fcf2f8c32ffa2ad6123cb9bf9fbf7b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 19 Sep 2016 14:52:48 -0700 Subject: [PATCH 312/968] Wrap FunctionName in a newtype struct. Function names display differently than normal strings since they need quotes and escaping. Move the FunctionName type into its own module. --- filetests/parser/rewrite.cton | 2 +- src/libcretonne/ir/funcname.rs | 77 ++++++++++++++++++++++++++++++++++ src/libcretonne/ir/function.rs | 2 +- src/libcretonne/ir/mod.rs | 4 +- src/libcretonne/ir/types.rs | 6 --- src/libcretonne/write.rs | 47 ++------------------- src/libreader/parser.rs | 12 +++--- 7 files changed, 91 insertions(+), 59 deletions(-) create mode 100644 src/libcretonne/ir/funcname.rs diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton index 48ad84869c..9f95cb93fa 100644 --- a/filetests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -29,7 +29,7 @@ ebb100(v20: i32): vx200 = iadd v20, v1000 jump ebb100(v1000) } -; sameln: function "use_value"() { +; sameln: function use_value() { ; nextln: ebb0(vx0: i32): ; nextln: v0 = iadd_imm vx0, 5 ; nextln: v1 = iadd vx0, v0 diff --git a/src/libcretonne/ir/funcname.rs b/src/libcretonne/ir/funcname.rs new file mode 100644 index 0000000000..0f6f106365 --- /dev/null +++ b/src/libcretonne/ir/funcname.rs @@ -0,0 +1,77 @@ +//! Function names. +//! +//! The name of a function doesn't have any meaning to Cretonne which compiles functions +//! independently. + +use std::fmt::{self, Write}; +use std::ascii::AsciiExt; + +/// The name of a function can be any UTF-8 string. +/// +/// Function names are mostly a testing and debugging tool. +/// In particular, `.cton` files use function names to identify functions. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct FunctionName(String); + +impl FunctionName { + pub fn new>(s: S) -> FunctionName { + FunctionName(s.into()) + } +} + +fn is_id_start(c: char) -> bool { + c.is_ascii() && (c == '_' || c.is_alphabetic()) +} + +fn is_id_continue(c: char) -> bool { + c.is_ascii() && (c == '_' || c.is_alphanumeric()) +} + +// The function name may need quotes if it doesn't parse as an identifier. +fn needs_quotes(name: &str) -> bool { + let mut iter = name.chars(); + if let Some(ch) = iter.next() { + !is_id_start(ch) || !iter.all(is_id_continue) + } else { + // A blank function name needs quotes. + true + } +} + +impl fmt::Display for FunctionName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if needs_quotes(&self.0) { + try!(f.write_char('"')); + for c in self.0.chars().flat_map(char::escape_default) { + try!(f.write_char(c)); + } + f.write_char('"') + } else { + f.write_str(&self.0) + } + } +} + +#[cfg(test)] +mod tests { + use super::{needs_quotes, FunctionName}; + + #[test] + fn quoting() { + assert_eq!(needs_quotes(""), true); + assert_eq!(needs_quotes("x"), false); + assert_eq!(needs_quotes(" "), true); + assert_eq!(needs_quotes("0"), true); + assert_eq!(needs_quotes("x0"), false); + } + + #[test] + fn escaping() { + assert_eq!(FunctionName::new("").to_string(), "\"\""); + assert_eq!(FunctionName::new("x").to_string(), "x"); + assert_eq!(FunctionName::new(" ").to_string(), "\" \""); + assert_eq!(FunctionName::new(" \n").to_string(), "\" \\n\""); + assert_eq!(FunctionName::new("a\u{1000}v").to_string(), + "\"a\\u{1000}v\""); + } +} diff --git a/src/libcretonne/ir/function.rs b/src/libcretonne/ir/function.rs index 415e8de9cc..ac6c962861 100644 --- a/src/libcretonne/ir/function.rs +++ b/src/libcretonne/ir/function.rs @@ -52,7 +52,7 @@ impl Function { /// Create a new empty, anomymous function. pub fn new() -> Function { - Self::with_name_signature(FunctionName::new(), Signature::new()) + Self::with_name_signature(FunctionName::default(), Signature::new()) } /// Get the signature of this function. diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index a26e48f15e..ae16eae735 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -10,8 +10,10 @@ pub mod jumptable; pub mod dfg; pub mod layout; pub mod function; +mod funcname; -pub use ir::types::{Type, FunctionName, Signature}; +pub use ir::funcname::FunctionName; +pub use ir::types::{Type, Signature}; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; pub use ir::instructions::{Opcode, InstructionData}; pub use ir::stackslot::StackSlotData; diff --git a/src/libcretonne/ir/types.rs b/src/libcretonne/ir/types.rs index 12dfc8c6ec..f4e0a6b75f 100644 --- a/src/libcretonne/ir/types.rs +++ b/src/libcretonne/ir/types.rs @@ -191,12 +191,6 @@ impl Default for Type { // // ====--------------------------------------------------------------------------------------====// -/// The name of a function can be any UTF-8 string. -/// -/// Function names are mostly a testing and debugging tool. In partucular, `.cton` files use -/// function names to identify functions. -pub type FunctionName = String; - /// Function argument extension options. /// /// On some architectures, small integer function arguments are extended to the width of a diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index aa5076f5d5..c17025d1a8 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -29,30 +29,8 @@ pub fn write_function(w: &mut Write, func: &Function) -> Result { // // ====--------------------------------------------------------------------------------------====// -// The function name may need quotes if it doesn't parse as an identifier. -fn needs_quotes(name: &str) -> bool { - let mut iter = name.chars(); - if let Some(ch) = iter.next() { - !ch.is_alphabetic() || !iter.all(char::is_alphanumeric) - } else { - // A blank function name needs quotes. - true - } -} - -// Use Rust's escape_default which provides a few simple \t \r \n \' \" \\ escapes and uses -// \u{xxxx} for anything else outside the ASCII printable range. -fn escaped(name: &str) -> String { - name.chars().flat_map(char::escape_default).collect() -} - fn write_spec(w: &mut Write, func: &Function) -> Result { - let sig = func.own_signature(); - if !needs_quotes(&func.name) { - write!(w, "function {}{}", func.name, sig) - } else { - write!(w, "function \"{}\"{}", escaped(&func.name), sig) - } + write!(w, "function {}{}", func.name, func.own_signature()) } fn write_preamble(w: &mut Write, func: &Function) -> result::Result { @@ -208,34 +186,15 @@ pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { #[cfg(test)] mod tests { - use super::{needs_quotes, escaped}; - use ir::{Function, StackSlotData}; + use ir::{Function, FunctionName, StackSlotData}; use ir::types; - #[test] - fn quoting() { - assert_eq!(needs_quotes(""), true); - assert_eq!(needs_quotes("x"), false); - assert_eq!(needs_quotes(" "), true); - assert_eq!(needs_quotes("0"), true); - assert_eq!(needs_quotes("x0"), false); - } - - #[test] - fn escaping() { - assert_eq!(escaped(""), ""); - assert_eq!(escaped("x"), "x"); - assert_eq!(escaped(" "), " "); - assert_eq!(escaped(" \n"), " \\n"); - assert_eq!(escaped("a\u{1000}v"), "a\\u{1000}v"); - } - #[test] fn basic() { let mut f = Function::new(); assert_eq!(f.to_string(), "function \"\"() {\n}\n"); - f.name.push_str("foo"); + f.name = FunctionName::new("foo".to_string()); assert_eq!(f.to_string(), "function foo() {\n}\n"); f.stack_slots.push(StackSlotData::new(4)); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index d76392d0f2..45177aace7 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -470,11 +470,11 @@ impl<'a> Parser<'a> { // // function ::= "function" * name signature { ... } // - fn parse_function_name(&mut self) -> Result { + fn parse_function_name(&mut self) -> Result { match self.token() { Some(Token::Identifier(s)) => { self.consume(); - Ok(s.to_string()) + Ok(FunctionName::new(s)) } _ => err!(self.loc, "expected function name"), } @@ -1161,7 +1161,7 @@ mod tests { }") .parse_function() .unwrap(); - assert_eq!(func.name, "foo"); + assert_eq!(func.name.to_string(), "foo"); let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); assert_eq!(ss0.to_string(), "ss0"); @@ -1190,7 +1190,7 @@ mod tests { }") .parse_function() .unwrap(); - assert_eq!(func.name, "ebbs"); + assert_eq!(func.name.to_string(), "ebbs"); let mut ebbs = func.layout.ebbs(); @@ -1219,7 +1219,7 @@ mod tests { ; More trailing.") .parse_function() .unwrap(); - assert_eq!(&func.name, "comment"); + assert_eq!(func.name.to_string(), "comment"); assert_eq!(comments.len(), 8); // no 'before' comment. assert_eq!(comments[0], Comment { @@ -1252,6 +1252,6 @@ mod tests { assert_eq!(tf.commands[0].command, "cfg"); assert_eq!(tf.commands[1].command, "verify"); assert_eq!(tf.functions.len(), 1); - assert_eq!(tf.functions[0].0.name, "comment"); + assert_eq!(tf.functions[0].0.name.to_string(), "comment"); } } From 57b6967ddd1c674674b75fd96b7e354caa7cb733 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 09:48:00 -0700 Subject: [PATCH 313/968] Add clear, is_empty, and resize methods to EntityMap. These are simply forwards from the underlying Vec. --- src/libcretonne/entity_map.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 2a76e3cce9..74c3cf5c46 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -70,6 +70,16 @@ impl EntityMap k.index() < self.elems.len() } + /// Is this map completely empty? + pub fn is_empty(&self) -> bool { + self.elems.is_empty() + } + + /// Remove all entries from this map. + pub fn clear(&mut self) { + self.elems.clear() + } + /// Iterate over all the keys in this map. pub fn keys(&self) -> Keys { Keys { @@ -137,12 +147,17 @@ impl EntityMap map } + /// Resize the map to have `n` entries by adding default entries as needed. + pub fn resize(&mut self, n: usize) { + self.elems.resize(n, V::default()); + } + /// Ensure that `k` is a valid key but adding default entries if necesssary. /// /// Return a mutable reference to the corresponding entry. pub fn ensure(&mut self, k: K) -> &mut V { if !self.is_valid(k) { - self.elems.resize(k.index() + 1, V::default()) + self.resize(k.index() + 1) } &mut self.elems[k.index()] } From 1d0ab91136bfb08f81499f9d8ce6e90c3ddbef32 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 10:17:16 -0700 Subject: [PATCH 314/968] Store instruction encodings in Function. This is a side-table of ISA-dependent information that will initially be filled out by the legalizer. --- src/libcretonne/ir/function.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libcretonne/ir/function.rs b/src/libcretonne/ir/function.rs index ac6c962861..3ff5aea00f 100644 --- a/src/libcretonne/ir/function.rs +++ b/src/libcretonne/ir/function.rs @@ -3,10 +3,11 @@ //! The `Function` struct defined in this module owns all of its extended basic blocks and //! instructions. -use ir::{FunctionName, Signature, StackSlot, StackSlotData, JumpTable, JumpTableData, - DataFlowGraph, Layout}; -use entity_map::{EntityMap, PrimaryEntityData}; use std::fmt::{self, Display, Debug, Formatter}; +use ir::{FunctionName, Signature, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData, + DataFlowGraph, Layout}; +use isa::Encoding; +use entity_map::{EntityMap, PrimaryEntityData}; use write::write_function; /// A function. @@ -32,6 +33,10 @@ pub struct Function { /// Layout of EBBs and instructions in the function body. pub layout: Layout, + + /// Encoding recipe and bits for the legal instructions. + /// Illegal instructions have the `Encoding::default()` value. + pub encodings: EntityMap, } impl PrimaryEntityData for StackSlotData {} @@ -47,6 +52,7 @@ impl Function { jump_tables: EntityMap::new(), dfg: DataFlowGraph::new(), layout: Layout::new(), + encodings: EntityMap::new(), } } From fab16941c8640120fbbdd8d1bd85681680ee99d0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 10:49:30 -0700 Subject: [PATCH 315/968] Add a stub implementation of the legalizer. This basic version only fills in the encoding of already-legal instructions. It doesn't have any way of transforming the illegal instructions yet. --- src/libcretonne/legalizer.rs | 54 ++++++++++++++++++++++++++++++++++++ src/libcretonne/lib.rs | 1 + 2 files changed, 55 insertions(+) create mode 100644 src/libcretonne/legalizer.rs diff --git a/src/libcretonne/legalizer.rs b/src/libcretonne/legalizer.rs new file mode 100644 index 0000000000..3358f77bb0 --- /dev/null +++ b/src/libcretonne/legalizer.rs @@ -0,0 +1,54 @@ +//! Legalize instructions. +//! +//! A legal instruction is one that can be mapped directly to a machine code instruction for the +//! target ISA. The `legalize_function()` function takes as input any function and transforms it +//! into an equivalent function using only legal instructions. +//! +//! The characteristics of legal instructions depend on the target ISA, so any given instruction +//! can be legal for one ISA and illegal for another. +//! +//! Besides transforming instructions, the legalizer also fills out the `function.encodings` map +//! which provides a legal encoding recipe for every instruction. +//! +//! The legalizer does not deal with register allocation constraints. These constraints are derived +//! from the encoding recipes, and solved later by the register allocator. + +use ir::Function; +use isa::TargetIsa; + +/// Legalize `func` for `isa`. +/// +/// - Transform any instructions that don't have a legal representation in `isa`. +/// - Fill out `func.encodings`. +/// +pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { + // TODO: This is very simplified and incomplete. + func.encodings.resize(func.dfg.num_insts()); + for ebb in func.layout.ebbs() { + for inst in func.layout.ebb_insts(ebb) { + match isa.encode(&func.dfg, &func.dfg[inst]) { + Some(encoding) => func.encodings[inst] = encoding, + None => { + // TODO: We should transform the instruction into legal equivalents. + // Possible strategies are: + // 1. Expand instruction into sequence of legal instructions. Possibly + // iteratively. + // 2. Split the controlling type variable into high and low parts. This applies + // both to SIMD vector types which can be halved and to integer types such + // as `i64` used on a 32-bit ISA. + // 3. Promote the controlling type variable to a larger type. This typically + // means expressing `i8` and `i16` arithmetic in terms if `i32` operations + // on RISC targets. (It may or may not be beneficial to promote small vector + // types versus splitting them.) + // 4. Convert to library calls. For example, floating point operations on an + // ISA with no IEEE 754 support. + // + // The iteration scheme used here is not going to cut it. Transforming + // instructions involves changing `function.layout` which is impossiblr while + // it is referenced by the two iterators. We need a layout cursor that can + // maintain a position *and* permit inserting and replacing instructions. + } + } + } + } +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index de6cf109b5..7285f95602 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -16,6 +16,7 @@ pub mod dominator_tree; pub mod entity_map; pub mod settings; pub mod verifier; +pub mod legalizer; mod write; mod constant_hash; From 64490a3587e1b14faff9334131411d0eaef039bf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 12:57:29 -0700 Subject: [PATCH 316/968] Don't have keywords in the lexer and parser. Instead of recognizing "function" as a keyword, simply match it as a context-sensitive keyword in the parser outside functions. --- filetests/parser/keywords.cton | 5 +++++ src/libreader/lexer.rs | 29 +++++++---------------------- src/libreader/parser.rs | 2 +- 3 files changed, 13 insertions(+), 23 deletions(-) create mode 100644 filetests/parser/keywords.cton diff --git a/filetests/parser/keywords.cton b/filetests/parser/keywords.cton new file mode 100644 index 0000000000..eb15f2624d --- /dev/null +++ b/filetests/parser/keywords.cton @@ -0,0 +1,5 @@ +test cat + +; 'function' is not a keyword, and can be used as the name of a function too. +function function() {} +; check: function function() diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 7631e306ad..c57b8290cf 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -26,7 +26,6 @@ pub enum Token<'a> { Colon, // ':' Equal, // '=' Arrow, // '->' - Function, // 'function' Float(&'a str), // Floating point immediate Integer(&'a str), // Integer immediate Type(types::Type), // i32, f32, b32x4, ... @@ -235,8 +234,6 @@ impl<'a> Lexer<'a> { // Scan a 'word', which is an identifier-like sequence of characters beginning with '_' or an // alphabetic char, followed by zero or more alphanumeric or '_' characters. - // - // fn scan_word(&mut self) -> Result, LocatedError> { let begin = self.pos; let loc = self.loc(); @@ -252,25 +249,13 @@ impl<'a> Lexer<'a> { } } let text = &self.source[begin..self.pos]; + let (prefix, suffix) = text.split_at(text.len() - trailing_digits); - match if trailing_digits == 0 { - Self::keyword(text) - } else { - // Look for numbered well-known entities like ebb15, v45, ... - let (prefix, suffix) = text.split_at(text.len() - trailing_digits); - Self::numbered_entity(prefix, suffix).or_else(|| Self::value_type(text, prefix, suffix)) - } { - Some(t) => token(t, loc), - None => token(Token::Identifier(text), loc), - } - } - - // Recognize a keyword. - fn keyword(text: &str) -> Option> { - match text { - "function" => Some(Token::Function), - _ => None, - } + // Look for numbered well-known entities like ebb15, v45, ... + token(Self::numbered_entity(prefix, suffix) + .or_else(|| Self::value_type(text, prefix, suffix)) + .unwrap_or(Token::Identifier(text)), + loc) } // If prefix is a well-known entity prefix and suffix is a valid entity number, return the @@ -458,7 +443,7 @@ mod tests { token(Token::Value(Value::table_with_number(1).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("vxvx4"), 1)); assert_eq!(lex.next(), token(Token::Identifier("function0"), 1)); - assert_eq!(lex.next(), token(Token::Function, 1)); + assert_eq!(lex.next(), token(Token::Identifier("function"), 1)); assert_eq!(lex.next(), token(Token::Type(types::B1), 1)); assert_eq!(lex.next(), token(Token::Type(types::I32.by(4).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("f32x5"), 1)); diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 45177aace7..6f65cbafa3 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -454,7 +454,7 @@ impl<'a> Parser<'a> { // function-spec ::= * "function" name signature // fn parse_function_spec(&mut self) -> Result<(Location, FunctionName, Signature)> { - try!(self.match_token(Token::Function, "expected 'function' keyword")); + try!(self.match_identifier("function", "expected 'function'")); let location = self.loc; // function-spec ::= "function" * name signature From 26332f6f918230a48fd1ff6155c38ab46d400afc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 13:20:33 -0700 Subject: [PATCH 317/968] Share split_entity_name between lexer and sourcemap. There's only one way of parsing entity names correctly. --- src/libreader/lexer.rs | 91 ++++++++++++++++++++++++++------------ src/libreader/sourcemap.rs | 43 +----------------- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index c57b8290cf..3454225dc8 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -6,6 +6,7 @@ // ====--------------------------------------------------------------------------------------====// use std::str::CharIndices; +use std::u16; use cretonne::ir::types; use cretonne::ir::{Value, Ebb}; use error::Location; @@ -72,6 +73,23 @@ fn error<'a>(error: Error, loc: Location) -> Result, LocatedErr }) } +/// Get the number of decimal digits at the end of `s`. +fn trailing_digits(s: &str) -> usize { + // It's faster to iterate backwards over bytes, and we're only counting ASCII digits. + s.as_bytes().iter().rev().cloned().take_while(|&b| b'0' <= b && b <= b'9').count() +} + +/// Pre-parse a supposed entity name by splitting it into two parts: A head of lowercase ASCII +/// letters and numeric tail. +pub fn split_entity_name(name: &str) -> Option<(&str, u32)> { + let (head, tail) = name.split_at(name.len() - trailing_digits(name)); + if tail.len() > 1 && tail.starts_with('0') { + None + } else { + tail.parse().ok().map(|n| (head, n)) + } +} + /// Lexical analysis. /// /// A `Lexer` reads text from a `&str` and provides a sequence of tokens. @@ -237,52 +255,42 @@ impl<'a> Lexer<'a> { fn scan_word(&mut self) -> Result, LocatedError> { let begin = self.pos; let loc = self.loc(); - let mut trailing_digits = 0usize; assert!(self.lookahead == Some('_') || self.lookahead.unwrap().is_alphabetic()); loop { match self.next_ch() { - Some(ch) if ch.is_digit(10) => trailing_digits += 1, - Some('_') => trailing_digits = 0, - Some(ch) if ch.is_alphabetic() => trailing_digits = 0, + Some('_') => {} + Some(ch) if ch.is_alphanumeric() => {} _ => break, } } let text = &self.source[begin..self.pos]; - let (prefix, suffix) = text.split_at(text.len() - trailing_digits); // Look for numbered well-known entities like ebb15, v45, ... - token(Self::numbered_entity(prefix, suffix) - .or_else(|| Self::value_type(text, prefix, suffix)) + token(split_entity_name(text) + .and_then(|(prefix, number)| { + Self::numbered_entity(prefix, number) + .or_else(|| Self::value_type(text, prefix, number)) + }) .unwrap_or(Token::Identifier(text)), loc) } // If prefix is a well-known entity prefix and suffix is a valid entity number, return the // decoded token. - fn numbered_entity(prefix: &str, suffix: &str) -> Option> { - // Reject non-canonical numbers like v0001. - if suffix.len() > 1 && suffix.starts_with('0') { - return None; - } - - let value: u32 = match suffix.parse() { - Ok(v) => v, - _ => return None, - }; - + fn numbered_entity(prefix: &str, number: u32) -> Option> { match prefix { - "v" => Value::direct_with_number(value).map(|v| Token::Value(v)), - "vx" => Value::table_with_number(value).map(|v| Token::Value(v)), - "ebb" => Ebb::with_number(value).map(|ebb| Token::Ebb(ebb)), - "ss" => Some(Token::StackSlot(value)), - "jt" => Some(Token::JumpTable(value)), + "v" => Value::direct_with_number(number).map(|v| Token::Value(v)), + "vx" => Value::table_with_number(number).map(|v| Token::Value(v)), + "ebb" => Ebb::with_number(number).map(|ebb| Token::Ebb(ebb)), + "ss" => Some(Token::StackSlot(number)), + "jt" => Some(Token::JumpTable(number)), _ => None, } } // Recognize a scalar or vector type. - fn value_type(text: &str, prefix: &str, suffix: &str) -> Option> { + fn value_type(text: &str, prefix: &str, number: u32) -> Option> { let is_vector = prefix.ends_with('x'); let scalar = if is_vector { &prefix[0..prefix.len() - 1] @@ -304,11 +312,11 @@ impl<'a> Lexer<'a> { _ => return None, }; if is_vector { - let lanes: u16 = match suffix.parse() { - Ok(v) => v, - _ => return None, - }; - base_type.by(lanes).map(|t| Token::Type(t)) + if number <= u16::MAX as u32 { + base_type.by(number as u16).map(|t| Token::Type(t)) + } else { + None + } } else { Some(Token::Type(base_type)) } @@ -356,11 +364,36 @@ impl<'a> Lexer<'a> { #[cfg(test)] mod tests { + use super::trailing_digits; use super::*; use cretonne::ir::types; use cretonne::ir::{Value, Ebb}; use error::Location; + #[test] + fn digits() { + assert_eq!(trailing_digits(""), 0); + assert_eq!(trailing_digits("x"), 0); + assert_eq!(trailing_digits("0x"), 0); + assert_eq!(trailing_digits("x1"), 1); + assert_eq!(trailing_digits("1x1"), 1); + assert_eq!(trailing_digits("1x01"), 2); + } + + #[test] + fn entity_name() { + assert_eq!(split_entity_name(""), None); + assert_eq!(split_entity_name("x"), None); + assert_eq!(split_entity_name("x+"), None); + assert_eq!(split_entity_name("x+1"), Some(("x+", 1))); + assert_eq!(split_entity_name("x-1"), Some(("x-", 1))); + assert_eq!(split_entity_name("1"), Some(("", 1))); + assert_eq!(split_entity_name("x1"), Some(("x", 1))); + assert_eq!(split_entity_name("xy0"), Some(("xy", 0))); + // Reject this non-canonical form. + assert_eq!(split_entity_name("inst01"), None); + } + fn token<'a>(token: Token<'a>, line: usize) -> Option, LocatedError>> { Some(super::token(token, Location { line_number: line })) } diff --git a/src/libreader/sourcemap.rs b/src/libreader/sourcemap.rs index 82f5ece750..2bff8e4b81 100644 --- a/src/libreader/sourcemap.rs +++ b/src/libreader/sourcemap.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; use cretonne::ir::{StackSlot, JumpTable, Ebb, Value, Inst}; use cretonne::ir::entities::AnyEntity; use error::{Result, Location}; +use lexer::split_entity_name; /// Mapping from source entity names to entity references that are valid in the parsed function. #[derive(Debug)] @@ -114,23 +115,6 @@ impl SourceMap { } } -/// Get the number of decimal digits at the end of `s`. -fn trailing_digits(s: &str) -> usize { - // It's faster to iterate backwards over bytes, and we're only counting ASCII digits. - s.as_bytes().iter().rev().cloned().take_while(|&b| b'0' <= b && b <= b'9').count() -} - -/// Pre-parse a supposed entity name by splitting it into two parts: A head of lowercase ASCII -/// letters and numeric tail. -fn split_entity_name(name: &str) -> Option<(&str, u32)> { - let (head, tail) = name.split_at(name.len() - trailing_digits(name)); - if tail.len() > 1 && tail.starts_with('0') { - None - } else { - tail.parse().ok().map(|n| (head, n)) - } -} - /// Interface for mutating a source map. /// @@ -211,33 +195,8 @@ impl MutableSourceMap for SourceMap { #[cfg(test)] mod tests { - use super::{trailing_digits, split_entity_name}; use parse_test; - #[test] - fn digits() { - assert_eq!(trailing_digits(""), 0); - assert_eq!(trailing_digits("x"), 0); - assert_eq!(trailing_digits("0x"), 0); - assert_eq!(trailing_digits("x1"), 1); - assert_eq!(trailing_digits("1x1"), 1); - assert_eq!(trailing_digits("1x01"), 2); - } - - #[test] - fn entity_name() { - assert_eq!(split_entity_name(""), None); - assert_eq!(split_entity_name("x"), None); - assert_eq!(split_entity_name("x+"), None); - assert_eq!(split_entity_name("x+1"), Some(("x+", 1))); - assert_eq!(split_entity_name("x-1"), Some(("x-", 1))); - assert_eq!(split_entity_name("1"), Some(("", 1))); - assert_eq!(split_entity_name("x1"), Some(("x", 1))); - assert_eq!(split_entity_name("xy0"), Some(("xy", 0))); - // Reject this non-canonical form. - assert_eq!(split_entity_name("inst01"), None); - } - #[test] fn details() { let tf = parse_test("function detail() { From 83adf341ec059547e69e7ceb68f24dd448d3ff1b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 16:09:45 -0700 Subject: [PATCH 318/968] Allow settings::Builder to be reused. When constructing the Flags object from the Builder, don't consume it, but take a reference instead. This makes it possible for the parser to accept multiple 'set' lines and apply them to different ISA specifications. --- meta/gen_settings.py | 8 ++++---- src/libcretonne/isa/mod.rs | 6 +++--- src/libcretonne/isa/riscv/mod.rs | 8 ++++---- src/libcretonne/isa/riscv/settings.rs | 12 ++++++------ src/libcretonne/settings.rs | 12 ++++++------ 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index 588c2cf798..c3c4710ea3 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -183,18 +183,18 @@ def gen_constructor(sgrp, parent, fmt): """ with fmt.indented('impl Flags {', '}'): - args = 'builder: Builder' + args = 'builder: &Builder' if sgrp.parent: p = sgrp.parent args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args) with fmt.indented( 'pub fn new({}) -> Flags {{'.format(args), '}'): - fmt.line('let bvec = builder.finish("{}");'.format(sgrp.name)) + fmt.line('let bvec = builder.state_for("{}");'.format(sgrp.name)) fmt.line('let mut bytes = [0; {}];'.format(sgrp.byte_size())) fmt.line('assert_eq!(bvec.len(), {});'.format(sgrp.settings_size)) with fmt.indented( - 'for (i, b) in bvec.into_iter().enumerate() {', '}'): - fmt.line('bytes[i] = b;') + 'for (i, b) in bvec.iter().enumerate() {', '}'): + fmt.line('bytes[i] = *b;') # Stop here without predicates. if len(sgrp.predicate_number) == sgrp.boolean_settings: diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index 1d788ffff1..a43b1a5cb4 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -24,7 +24,7 @@ //! use cretonne::isa; //! //! let shared_builder = settings::builder(); -//! let shared_flags = settings::Flags::new(shared_builder); +//! let shared_flags = settings::Flags::new(&shared_builder); //! //! match isa::lookup("riscv") { //! None => { @@ -66,14 +66,14 @@ fn riscv_builder() -> Option { /// Modify the ISA-specific settings before creating the `TargetIsa` trait object with `finish`. pub struct Builder { setup: settings::Builder, - constructor: fn(settings::Flags, settings::Builder) -> Box, + constructor: fn(settings::Flags, &settings::Builder) -> Box, } impl Builder { /// Combine the ISA-specific settings with the provided ISA-independent settings and allocate a /// fully configured `TargetIsa` trait object. pub fn finish(self, shared_flags: settings::Flags) -> Box { - (self.constructor)(shared_flags, self.setup) + (self.constructor)(shared_flags, &self.setup) } } diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index b1ee92a4a9..ea49358aa6 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -24,7 +24,7 @@ pub fn isa_builder() -> IsaBuilder { } fn isa_constructor(shared_flags: shared_settings::Flags, - builder: shared_settings::Builder) + builder: &shared_settings::Builder) -> Box { let level1 = if shared_flags.is_64bit() { &enc_tables::LEVEL1_RV64[..] @@ -72,7 +72,7 @@ mod tests { fn test_64bitenc() { let mut shared_builder = settings::builder(); shared_builder.set_bool("is_64bit", true).unwrap(); - let shared_flags = settings::Flags::new(shared_builder); + let shared_flags = settings::Flags::new(&shared_builder); let isa = isa::lookup("riscv").unwrap().finish(shared_flags); let mut dfg = DataFlowGraph::new(); @@ -119,7 +119,7 @@ mod tests { fn test_32bitenc() { let mut shared_builder = settings::builder(); shared_builder.set_bool("is_64bit", false).unwrap(); - let shared_flags = settings::Flags::new(shared_builder); + let shared_flags = settings::Flags::new(&shared_builder); let isa = isa::lookup("riscv").unwrap().finish(shared_flags); let mut dfg = DataFlowGraph::new(); @@ -174,7 +174,7 @@ mod tests { fn test_rv32m() { let mut shared_builder = settings::builder(); shared_builder.set_bool("is_64bit", false).unwrap(); - let shared_flags = settings::Flags::new(shared_builder); + let shared_flags = settings::Flags::new(&shared_builder); // Set the supports_m stting which in turn enables the use_m predicate that unlocks // encodings for imul. diff --git a/src/libcretonne/isa/riscv/settings.rs b/src/libcretonne/isa/riscv/settings.rs index ae765d9c15..57d8689ed1 100644 --- a/src/libcretonne/isa/riscv/settings.rs +++ b/src/libcretonne/isa/riscv/settings.rs @@ -14,9 +14,9 @@ mod tests { #[test] fn display_default() { - let shared = settings::Flags::new(settings::builder()); + let shared = settings::Flags::new(&settings::builder()); let b = builder(); - let f = Flags::new(&shared, b); + let f = Flags::new(&shared, &b); assert_eq!(f.to_string(), "[riscv]\n\ supports_m = false\n\ @@ -30,20 +30,20 @@ mod tests { #[test] fn predicates() { - let shared = settings::Flags::new(settings::builder()); + let shared = settings::Flags::new(&settings::builder()); let mut b = builder(); b.set_bool("supports_f", true).unwrap(); b.set_bool("supports_d", true).unwrap(); - let f = Flags::new(&shared, b); + let f = Flags::new(&shared, &b); assert_eq!(f.full_float(), true); let mut sb = settings::builder(); sb.set_bool("enable_simd", false).unwrap(); - let shared = settings::Flags::new(sb); + let shared = settings::Flags::new(&sb); let mut b = builder(); b.set_bool("supports_f", true).unwrap(); b.set_bool("supports_d", true).unwrap(); - let f = Flags::new(&shared, b); + let f = Flags::new(&shared, &b); assert_eq!(f.full_float(), false); } } diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index c7c0d1eb30..37d09d2ba8 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -16,7 +16,7 @@ //! let mut b = settings::builder(); //! b.set("opt_level", "fastest"); //! -//! let f = settings::Flags::new(b); +//! let f = settings::Flags::new(&b); //! assert_eq!(f.opt_level(), settings::OptLevel::Fastest); //! ``` @@ -57,9 +57,9 @@ impl Builder { } /// Extract contents of builder once everything is configured. - pub fn finish(self, name: &str) -> Vec { + pub fn state_for(&self, name: &str) -> &[u8] { assert_eq!(name, self.template.name); - self.bytes + &self.bytes[..] } /// Set the value of a single bit. @@ -256,7 +256,7 @@ mod tests { #[test] fn display_default() { let b = builder(); - let f = Flags::new(b); + let f = Flags::new(&b); assert_eq!(f.to_string(), "[shared]\n\ opt_level = \"default\"\n\ @@ -275,7 +275,7 @@ mod tests { assert_eq!(b.set_bool("enable_simd", true), Ok(())); assert_eq!(b.set_bool("enable_simd", false), Ok(())); - let f = Flags::new(b); + let f = Flags::new(&b); assert_eq!(f.enable_simd(), false); } @@ -289,7 +289,7 @@ mod tests { assert_eq!(b.set("opt_level", "best"), Ok(())); assert_eq!(b.set("enable_simd", "0"), Ok(())); - let f = Flags::new(b); + let f = Flags::new(&b); assert_eq!(f.enable_simd(), false); assert_eq!(f.opt_level(), super::OptLevel::Best); } From 36b143df995bc5ef27720b0ef70a053f5b623ddb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Sep 2016 13:38:36 -0700 Subject: [PATCH 319/968] Parse ISA specifications between test commands and functions. Some tests are only applicable to specific ISAs. This can be indicated with an ISA specification: test legalizer isa riscv function foo() { .... } The ISA specifications have the same format as the test lines: The name of the ISA following by optional settings until the end of the line. Also parse `set` commands mixed in with the `isa` commands. These are used to set ISA-independent settings as defined in meta/cretonne/settings.py. --- src/libcretonne/isa/mod.rs | 3 ++ src/libcretonne/isa/riscv/mod.rs | 4 ++ src/libreader/isaspec.rs | 49 +++++++++++++++++ src/libreader/lib.rs | 1 + src/libreader/parser.rs | 91 ++++++++++++++++++++++++++++++++ src/libreader/testfile.rs | 9 ++-- 6 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/libreader/isaspec.rs diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index a43b1a5cb4..fbde0208e6 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -88,6 +88,9 @@ impl settings::Configurable for Builder { } pub trait TargetIsa { + /// Get the name of this ISA. + fn name(&self) -> &'static str; + /// Encode an instruction after determining it is legal. /// /// If `inst` can legally be encoded in this ISA, produce the corresponding `Encoding` object. diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index ea49358aa6..62bca868a6 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -39,6 +39,10 @@ fn isa_constructor(shared_flags: shared_settings::Flags, } impl TargetIsa for Isa { + fn name(&self) -> &'static str { + "riscv" + } + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { lookup_enclist(inst.first_type(), inst.opcode(), diff --git a/src/libreader/isaspec.rs b/src/libreader/isaspec.rs new file mode 100644 index 0000000000..706c081e3a --- /dev/null +++ b/src/libreader/isaspec.rs @@ -0,0 +1,49 @@ +//! Parsed representation of `set` and `isa` commands. +//! +//! A test case file can contain `set` commands that set ISA-independent settings, and it can +//! contain `isa` commands that select an ISA and applies ISA-specific settings. +//! +//! If a test case file contains `isa` commands, the tests will only be run against the specified +//! ISAs. If the file contains no `isa` commands, the tests will be run against all supported ISAs. + +use cretonne::settings::{Flags, Configurable, Error as SetError}; +use cretonne::isa::TargetIsa; +use error::{Result, Location}; +use testcommand::TestOption; + +/// The ISA specifications in a `.cton` file. +pub enum IsaSpec { + /// The parsed file does not contain any `isa` commands, but it may contain `set` commands + /// which are reflected in the finished `Flags` object. + None(Flags), + + /// The parsed file does contains `isa` commands. + /// Each `isa` command is used to configure a `TargetIsa` trait object. + Some(Vec>), +} + +/// Parse an iterator of command line options and apply them to `config`. +pub fn parse_options<'a, I>(iter: I, config: &mut Configurable, loc: &Location) -> Result<()> + where I: Iterator +{ + for opt in iter.map(TestOption::new) { + match opt { + TestOption::Flag(name) => { + match config.set_bool(name, true) { + Ok(_) => {} + Err(SetError::BadName) => return err!(loc, "unknown flag '{}'", opt), + Err(_) => return err!(loc, "not a boolean flag: '{}'", opt), + } + } + TestOption::Value(name, value) => { + match config.set(name, value) { + Ok(_) => {} + Err(SetError::BadName) => return err!(loc, "unknown setting '{}'", opt), + Err(SetError::BadType) => return err!(loc, "invalid setting type: '{}'", opt), + Err(SetError::BadValue) => return err!(loc, "invalid setting value: '{}'", opt), + } + } + } + } + Ok(()) +} diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index f0f04fa381..da3cb35685 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -15,5 +15,6 @@ mod error; mod lexer; mod parser; mod testcommand; +mod isaspec; mod testfile; mod sourcemap; diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 6f65cbafa3..05fa9b831b 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -15,10 +15,13 @@ use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, BranchData, ReturnData}; +use cretonne::isa; +use cretonne::settings; use testfile::{TestFile, Details, Comment}; use error::{Location, Error, Result}; use lexer::{self, Lexer, Token}; use testcommand::TestCommand; +use isaspec; use sourcemap::{SourceMap, MutableSourceMap}; /// Parse the entire `text` into a list of functions. @@ -35,6 +38,7 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { let mut parser = Parser::new(text); Ok(TestFile { commands: parser.parse_test_commands(), + isa_spec: try!(parser.parse_isa_specs()), functions: try!(parser.parse_function_list()), }) } @@ -397,6 +401,63 @@ impl<'a> Parser<'a> { list } + /// Parse a list of ISA specs. + /// + /// Accept a mix of `isa` and `set` command lines. The `set` commands are cumulative. + /// + pub fn parse_isa_specs(&mut self) -> Result { + // Was there any `isa` commands? + let mut seen_isa = false; + // Location of last `set` command since the last `isa`. + let mut last_set_loc = None; + + let mut isas = Vec::new(); + let mut flag_builder = settings::builder(); + + while let Some(Token::Identifier(command)) = self.token() { + match command { + "set" => { + last_set_loc = Some(self.loc); + try!(isaspec::parse_options(self.consume_line().trim().split_whitespace(), + &mut flag_builder, + &self.loc)); + } + "isa" => { + last_set_loc = None; + seen_isa = true; + let loc = self.loc; + // Grab the whole line so the lexer won't go looking for tokens on the + // following lines. + let mut words = self.consume_line().trim().split_whitespace(); + // Look for `isa foo`. + let isa_name = match words.next() { + None => return err!(loc, "expected ISA name"), + Some(w) => w, + }; + let mut isa_builder = match isa::lookup(isa_name) { + None => return err!(loc, "unknown ISA '{}'", isa_name), + Some(b) => b, + }; + // Apply the ISA-specific settings to `isa_builder`. + try!(isaspec::parse_options(words, &mut isa_builder, &self.loc)); + + // Construct a trait object with the aggregrate settings. + isas.push(isa_builder.finish(settings::Flags::new(&flag_builder))); + } + _ => break, + } + } + if !seen_isa { + // No `isa` commands, but we allow for `set` commands. + Ok(isaspec::IsaSpec::None(settings::Flags::new(&flag_builder))) + } else if let Some(loc) = last_set_loc { + err!(loc, + "dangling 'set' command after ISA specification has no effect.") + } else { + Ok(isaspec::IsaSpec::Some(isas)) + } + } + /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. @@ -1115,6 +1176,7 @@ mod tests { use cretonne::ir::types::{self, ArgumentType, ArgumentExtension}; use cretonne::ir::entities::AnyEntity; use testfile::{Details, Comment}; + use isaspec::IsaSpec; use error::Error; #[test] @@ -1246,12 +1308,41 @@ mod tests { let tf = parse_test("; before test cfg option=5 test verify + set enable_float=false function comment() {}") .unwrap(); assert_eq!(tf.commands.len(), 2); assert_eq!(tf.commands[0].command, "cfg"); assert_eq!(tf.commands[1].command, "verify"); + match tf.isa_spec { + IsaSpec::None(s) => assert!(!s.enable_float()), + _ => panic!("unexpected ISAs"), + } assert_eq!(tf.functions.len(), 1); assert_eq!(tf.functions[0].0.name.to_string(), "comment"); } + + #[test] + fn isa_spec() { + assert!(parse_test("isa + function foo() {}") + .is_err()); + + assert!(parse_test("isa riscv + set enable_float=false + function foo() {}") + .is_err()); + + match parse_test("set enable_float=false + isa riscv + function foo() {}") + .unwrap() + .isa_spec { + IsaSpec::None(_) => panic!("Expected some ISA"), + IsaSpec::Some(v) => { + assert_eq!(v.len(), 1); + assert_eq!(v[0].name(), "riscv"); + } + } + } } diff --git a/src/libreader/testfile.rs b/src/libreader/testfile.rs index d63b1f9f9d..30ed59074b 100644 --- a/src/libreader/testfile.rs +++ b/src/libreader/testfile.rs @@ -7,16 +7,19 @@ use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; use testcommand::TestCommand; +use isaspec::IsaSpec; use sourcemap::SourceMap; use error::Location; /// A parsed test case. /// -/// This is the result of parsing a `.cton` file which contains a number of test commands followed -/// by the functions that should be tested. -#[derive(Debug)] +/// This is the result of parsing a `.cton` file which contains a number of test commands and ISA +/// specs followed by the functions that should be tested. pub struct TestFile<'a> { + /// `test foo ...` lines. pub commands: Vec>, + /// `isa bar ...` lines. + pub isa_spec: IsaSpec, pub functions: Vec<(Function, Details<'a>)>, } From 7587a51bd7c2e842c752adebd88c355b84149366 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Sep 2016 12:12:11 -0700 Subject: [PATCH 320/968] Pass flags and target ISAs to filetests. Add a `needs_isa()` method to the SubTest trait, and pass a TargetIsa trait object to those sub-tests that request it. When multiple sub-tests and ISAs are specified, test the cross product. If a sub-test requires an ISA, but none are specified, fail the test. In the future, it may be a good idea to generate a default set of ISAs and test against those. --- src/libcretonne/isa/mod.rs | 3 ++ src/libcretonne/isa/riscv/mod.rs | 4 +++ src/libreader/lib.rs | 1 + src/tools/filetest/runone.rs | 61 ++++++++++++++++++++++++++++---- src/tools/filetest/subtest.rs | 14 ++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/libcretonne/isa/mod.rs b/src/libcretonne/isa/mod.rs index fbde0208e6..67d5a91c29 100644 --- a/src/libcretonne/isa/mod.rs +++ b/src/libcretonne/isa/mod.rs @@ -91,6 +91,9 @@ pub trait TargetIsa { /// Get the name of this ISA. fn name(&self) -> &'static str; + /// Get the ISA-independent flags that were used to make this trait object. + fn flags(&self) -> &settings::Flags; + /// Encode an instruction after determining it is legal. /// /// If `inst` can legally be encoded in this ISA, produce the corresponding `Encoding` object. diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 62bca868a6..2ff352918d 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -43,6 +43,10 @@ impl TargetIsa for Isa { "riscv" } + fn flags(&self) -> &shared_settings::Flags { + &self.shared_flags + } + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { lookup_enclist(inst.first_type(), inst.opcode(), diff --git a/src/libreader/lib.rs b/src/libreader/lib.rs index da3cb35685..ef81e7f210 100644 --- a/src/libreader/lib.rs +++ b/src/libreader/lib.rs @@ -9,6 +9,7 @@ pub use error::{Location, Result, Error}; pub use parser::{parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; pub use testfile::{TestFile, Details}; +pub use isaspec::IsaSpec; pub use sourcemap::SourceMap; mod error; diff --git a/src/tools/filetest/runone.rs b/src/tools/filetest/runone.rs index e0d1e9d17b..d0199c45cf 100644 --- a/src/tools/filetest/runone.rs +++ b/src/tools/filetest/runone.rs @@ -1,11 +1,14 @@ //! Run the tests in a single test file. -use std::borrow::{Borrow, Cow}; +use std::borrow::Cow; use std::path::Path; use std::time; use cretonne::ir::Function; +use cretonne::isa::TargetIsa; +use cretonne::settings::Flags; use cretonne::verify_function; use cton_reader::parse_test; +use cton_reader::IsaSpec; use utils::read_to_string; use filetest::{TestResult, new_subtest}; use filetest::subtest::{SubTest, Context, Result}; @@ -20,16 +23,27 @@ pub fn run(path: &Path) -> TestResult { if testfile.functions.is_empty() { return Err("no functions found".to_string()); } + // Parse the test commands. let mut tests = try!(testfile.commands.iter().map(new_subtest).collect::>>()); + // Flags to use for those tests that don't need an ISA. + // This is the cumulative effect of all the `set` commands in the file. + let flags = match testfile.isa_spec { + IsaSpec::None(ref f) => f, + IsaSpec::Some(ref v) => v.last().expect("Empty ISA list").flags(), + }; + // Sort the tests so the mutators are at the end, and those that don't need the verifier are at // the front. tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); + // Expand the tests into (test, flags, isa) tuples. + let mut tuples = try!(test_tuples(&tests, &testfile.isa_spec, flags)); + // Isolate the last test in the hope that this is the only mutating test. // If so, we can completely avoid cloning functions. - let last_test = match tests.pop() { + let last_tuple = match tuples.pop() { None => return Err("no test commands found".to_string()), Some(t) => t, }; @@ -38,14 +52,16 @@ pub fn run(path: &Path) -> TestResult { let mut context = Context { details: details, verified: false, + flags: flags, + isa: None, }; - for test in &tests { - try!(run_one_test(test.borrow(), Cow::Borrowed(&func), &mut context)); + for tuple in &tuples { + try!(run_one_test(*tuple, Cow::Borrowed(&func), &mut context)); } // Run the last test with an owned function which means it won't need to clone it before // mutating. - try!(run_one_test(last_test.borrow(), Cow::Owned(func), &mut context)); + try!(run_one_test(last_tuple, Cow::Owned(func), &mut context)); } @@ -53,9 +69,42 @@ pub fn run(path: &Path) -> TestResult { Ok(started.elapsed()) } -fn run_one_test(test: &SubTest, func: Cow, context: &mut Context) -> Result<()> { +// Given a slice of tests, generate a vector of (test, flags, isa) tuples. +fn test_tuples<'a>(tests: &'a [Box], + isa_spec: &'a IsaSpec, + no_isa_flags: &'a Flags) + -> Result)>> { + let mut out = Vec::new(); + for test in tests { + if test.needs_isa() { + match *isa_spec { + IsaSpec::None(_) => { + // TODO: Generate a list of default ISAs. + return Err(format!("test {} requires an ISA", test.name())); + } + IsaSpec::Some(ref isas) => { + for isa in isas { + out.push((&**test, isa.flags(), Some(&**isa))); + } + } + } + } else { + out.push((&**test, no_isa_flags, None)); + } + } + Ok(out) +} + +fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), + func: Cow, + context: &mut Context<'a>) + -> Result<()> { + let (test, flags, isa) = tuple; let name = format!("{}({})", test.name(), func.name); + context.flags = flags; + context.isa = isa; + // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { try!(verify_function(&func).map_err(|e| e.to_string())); diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index b958193cd8..fee9bbef85 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -3,6 +3,8 @@ use std::result; use std::borrow::Cow; use cretonne::ir::Function; +use cretonne::isa::TargetIsa; +use cretonne::settings::Flags; use cton_reader::Details; use filecheck::{self, CheckerBuilder, Checker, Value as FCValue}; @@ -15,6 +17,13 @@ pub struct Context<'a> { /// Was the function verified before running this test? pub verified: bool, + + /// ISA-independent flags for this test. + pub flags: &'a Flags, + + /// Target ISA to test against. Only present for sub-tests whose `needs_isa` method returned + /// true. + pub isa: Option<&'a TargetIsa>, } /// Common interface for implementations of test commands. @@ -36,6 +45,11 @@ pub trait SubTest { false } + /// Does this test need a `TargetIsa` trait object? + fn needs_isa(&self) -> bool { + false + } + /// Run this test on `func`. fn run(&self, func: Cow, context: &Context) -> Result<()>; } From 1c4eb44ef767ee3dc6c4a4644d5dd8cc38e46caf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Sep 2016 16:53:03 -0700 Subject: [PATCH 321/968] Expose Vec::get() in EntityMap. --- src/libcretonne/entity_map.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libcretonne/entity_map.rs b/src/libcretonne/entity_map.rs index 74c3cf5c46..ba2075370f 100644 --- a/src/libcretonne/entity_map.rs +++ b/src/libcretonne/entity_map.rs @@ -70,6 +70,11 @@ impl EntityMap k.index() < self.elems.len() } + /// Get the element at `k` if it exists. + pub fn get(&self, k: K) -> Option<&V> { + self.elems.get(k.index()) + } + /// Is this map completely empty? pub fn is_empty(&self) -> bool { self.elems.is_empty() From ea901653dabac1548f5054c088f88c91d66e064e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Sep 2016 16:57:08 -0700 Subject: [PATCH 322/968] Print encodings as [R#10c] instead of [R/10c]. The # is a more conventional prefix for hexadecimal, and when ISA information is not available, there may be a decimal number in front which would be confusing. So prefer [1#10c] for the ISA-less encoding format. Here '1' is decimal and '#10c' is hexadecimal. --- meta/cretonne/__init__.py | 2 +- src/libcretonne/isa/encoding.rs | 4 ++-- src/libcretonne/isa/riscv/mod.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 82b0a02be6..90cbb2f983 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -1205,7 +1205,7 @@ class Encoding(object): self.isap = And.combine(recipe.isap, isap) def __str__(self): - return '[{}/{:02x}]'.format(self.recipe, self.encbits) + return '[{}#{:02x}]'.format(self.recipe, self.encbits) def ctrl_typevar(self): """ diff --git a/src/libcretonne/isa/encoding.rs b/src/libcretonne/isa/encoding.rs index 0bb1f6cc62..4c8e33a764 100644 --- a/src/libcretonne/isa/encoding.rs +++ b/src/libcretonne/isa/encoding.rs @@ -50,7 +50,7 @@ impl Default for Encoding { impl fmt::Display for Encoding { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_legal() { - write!(f, "#{}/{:02x}", self.recipe, self.bits) + write!(f, "{}#{:02x}", self.recipe, self.bits) } else { write!(f, "-") } @@ -68,7 +68,7 @@ impl fmt::Display for DisplayEncoding { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.encoding.is_legal() { write!(f, - "{}/{:02x}", + "{}#{:02x}", self.recipe_names[self.encoding.recipe()], self.encoding.bits) } else { diff --git a/src/libcretonne/isa/riscv/mod.rs b/src/libcretonne/isa/riscv/mod.rs index 2ff352918d..cdc49f6973 100644 --- a/src/libcretonne/isa/riscv/mod.rs +++ b/src/libcretonne/isa/riscv/mod.rs @@ -97,7 +97,7 @@ mod tests { }; // ADDI is I/0b00100 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64).unwrap()), "I/04"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64).unwrap()), "I#04"); // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -119,7 +119,7 @@ mod tests { }; // ADDIW is I/0b00110 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I/06"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I#06"); } // Same as above, but for RV32. @@ -166,7 +166,7 @@ mod tests { }; // ADDI is I/0b00100 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I/04"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I#04"); // Create an imul.i32 which is encodable in RV32, but only when use_m is true. let mul32 = InstructionData::Binary { @@ -201,6 +201,6 @@ mod tests { ty: types::I32, args: [arg32, arg32], }; - assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32).unwrap()), "R/10c"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32).unwrap()), "R#10c"); } } From 6a71613d9283f4b93fcff3412d03a127a50b7903 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Sep 2016 17:21:13 -0700 Subject: [PATCH 323/968] Write out encoding annotations on instructions. All instructions with associated encodings are now annotated with encoding information in a column before the code. When write_function() is givan a TargetIsa reference, the annotations use ISA-specific names. Otherwise everything is numeric. --- src/libcretonne/ir/function.rs | 4 ++-- src/libcretonne/lib.rs | 1 + src/libcretonne/write.rs | 38 +++++++++++++++++++++++++++------- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/libcretonne/ir/function.rs b/src/libcretonne/ir/function.rs index 3ff5aea00f..844101bf95 100644 --- a/src/libcretonne/ir/function.rs +++ b/src/libcretonne/ir/function.rs @@ -69,12 +69,12 @@ impl Function { impl Display for Function { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write_function(fmt, self) + write_function(fmt, self, None) } } impl Debug for Function { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write_function(fmt, self) + write_function(fmt, self, None) } } diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 7285f95602..f0a328ba8f 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -6,6 +6,7 @@ // ====------------------------------------------------------------------------------------==== // pub use verifier::verify_function; +pub use write::write_function; pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index c17025d1a8..bfdd13b32f 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -5,11 +5,13 @@ //! `cretonne-reader` crate. use ir::{Function, Ebb, Inst, Value, Type}; +use isa::TargetIsa; use std::fmt::{Result, Error, Write}; use std::result; /// Write `func` to `w` as equivalent text. -pub fn write_function(w: &mut Write, func: &Function) -> Result { +/// Use `isa` to emit ISA-dependent annotations. +pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) -> Result { try!(write_spec(w, func)); try!(writeln!(w, " {{")); let mut any = try!(write_preamble(w, func)); @@ -17,7 +19,7 @@ pub fn write_function(w: &mut Write, func: &Function) -> Result { if any { try!(writeln!(w, "")); } - try!(write_ebb(w, func, ebb)); + try!(write_ebb(w, func, isa, ebb)); any = true; } writeln!(w, "}}") @@ -67,6 +69,11 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // ebb10(vx4: f64, vx5: b1): // + // If we're writing encoding annotations, shift by 20. + if !func.encodings.is_empty() { + try!(write!(w, " ")); + } + let mut args = func.dfg.ebb_args(ebb); match args.next() { None => return writeln!(w, "{}:", ebb), @@ -83,10 +90,10 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { writeln!(w, "):") } -pub fn write_ebb(w: &mut Write, func: &Function, ebb: Ebb) -> Result { +pub fn write_ebb(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, ebb: Ebb) -> Result { try!(write_ebb_header(w, func, ebb)); for inst in func.layout.ebb_insts(ebb) { - try!(write_instruction(w, func, inst)); + try!(write_instruction(w, func, isa, inst)); } Ok(()) } @@ -127,10 +134,27 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { Some(rtype) } -pub fn write_instruction(w: &mut Write, func: &Function, inst: Inst) -> Result { - try!(write!(w, " ")); +fn write_instruction(w: &mut Write, + func: &Function, + isa: Option<&TargetIsa>, + inst: Inst) + -> Result { + // Write out encoding info. + if let Some(enc) = func.encodings.get(inst).cloned() { + let mut s = String::with_capacity(16); + if let Some(isa) = isa { + try!(write!(s, "[{}]", isa.display_enc(enc))); + } else { + try!(write!(s, "[{}]", enc)); + } + // Align instruction following ISA annotation to col 24. + try!(write!(w, "{:23} ", s)); + } else { + // No annotations, simply indent by 4. + try!(write!(w, " ")); + } - // First write out the result values, if any. + // Write out the result values, if any. let mut has_results = false; for r in func.dfg.inst_results(inst) { if !has_results { From 65f69fb0885a118be2dc83837c74ae72ae4416de Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Sep 2016 13:32:46 -0700 Subject: [PATCH 324/968] Add a 'test legaliser' filetest command. This test command sends each function through legalize_function() and then filecheck. --- filetests/isa/riscv/encoding.cton | 19 +++++++++++++ src/libcretonne/lib.rs | 3 ++- src/tools/filetest/legalizer.rs | 45 +++++++++++++++++++++++++++++++ src/tools/filetest/mod.rs | 2 ++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 filetests/isa/riscv/encoding.cton create mode 100644 src/tools/filetest/legalizer.rs diff --git a/filetests/isa/riscv/encoding.cton b/filetests/isa/riscv/encoding.cton new file mode 100644 index 0000000000..2ba06007bb --- /dev/null +++ b/filetests/isa/riscv/encoding.cton @@ -0,0 +1,19 @@ +test legalizer +isa riscv supports_m=1 + +function int32(i32, i32) { +ebb0(v1: i32, v2: i32): + v10 = iadd v1, v2 + ; check: [R#0c] + ; sameln: $v10 = iadd + + v11 = isub v1, v2 + ; check: [R#200c] + ; sameln: $v11 = isub + + v12 = imul v1, v2 + ; check: [R#10c] + ; sameln: $v12 = imul + + return +} diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index f0a328ba8f..26a87bd223 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -7,6 +7,7 @@ pub use verifier::verify_function; pub use write::write_function; +pub use legalizer::legalize_function; pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -17,11 +18,11 @@ pub mod dominator_tree; pub mod entity_map; pub mod settings; pub mod verifier; -pub mod legalizer; mod write; mod constant_hash; mod predicates; +mod legalizer; #[cfg(test)] pub mod test_utils; diff --git a/src/tools/filetest/legalizer.rs b/src/tools/filetest/legalizer.rs new file mode 100644 index 0000000000..ea53a703b7 --- /dev/null +++ b/src/tools/filetest/legalizer.rs @@ -0,0 +1,45 @@ +//! Test command for checking the IL legalizer. +//! +//! The `test legalizer` test command runs each function through `legalize_function()` and sends +//! the result to filecheck. + +use std::borrow::Cow; +use cretonne::{legalize_function, write_function}; +use cretonne::ir::Function; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result, run_filecheck}; + +struct TestLegalizer; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "legalizer"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestLegalizer)) + } +} + +impl SubTest for TestLegalizer { + fn name(&self) -> Cow { + Cow::from("legalizer") + } + + fn is_mutating(&self) -> bool { + true + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let mut func = func.into_owned(); + let isa = context.isa.expect("legalizer needs an ISA"); + legalize_function(&mut func, isa); + + let mut text = String::new(); + try!(write_function(&mut text, &func, Some(isa)).map_err(|e| e.to_string())); + run_filecheck(&text, context) + } +} diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index a924cb04eb..28a9b94d45 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -17,6 +17,7 @@ mod runone; mod concurrent; mod domtree; mod verifier; +mod legalizer; /// The result of running the test in a file. pub type TestResult = Result; @@ -55,6 +56,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "print-cfg" => print_cfg::subtest(parsed), "domtree" => domtree::subtest(parsed), "verifier" => verifier::subtest(parsed), + "legalizer" => legalizer::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } From 0dd16a360d15e6c24c12828ced40d209e52c1d7f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 22 Sep 2016 14:07:31 -0700 Subject: [PATCH 325/968] Basic *.cton syntax mode for Vim. --- misc/vim/ftdetect/cton.vim | 1 + misc/vim/syntax/cton.vim | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 misc/vim/ftdetect/cton.vim create mode 100644 misc/vim/syntax/cton.vim diff --git a/misc/vim/ftdetect/cton.vim b/misc/vim/ftdetect/cton.vim new file mode 100644 index 0000000000..9d7754c472 --- /dev/null +++ b/misc/vim/ftdetect/cton.vim @@ -0,0 +1 @@ +au BufRead,BufNewFile *.cton set filetype=cton diff --git a/misc/vim/syntax/cton.vim b/misc/vim/syntax/cton.vim new file mode 100644 index 0000000000..2d2dcd0fef --- /dev/null +++ b/misc/vim/syntax/cton.vim @@ -0,0 +1,34 @@ +" Vim syntax file +" Language: Cretonne +" Maintainer: Jakob Stoklund Olesen / +syn match ctonEntity /\<\(v\|vx\|ss\|jt\|\)\d\+\>/ +syn match ctonLabel /\/ + +syn match ctonNumber /-\?\<\d\+\>/ +syn match ctonNumber /-\?\<0x\x\+\(\.\x*\)\(p[+-]\?\d\+\)\?\>/ + +syn region ctonCommentLine start=";" end="$" contains=ctonFilecheck + +hi def link ctonHeader Keyword +hi def link ctonDecl Keyword +hi def link ctonType Type +hi def link ctonEntity Identifier +hi def link ctonLabel Label +hi def link ctonNumber Number +hi def link ctonCommentLine Comment +hi def link ctonFilecheck SpecialComment + +let b:current_syntax = "cton" From 7ec54a5a019ae1f02e3ac14d4b5c43aeb6bc643c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 23 Sep 2016 09:38:17 -0700 Subject: [PATCH 326/968] Add a Cretonne testing guide. Describe the basics of Rust-level tests, and go into more detail about the file-level tests. --- docs/cton_lexer.py | 19 ++- docs/index.rst | 3 +- docs/langref.rst | 10 +- docs/testing.rst | 295 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 10 deletions(-) create mode 100644 docs/testing.rst diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index eaa856c444..868bb23241 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -19,6 +19,17 @@ class CretonneLexer(RegexLexer): tokens = { 'root': [ + # Test header lines. + (r'^(test|isa|set)(?:( +)([-\w]+)' + + r'(?:(=)(?:(\d+)|(yes|no|true|false|on|off)|(\w+)))?)*' + + r'( *)$', + bygroups(Keyword.Namespace, Whitespace, Name.Attribute, + Operator, Number.Integer, Keyword.Constant, + Name.Constant, Whitespace)), + # Comments with filecheck or other test directive. + (r'(; *)([a-z]+:)(.*?)$', + bygroups(Comment.Single, Comment.Special, Comment.Single)), + # Plain comments. (r';.*?$', Comment.Single), # Strings are in double quotes, support \xx escapes only. (r'"([^"\\]+|\\[0-9a-fA-F]{2})*"', String), @@ -30,16 +41,16 @@ class CretonneLexer(RegexLexer): (r'[-+]?0[xX][0-9a-fA-F]*\.[0-9a-fA-F]*([pP]\d+)?', Number.Hex), (r'[-+]?(\d+\.\d+([eE]\d+)?|[sq]NaN|Inf)', Number.Float), (r'[-+]?\d+', Number.Integer), - # Reserved words. - (keywords('function'), Keyword), # Known attributes. (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), Name.Attribute), # Well known value types. (r'\b(b\d+|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value + # vx = value # ss = stack slot - (r'(v|ss)\d+', Name.Variable), + # jt = jump table + (r'(vx?|ss|jt)\d+', Name.Variable), # ebb = extended basic block (r'(ebb)\d+', Name.Label), # Match instruction names in context. @@ -52,7 +63,7 @@ class CretonneLexer(RegexLexer): (r'->|=|:', Operator), (r'[{}(),.]', Punctuation), (r'[ \t]+', Text), - ] + ], } diff --git a/docs/index.rst b/docs/index.rst index 88d9257213..b5a23ab28b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,10 +4,11 @@ Cretonne Code Generator Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 1 langref metaref + testing Indices and tables ================== diff --git a/docs/langref.rst b/docs/langref.rst index 848d140db8..10385cb7f4 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -48,11 +48,11 @@ fall through to the next EBB without an explicit branch. A ``.cton`` file consists of a sequence of independent function definitions: .. productionlist:: - function-list : { function } - function : function-spec "{" preamble function-body "}" - function-spec : "function" function-name signature - preamble : { preamble-decl } - function-body : { extended-basic-block } + function_list : { function } + function : function_spec "{" preamble function_body "}" + function_spec : "function" function_name signature + preamble : { preamble_decl } + function_body : { extended_basic_block } Static single assignment form ----------------------------- diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 0000000000..c902dc70f6 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,295 @@ +**************** +Testing Cretonne +**************** + +Cretonne is tested at multiple levels of abstraction and integration. When +possible, Rust unit tests are used to verify single functions and types. When +testing the interaction between compiler passes, file-level tests are +appropriate. + +The top-level shell script :file:`test-all.sh` runs all of the tests in the +Cretonne repository. + +Rust tests +========== + +.. highlight:: rust + +Rust and Cargo have good support for testing. Cretonne uses unit tests, doc +tests, and integration tests where appropriate. + +Unit tests +---------- + +Unit test live in a ``tests`` sub-module of the code they are testing:: + + pub fn add(x: u32, y: u32) { + x + y + } + + #[cfg(test)] + mod tests { + use super::add; + + #[test] + check_add() { + assert_eq!(add(2, 2), 4); + } + } + +Since sub-modules have access to non-public items in a Rust module, unit tests +can be used to test module-internal functions and types too. + +Doc tests +--------- + +Documentation comments can contain code snippets which are also compiled and +tested:: + + //! The `Flags` struct is immutable once it has been created. A `Builder` instance is used to + //! create it. + //! + //! # Example + //! ``` + //! use cretonne::settings::{self, Configurable}; + //! + //! let mut b = settings::builder(); + //! b.set("opt_level", "fastest"); + //! + //! let f = settings::Flags::new(&b); + //! assert_eq!(f.opt_level(), settings::OptLevel::Fastest); + //! ``` + +These tests are useful for demonstrating how to use an API, and running them +regularly makes sure that they stay up to date. Documentation tests are not +appropriate for lots of assertions, use unit tests for that. + +Integration tests +----------------- + +Integration tests are Rust source files thar are compiled and linked +individually. They are used to exercise the external API of the crates under +test. + +These tests are usually found in the :file:`src/tools/tests` sub-directory where they +have access to all the crates in the Cretonne repository. The +:file:`libcretonne` and :file:`libreader` crates have no external dependencies +which can make testing tedious. + +File tests +========== + +.. highlight:: cton + +Compilers work with large data structures representing programs, and it quickly +gets unwieldy to generate test data programmatically. File-level tests make it +easier to provide substantial input functions for the compiler tests. + +File tests are :file:`*.cton` files in the :file:`filetests/` directory +hierarchy. Each file has a header describing what to test followed by a number +of input functions in the :doc:`Cretonne textual intermediate language +`: + +.. productionlist:: + test_file : test_header `function_list` + test_header : test_commands (`isa_specs` | `settings`) + test_commands : test_command { test_command } + test_command : "test" test_name { option } "\n" + +The available test commands are described below. + +Many test comands only make sense in the context of a target instruction set +architecture. These tests require one or more ISA specifications in the test +header: + +.. productionlist:: + isa_specs : { [`settings`] isa_spec } + isa_spec : "isa" isa_name { `option` } "\n" + +The options given on the ``isa`` line modify the ISA-specific settings defined in +:file:`meta/isa/*/setings.py`. + +All types of tests allow shared Cretonne settings to be modified: + +.. productionlist:: + settings : { setting } + setting : "set" { option } "\n" + option : flag | setting "=" value + +The shared settings available for all target ISAs are defined in +:file:`meta/cretonne/settings.py`. + +The ``set`` lines apply settings cumulatively:: + + test legalizer + set opt_level=best + set is_64bit=1 + isa riscv + set is_64bit=0 + isa riscv supports_m=false + + function foo() {} + +This example will run the legalizer test twice. Both runs will have +``opt_level=best``, but they will have different ``is_64bit`` settings. The 32-bit +run will also have the RISC-V specific flag ``supports_m`` disabled. + +Filecheck +--------- + +Many of the test commands bescribed below use *filecheck* to verify their +output. Filecheck is a Rust implementation of the LLVM tool of the same name. +See the :file:`libfilecheck` documentation for details of its syntax. + +Comments in :file:`.cton` files are associated with the entity they follow. +This typically means and instruction or the whole function. Those tests that +use filecheck will extract comments associated with each function (or its +entities) and scan them for filecheck directives. The test output for each +function is then matched againts the filecheck directives for that function. + +Note that LLVM's file tests don't separate filecheck directives by their +associated function. It verifies the concatenated output against all filecheck +directives in the test file. LLVM's :command:`FileCheck` command has a +``CHECK-LABEL:`` directive to help separate the output from different functions. +Cretonne's tests don't need this. + +Filecheck variables +~~~~~~~~~~~~~~~~~~~ + +Cretonne's IL parser causes entities like values and EBBs to be renumbered. It +maintains a source mapping to resolve references in the text, but when a +function is written out as text as part of a test, all of the entities have the +new numbers. This can complicate the filecheck directives since they need to +refer to the new entity numbers, not the ones in the adjacent source text. + +To help with this, the parser's source-to-entity mapping is made available as +predefined filecheck variables. A value by the source name ``v10`` can be +referenced as the filecheck variable ``$v10``. The variable expands to the +renumbered entity name. + +`test cat` +---------- + +This is one of the simplest file tests, used for testing the conversion to and +from textual IL. The ``test cat`` command simply parses each function and +converts it back to text again. The text of each function is then matched +against the associated filecheck directives. + +Example:: + + function r1() -> i32, f32 { + ebb1: + v10 = iconst.i32 3 + v20 = f32const 0.0 + return v10, v20 + } + ; sameln: function r1() -> i32, f32 { + ; nextln: ebb0: + ; nextln: v0 = iconst.i32 3 + ; nextln: v1 = f32const 0.0 + ; nextln: return v0, v1 + ; nextln: } + +Notice that the values ``v10`` and ``v20`` in the source were renumbered to +``v0`` and ``v1`` respectively during parsing. The equivalent test using +filecheck variables would be:: + + function r1() -> i32, f32 { + ebb1: + v10 = iconst.i32 3 + v20 = f32const 0.0 + return v10, v20 + } + ; sameln: function r1() -> i32, f32 { + ; nextln: ebb0: + ; nextln: $v10 = iconst.i32 3 + ; nextln: $v20 = f32const 0.0 + ; nextln: return $v10, $v20 + ; nextln: } + +`test verifier` +--------------- + +Run each function through the IL verifier and check that it produces the +expected error messages. + +Expected error messages are indicated with an ``error:`` directive *on the +instruction that produces the verifier error*. Both the error message and +reported location of the error is verified:: + + test verifier + + function test(i32) { + ebb0(v0: i32): + jump ebb1 ; error: terminator + return + } + +This example test passed if the verifier fails with an error message containing +the sub-string ``"terminator"`` *and* the error is reported for the ``jump`` +instruction. + +If a function contains no ``error:`` annotations, the test passes if the +function verifies correctly. + +`test print-cfg` +---------------- + +Print the control flow graph of each function as a Graphviz graph, and run +filecheck over the result. See also the :command:`cton-util print-cfg` +command:: + + ; For testing cfg generation. This code is nonsense. + test print-cfg + test verifier + + function nonsense(i32, i32) -> f32 { + ; check: digraph nonsense { + ; regex: I=\binst\d+\b + ; check: label="{ebb0 | <$(BRZ=$I)>brz ebb2 | <$(JUMP=$I)>jump ebb1}"] + + ebb0(v1: i32, v2: i32): + brz v2, ebb2 ; unordered: ebb0:$BRZ -> ebb2 + v4 = iconst.i32 0 + jump ebb1(v4) ; unordered: ebb0:$JUMP -> ebb1 + + ebb1(v5: i32): + return v1 + + ebb2: + v100 = f32const 0.0 + return v100 + } + +`test domtree` +-------------- + +Compute the dominator tree of each function and validate it against the +``dominates:`` annotations:: + + test domtree + + function test(i32) { + ebb0(v0: i32): + jump ebb1 ; dominates: ebb1 + ebb1: + brz v0, ebb3 ; dominates: ebb3 + jump ebb2 ; dominates: ebb2 + ebb2: + jump ebb3 + ebb3: + return + } + +Every reachable extended basic block except for the entry block has an +*immediate dominator* which is a jump or branch instruction. This test passes +if the ``dominates:`` annotations on the immediate dominator instructions are +both correct and complete. + +`test legalizer` +---------------- + +Legalize each function for the specified target ISA and run the resulting +function through filecheck. This test command can be used to validate the +encodings selected for legal instructions as well as the instruction +transformations performed by the legalizer. From 9cb3451432e9cde3785f5aec51b31bf10eb9632d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 23 Sep 2016 13:32:26 -0700 Subject: [PATCH 327/968] Integer add with carry instructions. Integer addition with carry in/out/both. --- filetests/parser/ternary.cton | 13 +++++++++ meta/cretonne/base.py | 44 +++++++++++++++++++++++++++++- meta/cretonne/formats.py | 4 +++ meta/gen_instr.py | 5 ++-- src/libcretonne/ir/instructions.rs | 18 ++++++++++++ src/libcretonne/write.rs | 1 + src/libreader/parser.rs | 24 ++++++++++++++-- 7 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 filetests/parser/ternary.cton diff --git a/filetests/parser/ternary.cton b/filetests/parser/ternary.cton new file mode 100644 index 0000000000..5305126c49 --- /dev/null +++ b/filetests/parser/ternary.cton @@ -0,0 +1,13 @@ +test cat +test verifier + +function add_i96(i32, i32, i32, i32, i32, i32) -> i32, i32, i32 { +ebb1(v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32): + v10, v11 = iadd_cout v1, v4 + ;check: $v10, $v11 = iadd_cout $v1, $v4 + v20, v21 = iadd_carry v2, v5, v11 + ; check: $v20, $v21 = iadd_carry $v2, $v5, $v11 + v30 = iadd_cin v3, v6, v21 + ; check: $v30 = iadd_cin $v3, $v6, $v21 + return v10, v20, v30 +} diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 30d890b076..f94668da4a 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -6,7 +6,7 @@ support. """ from __future__ import absolute_import from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args -from .types import i8, f32, f64 +from .types import i8, f32, f64, b1 from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from . import entities @@ -405,6 +405,48 @@ isub_imm = Instruction( """, ins=(X, y), outs=a) +# +# Integer arithmetic with carry. +# +a = Operand('a', iB) +x = Operand('x', iB) +y = Operand('y', iB) +cin = Operand('cin', b1, doc="Input carry flag") +cout = Operand('cout', b1, doc="Output carry flag") + +iadd_cin = Instruction( + 'iadd_cin', """ + Add integers with carry in. + + Same as :inst:`iadd` with an additional carry input. + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, y, cin), outs=a) + +iadd_cout = Instruction( + 'iadd_cout', """ + Add integers with carry out. + + Same as :inst:`iadd` with an additional carry output. + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, y), outs=(a, cout)) + +iadd_carry = Instruction( + 'iadd_carry', """ + Add integers with carry in and out. + + Same as :inst:`iadd` with an additional carry output. + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, y, cin), outs=(a, cout)) + # # Bitwise operations. # diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 06f4593086..48b9d978af 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -30,6 +30,10 @@ BinaryOverflow = InstructionFormat(value, value, multiple_results=True) # The fma instruction has the same constraint on all inputs. Ternary = InstructionFormat(value, value, value, typevar_operand=1) +# Carry in *and* carry out for `iadd_carry` and friends. +TernaryOverflow = InstructionFormat( + value, value, value, multiple_results=True, boxed_storage=True) + InsertLane = InstructionFormat(value, ('lane', uimm8), value) ExtractLane = InstructionFormat(value, ('lane', uimm8)) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 139acf59b7..6c67077cb0 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -153,9 +153,8 @@ def gen_instruction_data_impl(fmt): if f.boxed_storage: fmt.line( n + - ' {{ ref data, .. }} => ' + - 'Some(data.args[{}]),' - .format(i)) + ' { ref data, .. } => ' + + ('Some(data.args[{}]),'.format(i))) else: fmt.line( n + diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index c8228d1f43..bfc1fe65f3 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -149,6 +149,11 @@ pub enum InstructionData { ty: Type, args: [Value; 3], }, + TernaryOverflow { + opcode: Opcode, + ty: Type, + data: Box, + }, InsertLane { opcode: Opcode, ty: Type, @@ -254,6 +259,19 @@ impl Default for VariableArgs { } } +/// Payload data for ternary instructions with multiple results, such as `iadd_carry`. +#[derive(Clone, Debug)] +pub struct TernaryOverflowData { + pub second_result: Value, + pub args: [Value; 3], +} + +impl Display for TernaryOverflowData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}, {}, {}", self.args[0], self.args[1], self.args[2]) + } +} + /// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit /// in the allowed InstructionData size. #[derive(Clone, Debug)] diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index bfdd13b32f..ea73d18666 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -190,6 +190,7 @@ fn write_instruction(w: &mut Write, BinaryImmRev { imm, arg, .. } => writeln!(w, " {}, {}", imm, arg), BinaryOverflow { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), Ternary { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), + TernaryOverflow { ref data, .. } => writeln!(w, " {}", data), InsertLane { lane, args, .. } => writeln!(w, " {}, {}, {}", args[0], lane, args[1]), ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), IntCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 05fa9b831b..723cfb1951 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -13,8 +13,8 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; -use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, JumpData, - BranchData, ReturnData}; +use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, + TernaryOverflowData, JumpData, BranchData, ReturnData}; use cretonne::isa; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -138,6 +138,10 @@ impl Context { try!(self.map.rewrite_values(args, loc)); } + InstructionData::TernaryOverflow { ref mut data, .. } => { + try!(self.map.rewrite_values(&mut data.args, loc)); + } + InstructionData::Jump { ref mut data, .. } => { try!(self.map.rewrite_ebb(&mut data.destination, loc)); try!(self.map.rewrite_values(&mut data.arguments, loc)); @@ -1066,6 +1070,22 @@ impl<'a> Parser<'a> { args: [ctrl_arg, true_arg, false_arg], } } + InstructionFormat::TernaryOverflow => { + // Names here refer to the `iadd_carry` instruction. + let lhs = try!(self.match_value("expected SSA value first operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let rhs = try!(self.match_value("expected SSA value second operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let cin = try!(self.match_value("expected SSA value third operand")); + InstructionData::TernaryOverflow { + opcode: opcode, + ty: VOID, + data: Box::new(TernaryOverflowData { + second_result: NO_VALUE, + args: [lhs, rhs, cin], + }), + } + } InstructionFormat::Jump => { // Parse the destination EBB number. Don't translate source to local numbers yet. let ebb_num = try!(self.match_ebb("expected jump destination EBB")); From f66d84fd958e511a3993604f5f476b1fa290350c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 23 Sep 2016 15:47:39 -0700 Subject: [PATCH 328/968] Integer subtraction with borrow flags. This is the x86-style of borrow flags. ARM uses subtract-with-carry which inverts the sense of the carry flag. --- filetests/parser/ternary.cton | 11 +++++ meta/cretonne/base.py | 79 +++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/filetests/parser/ternary.cton b/filetests/parser/ternary.cton index 5305126c49..99cb15b566 100644 --- a/filetests/parser/ternary.cton +++ b/filetests/parser/ternary.cton @@ -11,3 +11,14 @@ ebb1(v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32): ; check: $v30 = iadd_cin $v3, $v6, $v21 return v10, v20, v30 } + +function sub_i96(i32, i32, i32, i32, i32, i32) -> i32, i32, i32 { +ebb1(v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32): + v10, v11 = isub_bout v1, v4 + ;check: $v10, $v11 = isub_bout $v1, $v4 + v20, v21 = isub_borrow v2, v5, v11 + ; check: $v20, $v21 = isub_borrow $v2, $v5, $v11 + v30 = isub_bin v3, v6, v21 + ; check: $v30 = isub_bin $v3, $v6, $v21 + return v10, v20, v30 +} diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index f94668da4a..150155d908 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -406,24 +406,30 @@ isub_imm = Instruction( ins=(X, y), outs=a) # -# Integer arithmetic with carry. +# Integer arithmetic with carry and/or borrow. # a = Operand('a', iB) x = Operand('x', iB) y = Operand('y', iB) -cin = Operand('cin', b1, doc="Input carry flag") -cout = Operand('cout', b1, doc="Output carry flag") +c_in = Operand('c_in', b1, doc="Input carry flag") +c_out = Operand('c_out', b1, doc="Output carry flag") +b_in = Operand('b_in', b1, doc="Input borrow flag") +b_out = Operand('b_out', b1, doc="Output borrow flag") iadd_cin = Instruction( 'iadd_cin', """ Add integers with carry in. - Same as :inst:`iadd` with an additional carry input. + Same as :inst:`iadd` with an additional carry input. Computes: + + .. math:: + + a = x + y + c_{in} \pmod 2^B Polymorphic over all scalar integer types, but does not support vector types. """, - ins=(x, y, cin), outs=a) + ins=(x, y, c_in), outs=a) iadd_cout = Instruction( 'iadd_cout', """ @@ -431,21 +437,78 @@ iadd_cout = Instruction( Same as :inst:`iadd` with an additional carry output. + .. math:: + + a &= x + y \pmod 2^B \\ + c_{out} &= x+y >= 2^B + Polymorphic over all scalar integer types, but does not support vector types. """, - ins=(x, y), outs=(a, cout)) + ins=(x, y), outs=(a, c_out)) iadd_carry = Instruction( 'iadd_carry', """ Add integers with carry in and out. - Same as :inst:`iadd` with an additional carry output. + Same as :inst:`iadd` with an additional carry input and output. + + .. math:: + + a &= x + y + c_{in} \pmod 2^B \\ + c_{out} &= x + y + c_{in} >= 2^B Polymorphic over all scalar integer types, but does not support vector types. """, - ins=(x, y, cin), outs=(a, cout)) + ins=(x, y, c_in), outs=(a, c_out)) + +isub_bin = Instruction( + 'isub_bin', """ + Subtract integers with borrow in. + + Same as :inst:`isub` with an additional borrow flag input. Computes: + + .. math:: + + a = x - (y + b_{in}) \pmod 2^B + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, y, b_in), outs=a) + +isub_bout = Instruction( + 'isub_bout', """ + Subtract integers with borrow out. + + Same as :inst:`isub` with an additional borrow flag output. + + .. math:: + + a &= x - y \pmod 2^B \\ + b_{out} &= x < y + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, y), outs=(a, b_out)) + +isub_borrow = Instruction( + 'isub_borrow', """ + Subtract integers with borrow in and out. + + Same as :inst:`isub` with an additional borrow flag input and output. + + .. math:: + + a &= x - (y + b_{in}) \pmod 2^B \\ + b_{out} &= x < y + b_{in} + + Polymorphic over all scalar integer types, but does not support vector + types. + """, + ins=(x, y, b_in), outs=(a, b_out)) # # Bitwise operations. From 686aa4ec1d3fdf2e624e9489d9fbf0fcefc34049 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 23 Sep 2016 16:41:14 -0700 Subject: [PATCH 329/968] Add an autoinstgroup Sphinx directive. This directive documents an instruction group and lists all instructions contained in the group, whether they have been documented or not. --- docs/cton_domain.py | 36 ++++++++++++++++++++++++++++++++++++ docs/langref.rst | 13 +++++++++++++ 2 files changed, 49 insertions(+) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index ba762b4df3..b45aa5e677 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -173,6 +173,10 @@ class CtonInst(CtonObject): return name +class CtonInstGroup(CtonObject): + """A Cretonne IL instruction group.""" + + class CretonneDomain(Domain): """Cretonne domain for intermediate language objects.""" name = 'cton' @@ -186,11 +190,13 @@ class CretonneDomain(Domain): directives = { 'type': CtonType, 'inst': CtonInst, + 'instgroup': CtonInstGroup, } roles = { 'type': XRefRole(), 'inst': XRefRole(), + 'instgroup': XRefRole(), } initial_data = { @@ -327,9 +333,39 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): sourcename) +class InstGroupDocumenter(sphinx.ext.autodoc.ModuleLevelDocumenter): + # Invoke with .. autoinstgroup:: + objtype = 'instgroup' + # Convert into cton:instgroup directives + domain = 'cton' + directivetype = 'instgroup' + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + return False + + def format_name(self): + return "{}.{}".format(self.modname, ".".join(self.objpath)) + + def add_content(self, more_content, no_docstring=False): + super(InstGroupDocumenter, self).add_content( + more_content, no_docstring) + sourcename = self.get_sourcename() + indexed = self.env.domaindata['cton']['objects'] + + names = [inst.name for inst in self.object.instructions] + names.sort() + for name in names: + if name in indexed: + self.add_line(u':cton:inst:`{}`'.format(name), sourcename) + else: + self.add_line(u'``{}``'.format(name), sourcename) + + def setup(app): app.add_domain(CretonneDomain) app.add_autodocumenter(TypeDocumenter) app.add_autodocumenter(InstDocumenter) + app.add_autodocumenter(InstGroupDocumenter) return {'version': '0.1'} diff --git a/docs/langref.rst b/docs/langref.rst index 10385cb7f4..99a6e1ca62 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -308,6 +308,7 @@ Quiet NaNs Signaling NaNs Displayed as ``-sNaN:0xT``. + Control flow ============ @@ -814,6 +815,18 @@ Conversion operations .. autoinst:: fcvt_from_uint .. autoinst:: fcvt_from_sint + +Base instruction group +====================== + +All of the shared instructions are part of the :instgroup:`base` instruction +group. + +.. autoinstgroup:: cretonne.base.instructions + +Target ISAs may define further instructions in their own instruction groups. + + Glossary ======== From d915718526200d0a9473b4d84a53a9924a301dcd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 23 Sep 2016 16:49:35 -0700 Subject: [PATCH 330/968] Add documentation links to all existing instructions. --- docs/langref.rst | 20 ++++++++++++++------ meta/cretonne/base.py | 12 ++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 99a6e1ca62..e42d82ecf4 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -674,14 +674,14 @@ Integer operations .. autoinst:: icmp .. autoinst:: iadd .. autoinst:: iadd_imm +.. autoinst:: iadd_cin +.. autoinst:: iadd_cout +.. autoinst:: iadd_carry .. autoinst:: isub .. autoinst:: isub_imm - -.. todo:: Integer overflow arithmetic - - Add instructions for add with carry out / carry in and so on. Enough to - implement larger integer types efficiently. It should also be possible to - legalize :type:`i64` arithmetic to terms of :type:`i32` operations. +.. autoinst:: isub_bin +.. autoinst:: isub_bout +.. autoinst:: isub_borrow .. autoinst:: imul .. autoinst:: imul_imm @@ -722,8 +722,11 @@ bitwise operations are working on the binary representation of the values. When operating on boolean values, the bitwise operations work as logical operators. .. autoinst:: band +.. autoinst:: band_imm .. autoinst:: bor +.. autoinst:: bor_imm .. autoinst:: bxor +.. autoinst:: bxor_imm .. autoinst:: bnot .. todo:: Redundant bitwise operators. @@ -740,10 +743,15 @@ type, and all the lanes are shifted the same amount. The shift amount is masked to the number of bits in a *lane*, not the full size of the vector type. .. autoinst:: rotl +.. autoinst:: rotl_imm .. autoinst:: rotr +.. autoinst:: rotr_imm .. autoinst:: ishl +.. autoinst:: ishl_imm .. autoinst:: ushr +.. autoinst:: ushr_imm .. autoinst:: sshr +.. autoinst:: sshr_imm The bit-counting instructions below are scalar only. diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 150155d908..f7b6fb06e4 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -417,7 +417,7 @@ b_in = Operand('b_in', b1, doc="Input borrow flag") b_out = Operand('b_out', b1, doc="Output borrow flag") iadd_cin = Instruction( - 'iadd_cin', """ + 'iadd_cin', r""" Add integers with carry in. Same as :inst:`iadd` with an additional carry input. Computes: @@ -432,7 +432,7 @@ iadd_cin = Instruction( ins=(x, y, c_in), outs=a) iadd_cout = Instruction( - 'iadd_cout', """ + 'iadd_cout', r""" Add integers with carry out. Same as :inst:`iadd` with an additional carry output. @@ -448,7 +448,7 @@ iadd_cout = Instruction( ins=(x, y), outs=(a, c_out)) iadd_carry = Instruction( - 'iadd_carry', """ + 'iadd_carry', r""" Add integers with carry in and out. Same as :inst:`iadd` with an additional carry input and output. @@ -464,7 +464,7 @@ iadd_carry = Instruction( ins=(x, y, c_in), outs=(a, c_out)) isub_bin = Instruction( - 'isub_bin', """ + 'isub_bin', r""" Subtract integers with borrow in. Same as :inst:`isub` with an additional borrow flag input. Computes: @@ -479,7 +479,7 @@ isub_bin = Instruction( ins=(x, y, b_in), outs=a) isub_bout = Instruction( - 'isub_bout', """ + 'isub_bout', r""" Subtract integers with borrow out. Same as :inst:`isub` with an additional borrow flag output. @@ -495,7 +495,7 @@ isub_bout = Instruction( ins=(x, y), outs=(a, b_out)) isub_borrow = Instruction( - 'isub_borrow', """ + 'isub_borrow', r""" Subtract integers with borrow in and out. Same as :inst:`isub` with an additional borrow flag input and output. From b06668aa8a8df34bd799c72071c88752dc9c759a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 10:53:53 -0700 Subject: [PATCH 331/968] Move TypeVar and TypeSet into their own Python package. These classes are not very entangled with the rest of __init__, and we'll be expanding them a bit. --- docs/metaref.rst | 2 +- meta/cretonne/__init__.py | 94 +------------------------------------ meta/cretonne/base.py | 3 +- meta/cretonne/typevar.py | 98 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 95 deletions(-) create mode 100644 meta/cretonne/typevar.py diff --git a/docs/metaref.rst b/docs/metaref.rst index 8df75d80a2..4edf708041 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -96,7 +96,7 @@ instances that refer to a *type variable* instead of a concrete value type. Polymorphism only works for SSA value operands. Other operands have a fixed operand kind. -.. autoclass:: TypeVar +.. autoclass:: cretonne.typevar.TypeVar :members: If multiple operands refer to the same type variable they will be required to diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 90cbb2f983..d318b27ef3 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -8,7 +8,7 @@ from __future__ import absolute_import import re import math import importlib -from collections import namedtuple, OrderedDict +from collections import OrderedDict from .predicates import And @@ -518,98 +518,6 @@ class BoolType(ScalarType): return 'BoolType(bits={})'.format(self.bits) -# Parametric polymorphism. - - -#: A `TypeSet` represents a set of types. We don't allow arbitrary subsets of -#: types, but use a parametrized approach instead. -#: This is represented as a named tuple so it can be used as a dictionary key. -TypeSet = namedtuple( - 'TypeSet', [ - 'allow_scalars', - 'allow_simd', - 'base', - 'all_ints', - 'all_floats', - 'all_bools']) - - -class TypeVar(object): - """ - Type variables can be used in place of concrete types when defining - instructions. This makes the instructions *polymorphic*. - - A type variable is restricted to vary over a subset of the value types. - This subset is specified by a set of flags that control the permitted base - types and whether the type variable can assume scalar or vector types, or - both. - - :param name: Short name of type variable used in instruction descriptions. - :param doc: Documentation string. - :param base: Single base type or list of base types. Use this to specify an - exact set of base types if the general categories below are not good - enough. - :param ints: Allow all integer base types. - :param floats: Allow all floating point base types. - :param bools: Allow all boolean base types. - :param scalars: Allow type variable to assume scalar types. - :param simd: Allow type variable to assume vector types. - """ - - def __init__( - self, name, doc, base=None, - ints=False, floats=False, bools=False, - scalars=True, simd=False, - derived_func=None): - self.name = name - self.__doc__ = doc - self.base = base - self.is_derived = isinstance(base, TypeVar) - if self.is_derived: - assert derived_func - self.derived_func = derived_func - self.name = '{}({})'.format(derived_func, base.name) - else: - self.type_set = TypeSet( - allow_scalars=scalars, - allow_simd=simd, - base=base, - all_ints=ints, - all_floats=floats, - all_bools=bools) - - def __str__(self): - return "`{}`".format(self.name) - - def lane_of(self): - """ - Return a derived type variable that is the scalar lane type of this - type variable. - - When this type variable assumes a scalar type, the derived type will be - the same scalar type. - """ - return TypeVar(None, None, base=self, derived_func='LaneOf') - - def as_bool(self): - """ - Return a derived type variable that has the same vector geometry as - this type variable, but with boolean lanes. Scalar types map to `b1`. - """ - return TypeVar(None, None, base=self, derived_func='AsBool') - - def operand_kind(self): - # When a `TypeVar` object is used to describe the type of an `Operand` - # in an instruction definition, the kind of that operand is an SSA - # value. - return value - - def free_typevar(self): - if isinstance(self.base, TypeVar): - return self.base - else: - return self - # Defining instructions. diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index f7b6fb06e4..fdca2484f9 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -5,7 +5,8 @@ This module defines the basic Cretonne instruction set that all targets support. """ from __future__ import absolute_import -from . import TypeVar, Operand, Instruction, InstructionGroup, variable_args +from . import Operand, Instruction, InstructionGroup, variable_args +from .typevar import TypeVar from .types import i8, f32, f64, b1 from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from . import entities diff --git a/meta/cretonne/typevar.py b/meta/cretonne/typevar.py new file mode 100644 index 0000000000..ec2693829b --- /dev/null +++ b/meta/cretonne/typevar.py @@ -0,0 +1,98 @@ +""" +Type variables for Parametric polymorphism. + +Cretonne instructions and instruction transformations can be specified to be +polymorphic by using type variables. +""" +from __future__ import absolute_import +from collections import namedtuple +from . import value + +#: A `TypeSet` represents a set of types. We don't allow arbitrary subsets of +#: types, but use a parametrized approach instead. +#: This is represented as a named tuple so it can be used as a dictionary key. +TypeSet = namedtuple( + 'TypeSet', [ + 'allow_scalars', + 'allow_simd', + 'base', + 'all_ints', + 'all_floats', + 'all_bools']) + + +class TypeVar(object): + """ + Type variables can be used in place of concrete types when defining + instructions. This makes the instructions *polymorphic*. + + A type variable is restricted to vary over a subset of the value types. + This subset is specified by a set of flags that control the permitted base + types and whether the type variable can assume scalar or vector types, or + both. + + :param name: Short name of type variable used in instruction descriptions. + :param doc: Documentation string. + :param base: Single base type or list of base types. Use this to specify an + exact set of base types if the general categories below are not good + enough. + :param ints: Allow all integer base types. + :param floats: Allow all floating point base types. + :param bools: Allow all boolean base types. + :param scalars: Allow type variable to assume scalar types. + :param simd: Allow type variable to assume vector types. + """ + + def __init__( + self, name, doc, base=None, + ints=False, floats=False, bools=False, + scalars=True, simd=False, + derived_func=None): + self.name = name + self.__doc__ = doc + self.base = base + self.is_derived = isinstance(base, TypeVar) + if self.is_derived: + assert derived_func + self.derived_func = derived_func + self.name = '{}({})'.format(derived_func, base.name) + else: + self.type_set = TypeSet( + allow_scalars=scalars, + allow_simd=simd, + base=base, + all_ints=ints, + all_floats=floats, + all_bools=bools) + + def __str__(self): + return "`{}`".format(self.name) + + def lane_of(self): + """ + Return a derived type variable that is the scalar lane type of this + type variable. + + When this type variable assumes a scalar type, the derived type will be + the same scalar type. + """ + return TypeVar(None, None, base=self, derived_func='LaneOf') + + def as_bool(self): + """ + Return a derived type variable that has the same vector geometry as + this type variable, but with boolean lanes. Scalar types map to `b1`. + """ + return TypeVar(None, None, base=self, derived_func='AsBool') + + def operand_kind(self): + # When a `TypeVar` object is used to describe the type of an `Operand` + # in an instruction definition, the kind of that operand is an SSA + # value. + return value + + def free_typevar(self): + if isinstance(self.base, TypeVar): + return self.base + else: + return self From d45b011fa215c5129f90bea89c2ce06d92b2b973 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 13:31:31 -0700 Subject: [PATCH 332/968] Represent type sets with ranges. Allow limits on the smallest and largest integer type in the set, the highest and lowest number of lanes etc. --- meta/cretonne/typevar.py | 175 ++++++++++++++++++++++++----- meta/gen_instr.py | 10 +- src/libcretonne/ir/instructions.rs | 136 +++++++++++++--------- src/libcretonne/ir/types.rs | 12 ++ 4 files changed, 240 insertions(+), 93 deletions(-) diff --git a/meta/cretonne/typevar.py b/meta/cretonne/typevar.py index ec2693829b..7ee7182ad7 100644 --- a/meta/cretonne/typevar.py +++ b/meta/cretonne/typevar.py @@ -5,20 +5,127 @@ Cretonne instructions and instruction transformations can be specified to be polymorphic by using type variables. """ from __future__ import absolute_import -from collections import namedtuple +import math from . import value -#: A `TypeSet` represents a set of types. We don't allow arbitrary subsets of -#: types, but use a parametrized approach instead. -#: This is represented as a named tuple so it can be used as a dictionary key. -TypeSet = namedtuple( - 'TypeSet', [ - 'allow_scalars', - 'allow_simd', - 'base', - 'all_ints', - 'all_floats', - 'all_bools']) + +MAX_LANES = 256 +MAX_BITS = 64 + + +def is_power_of_two(x): + return x > 0 and x & (x-1) == 0 + + +def int_log2(x): + return int(math.log(x, 2)) + + +class TypeSet(object): + """ + A set of types. + + We don't allow arbitrary subsets of types, but use a parametrized approach + instead. + + Objects of this class can be used as dictionary keys. + + Parametrized type sets are specified in terms of ranges: + + - The permitted range of vector lanes, where 1 indicates a scalar type. + - The permitted range of integer types. + - The permitted range of floating point types, and + - The permitted range of boolean types. + + The ranges are inclusive from smallest bit-width to largest bit-width. + + :param lanes: `(min, max)` inclusive range of permitted vector lane counts. + :param ints: `(min, max)` inclusive range of permitted scalar integer + widths. + :param floats: `(min, max)` inclusive range of permitted scalar floating + point widths. + :param bools: `(min, max)` inclusive range of permitted scalar boolean + widths. + """ + + def __init__(self, lanes, ints=None, floats=None, bools=None): + self.min_lanes, self.max_lanes = lanes + assert is_power_of_two(self.min_lanes) + assert is_power_of_two(self.max_lanes) + assert self.max_lanes <= MAX_LANES + + if ints: + if ints is True: + ints = (8, MAX_BITS) + self.min_int, self.max_int = ints + assert is_power_of_two(self.min_int) + assert is_power_of_two(self.max_int) + assert self.max_int <= MAX_BITS + else: + self.min_int = None + self.max_int = None + + if floats: + if floats is True: + floats = (32, 64) + self.min_float, self.max_float = floats + assert is_power_of_two(self.min_float) + assert self.min_float >= 32 + assert is_power_of_two(self.max_float) + assert self.max_float <= 64 + else: + self.min_float = None + self.max_float = None + + if bools: + if bools is True: + bools = (1, MAX_BITS) + self.min_bool, self.max_bool = bools + assert is_power_of_two(self.min_bool) + assert is_power_of_two(self.max_bool) + assert self.max_bool <= MAX_BITS + else: + self.min_bool = None + self.max_bool = None + + def typeset_key(self): + """Key tuple used for hashing and equality.""" + return (self.min_lanes, self.max_lanes, + self.min_int, self.max_int, + self.min_float, self.max_float, + self.min_bool, self.max_bool) + + def __hash__(self): + return hash(self.typeset_key()) + + def __eq__(self, other): + return self.typeset_key() == other.typeset_key() + + def __repr__(self): + s = 'TypeSet(lanes=({}, {})'.format(self.min_lanes, self.max_lanes) + if self.min_int is not None: + s += ', ints=({}, {})'.format(self.min_int, self.max_int) + if self.min_float is not None: + s += ', floats=({}, {})'.format(self.min_float, self.max_float) + if self.min_bool is not None: + s += ', bools=({}, {})'.format(self.min_bool, self.max_bool) + return s + ')' + + def emit_fields(self, fmt): + """Emit field initializers for this typeset.""" + fmt.comment(repr(self)) + fields = ('lanes', 'int', 'float', 'bool') + for field in fields: + min_val = getattr(self, 'min_' + field) + max_val = getattr(self, 'max_' + field) + if min_val is None: + fmt.line('min_{}: 0,'.format(field)) + fmt.line('max_{}: 0,'.format(field)) + else: + fmt.line('min_{}: {},'.format( + field, int_log2(min_val))) + fmt.line('max_{}: {},'.format( + field, int_log2(max_val) + 1)) class TypeVar(object): @@ -33,37 +140,45 @@ class TypeVar(object): :param name: Short name of type variable used in instruction descriptions. :param doc: Documentation string. - :param base: Single base type or list of base types. Use this to specify an - exact set of base types if the general categories below are not good - enough. - :param ints: Allow all integer base types. - :param floats: Allow all floating point base types. - :param bools: Allow all boolean base types. + :param ints: Allow all integer base types, or `(min, max)` bit-range. + :param floats: Allow all floating point base types, or `(min, max)` + bit-range. + :param bools: Allow all boolean base types, or `(min, max)` bit-range. :param scalars: Allow type variable to assume scalar types. - :param simd: Allow type variable to assume vector types. + :param simd: Allow type variable to assume vector types, or `(min, max)` + lane count range. """ def __init__( - self, name, doc, base=None, + self, name, doc, ints=False, floats=False, bools=False, scalars=True, simd=False, - derived_func=None): + base=None, derived_func=None): self.name = name self.__doc__ = doc - self.base = base self.is_derived = isinstance(base, TypeVar) - if self.is_derived: + if base: + assert self.is_derived assert derived_func + self.base = base self.derived_func = derived_func self.name = '{}({})'.format(derived_func, base.name) else: + min_lanes = 1 if scalars else 2 + if simd: + if simd is True: + max_lanes = MAX_LANES + else: + min_lanes, max_lanes = simd + assert not scalars or min_lanes <= 2 + else: + max_lanes = 1 + self.type_set = TypeSet( - allow_scalars=scalars, - allow_simd=simd, - base=base, - all_ints=ints, - all_floats=floats, - all_bools=bools) + lanes=(min_lanes, max_lanes), + ints=ints, + floats=floats, + bools=bools) def __str__(self): return "`{}`".format(self.name) @@ -92,7 +207,7 @@ class TypeVar(object): return value def free_typevar(self): - if isinstance(self.base, TypeVar): + if self.is_derived: return self.base else: return self diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 6c67077cb0..cc636cf83a 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -344,15 +344,7 @@ def gen_type_constraints(fmt, instrs): .format(len(type_sets.table)), '];'): for ts in type_sets.table: with fmt.indented('ValueTypeSet {', '},'): - if ts.base: - fmt.line('base: {},'.format(ts.base.rust_name())) - else: - fmt.line('base: types::VOID,') - for field in ts._fields: - if field == 'base': - continue - fmt.line('{}: {},'.format( - field, str(getattr(ts, field)).lower())) + ts.emit_fields(fmt) fmt.comment('Table of operand constraint sequences.') with fmt.indented( diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index bfc1fe65f3..afd6ba319f 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -480,13 +480,14 @@ impl OpcodeConstraints { /// A value type set describes the permitted set of types for a type variable. #[derive(Clone, Copy)] pub struct ValueTypeSet { - allow_scalars: bool, - allow_simd: bool, - - base: Type, - all_ints: bool, - all_floats: bool, - all_bools: bool, + min_lanes: u8, + max_lanes: u8, + min_int: u8, + max_int: u8, + min_float: u8, + max_float: u8, + min_bool: u8, + max_bool: u8, } impl ValueTypeSet { @@ -494,42 +495,38 @@ impl ValueTypeSet { /// /// Note that the base type set does not have to be included in the type set proper. fn is_base_type(&self, scalar: Type) -> bool { - scalar == self.base || (self.all_ints && scalar.is_int()) || - (self.all_floats && scalar.is_float()) || (self.all_bools && scalar.is_bool()) + let l2b = scalar.log2_lane_bits(); + if scalar.is_int() { + self.min_int <= l2b && l2b < self.max_int + } else if scalar.is_float() { + self.min_float <= l2b && l2b < self.max_float + } else if scalar.is_bool() { + self.min_bool <= l2b && l2b < self.max_bool + } else { + false + } } /// Does `typ` belong to this set? pub fn contains(&self, typ: Type) -> bool { - let allowed = if typ.is_scalar() { - self.allow_scalars - } else { - self.allow_simd - }; - allowed && self.is_base_type(typ.lane_type()) + let l2l = typ.log2_lane_count(); + self.min_lanes <= l2l && l2l < self.max_lanes && self.is_base_type(typ.lane_type()) } /// Get an example member of this type set. /// /// This is used for error messages to avoid suggesting invalid types. pub fn example(&self) -> Type { - if self.base != types::VOID { - return self.base; - } - let t = if self.all_ints { + let t = if self.max_int > 5 { types::I32 - } else if self.all_floats { + } else if self.max_float > 5 { types::F32 - } else if self.allow_scalars { - types::B1 - } else { + } else if self.max_bool > 5 { types::B32 - }; - - if self.allow_scalars { - t } else { - t.by(4).unwrap() - } + types::B1 + }; + t.by(1 << self.min_lanes).unwrap() } } @@ -611,43 +608,74 @@ mod tests { use ir::types::*; let vts = ValueTypeSet { - allow_scalars: true, - allow_simd: true, - base: VOID, - all_ints: true, - all_floats: false, - all_bools: true, + min_lanes: 0, + max_lanes: 8, + min_int: 3, + max_int: 7, + min_float: 0, + max_float: 0, + min_bool: 3, + max_bool: 7, }; + assert!(vts.contains(I32)); + assert!(vts.contains(I64)); + assert!(vts.contains(I32X4)); + assert!(!vts.contains(F32)); + assert!(!vts.contains(B1)); + assert!(vts.contains(B8)); + assert!(vts.contains(B64)); assert_eq!(vts.example().to_string(), "i32"); let vts = ValueTypeSet { - allow_scalars: true, - allow_simd: true, - base: VOID, - all_ints: false, - all_floats: true, - all_bools: true, + min_lanes: 0, + max_lanes: 8, + min_int: 0, + max_int: 0, + min_float: 5, + max_float: 7, + min_bool: 3, + max_bool: 7, }; assert_eq!(vts.example().to_string(), "f32"); let vts = ValueTypeSet { - allow_scalars: false, - allow_simd: true, - base: VOID, - all_ints: false, - all_floats: true, - all_bools: true, + min_lanes: 1, + max_lanes: 8, + min_int: 0, + max_int: 0, + min_float: 5, + max_float: 7, + min_bool: 3, + max_bool: 7, }; - assert_eq!(vts.example().to_string(), "f32x4"); + assert_eq!(vts.example().to_string(), "f32x2"); let vts = ValueTypeSet { - allow_scalars: false, - allow_simd: true, - base: VOID, - all_ints: false, - all_floats: false, - all_bools: true, + min_lanes: 2, + max_lanes: 8, + min_int: 0, + max_int: 0, + min_float: 0, + max_float: 0, + min_bool: 3, + max_bool: 7, }; + assert!(!vts.contains(B32X2)); + assert!(vts.contains(B32X4)); assert_eq!(vts.example().to_string(), "b32x4"); + + let vts = ValueTypeSet { + // TypeSet(lanes=(1, 256), ints=(8, 64)) + min_lanes: 0, + max_lanes: 9, + min_int: 3, + max_int: 7, + min_float: 0, + max_float: 0, + min_bool: 0, + max_bool: 0, + }; + assert!(vts.contains(I32)); + assert!(vts.contains(I32X4)); } } diff --git a/src/libcretonne/ir/types.rs b/src/libcretonne/ir/types.rs index f4e0a6b75f..1eb546bde6 100644 --- a/src/libcretonne/ir/types.rs +++ b/src/libcretonne/ir/types.rs @@ -43,6 +43,18 @@ impl Type { Type(self.0 & 0x0f) } + /// Get log2 of the number of bits in a lane. + pub fn log2_lane_bits(self) -> u8 { + match self.lane_type() { + B1 => 0, + B8 | I8 => 3, + B16 | I16 => 4, + B32 | I32 | F32 => 5, + B64 | I64 | F64 => 6, + _ => 0, + } + } + /// Get the number of bits in a lane. pub fn lane_bits(self) -> u8 { match self.lane_type() { From d7e9d4dade0ec217ec622f706944ee817882d1db Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 13:45:05 -0700 Subject: [PATCH 333/968] Run Python unittests and doctests. Add a meta/check.sh script that runs unit tests before the syntax linters. Add unittest converters for the existing doctests. --- meta/{check-py3k.sh => check.sh} | 6 +++++- meta/constant_hash.py | 4 ---- meta/srcgen.py | 4 ---- meta/test_constant_hash.py | 8 ++++++++ meta/test_srcgen.py | 8 ++++++++ 5 files changed, 21 insertions(+), 9 deletions(-) rename meta/{check-py3k.sh => check.sh} (78%) create mode 100644 meta/test_constant_hash.py create mode 100644 meta/test_srcgen.py diff --git a/meta/check-py3k.sh b/meta/check.sh similarity index 78% rename from meta/check-py3k.sh rename to meta/check.sh index 3ac939ea15..f01653b57e 100755 --- a/meta/check-py3k.sh +++ b/meta/check.sh @@ -1,8 +1,12 @@ #!/bin/bash +set -e +cd $(dirname "$0") + +# Run unit tests. +python -m unittest discover # Check Python sources for Python 3 compatibility using pylint. # # Install pylint with 'pip install pylint'. -cd $(dirname "$0") pylint --py3k --reports=no -- *.py cretonne isa flake8 . diff --git a/meta/constant_hash.py b/meta/constant_hash.py index aa43694a50..38911278ed 100644 --- a/meta/constant_hash.py +++ b/meta/constant_hash.py @@ -74,7 +74,3 @@ def compute_quadratic(items, hash_function): table[h] = i return table - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/meta/srcgen.py b/meta/srcgen.py index 0b8a7a2973..962a881436 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -121,7 +121,3 @@ class Formatter(object): """Add a (multi-line) documentation comment.""" s = re.sub('^', self.indent + '/// ', s, flags=re.M) + '\n' self.lines.append(s) - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/meta/test_constant_hash.py b/meta/test_constant_hash.py new file mode 100644 index 0000000000..e76f09aed9 --- /dev/null +++ b/meta/test_constant_hash.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import +import doctest +import constant_hash + + +def load_tests(loader, tests, ignore): + tests.addTests(doctest.DocTestSuite(constant_hash)) + return tests diff --git a/meta/test_srcgen.py b/meta/test_srcgen.py new file mode 100644 index 0000000000..2fb5e0fb61 --- /dev/null +++ b/meta/test_srcgen.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import +import doctest +import srcgen + + +def load_tests(loader, tests, ignore): + tests.addTests(doctest.DocTestSuite(srcgen)) + return tests From 470507dd9befe15675f3754a4c54fc122a55b79c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 14:36:02 -0700 Subject: [PATCH 334/968] Add some Python tests for TypeSet. --- meta/cretonne/test_typevar.py | 45 +++++++++++++++++++++++++++++++++++ meta/cretonne/typevar.py | 45 ++++++++++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 meta/cretonne/test_typevar.py diff --git a/meta/cretonne/test_typevar.py b/meta/cretonne/test_typevar.py new file mode 100644 index 0000000000..128b32b71f --- /dev/null +++ b/meta/cretonne/test_typevar.py @@ -0,0 +1,45 @@ +from __future__ import absolute_import +from unittest import TestCase +from doctest import DocTestSuite +from . import typevar +from .typevar import TypeSet + + +def load_tests(loader, tests, ignore): + tests.addTests(DocTestSuite(typevar)) + return tests + + +class TestTypeSet(TestCase): + def test_invalid(self): + with self.assertRaises(AssertionError): + TypeSet(lanes=(2, 1)) + with self.assertRaises(AssertionError): + TypeSet(ints=(32, 16)) + with self.assertRaises(AssertionError): + TypeSet(floats=(32, 16)) + with self.assertRaises(AssertionError): + TypeSet(bools=(32, 16)) + with self.assertRaises(AssertionError): + TypeSet(ints=(32, 33)) + + def test_hash(self): + a = TypeSet(lanes=True, ints=True, floats=True) + b = TypeSet(lanes=True, ints=True, floats=True) + c = TypeSet(lanes=True, ints=(8, 16), floats=True) + self.assertEqual(a, b) + self.assertNotEqual(a, c) + s = set() + s.add(a) + self.assertTrue(a in s) + self.assertTrue(b in s) + self.assertFalse(c in s) + + def test_hash_modified(self): + a = TypeSet(lanes=True, ints=True, floats=True) + s = set() + s.add(a) + a.max_int = 32 + # Can't rehash after modification. + with self.assertRaises(AssertionError): + a in s diff --git a/meta/cretonne/typevar.py b/meta/cretonne/typevar.py index 7ee7182ad7..e91c6ab002 100644 --- a/meta/cretonne/typevar.py +++ b/meta/cretonne/typevar.py @@ -39,6 +39,26 @@ class TypeSet(object): The ranges are inclusive from smallest bit-width to largest bit-width. + A typeset representing scalar integer types `i8` through `i32`: + + >>> TypeSet(ints=(8, 32)) + TypeSet(lanes=(1, 1), ints=(8, 32)) + + Passing `True` instead of a range selects all available scalar types: + + >>> TypeSet(ints=True) + TypeSet(lanes=(1, 1), ints=(8, 64)) + >>> TypeSet(floats=True) + TypeSet(lanes=(1, 1), floats=(32, 64)) + >>> TypeSet(bools=True) + TypeSet(lanes=(1, 1), bools=(1, 64)) + + Similarly, passing `True` for the lanes selects all possible scalar and + vector types: + + >>> TypeSet(lanes=True, ints=True) + TypeSet(lanes=(1, 256), ints=(8, 64)) + :param lanes: `(min, max)` inclusive range of permitted vector lane counts. :param ints: `(min, max)` inclusive range of permitted scalar integer widths. @@ -48,11 +68,18 @@ class TypeSet(object): widths. """ - def __init__(self, lanes, ints=None, floats=None, bools=None): - self.min_lanes, self.max_lanes = lanes - assert is_power_of_two(self.min_lanes) - assert is_power_of_two(self.max_lanes) - assert self.max_lanes <= MAX_LANES + def __init__(self, lanes=None, ints=None, floats=None, bools=None): + if lanes: + if lanes is True: + lanes = (1, MAX_LANES) + self.min_lanes, self.max_lanes = lanes + assert is_power_of_two(self.min_lanes) + assert is_power_of_two(self.max_lanes) + assert self.max_lanes <= MAX_LANES + else: + self.min_lanes = 1 + self.max_lanes = 1 + assert self.min_lanes <= self.max_lanes if ints: if ints is True: @@ -61,6 +88,7 @@ class TypeSet(object): assert is_power_of_two(self.min_int) assert is_power_of_two(self.max_int) assert self.max_int <= MAX_BITS + assert self.min_int <= self.max_int else: self.min_int = None self.max_int = None @@ -73,6 +101,7 @@ class TypeSet(object): assert self.min_float >= 32 assert is_power_of_two(self.max_float) assert self.max_float <= 64 + assert self.min_float <= self.max_float else: self.min_float = None self.max_float = None @@ -84,6 +113,7 @@ class TypeSet(object): assert is_power_of_two(self.min_bool) assert is_power_of_two(self.max_bool) assert self.max_bool <= MAX_BITS + assert self.min_bool <= self.max_bool else: self.min_bool = None self.max_bool = None @@ -96,7 +126,10 @@ class TypeSet(object): self.min_bool, self.max_bool) def __hash__(self): - return hash(self.typeset_key()) + h = hash(self.typeset_key()) + assert h == getattr(self, 'prev_hash', h), "TypeSet changed!" + self.prev_hash = h + return h def __eq__(self, other): return self.typeset_key() == other.typeset_key() From 65caf2d9a14e8f272b841fedcfb5df36d6e65ad7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 14:53:37 -0700 Subject: [PATCH 335/968] In-place intersection of type sets. --- meta/cretonne/typevar.py | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/meta/cretonne/typevar.py b/meta/cretonne/typevar.py index e91c6ab002..918933b7c6 100644 --- a/meta/cretonne/typevar.py +++ b/meta/cretonne/typevar.py @@ -160,6 +160,47 @@ class TypeSet(object): fmt.line('max_{}: {},'.format( field, int_log2(max_val) + 1)) + def __iand__(self, other): + """ + Intersect self with other type set. + + >>> a = TypeSet(lanes=True, ints=(16, 32)) + >>> a + TypeSet(lanes=(1, 256), ints=(16, 32)) + >>> b = TypeSet(lanes=(4, 16), ints=True) + >>> a &= b + >>> a + TypeSet(lanes=(4, 16), ints=(16, 32)) + + >>> a = TypeSet(lanes=True, bools=(1, 8)) + >>> b = TypeSet(lanes=True, bools=(16, 32)) + >>> a &= b + >>> a + TypeSet(lanes=(1, 256)) + """ + self.min_lanes = max(self.min_lanes, other.min_lanes) + self.max_lanes = min(self.max_lanes, other.max_lanes) + + self.min_int = max(self.min_int, other.min_int) + self.max_int = min(self.max_int, other.max_int) + if self.min_int > self.max_int: + self.min_int = None + self.max_int = None + + self.min_float = max(self.min_float, other.min_float) + self.max_float = min(self.max_float, other.max_float) + if self.min_float > self.max_float: + self.min_float = None + self.max_float = None + + self.min_bool = max(self.min_bool, other.min_bool) + self.max_bool = min(self.max_bool, other.max_bool) + if self.min_bool > self.max_bool: + self.min_bool = None + self.max_bool = None + + return self + class TypeVar(object): """ From 60b2257331891b010aa6714a44035318d2ac9669 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 15:39:54 -0700 Subject: [PATCH 336/968] Add HalfWidth and DoubleWidth type variable functions. These functions compute types with half or double the number of bits in each lane. --- meta/cretonne/test_typevar.py | 21 +++++++++- meta/cretonne/typevar.py | 30 ++++++++++++++ src/libcretonne/ir/instructions.rs | 8 ++++ src/libcretonne/ir/types.rs | 65 +++++++++++++++++++++++++++++- 4 files changed, 122 insertions(+), 2 deletions(-) diff --git a/meta/cretonne/test_typevar.py b/meta/cretonne/test_typevar.py index 128b32b71f..a841acfdb9 100644 --- a/meta/cretonne/test_typevar.py +++ b/meta/cretonne/test_typevar.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from unittest import TestCase from doctest import DocTestSuite from . import typevar -from .typevar import TypeSet +from .typevar import TypeSet, TypeVar def load_tests(loader, tests, ignore): @@ -43,3 +43,22 @@ class TestTypeSet(TestCase): # Can't rehash after modification. with self.assertRaises(AssertionError): a in s + + +class TestTypeVar(TestCase): + def test_functions(self): + x = TypeVar('x', 'all ints', ints=True) + with self.assertRaises(AssertionError): + x.double_width() + with self.assertRaises(AssertionError): + x.half_width() + + x2 = TypeVar('x2', 'i16 and up', ints=(16, 64)) + with self.assertRaises(AssertionError): + x2.double_width() + self.assertEqual(str(x2.half_width()), '`HalfWidth(x2)`') + + x3 = TypeVar('x3', 'up to i32', ints=(8, 32)) + self.assertEqual(str(x3.double_width()), '`DoubleWidth(x3)`') + with self.assertRaises(AssertionError): + x3.half_width() diff --git a/meta/cretonne/typevar.py b/meta/cretonne/typevar.py index 918933b7c6..a29548af38 100644 --- a/meta/cretonne/typevar.py +++ b/meta/cretonne/typevar.py @@ -274,6 +274,36 @@ class TypeVar(object): """ return TypeVar(None, None, base=self, derived_func='AsBool') + def half_width(self): + """ + Return a derived type variable that has the same number of vector lanes + as this one, but the lanes are half the width. + """ + ts = self.type_set + if ts.min_int: + assert ts.min_int > 8, "Can't halve all integer types" + if ts.min_float: + assert ts.min_float > 32, "Can't halve all float types" + if ts.min_bool: + assert ts.min_bool > 8, "Can't halve all boolean types" + + return TypeVar(None, None, base=self, derived_func='HalfWidth') + + def double_width(self): + """ + Return a derived type variable that has the same number of vector lanes + as this one, but the lanes are double the width. + """ + ts = self.type_set + if ts.max_int: + assert ts.max_int < MAX_BITS, "Can't double all integer types." + if ts.max_float: + assert ts.max_float < MAX_BITS, "Can't double all float types." + if ts.max_bool: + assert ts.max_bool < MAX_BITS, "Can't double all boolean types." + + return TypeVar(None, None, base=self, derived_func='DoubleWidth') + def operand_kind(self): # When a `TypeVar` object is used to describe the type of an `Operand` # in an instruction definition, the kind of that operand is an SSA diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index afd6ba319f..f60d23c60c 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -547,6 +547,12 @@ enum OperandConstraint { /// This operand is `ctrlType.as_bool()`. AsBool, + + /// This operand is `ctrlType.half_width()`. + HalfWidth, + + /// This operand is `ctrlType.double_width()`. + DoubleWidth, } impl OperandConstraint { @@ -562,6 +568,8 @@ impl OperandConstraint { Same => Some(ctrl_type), LaneOf => Some(ctrl_type.lane_type()), AsBool => Some(ctrl_type.as_bool()), + HalfWidth => Some(ctrl_type.half_width().expect("invalid type for half_width")), + DoubleWidth => Some(ctrl_type.double_width().expect("invalid type for double_width")), } } } diff --git a/src/libcretonne/ir/types.rs b/src/libcretonne/ir/types.rs index 1eb546bde6..7c9175f460 100644 --- a/src/libcretonne/ir/types.rs +++ b/src/libcretonne/ir/types.rs @@ -67,7 +67,7 @@ impl Type { } } - /// Get a type with the same number of lanes as this type, but with the lanes replaces by + /// Get a type with the same number of lanes as this type, but with the lanes replaced by /// booleans of the same size. pub fn as_bool(self) -> Type { // Replace the low 4 bits with the boolean version, preserve the high 4 bits. @@ -81,6 +81,38 @@ impl Type { Type(lane.0 | (self.0 & 0xf0)) } + /// Get a type with the same number of lanes as this type, but with lanes that are half the + /// number of bits. + pub fn half_width(self) -> Option { + let lane = match self.lane_type() { + I16 => I8, + I32 => I16, + I64 => I32, + F64 => F32, + B16 => B8, + B32 => B16, + B64 => B32, + _ => return None, + }; + Some(Type(lane.0 | (self.0 & 0xf0))) + } + + /// Get a type with the same number of lanes as this type, but with lanes that are twice the + /// number of bits. + pub fn double_width(self) -> Option { + let lane = match self.lane_type() { + I8 => I16, + I16 => I32, + I32 => I64, + F32 => F64, + B8 => B16, + B16 => B32, + B32 => B64, + _ => return None, + }; + Some(Type(lane.0 | (self.0 & 0xf0))) + } + /// Is this the VOID type? pub fn is_void(self) -> bool { self == VOID @@ -333,6 +365,37 @@ mod tests { assert_eq!(F64.lane_bits(), 64); } + #[test] + fn typevar_functions() { + assert_eq!(VOID.half_width(), None); + assert_eq!(B1.half_width(), None); + assert_eq!(B8.half_width(), None); + assert_eq!(B16.half_width(), Some(B8)); + assert_eq!(B32.half_width(), Some(B16)); + assert_eq!(B64.half_width(), Some(B32)); + assert_eq!(I8.half_width(), None); + assert_eq!(I16.half_width(), Some(I8)); + assert_eq!(I32.half_width(), Some(I16)); + assert_eq!(I32X4.half_width(), Some(I16X4)); + assert_eq!(I64.half_width(), Some(I32)); + assert_eq!(F32.half_width(), None); + assert_eq!(F64.half_width(), Some(F32)); + + assert_eq!(VOID.double_width(), None); + assert_eq!(B1.double_width(), None); + assert_eq!(B8.double_width(), Some(B16)); + assert_eq!(B16.double_width(), Some(B32)); + assert_eq!(B32.double_width(), Some(B64)); + assert_eq!(B64.double_width(), None); + assert_eq!(I8.double_width(), Some(I16)); + assert_eq!(I16.double_width(), Some(I32)); + assert_eq!(I32.double_width(), Some(I64)); + assert_eq!(I32X4.double_width(), Some(I64X4)); + assert_eq!(I64.double_width(), None); + assert_eq!(F32.double_width(), Some(F64)); + assert_eq!(F64.double_width(), None); + } + #[test] fn vectors() { let big = F64.by(256).unwrap(); From 2a2871e739d98378234f6b223fbde0595600c5f1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 16:09:26 -0700 Subject: [PATCH 337/968] Expand OpcodeConstraints to 32 bits. Make room for 255 different type sets and 2^16 entries in the operand constraints table. --- meta/gen_instr.py | 15 ++++---- src/libcretonne/ir/instructions.rs | 57 ++++++++++++++---------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index cc636cf83a..582c0477f0 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -297,8 +297,8 @@ def gen_type_constraints(fmt, instrs): # Preload table with constraints for typical binops. operand_seqs.add(['Same'] * 3) - # TypeSet indexes are encoded in 3 bits, with `111` reserved. - typeset_limit = 7 + # TypeSet indexes are encoded in 8 bits, with `0xff` reserved. + typeset_limit = 0xff fmt.comment('Table of opcode constraints.') with fmt.indented( @@ -331,11 +331,14 @@ def gen_type_constraints(fmt, instrs): 'Polymorphic over {}'.format(ctrl_typevar.type_set)) # Compute the bit field encoding, c.f. instructions.rs. assert fixed_results < 8, "Bit field encoding too tight" - bits = (offset << 8) | (ctrl_typeset << 4) | fixed_results + flags = fixed_results if use_typevar_operand: - bits |= 8 - assert bits < 0x10000, "Constraint table too large for bit field" - fmt.line('OpcodeConstraints({:#06x}),'.format(bits)) + flags |= 8 + + with fmt.indented('OpcodeConstraints {', '},'): + fmt.line('flags: {:#04x},'.format(flags)) + fmt.line('typeset_offset: {},'.format(ctrl_typeset)) + fmt.line('constraint_offset: {},'.format(offset)) fmt.comment('Table of value type sets.') assert len(type_sets.table) <= typeset_limit, "Too many type sets" diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index f60d23c60c..9e259273cc 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -398,52 +398,49 @@ pub enum BranchInfo<'a> { /// The `InstructionFormat` determines the constraints on most operands, but `Value` operands and /// results are not determined by the format. Every `Opcode` has an associated /// `OpcodeConstraints` object that provides the missing details. -/// -/// Since there can be a lot of opcodes, the `OpcodeConstraints` object is encoded as a bit field -/// by the `meta/gen_instr.py` script. -/// -/// The bit field bits are: -/// -/// Bits 0-2: -/// Number of fixed result values. This does not include `variable_args` results as are -/// produced by call instructions. -/// -/// Bit 3: -/// This opcode is polymorphic and the controlling type variable can be inferred from the -/// designated input operand. This is the `typevar_operand` index given to the -/// `InstructionFormat` meta language object. When bit 0 is not set, the controlling type -/// variable must be the first output value instead. -/// -/// Bits 4-7: -/// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. -/// -/// Bits 8-15: -/// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first -/// `fixed_results()` entries describe the result constraints, then follows constraints for the -/// fixed `Value` input operands. The number of `Value` inputs isdetermined by the instruction -/// format. -/// #[derive(Clone, Copy)] -pub struct OpcodeConstraints(u16); +pub struct OpcodeConstraints { + /// Flags for this opcode encoded as a bit field: + /// + /// Bits 0-2: + /// Number of fixed result values. This does not include `variable_args` results as are + /// produced by call instructions. + /// + /// Bit 3: + /// This opcode is polymorphic and the controlling type variable can be inferred from the + /// designated input operand. This is the `typevar_operand` index given to the + /// `InstructionFormat` meta language object. When bit 0 is not set, the controlling type + /// variable must be the first output value instead. + flags: u8, + + /// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. + typeset_offset: u8, + + /// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first + /// `fixed_results()` entries describe the result constraints, then follows constraints for the + /// fixed `Value` input operands. The number of `Value` inputs is determined by the instruction + /// format. + constraint_offset: u16, +} impl OpcodeConstraints { /// Can the controlling type variable for this opcode be inferred from the designated value /// input operand? /// This also implies that this opcode is polymorphic. pub fn use_typevar_operand(self) -> bool { - (self.0 & 0x8) != 0 + (self.flags & 0x8) != 0 } /// Get the number of *fixed* result values produced by this opcode. /// This does not include `variable_args` produced by calls. pub fn fixed_results(self) -> usize { - (self.0 & 0x7) as usize + (self.flags & 0x7) as usize } /// Get the offset into `TYPE_SETS` for the controlling type variable. /// Returns `None` if the instruction is not polymorphic. fn typeset_offset(self) -> Option { - let offset = ((self.0 & 0xff) >> 4) as usize; + let offset = self.typeset_offset as usize; if offset < TYPE_SETS.len() { Some(offset) } else { @@ -453,7 +450,7 @@ impl OpcodeConstraints { /// Get the offset into OPERAND_CONSTRAINTS where the descriptors for this opcode begin. fn constraint_offset(self) -> usize { - (self.0 >> 8) as usize + self.constraint_offset as usize } /// Get the value type of result number `n`, having resolved the controlling type variable to From 29c449f117c32fc7bd042dd056e87ceb6e77a128 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 16:22:32 -0700 Subject: [PATCH 338/968] Add legalization helper instructions. The isplit_lohi instruction breaks an integer into two halves. This will typically be used to get the two halves of an `i64` value on 32-bit CPUs. The iconcat_lohi is the reverse operation. It reconstructs the `i64` from the low and high bits. --- docs/langref.rst | 8 +++++++ meta/cretonne/base.py | 38 ++++++++++++++++++++++++++++++ meta/cretonne/formats.py | 1 + src/libcretonne/ir/instructions.rs | 6 +++++ src/libcretonne/write.rs | 1 + src/libreader/parser.rs | 9 +++++++ 6 files changed, 63 insertions(+) diff --git a/docs/langref.rst b/docs/langref.rst index e42d82ecf4..e4d2d9dbd3 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -823,6 +823,14 @@ Conversion operations .. autoinst:: fcvt_from_uint .. autoinst:: fcvt_from_sint +Legalization operations +----------------------- + +These instructions are used as helpers when legalizing types and operations for +the target ISA. + +.. autoinst:: isplit_lohi +.. autoinst:: iconcat_lohi Base instruction group ====================== diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index fdca2484f9..fc8de5d78f 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -1105,4 +1105,42 @@ fcvt_from_sint = Instruction( """, ins=x, outs=a) +# +# Legalization helper instructions. +# + +WideInt = TypeVar( + 'WideInt', 'A scalar integer type from `i16` upwards', + ints=(16, 64)) +x = Operand('x', WideInt) +lo = Operand( + 'lo', WideInt.half_width(), 'The low bits of `x`') +hi = Operand( + 'hi', WideInt.half_width(), 'The high bits of `x`') + +isplit_lohi = Instruction( + 'isplit_lohi', r""" + Split a scalar integer into low and high parts. + + Returns the low half of `x` and the high half of `x` as two independent + values. + """, + ins=x, outs=(lo, hi)) + + +NarrowInt = TypeVar( + 'NarrowInt', 'A scalar integer type up to `i32`', + ints=(8, 32)) +lo = Operand('lo', NarrowInt) +hi = Operand('hi', NarrowInt) +a = Operand( + 'a', NarrowInt.double_width(), + doc='The concatenation of `lo` and `hi`') + +iconcat_lohi = Instruction( + 'iconcat_lohi', r""" + Concatenate low and high bits to form a larger integer type. + """, + ins=(lo, hi), outs=a) + instructions.close() diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 48b9d978af..df0019bff4 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -17,6 +17,7 @@ UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) UnaryIeee64 = InstructionFormat(ieee64) UnaryImmVector = InstructionFormat(immvector) +UnarySplit = InstructionFormat(value, multiple_results=True) Binary = InstructionFormat(value, value) BinaryImm = InstructionFormat(value, imm64) diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 9e259273cc..fd7fd5f25b 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -120,6 +120,12 @@ pub enum InstructionData { opcode: Opcode, ty: Type, // TBD: imm: Box }, + UnarySplit { + opcode: Opcode, + ty: Type, + second_result: Value, + arg: Value, + }, Binary { opcode: Opcode, ty: Type, diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index ea73d18666..8f7acde772 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -185,6 +185,7 @@ fn write_instruction(w: &mut Write, UnaryIeee32 { imm, .. } => writeln!(w, " {}", imm), UnaryIeee64 { imm, .. } => writeln!(w, " {}", imm), UnaryImmVector { .. } => writeln!(w, " [...]"), + UnarySplit { arg, .. } => writeln!(w, " {}", arg), Binary { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), BinaryImmRev { imm, arg, .. } => writeln!(w, " {}, {}", imm, arg), diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 723cfb1951..258f920b81 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -119,6 +119,7 @@ impl Context { InstructionData::UnaryImmVector { .. } => {} InstructionData::Unary { ref mut arg, .. } | + InstructionData::UnarySplit { ref mut arg, .. } | InstructionData::BinaryImm { ref mut arg, .. } | InstructionData::BinaryImmRev { ref mut arg, .. } | InstructionData::ExtractLane { ref mut arg, .. } | @@ -1013,6 +1014,14 @@ impl<'a> Parser<'a> { InstructionFormat::UnaryImmVector => { unimplemented!(); } + InstructionFormat::UnarySplit => { + InstructionData::UnarySplit { + opcode: opcode, + ty: VOID, + second_result: NO_VALUE, + arg: try!(self.match_value("expected SSA value operand")), + } + } InstructionFormat::Binary => { let lhs = try!(self.match_value("expected SSA value first operand")); try!(self.match_token(Token::Comma, "expected ',' between operands")); From 7c91bacafebddcf0aa59c7ebf53f5282518103ed Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 22 Sep 2016 12:59:31 -0700 Subject: [PATCH 339/968] Define AST nodes and instruction transformations. Enable syntax: iadd(x, y) which creates an Apply node. Enable syntax: z << iadd(x, y) which creates a Def node. Add an XForm class which represents source and destination patterns as RTL lists. --- meta/cretonne/__init__.py | 26 +++++- meta/cretonne/ast.py | 122 ++++++++++++++++++++++++ meta/cretonne/test_ast.py | 28 ++++++ meta/cretonne/test_xform.py | 59 ++++++++++++ meta/cretonne/xform.py | 182 ++++++++++++++++++++++++++++++++++++ 5 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 meta/cretonne/ast.py create mode 100644 meta/cretonne/test_ast.py create mode 100644 meta/cretonne/test_xform.py create mode 100644 meta/cretonne/xform.py diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index d318b27ef3..a5fe761bce 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -10,6 +10,7 @@ import math import importlib from collections import OrderedDict from .predicates import And +from .ast import Apply camel_re = re.compile('(^|_)([a-z])') @@ -603,6 +604,12 @@ class Operand(object): def __str__(self): return "`{}`".format(self.name) + def is_value(self): + """ + Is this an SSA value operand? + """ + return self.kind is value + class InstructionFormat(object): """ @@ -779,10 +786,13 @@ class Instruction(object): self.format = InstructionFormat.lookup(self.ins, self.outs) # Indexes into outs for value results. Others are `variable_args`. self.value_results = tuple( - i for i, o in enumerate(self.outs) if o.kind is value) + i for i, o in enumerate(self.outs) if o.is_value()) self._verify_polymorphic() InstructionGroup.append(self) + def __str__(self): + return self.name + def _verify_polymorphic(self): """ Check if this instruction is polymorphic, and verify its use of type @@ -910,6 +920,13 @@ class Instruction(object): assert not self.is_polymorphic, self return (self, ()) + def __call__(self, *args): + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + return Apply(self, args) + class BoundInstruction(object): """ @@ -951,6 +968,13 @@ class BoundInstruction(object): assert len(self.typevars) == 1 + len(self.inst.other_typevars) return (self.inst, self.typevars) + def __call__(self, *args): + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + return Apply(self, args) + # Defining target ISAs. diff --git a/meta/cretonne/ast.py b/meta/cretonne/ast.py new file mode 100644 index 0000000000..8c9c8576cb --- /dev/null +++ b/meta/cretonne/ast.py @@ -0,0 +1,122 @@ +""" +Abstract syntax trees. + +This module defines classes that can be used to create abstract syntax trees +for patern matching an rewriting of cretonne instructions. +""" +from __future__ import absolute_import + + +class Def(object): + """ + An AST definition associates a set of variables with the values produced by + an expression. + + Example: + + >>> from .base import iadd_cout, iconst + >>> x = Var('x') + >>> y = Var('y') + >>> x << iconst(4) + (Var(x),) << Apply(iconst, (4,)) + >>> (x, y) << iadd_cout(4, 5) + (Var(x), Var(y)) << Apply(iadd_cout, (4, 5)) + + The `<<` operator is used to create variable definitions. + + :param defs: Single variable or tuple of variables to be defined. + :param expr: Expression generating the values. + """ + + def __init__(self, defs, expr): + if not isinstance(defs, tuple): + defs = (defs,) + assert isinstance(expr, Expr) + self.defs = defs + self.expr = expr + + def __repr__(self): + return "{} << {!r}".format(self.defs, self.expr) + + def __str__(self): + if len(self.defs) == 1: + return "{!s} << {!s}".format(self.defs[0], self.expr) + else: + return "({}) << {!s}".format(", ".join(self.defs), self.expr) + + +class Expr(object): + """ + An AST expression. + """ + + def __rlshift__(self, other): + """ + Define variables using `var << expr` or `(v1, v2) << expr`. + """ + return Def(other, self) + + +class Var(Expr): + """ + A free variable. + """ + + def __init__(self, name): + self.name = name + # Bitmask of contexts where this variable is defined. + # See XForm._rewrite_defs(). + self.defctx = 0 + + def __str__(self): + return self.name + + def __repr__(self): + s = self.name + if self.defctx: + s += ", d={:02b}".format(self.defctx) + return "Var({})".format(s) + + +class Apply(Expr): + """ + Apply an instruction to arguments. + + An `Apply` AST expression is created by using function call syntax on + instructions. This applies to both bound and unbound polymorphic + instructions: + + >>> from .base import jump, iadd + >>> jump('next', ()) + Apply(jump, ('next', ())) + >>> iadd.i32('x', 'y') + Apply(iadd.i32, ('x', 'y')) + + :param inst: The instruction being applied, an `Instruction` or + `BoundInstruction` instance. + :param args: Tuple of arguments. + """ + + def __init__(self, inst, args): + from . import BoundInstruction + if isinstance(inst, BoundInstruction): + self.inst = inst.inst + self.typevars = inst.typevars + else: + self.inst = inst + self.typevars = () + self.args = args + assert len(self.inst.ins) == len(args) + + def instname(self): + i = self.inst.name + for t in self.typevars: + i += '.{}'.format(t) + return i + + def __repr__(self): + return "Apply({}, {})".format(self.instname(), self.args) + + def __str__(self): + args = ', '.join(map(str, self.args)) + return '{}({})'.format(self.instname(), args) diff --git a/meta/cretonne/test_ast.py b/meta/cretonne/test_ast.py new file mode 100644 index 0000000000..71791db3f6 --- /dev/null +++ b/meta/cretonne/test_ast.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import +from unittest import TestCase +from doctest import DocTestSuite +from . import ast +from .base import jump, iadd + + +def load_tests(loader, tests, ignore): + tests.addTests(DocTestSuite(ast)) + return tests + + +x = 'x' +y = 'y' +a = 'a' + + +class TestPatterns(TestCase): + def test_apply(self): + i = jump(x, y) + self.assertEqual(repr(i), "Apply(jump, ('x', 'y'))") + + i = iadd.i32(x, y) + self.assertEqual(repr(i), "Apply(iadd.i32, ('x', 'y'))") + + def test_single_ins(self): + pat = a << iadd.i32(x, y) + self.assertEqual(repr(pat), "('a',) << Apply(iadd.i32, ('x', 'y'))") diff --git a/meta/cretonne/test_xform.py b/meta/cretonne/test_xform.py new file mode 100644 index 0000000000..b472a8f458 --- /dev/null +++ b/meta/cretonne/test_xform.py @@ -0,0 +1,59 @@ +from __future__ import absolute_import +from unittest import TestCase +from doctest import DocTestSuite +from . import xform +from .base import iadd, iadd_imm, iconst +from .ast import Var +from .xform import Rtl, XForm + + +def load_tests(loader, tests, ignore): + tests.addTests(DocTestSuite(xform)) + return tests + + +x = Var('x') +y = Var('y') +a = Var('a') +c = Var('c') + + +class TestXForm(TestCase): + def test_macro_pattern(self): + src = Rtl(a << iadd_imm(x, y)) + dst = Rtl( + c << iconst(y), + a << iadd(x, c)) + XForm(src, dst) + + def test_def_input(self): + # Src pattern has a def which is an input in dst. + src = Rtl(a << iadd_imm(x, 1)) + dst = Rtl(y << iadd_imm(a, 1)) + with self.assertRaisesRegexp( + AssertionError, + "'a' used as both input and def"): + XForm(src, dst) + + def test_input_def(self): + # Converse of the above. + src = Rtl(y << iadd_imm(a, 1)) + dst = Rtl(a << iadd_imm(x, 1)) + with self.assertRaisesRegexp( + AssertionError, + "'a' used as both input and def"): + XForm(src, dst) + + def test_extra_input(self): + src = Rtl(a << iadd_imm(x, 1)) + dst = Rtl(a << iadd(x, y)) + with self.assertRaisesRegexp(AssertionError, "extra inputs in dst"): + XForm(src, dst) + + def test_double_def(self): + src = Rtl( + a << iadd_imm(x, 1), + a << iadd(x, y)) + dst = Rtl(a << iadd(x, y)) + with self.assertRaisesRegexp(AssertionError, "'a' multiply defined"): + XForm(src, dst) diff --git a/meta/cretonne/xform.py b/meta/cretonne/xform.py new file mode 100644 index 0000000000..16a9eae870 --- /dev/null +++ b/meta/cretonne/xform.py @@ -0,0 +1,182 @@ +""" +Instruction transformations. +""" +from __future__ import absolute_import +from .ast import Def, Var, Apply + + +SRCCTX = 1 +DSTCTX = 2 + + +class Rtl(object): + """ + Register Transfer Language list. + + An RTL object contains a list of register assignments in the form of `Def` + objects and/or Apply objects for side-effecting instructions. + + An RTL list can represent both a source pattern to be matched, or a + destination pattern to be inserted. + """ + + def __init__(self, *args): + self.rtl = args + + def __iter__(self): + return iter(self.rtl) + + +class XForm(object): + """ + An instruction transformation consists of a source and destination pattern. + + Patterns are expressed in *register transfer language* as tuples of + `ast.Def` or `ast.Expr` nodes. + + A legalization pattern must have a source pattern containing only a single + instruction. + + >>> from .base import iconst, iadd, iadd_imm + >>> a = Var('a') + >>> c = Var('c') + >>> v = Var('v') + >>> x = Var('x') + >>> XForm( + ... Rtl(c << iconst(v), + ... a << iadd(x, c)), + ... Rtl(a << iadd_imm(x, v))) + XForm(inputs=[Var(v), Var(x)], defs=[Var(c, d=01), Var(a, d=11)], + c << iconst(v) + a << iadd(x, c) + => + a << iadd_imm(x, v) + ) + """ + + def __init__(self, src, dst): + self.src = src + self.dst = dst + # Variables that are inputs to the source pattern. + self.inputs = list() + # Variables defined in either src or dst. + self.defs = list() + + # Rewrite variables in src and dst RTL lists to our own copies. + # Map name -> private Var. + symtab = dict() + self._rewrite_rtl(src, symtab, SRCCTX) + num_src_inputs = len(self.inputs) + self._rewrite_rtl(dst, symtab, DSTCTX) + + # Check for inconsistently used inputs. + for i in self.inputs: + if i.defctx: + raise AssertionError( + "'{}' used as both input and def".format(i)) + + # Check for spurious inputs in dst. + if len(self.inputs) > num_src_inputs: + raise AssertionError( + "extra inputs in dst RTL: {}".format( + self.inputs[num_src_inputs:])) + + def __repr__(self): + s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs) + s += '\n '.join(str(n) for n in self.src) + s += '\n=>\n ' + s += '\n '.join(str(n) for n in self.dst) + s += '\n)' + return s + + def _rewrite_rtl(self, rtl, symtab, context): + for line in rtl: + if isinstance(line, Def): + line.defs = tuple( + self._rewrite_defs(line.defs, symtab, context)) + expr = line.expr + else: + expr = line + self._rewrite_expr(expr, symtab, context) + + def _rewrite_expr(self, expr, symtab, context): + """ + Find all uses of variables in `expr` and replace them with our own + local symbols. + """ + + # Accept a whole expression tree. + stack = [expr] + while len(stack) > 0: + expr = stack.pop() + expr.args = tuple( + self._rewrite_uses(expr, stack, symtab, context)) + + def _rewrite_defs(self, defs, symtab, context): + """ + Given a tuple of symbols defined in a Def, rewrite them to local + symbols. Yield the new locals. + """ + for sym in defs: + name = str(sym) + if name in symtab: + var = symtab[name] + if var.defctx & context: + raise AssertionError("'{}' multiply defined".format(name)) + else: + var = Var(name) + symtab[name] = var + self.defs.append(var) + var.defctx |= context + yield var + + def _rewrite_uses(self, expr, stack, symtab, context): + """ + Given an `Apply` expr, rewrite all uses in its arguments to local + variables. Yield a sequence of new arguments. + + Append any `Apply` arguments to `stack`. + """ + for arg, operand in zip(expr.args, expr.inst.ins): + # Nested instructions are allowed. Visit recursively. + if isinstance(arg, Apply): + stack.push(arg) + yield arg + continue + if not isinstance(arg, Var): + assert not operand.is_value(), "Value arg must be `Var`" + yield arg + continue + # This is supposed to be a symbolic value reference. + name = str(arg) + if name in symtab: + var = symtab[name] + # The variable must be used consistenty as a def or input. + if var.defctx and (var.defctx & context) == 0: + raise AssertionError( + "'{}' used as both input and def" + .format(name)) + else: + # First time use of variable. + var = Var(name) + symtab[name] = var + self.inputs.append(var) + yield var + + +class XFormGroup(object): + """ + A group of related transformations. + """ + + def __init__(self): + self.xforms = list() + + def legalize(self, src, dst): + """ + Add a legalization pattern to this group. + + :param src: Single `Def` or `Apply` to be legalized. + :param dst: `Rtl` list of replacement instructions. + """ + self.xforms.append(XForm(Rtl(src), dst)) From a460a637ddd4e35acc468c01528ead973c50751b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 07:59:47 -0700 Subject: [PATCH 340/968] Add legalization patterns. --- meta/cretonne/legalize.py | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 meta/cretonne/legalize.py diff --git a/meta/cretonne/legalize.py b/meta/cretonne/legalize.py new file mode 100644 index 0000000000..14eb879abd --- /dev/null +++ b/meta/cretonne/legalize.py @@ -0,0 +1,48 @@ +""" +Patterns for legalizing the `base` instruction set. + +The base Cretonne instruction set is 'fat', and many instructions don't have +legal representations in a given target ISA. This module defines legalization +patterns that describe how base instructions can be transformed to other base +instructions that are legal. +""" +from __future__ import absolute_import +from .base import iadd, iadd_cout, iadd_cin, isplit_lohi, iconcat_lohi +from .base import isub, isub_bin, isub_bout +from .ast import Var +from .xform import Rtl, XFormGroup + + +narrow = XFormGroup() + +x = Var('x') +y = Var('y') +a = Var('a') +b = Var('b') +c = Var('c') +xl = Var('xl') +xh = Var('xh') +yl = Var('yl') +yh = Var('yh') +al = Var('al') +ah = Var('ah') + +narrow.legalize( + a << iadd(x, y), + Rtl( + (xl, xh) << isplit_lohi(x), + (yl, yh) << isplit_lohi(y), + (al, c) << iadd_cout(xl, yl), + ah << iadd_cin(xh, yh, c), + a << iconcat_lohi(al, ah) + )) + +narrow.legalize( + a << isub(x, y), + Rtl( + (xl, xh) << isplit_lohi(x), + (yl, yh) << isplit_lohi(y), + (al, b) << isub_bout(xl, yl), + ah << isub_bin(xh, yh, b), + a << iconcat_lohi(al, ah) + )) From 67abb2d2f678e71a1e07b8162788ed06b45633fe Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Oct 2016 10:44:33 -0700 Subject: [PATCH 341/968] Create a phantom workspace manifest for all crates. Share a single Cargo.lock and target directory at the repo top-level. --- .gitignore | 2 + .travis.yml | 5 +- Cargo.toml | 3 + src/.gitignore | 2 - src/tools/Cargo.lock | 159 ------------------------------------------- test-all.sh | 4 +- 6 files changed, 8 insertions(+), 167 deletions(-) create mode 100644 Cargo.toml delete mode 100644 src/.gitignore delete mode 100644 src/tools/Cargo.lock diff --git a/.gitignore b/.gitignore index 002a368e2b..9ceaac3ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ *.swp *.swo tags +target +Cargo.lock diff --git a/.travis.yml b/.travis.yml index 33eed41b71..034d0e6c55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,4 @@ rust: - beta - nightly script: ./test-all.sh -cache: - - cargo - - directories: - - src/tools/target +cache: cargo diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..63597ad428 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +# Phantom workspace manifest for all Cretonne crates. +[workspace] +members = ["src/tools"] diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index d6168f7c42..0000000000 --- a/src/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -lib*/Cargo.lock diff --git a/src/tools/Cargo.lock b/src/tools/Cargo.lock deleted file mode 100644 index 28d0f79f5b..0000000000 --- a/src/tools/Cargo.lock +++ /dev/null @@ -1,159 +0,0 @@ -[root] -name = "cretonne-tools" -version = "0.0.0" -dependencies = [ - "cretonne 0.0.0", - "cretonne-reader 0.0.0", - "docopt 0.6.83 (registry+https://github.com/rust-lang/crates.io-index)", - "filecheck 0.0.0", - "num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "aho-corasick" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cretonne" -version = "0.0.0" - -[[package]] -name = "cretonne-reader" -version = "0.0.0" -dependencies = [ - "cretonne 0.0.0", -] - -[[package]] -name = "docopt" -version = "0.6.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "filecheck" -version = "0.0.0" -dependencies = [ - "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lazy_static" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memchr" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num_cpus" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex" -version = "0.1.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rustc-serialize" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "strsim" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "thread-id" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf8-ranges" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" -"checksum docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc0acb4ce0828c6a5a11d47baa432fe885881c27428c3a4e473e454ffe57a76" -"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c96061f0c8a2dc27482e394d82e23073569de41d73cd736672ccd3e5c7471bfd" -"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)" = "e58a1b7d2bfecc0746e8587c30a53d01ea7bc0e98fac54e5aaa375b94338a0cc" -"checksum regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "baa04823ba7be7ed0bed3d0704c7b923019d9c4e4931c5af2804c7c7a0e3d00b" -"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" -"checksum strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4d73a2c36a4d095ed1a6df5cbeac159863173447f7a82b3f4757426844ab825" -"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" -"checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" -"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" -"checksum winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3969e500d618a5e974917ddefd0ba152e4bcaae5eb5d9b8c1fbc008e9e28c24e" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/test-all.sh b/test-all.sh index 87aea4573a..4cf436e100 100755 --- a/test-all.sh +++ b/test-all.sh @@ -51,12 +51,12 @@ done # Build cton-util for parser testing. cd "$topdir/src/tools" banner "Rust documentation" -echo "open $topdir/src/tools/target/doc/cretonne/index.html" +echo "open $topdir/target/doc/cretonne/index.html" cargo doc banner "Rust release build" cargo build --release -export CTONUTIL="$topdir/src/tools/target/release/cton-util" +export CTONUTIL="$topdir/target/release/cton-util" cd "$topdir" banner "File tests" From 02eae6afd02f3b03d54687e249cee507242ec658 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 13:41:27 -0700 Subject: [PATCH 342/968] Upgrade to rustfmt 0.6.2 --- test-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-all.sh b/test-all.sh index 4cf436e100..843b1e82be 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.6.0" +RUSTFMT_VERSION="0.6.2" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" From 2372486ec5d0cc08bb9b87163018c504ea7b89d8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 09:48:52 -0700 Subject: [PATCH 343/968] Track InstructionData member names. Entity references in instruction format operands also have member names in the InstructionData struct. Track them the same way we track immediate operand member names. Value operands still go in the arg / args members. --- meta/cretonne/__init__.py | 16 ++++++++-------- meta/cretonne/entities.py | 7 +++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index a5fe761bce..825b30a8dc 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -293,6 +293,7 @@ class OperandKind(object): def __init__(self, name, doc): self.name = name self.__doc__ = doc + self.default_member = None # The camel-cased name of an operand kind is also the Rust type used to # represent it. self.camel_name = camel_case(name) @@ -347,8 +348,7 @@ class ImmediateKind(OperandKind): """ def __init__(self, name, doc, default_member='imm'): - self.name = name - self.__doc__ = doc + super(ImmediateKind, self).__init__(name, doc) self.default_member = default_member def __repr__(self): @@ -362,9 +362,9 @@ class EntityRefKind(OperandKind): The kind of an entity reference instruction operand. """ - def __init__(self, name, doc): - self.name = name - self.__doc__ = doc + def __init__(self, name, doc, default_member=None): + super(EntityRefKind, self).__init__(name, doc) + self.default_member = default_member or name def __repr__(self): return 'EntityRefKind({})'.format(self.name) @@ -686,17 +686,17 @@ class InstructionFormat(object): Yields the operand kinds. """ + self.members = list() for i, k in enumerate(kinds): if isinstance(k, tuple): member, k = k else: - member = None + member = k.default_member yield k + self.members.append(member) # Create `FormatField` instances for the immediates. if isinstance(k, ImmediateKind): - if not member: - member = k.default_member assert not hasattr(self, member), "Duplicate member name" field = FormatField(self, i, member) setattr(self, member, field) diff --git a/meta/cretonne/entities.py b/meta/cretonne/entities.py index bd55fa6eed..907fb64359 100644 --- a/meta/cretonne/entities.py +++ b/meta/cretonne/entities.py @@ -9,7 +9,9 @@ from . import EntityRefKind #: A reference to an extended basic block in the same function. #: This is primarliy used in control flow instructions. -ebb = EntityRefKind('ebb', 'An extended basic block in the same function.') +ebb = EntityRefKind( + 'ebb', 'An extended basic block in the same function.', + default_member='destination') #: A reference to a stack slot declared in the function preamble. stack_slot = EntityRefKind('stack_slot', 'A stack slot.') @@ -23,4 +25,5 @@ signature = EntityRefKind('signature', 'A function signature.') function = EntityRefKind('function', 'An external function.') #: A reference to a jump table declared in the function preamble. -jump_table = EntityRefKind('jump_table', 'A jump table.') +jump_table = EntityRefKind( + 'jump_table', 'A jump table.', default_member='table') From b258644d07bee06dd58f2038349fbd2250ca8550 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 10:07:00 -0700 Subject: [PATCH 344/968] Use 'varargs' consistently for VariableArgs members. The meta code generators need to be able to infer these too. --- meta/cretonne/__init__.py | 13 ++++++------- src/libcretonne/ir/instructions.rs | 24 ++++++++++++------------ src/libcretonne/test_utils/make_inst.rs | 6 +++--- src/libcretonne/write.rs | 4 ++-- src/libreader/parser.rs | 14 +++++++------- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 825b30a8dc..05366b0720 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -290,10 +290,10 @@ class OperandKind(object): instruction. """ - def __init__(self, name, doc): + def __init__(self, name, doc, default_member=None): self.name = name self.__doc__ = doc - self.default_member = None + self.default_member = default_member # The camel-cased name of an operand kind is also the Rust type used to # represent it. self.camel_name = camel_case(name) @@ -334,7 +334,8 @@ variable_args = OperandKind( Use this to represent arguemtns passed to a function call, arguments passed to an extended basic block, or a variable number of results returned from an instruction. - """) + """, + default_member='varargs') # Instances of immediate operand types are provided in the @@ -348,8 +349,7 @@ class ImmediateKind(OperandKind): """ def __init__(self, name, doc, default_member='imm'): - super(ImmediateKind, self).__init__(name, doc) - self.default_member = default_member + super(ImmediateKind, self).__init__(name, doc, default_member) def __repr__(self): return 'ImmediateKind({})'.format(self.name) @@ -363,8 +363,7 @@ class EntityRefKind(OperandKind): """ def __init__(self, name, doc, default_member=None): - super(EntityRefKind, self).__init__(name, doc) - self.default_member = default_member or name + super(EntityRefKind, self).__init__(name, doc, default_member or name) def __repr__(self): return 'EntityRefKind({})'.format(self.name) diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index fd7fd5f25b..6b3e0e5ffe 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -283,15 +283,15 @@ impl Display for TernaryOverflowData { #[derive(Clone, Debug)] pub struct JumpData { pub destination: Ebb, - pub arguments: VariableArgs, + pub varargs: VariableArgs, } impl Display for JumpData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.arguments.is_empty() { + if self.varargs.is_empty() { write!(f, "{}", self.destination) } else { - write!(f, "{}({})", self.destination, self.arguments) + write!(f, "{}({})", self.destination, self.varargs) } } } @@ -302,14 +302,14 @@ impl Display for JumpData { pub struct BranchData { pub arg: Value, pub destination: Ebb, - pub arguments: VariableArgs, + pub varargs: VariableArgs, } impl Display for BranchData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { try!(write!(f, "{}, {}", self.arg, self.destination)); - if !self.arguments.is_empty() { - try!(write!(f, "({})", self.arguments)); + if !self.varargs.is_empty() { + try!(write!(f, "({})", self.varargs)); } Ok(()) } @@ -322,12 +322,12 @@ pub struct CallData { second_result: Value, // Dynamically sized array containing call argument values. - pub args: VariableArgs, + pub varargs: VariableArgs, } impl Display for CallData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "TBD({})", self.args) + write!(f, "TBD({})", self.varargs) } } @@ -335,7 +335,7 @@ impl Display for CallData { #[derive(Clone, Debug)] pub struct ReturnData { // Dynamically sized array containing return values. - pub args: VariableArgs, + pub varargs: VariableArgs, } impl InstructionData { @@ -346,7 +346,7 @@ impl InstructionData { ty: return_type, data: Box::new(CallData { second_result: NO_VALUE, - args: VariableArgs::new(), + varargs: VariableArgs::new(), }), } } @@ -364,10 +364,10 @@ impl InstructionData { pub fn analyze_branch<'a>(&'a self) -> BranchInfo<'a> { match self { &InstructionData::Jump { ref data, .. } => { - BranchInfo::SingleDest(data.destination, &data.arguments) + BranchInfo::SingleDest(data.destination, &data.varargs) } &InstructionData::Branch { ref data, .. } => { - BranchInfo::SingleDest(data.destination, &data.arguments) + BranchInfo::SingleDest(data.destination, &data.varargs) } &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), _ => BranchInfo::NotABranch, diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs index a072b8ba8f..9ab4429b76 100644 --- a/src/libcretonne/test_utils/make_inst.rs +++ b/src/libcretonne/test_utils/make_inst.rs @@ -11,7 +11,7 @@ pub fn jump(func: &mut Function, dest: Ebb) -> Inst { ty: types::VOID, data: Box::new(JumpData { destination: dest, - arguments: VariableArgs::new(), + varargs: VariableArgs::new(), }), }) } @@ -23,7 +23,7 @@ pub fn branch(func: &mut Function, dest: Ebb) -> Inst { data: Box::new(BranchData { arg: NO_VALUE, destination: dest, - arguments: VariableArgs::new(), + varargs: VariableArgs::new(), }), }) } @@ -32,6 +32,6 @@ pub fn ret(func: &mut Function) -> Inst { func.dfg.make_inst(InstructionData::Return { opcode: Opcode::Return, ty: types::VOID, - data: Box::new(ReturnData { args: VariableArgs::new() }), + data: Box::new(ReturnData { varargs: VariableArgs::new() }), }) } diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index 8f7acde772..e49c8382bf 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -201,10 +201,10 @@ fn write_instruction(w: &mut Write, BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), Call { ref data, .. } => writeln!(w, " {}", data), Return { ref data, .. } => { - if data.args.is_empty() { + if data.varargs.is_empty() { writeln!(w, "") } else { - writeln!(w, " {}", data.args) + writeln!(w, " {}", data.varargs) } } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 258f920b81..eb8c78de38 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -145,21 +145,21 @@ impl Context { InstructionData::Jump { ref mut data, .. } => { try!(self.map.rewrite_ebb(&mut data.destination, loc)); - try!(self.map.rewrite_values(&mut data.arguments, loc)); + try!(self.map.rewrite_values(&mut data.varargs, loc)); } InstructionData::Branch { ref mut data, .. } => { try!(self.map.rewrite_value(&mut data.arg, loc)); try!(self.map.rewrite_ebb(&mut data.destination, loc)); - try!(self.map.rewrite_values(&mut data.arguments, loc)); + try!(self.map.rewrite_values(&mut data.varargs, loc)); } InstructionData::Call { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.args, loc)); + try!(self.map.rewrite_values(&mut data.varargs, loc)); } InstructionData::Return { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.args, loc)); + try!(self.map.rewrite_values(&mut data.varargs, loc)); } } } @@ -1104,7 +1104,7 @@ impl<'a> Parser<'a> { ty: VOID, data: Box::new(JumpData { destination: ebb_num, - arguments: args, + varargs: args, }), } } @@ -1119,7 +1119,7 @@ impl<'a> Parser<'a> { data: Box::new(BranchData { arg: ctrl_arg, destination: ebb_num, - arguments: args, + varargs: args, }), } } @@ -1178,7 +1178,7 @@ impl<'a> Parser<'a> { InstructionData::Return { opcode: opcode, ty: VOID, - data: Box::new(ReturnData { args: args }), + data: Box::new(ReturnData { varargs: args }), } } InstructionFormat::BranchTable => { From b42d85ae24a12f420d60655fb40284b07a9f0ac0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 13:35:18 -0700 Subject: [PATCH 345/968] Move signatures into new ir::extfunc module. This new module will gain more data types dealing with external function calls. --- src/libcretonne/ir/extfunc.rs | 135 ++++++++++++++++++++++++++++++++++ src/libcretonne/ir/mod.rs | 4 +- src/libcretonne/ir/types.rs | 127 -------------------------------- src/libreader/parser.rs | 7 +- 4 files changed, 142 insertions(+), 131 deletions(-) create mode 100644 src/libcretonne/ir/extfunc.rs diff --git a/src/libcretonne/ir/extfunc.rs b/src/libcretonne/ir/extfunc.rs new file mode 100644 index 0000000000..78b723398e --- /dev/null +++ b/src/libcretonne/ir/extfunc.rs @@ -0,0 +1,135 @@ +//! External function calls. +//! +//! To a Cretonne function, all functions are "external". Directly called functions must be +//! declared in the preamble, and all function calls must have a signature. +//! +//! This module declares the data types used to represent external functions and call signatures. + +use std::fmt::{self, Display, Formatter}; +use ir::Type; + +/// Function signature. +/// +/// The function signature describes the types of arguments and return values along with other +/// details that are needed to call a function correctly. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Signature { + pub argument_types: Vec, + pub return_types: Vec, +} + +impl Signature { + pub fn new() -> Signature { + Signature { + argument_types: Vec::new(), + return_types: Vec::new(), + } + } +} + +fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { + match args.split_first() { + None => {} + Some((first, rest)) => { + try!(write!(f, "{}", first)); + for arg in rest { + try!(write!(f, ", {}", arg)); + } + } + } + Ok(()) +} + +impl Display for Signature { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + try!(write!(f, "(")); + try!(write_list(f, &self.argument_types)); + try!(write!(f, ")")); + if !self.return_types.is_empty() { + try!(write!(f, " -> ")); + try!(write_list(f, &self.return_types)); + } + Ok(()) + } +} + +/// Function argument or return value type. +/// +/// This describes the value type being passed to or from a function along with flags that affect +/// how the argument is passed. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct ArgumentType { + pub value_type: Type, + pub extension: ArgumentExtension, + /// Place this argument in a register if possible. + pub inreg: bool, +} + +impl ArgumentType { + pub fn new(vt: Type) -> ArgumentType { + ArgumentType { + value_type: vt, + extension: ArgumentExtension::None, + inreg: false, + } + } +} + +impl Display for ArgumentType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + try!(write!(f, "{}", self.value_type)); + match self.extension { + ArgumentExtension::None => {} + ArgumentExtension::Uext => try!(write!(f, " uext")), + ArgumentExtension::Sext => try!(write!(f, " sext")), + } + if self.inreg { + try!(write!(f, " inreg")); + } + Ok(()) + } +} + +/// Function argument extension options. +/// +/// On some architectures, small integer function arguments are extended to the width of a +/// general-purpose register. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ArgumentExtension { + /// No extension, high bits are indeterminate. + None, + /// Unsigned extension: high bits in register are 0. + Uext, + /// Signed extension: high bits in register replicate sign bit. + Sext, +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::types::{I32, F32, B8}; + + #[test] + fn argument_type() { + let mut t = ArgumentType::new(I32); + assert_eq!(t.to_string(), "i32"); + t.extension = ArgumentExtension::Uext; + assert_eq!(t.to_string(), "i32 uext"); + t.inreg = true; + assert_eq!(t.to_string(), "i32 uext inreg"); + } + + #[test] + fn signatures() { + let mut sig = Signature::new(); + assert_eq!(sig.to_string(), "()"); + sig.argument_types.push(ArgumentType::new(I32)); + assert_eq!(sig.to_string(), "(i32)"); + sig.return_types.push(ArgumentType::new(F32)); + assert_eq!(sig.to_string(), "(i32) -> f32"); + sig.argument_types.push(ArgumentType::new(I32.by(4).unwrap())); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32"); + sig.return_types.push(ArgumentType::new(B8)); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8"); + } +} diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index ae16eae735..285fab96e5 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -11,9 +11,11 @@ pub mod dfg; pub mod layout; pub mod function; mod funcname; +mod extfunc; pub use ir::funcname::FunctionName; -pub use ir::types::{Type, Signature}; +pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension}; +pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; pub use ir::instructions::{Opcode, InstructionData}; pub use ir::stackslot::StackSlotData; diff --git a/src/libcretonne/ir/types.rs b/src/libcretonne/ir/types.rs index 7c9175f460..99390ee22a 100644 --- a/src/libcretonne/ir/types.rs +++ b/src/libcretonne/ir/types.rs @@ -1,4 +1,3 @@ - //! Common types for the Cretonne code generator. use std::default::Default; @@ -229,108 +228,6 @@ impl Default for Type { } } -// ====--------------------------------------------------------------------------------------====// -// -// Function signatures -// -// ====--------------------------------------------------------------------------------------====// - -/// Function argument extension options. -/// -/// On some architectures, small integer function arguments are extended to the width of a -/// general-purpose register. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum ArgumentExtension { - /// No extension, high bits are indeterminate. - None, - /// Unsigned extension: high bits in register are 0. - Uext, - /// Signed extension: high bits in register replicate sign bit. - Sext, -} - -/// Function argument or return value type. -/// -/// This describes the value type being passed to or from a function along with flags that affect -/// how the argument is passed. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct ArgumentType { - pub value_type: Type, - pub extension: ArgumentExtension, - /// Place this argument in a register if possible. - pub inreg: bool, -} - -/// Function signature. -/// -/// The function signature describes the types of arguments and return values along with other -/// details that are needed to call a function correctly. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Signature { - pub argument_types: Vec, - pub return_types: Vec, -} - -impl ArgumentType { - pub fn new(vt: Type) -> ArgumentType { - ArgumentType { - value_type: vt, - extension: ArgumentExtension::None, - inreg: false, - } - } -} - -impl Display for ArgumentType { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "{}", self.value_type)); - match self.extension { - ArgumentExtension::None => {} - ArgumentExtension::Uext => try!(write!(f, " uext")), - ArgumentExtension::Sext => try!(write!(f, " sext")), - } - if self.inreg { - try!(write!(f, " inreg")); - } - Ok(()) - } -} - -impl Signature { - pub fn new() -> Signature { - Signature { - argument_types: Vec::new(), - return_types: Vec::new(), - } - } -} - -fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { - match args.split_first() { - None => {} - Some((first, rest)) => { - try!(write!(f, "{}", first)); - for arg in rest { - try!(write!(f, ", {}", arg)); - } - } - } - Ok(()) -} - -impl Display for Signature { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "(")); - try!(write_list(f, &self.argument_types)); - try!(write!(f, ")")); - if !self.return_types.is_empty() { - try!(write!(f, " -> ")); - try!(write_list(f, &self.return_types)); - } - Ok(()) - } -} - #[cfg(test)] mod tests { use super::*; @@ -442,28 +339,4 @@ mod tests { assert_eq!(I8.by(512), None); assert_eq!(VOID.by(4), None); } - - #[test] - fn argument_type() { - let mut t = ArgumentType::new(I32); - assert_eq!(t.to_string(), "i32"); - t.extension = ArgumentExtension::Uext; - assert_eq!(t.to_string(), "i32 uext"); - t.inreg = true; - assert_eq!(t.to_string(), "i32 uext inreg"); - } - - #[test] - fn signatures() { - let mut sig = Signature::new(); - assert_eq!(sig.to_string(), "()"); - sig.argument_types.push(ArgumentType::new(I32)); - assert_eq!(sig.to_string(), "(i32)"); - sig.return_types.push(ArgumentType::new(F32)); - assert_eq!(sig.to_string(), "(i32) -> f32"); - sig.argument_types.push(ArgumentType::new(I32.by(4).unwrap())); - assert_eq!(sig.to_string(), "(i32, i32x4) -> f32"); - sig.return_types.push(ArgumentType::new(B8)); - assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8"); - } } diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index eb8c78de38..5023195e11 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -9,8 +9,8 @@ use std::str::FromStr; use std::u32; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, - JumpTableData}; -use cretonne::ir::types::{VOID, Signature, ArgumentType, ArgumentExtension}; + JumpTableData, Signature, ArgumentType, ArgumentExtension}; +use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, @@ -1202,7 +1202,8 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::ir::types::{self, ArgumentType, ArgumentExtension}; + use cretonne::ir::{ArgumentType, ArgumentExtension}; + use cretonne::ir::types; use cretonne::ir::entities::AnyEntity; use testfile::{Details, Comment}; use isaspec::IsaSpec; From 7cf25a073b83f5a9f3eef90cf9b35710b8151baa Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 14:15:29 -0700 Subject: [PATCH 346/968] Add FuncRef and SigRef entity references. These refer to external functions and function signatures declared in the preamble. Since we're already using the type names 'Signature' and 'Function', these entity references don't folow the usual EntityData / Entity naming convention. --- docs/langref.rst | 4 +- meta/cretonne/entities.py | 4 +- meta/cretonne/formats.py | 4 +- src/libcretonne/ir/entities.rs | 78 ++++++++++++++++++++++++++++++ src/libcretonne/ir/instructions.rs | 22 ++------- src/libcretonne/ir/mod.rs | 2 +- src/libreader/lexer.rs | 4 ++ 7 files changed, 94 insertions(+), 24 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index e4d2d9dbd3..b2ced79300 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -394,11 +394,11 @@ preamble`: This simple example illustrates direct function calls and signatures:: function gcd(i32 uext, i32 uext) -> i32 uext "C" { - f1 = function divmod(i32 uext, i32 uext) -> i32 uext, i32 uext + fn1 = function divmod(i32 uext, i32 uext) -> i32 uext, i32 uext ebb1(v1: i32, v2: i32): brz v2, ebb2 - v3, v4 = call f1(v1, v2) + v3, v4 = call fn1(v1, v2) br ebb1(v2, v4) ebb2: diff --git a/meta/cretonne/entities.py b/meta/cretonne/entities.py index 907fb64359..317006f79f 100644 --- a/meta/cretonne/entities.py +++ b/meta/cretonne/entities.py @@ -18,11 +18,11 @@ stack_slot = EntityRefKind('stack_slot', 'A stack slot.') #: A reference to a function sugnature declared in the function preamble. #: Tbis is used to provide the call signature in an indirect call instruction. -signature = EntityRefKind('signature', 'A function signature.') +sig_ref = EntityRefKind('sig_ref', 'A function signature.') #: A reference to an external function declared in the function preamble. #: This is used to provide the callee and signature in a call instruction. -function = EntityRefKind('function', 'An external function.') +func_ref = EntityRefKind('func_ref', 'An external function.') #: A reference to a jump table declared in the function preamble. jump_table = EntityRefKind( diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index df0019bff4..11a1c5ef3b 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -8,7 +8,7 @@ in this module. from __future__ import absolute_import from . import InstructionFormat, value, variable_args from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc -from .entities import ebb, function, jump_table +from .entities import ebb, func_ref, jump_table Nullary = InstructionFormat() @@ -46,7 +46,7 @@ Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) BranchTable = InstructionFormat(value, jump_table) Call = InstructionFormat( - function, variable_args, multiple_results=True, boxed_storage=True) + func_ref, variable_args, multiple_results=True, boxed_storage=True) Return = InstructionFormat(variable_args, boxed_storage=True) diff --git a/src/libcretonne/ir/entities.rs b/src/libcretonne/ir/entities.rs index 5672e5b3d3..bd8913e5bf 100644 --- a/src/libcretonne/ir/entities.rs +++ b/src/libcretonne/ir/entities.rs @@ -245,6 +245,68 @@ impl Default for JumpTable { } } +/// A reference to an external function. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct FuncRef(u32); + +impl EntityRef for FuncRef { + fn new(index: usize) -> FuncRef { + assert!(index < (u32::MAX as usize)); + FuncRef(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } +} + +/// Display a `FuncRef` reference as "fn12". +impl Display for FuncRef { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "fn{}", self.0) + } +} + +/// A guaranteed invalid function reference. +pub const NO_FUNC_REF: FuncRef = FuncRef(u32::MAX); + +impl Default for FuncRef { + fn default() -> FuncRef { + NO_FUNC_REF + } +} + +/// A reference to a function signature. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct SigRef(u32); + +impl EntityRef for SigRef { + fn new(index: usize) -> SigRef { + assert!(index < (u32::MAX as usize)); + SigRef(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } +} + +/// Display a `SigRef` reference as "sig12". +impl Display for SigRef { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "sig{}", self.0) + } +} + +/// A guaranteed invalid function reference. +pub const NO_SIG_REF: SigRef = SigRef(u32::MAX); + +impl Default for SigRef { + fn default() -> SigRef { + NO_SIG_REF + } +} + /// A reference to any of the entities defined in this module. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum AnyEntity { @@ -255,6 +317,8 @@ pub enum AnyEntity { Value(Value), StackSlot(StackSlot), JumpTable(JumpTable), + FuncRef(FuncRef), + SigRef(SigRef), } impl Display for AnyEntity { @@ -266,6 +330,8 @@ impl Display for AnyEntity { AnyEntity::Value(r) => r.fmt(fmt), AnyEntity::StackSlot(r) => r.fmt(fmt), AnyEntity::JumpTable(r) => r.fmt(fmt), + AnyEntity::FuncRef(r) => r.fmt(fmt), + AnyEntity::SigRef(r) => r.fmt(fmt), } } } @@ -300,6 +366,18 @@ impl From for AnyEntity { } } +impl From for AnyEntity { + fn from(r: FuncRef) -> AnyEntity { + AnyEntity::FuncRef(r) + } +} + +impl From for AnyEntity { + fn from(r: SigRef) -> AnyEntity { + AnyEntity::SigRef(r) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 6b3e0e5ffe..56ce5ed5fd 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -10,8 +10,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use ir::{Value, Type, Ebb, JumpTable}; -use ir::entities::NO_VALUE; +use ir::{Value, Type, Ebb, JumpTable, FuncRef}; use ir::immediates::{Imm64, Ieee32, Ieee64}; use ir::condcodes::*; use ir::types; @@ -321,7 +320,10 @@ pub struct CallData { /// Second result value for a call producing multiple return values. second_result: Value, - // Dynamically sized array containing call argument values. + /// Callee function. + pub func_ref: FuncRef, + + /// Dynamically sized array containing call argument values. pub varargs: VariableArgs, } @@ -338,20 +340,6 @@ pub struct ReturnData { pub varargs: VariableArgs, } -impl InstructionData { - /// Create data for a call instruction. - pub fn call(opc: Opcode, return_type: Type) -> InstructionData { - InstructionData::Call { - opcode: opc, - ty: return_type, - data: Box::new(CallData { - second_result: NO_VALUE, - varargs: VariableArgs::new(), - }), - } - } -} - /// Analyzing an instruction. /// /// Avoid large matches on instruction formats by using the methods efined here to examine diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 285fab96e5..90581d5f12 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -16,7 +16,7 @@ mod extfunc; pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension}; pub use ir::types::Type; -pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable}; +pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; diff --git a/src/libreader/lexer.rs b/src/libreader/lexer.rs index 3454225dc8..04801b2644 100644 --- a/src/libreader/lexer.rs +++ b/src/libreader/lexer.rs @@ -34,6 +34,8 @@ pub enum Token<'a> { Ebb(Ebb), // ebb3 StackSlot(u32), // ss3 JumpTable(u32), // jt2 + FuncRef(u32), // fn2 + SigRef(u32), // sig2 Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) } @@ -285,6 +287,8 @@ impl<'a> Lexer<'a> { "ebb" => Ebb::with_number(number).map(|ebb| Token::Ebb(ebb)), "ss" => Some(Token::StackSlot(number)), "jt" => Some(Token::JumpTable(number)), + "fn" => Some(Token::FuncRef(number)), + "sig" => Some(Token::SigRef(number)), _ => None, } } From b8a537bb13296d4193990c2c204d1cfae67169b2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 15:24:59 -0700 Subject: [PATCH 347/968] Add simple Uimm8 and ImmVector immediate types. Implement the boxed storage of the UnaryImmVector instruction format. --- meta/cretonne/formats.py | 2 +- src/libcretonne/ir/immediates.rs | 11 +++++++++++ src/libcretonne/ir/instructions.rs | 25 +++++++++++++++++++++---- src/libcretonne/write.rs | 2 +- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/meta/cretonne/formats.py b/meta/cretonne/formats.py index 11a1c5ef3b..8e5d4e25cb 100644 --- a/meta/cretonne/formats.py +++ b/meta/cretonne/formats.py @@ -16,7 +16,7 @@ Unary = InstructionFormat(value) UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) UnaryIeee64 = InstructionFormat(ieee64) -UnaryImmVector = InstructionFormat(immvector) +UnaryImmVector = InstructionFormat(immvector, boxed_storage=True) UnarySplit = InstructionFormat(value, multiple_results=True) Binary = InstructionFormat(value, value) diff --git a/src/libcretonne/ir/immediates.rs b/src/libcretonne/ir/immediates.rs index 99276bfd66..e8d4a20773 100644 --- a/src/libcretonne/ir/immediates.rs +++ b/src/libcretonne/ir/immediates.rs @@ -123,6 +123,11 @@ impl FromStr for Imm64 { } } +/// 8-bit unsigned integer immediate operand. +/// +/// This is used to indicate lane indexes typically. +pub type Uimm8 = u8; + /// An IEEE binary32 immediate floating point value. /// /// All bit patterns are allowed. @@ -420,6 +425,12 @@ impl FromStr for Ieee64 { } } +/// Arbitrary vector immediate. +/// +/// This kind of immediate can represent any kind of SIMD vector constant. +/// The representation is simply the sequence of bytes that would be used to store the vector. +pub type ImmVector = Vec; + #[cfg(test)] mod tests { use super::*; diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index 56ce5ed5fd..b971a05ffa 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use std::ops::{Deref, DerefMut}; use ir::{Value, Type, Ebb, JumpTable, FuncRef}; -use ir::immediates::{Imm64, Ieee32, Ieee64}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::*; use ir::types; @@ -117,7 +117,8 @@ pub enum InstructionData { }, UnaryImmVector { opcode: Opcode, - ty: Type, // TBD: imm: Box + ty: Type, + data: Box, }, UnarySplit { opcode: Opcode, @@ -162,13 +163,13 @@ pub enum InstructionData { InsertLane { opcode: Opcode, ty: Type, - lane: u8, + lane: Uimm8, args: [Value; 2], }, ExtractLane { opcode: Opcode, ty: Type, - lane: u8, + lane: Uimm8, arg: Value, }, IntCompare { @@ -264,6 +265,22 @@ impl Default for VariableArgs { } } +/// Payload data for `vconst`. +#[derive(Clone, Debug)] +pub struct UnaryImmVectorData { + pub imm: ImmVector, +} + +impl Display for UnaryImmVectorData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + try!(write!(f, "#")); + for b in &self.imm { + try!(write!(f, "{:02x}", b)); + } + Ok(()) + } +} + /// Payload data for ternary instructions with multiple results, such as `iadd_carry`. #[derive(Clone, Debug)] pub struct TernaryOverflowData { diff --git a/src/libcretonne/write.rs b/src/libcretonne/write.rs index e49c8382bf..3ef72d7178 100644 --- a/src/libcretonne/write.rs +++ b/src/libcretonne/write.rs @@ -184,7 +184,7 @@ fn write_instruction(w: &mut Write, UnaryImm { imm, .. } => writeln!(w, " {}", imm), UnaryIeee32 { imm, .. } => writeln!(w, " {}", imm), UnaryIeee64 { imm, .. } => writeln!(w, " {}", imm), - UnaryImmVector { .. } => writeln!(w, " [...]"), + UnaryImmVector { ref data, .. } => writeln!(w, " {}", data), UnarySplit { arg, .. } => writeln!(w, " {}", arg), Binary { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), From f78baf9c0b98916d4adeee9a2d67c3c692b671f0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 15:34:50 -0700 Subject: [PATCH 348/968] Capture the Rust type used to represent an operand kind. The Rust type is usually the camel-cased name of the operand kind, but there are variations, so allow an explicit rust_type='IntCC' when defining operand kinds. --- meta/cretonne/__init__.py | 14 ++++++++------ meta/cretonne/immediates.py | 9 ++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 05366b0720..a6cef5397b 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -290,13 +290,13 @@ class OperandKind(object): instruction. """ - def __init__(self, name, doc, default_member=None): + def __init__(self, name, doc, default_member=None, rust_type=None): self.name = name self.__doc__ = doc self.default_member = default_member # The camel-cased name of an operand kind is also the Rust type used to # represent it. - self.camel_name = camel_case(name) + self.rust_type = rust_type or camel_case(name) def __str__(self): return self.name @@ -348,8 +348,9 @@ class ImmediateKind(OperandKind): `InstructionData` data structure. """ - def __init__(self, name, doc, default_member='imm'): - super(ImmediateKind, self).__init__(name, doc, default_member) + def __init__(self, name, doc, default_member='imm', rust_type=None): + super(ImmediateKind, self).__init__( + name, doc, default_member, rust_type) def __repr__(self): return 'ImmediateKind({})'.format(self.name) @@ -362,8 +363,9 @@ class EntityRefKind(OperandKind): The kind of an entity reference instruction operand. """ - def __init__(self, name, doc, default_member=None): - super(EntityRefKind, self).__init__(name, doc, default_member or name) + def __init__(self, name, doc, default_member=None, rust_type=None): + super(EntityRefKind, self).__init__( + name, doc, default_member or name, rust_type) def __repr__(self): return 'EntityRefKind({})'.format(self.name) diff --git a/meta/cretonne/immediates.py b/meta/cretonne/immediates.py index ca8affded0..34b81dc79d 100644 --- a/meta/cretonne/immediates.py +++ b/meta/cretonne/immediates.py @@ -28,7 +28,10 @@ ieee32 = ImmediateKind('ieee32', 'A 32-bit immediate floating point number.') ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') #: A large SIMD vector constant. -immvector = ImmediateKind('immvector', 'An immediate SIMD vector.') +immvector = ImmediateKind( + 'immvector', + 'An immediate SIMD vector.', + rust_type='ImmVector') #: A condition code for comparing integer values. #: @@ -37,7 +40,7 @@ immvector = ImmediateKind('immvector', 'An immediate SIMD vector.') intcc = ImmediateKind( 'intcc', 'An integer comparison condition code.', - default_member='cond') + default_member='cond', rust_type='IntCC') #: A condition code for comparing floating point values. #: @@ -46,4 +49,4 @@ intcc = ImmediateKind( floatcc = ImmediateKind( 'floatcc', 'A floating point comparison condition code.', - default_member='cond') + default_member='cond', rust_type='FloatCC') From 5a2f8cbdf8b0e9637652028485ec9d7fd5106692 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 16:01:06 -0700 Subject: [PATCH 349/968] Move second_result outside boxed storage. In instruction formats that have multiple results AND boxed storage, place the second_result field outside the boxed storage. The 16-byte instruction format has room for opcode, type, second_result in the first 8 bytes, and the boxed pointer in the last 8 bytes. This provides a simpler implementation of the second_result() and second_result_mut() InstructionData methods. --- meta/gen_instr.py | 38 ++++++++++-------------------- src/libcretonne/ir/instructions.rs | 6 ++--- src/libreader/parser.rs | 6 ++--- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 582c0477f0..26783ae82f 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -87,23 +87,16 @@ def gen_instruction_data_impl(fmt): 'pub fn second_result(&self) -> Option {', '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: - if not f.multiple_results: - # Single or no results. - fmt.line( - 'InstructionData::{} {{ .. }} => None,' - .format(f.name)) - elif f.boxed_storage: - # Multiple results, boxed storage. - fmt.line( - 'InstructionData::' + f.name + - ' { ref data, .. }' + - ' => Some(data.second_result),') - else: - # Multiple results, inline storage. + if f.multiple_results: fmt.line( 'InstructionData::' + f.name + ' { second_result, .. }' + ' => Some(second_result),') + else: + # Single or no results. + fmt.line( + 'InstructionData::{} {{ .. }} => None,' + .format(f.name)) fmt.doc_comment('Mutable reference to second result value, if any.') with fmt.indented( @@ -111,23 +104,16 @@ def gen_instruction_data_impl(fmt): " -> Option<&'a mut Value> {", '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: - if not f.multiple_results: - # Single or no results. - fmt.line( - 'InstructionData::{} {{ .. }} => None,' - .format(f.name)) - elif f.boxed_storage: - # Multiple results, boxed storage. - fmt.line( - 'InstructionData::' + f.name + - ' { ref mut data, .. }' + - ' => Some(&mut data.second_result),') - else: - # Multiple results, inline storage. + if f.multiple_results: fmt.line( 'InstructionData::' + f.name + ' { ref mut second_result, .. }' + ' => Some(second_result),') + else: + # Single or no results. + fmt.line( + 'InstructionData::{} {{ .. }} => None,' + .format(f.name)) fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index b971a05ffa..37b1106d36 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -158,6 +158,7 @@ pub enum InstructionData { TernaryOverflow { opcode: Opcode, ty: Type, + second_result: Value, data: Box, }, InsertLane { @@ -203,6 +204,7 @@ pub enum InstructionData { Call { opcode: Opcode, ty: Type, + second_result: Value, data: Box, }, Return { @@ -284,7 +286,6 @@ impl Display for UnaryImmVectorData { /// Payload data for ternary instructions with multiple results, such as `iadd_carry`. #[derive(Clone, Debug)] pub struct TernaryOverflowData { - pub second_result: Value, pub args: [Value; 3], } @@ -334,9 +335,6 @@ impl Display for BranchData { /// Payload of a call instruction. #[derive(Clone, Debug)] pub struct CallData { - /// Second result value for a call producing multiple return values. - second_result: Value, - /// Callee function. pub func_ref: FuncRef, diff --git a/src/libreader/parser.rs b/src/libreader/parser.rs index 5023195e11..5c86e4e21a 100644 --- a/src/libreader/parser.rs +++ b/src/libreader/parser.rs @@ -1089,10 +1089,8 @@ impl<'a> Parser<'a> { InstructionData::TernaryOverflow { opcode: opcode, ty: VOID, - data: Box::new(TernaryOverflowData { - second_result: NO_VALUE, - args: [lhs, rhs, cin], - }), + second_result: NO_VALUE, + data: Box::new(TernaryOverflowData { args: [lhs, rhs, cin] }), } } InstructionFormat::Jump => { From 8ca970ba5cdbc73cd003f5f2c5ca5e873c150d65 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Oct 2016 10:51:05 -0700 Subject: [PATCH 350/968] Generate a Builder data type. WIP. The Builder provides a convenient interface for inserting instructions into an extended basic block. The bulk of the builder methods are generated automatically from the meta language instruction descriptions. Still TODO: Keep track of an insertion position. --- meta/cretonne/base.py | 5 +- meta/gen_instr.py | 179 ++++++++++++++++++++++++++++++++++ src/libcretonne/ir/builder.rs | 27 +++++ src/libcretonne/ir/mod.rs | 4 +- 4 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/libcretonne/ir/builder.rs diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index fc8de5d78f..b665cf5d71 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -1057,7 +1057,7 @@ fdemote = Instruction( ins=x, outs=a) x = Operand('x', Float) -a = Operand('a', Int) +a = Operand('a', IntTo) fcvt_to_uint = Instruction( 'fcvt_to_uint', r""" @@ -1083,6 +1083,9 @@ fcvt_to_sint = Instruction( """, ins=x, outs=a) +x = Operand('x', Int) +a = Operand('a', FloatTo) + fcvt_from_uint = Instruction( 'fcvt_from_uint', r""" Convert unsigned integer to floating point. diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 26783ae82f..8ae6d47ef7 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -343,6 +343,180 @@ def gen_type_constraints(fmt, instrs): fmt.line('OperandConstraint::{},'.format(c)) +def gen_format_constructor(iform, fmt): + """ + Emit a method for creating and inserting inserting an `iform` instruction, + where `iform` is an instruction format. + + Instruction formats that can produce multiple results take a `ctrl_typevar` + argument for deducing the result types. Others take a `result_type` + argument. + """ + + # Construct method arguments. + args = ['&mut self', 'opcode: Opcode'] + + if iform.multiple_results: + args.append('ctrl_typevar: Type') + # `dfg::make_inst_results` will compute the result type. + result_type = 'types::VOID' + else: + args.append('result_type: Type') + result_type = 'result_type' + + # Normal operand arguments. + for idx, kind in enumerate(iform.kinds): + args.append('op{}: {}'.format(idx, kind.rust_type)) + + proto = '{}({}) -> Inst'.format(iform.name, ', '.join(args)) + fmt.line('#[allow(non_snake_case)]') + with fmt.indented('pub fn {} {{'.format(proto), '}'): + # Generate the instruction data. + with fmt.indented( + 'let data = InstructionData::{} {{'.format(iform.name), '};'): + fmt.line('opcode: opcode,') + fmt.line('ty: {},'.format(result_type)) + if iform.multiple_results: + fmt.line('second_result: Value::default(),') + if iform.boxed_storage: + with fmt.indented( + 'data: Box::new(instructions::{}Data {{' + .format(iform.name), '}),'): + gen_member_inits(iform, fmt) + else: + gen_member_inits(iform, fmt) + + # Create result values if necessary. + if iform.multiple_results: + fmt.line('let inst = self.insert_inst(data);') + fmt.line('self.dfg.make_inst_results(inst, ctrl_typevar);') + fmt.line('inst') + else: + fmt.line('self.insert_inst(data)') + + +def gen_member_inits(iform, fmt): + """ + Emit member initializers for an `iform` instruction. + """ + + # Values first. + if len(iform.value_operands) == 1: + fmt.line('arg: op{},'.format(iform.value_operands[0])) + elif len(iform.value_operands) > 1: + fmt.line('args: [{}],'.format( + ', '.join('op{}'.format(i) for i in iform.value_operands))) + + # Immediates and entity references. + for idx, member in enumerate(iform.members): + if member: + fmt.line('{}: op{},'.format(member, idx)) + + +def gen_inst_builder(inst, fmt): + """ + Emit a method for generating the instruction `inst`. + + The method will create and insert an instruction, then return the result + values, or the instruction reference itself for instructions that don't + have results. + """ + + # Construct method arguments. + args = ['&mut self'] + + # The controlling type variable will be inferred from the input values if + # possible. Otherwise, it is the first method argument. + if inst.is_polymorphic and not inst.use_typevar_operand: + args.append('{}: Type'.format(inst.ctrl_typevar.name)) + + tmpl_types = list() + into_args = list() + for op in inst.ins: + if isinstance(op.kind, cretonne.ImmediateKind): + t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name) + tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type)) + into_args.append(op.name) + else: + t = op.kind.rust_type + args.append('{}: {}'.format(op.name, t)) + + # Return the inst reference for result-less instructions. + if len(inst.value_results) == 0: + rtype = 'Inst' + elif len(inst.value_results) == 1: + rtype = 'Value' + else: + rvals = ', '.join(len(inst.value_results) * ['Value']) + rtype = '({})'.format(rvals) + + method = inst.name + if method == 'return': + # Avoid Rust keywords + method = '_' + method + + if len(tmpl_types) > 0: + tmpl = '<{}>'.format(', '.join(tmpl_types)) + else: + tmpl = '' + proto = '{}{}({}) -> {}'.format(method, tmpl, ', '.join(args), rtype) + + fmt.line('#[allow(non_snake_case)]') + with fmt.indented('pub fn {} {{'.format(proto), '}'): + # Convert all of the `Into<>` arguments. + for arg in into_args: + fmt.line('let {} = {}.into();'.format(arg, arg)) + + # Arguments for instruction constructor. + args = ['Opcode::' + inst.camel_name] + + if inst.is_polymorphic and not inst.use_typevar_operand: + # This was an explicit method argument. + args.append(inst.ctrl_typevar.name) + elif len(inst.value_results) == 0: + args.append('types::VOID') + elif inst.is_polymorphic: + # Infer the controlling type variable from the input operands. + fmt.line( + 'let ctrl_typevar = self.dfg.value_type({});' + .format(inst.ins[inst.format.typevar_operand].name)) + args.append('ctrl_typevar') + else: + # This non-polymorphic instruction has a fixed result type. + args.append( + 'types::' + + inst.outs[inst.value_results[0]].typ.name.upper()) + + args.extend(op.name for op in inst.ins) + args = ', '.join(args) + fmt.line('let inst = self.{}({});'.format(inst.format.name, args)) + + if len(inst.value_results) == 0: + fmt.line('inst') + elif len(inst.value_results) == 1: + fmt.line('self.dfg.first_result(inst)') + else: + fmt.line('let mut results = self.dfg.inst_results(inst);') + fmt.line('({})'.format(', '.join( + len(inst.value_results) * ['results.next().unwrap()']))) + + +def gen_builder(insts, fmt): + """ + Generate a Builder trait with methods for all instructions. + """ + fmt.doc_comment( + 'Methods for inserting instructions by instruction format.') + with fmt.indented("impl<'a> Builder<'a> {", '}'): + for f in cretonne.InstructionFormat.all_formats: + gen_format_constructor(f, fmt) + + fmt.doc_comment('Methods for inserting instructions by opcode.') + with fmt.indented("impl<'a> Builder<'a> {", '}'): + for inst in insts: + gen_inst_builder(inst, fmt) + + def generate(isas, out_dir): groups = collect_instr_groups(isas) @@ -353,3 +527,8 @@ def generate(isas, out_dir): instrs = gen_opcodes(groups, fmt) gen_type_constraints(fmt, instrs) fmt.update_file('opcodes.rs', out_dir) + + # builder.rs + fmt = srcgen.Formatter() + gen_builder(instrs, fmt) + fmt.update_file('builder.rs', out_dir) diff --git a/src/libcretonne/ir/builder.rs b/src/libcretonne/ir/builder.rs new file mode 100644 index 0000000000..4e81027e9b --- /dev/null +++ b/src/libcretonne/ir/builder.rs @@ -0,0 +1,27 @@ +//! Cretonne instruction builder. +//! +//! A `Builder` provides a convenient interface for inserting instructions into a Cretonne +//! function. Many of its methods are generated from the meta language instruction definitions. + +use ir::{types, instructions}; +use ir::{InstructionData, DataFlowGraph}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, FuncRef}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; +use ir::condcodes::{IntCC, FloatCC}; + +pub struct Builder<'a> { + dfg: &'a mut DataFlowGraph, +} + +impl<'a> Builder<'a> { + // Create and insert an instruction. + // This method is used by the generated format-specific methods. + fn insert_inst(&mut self, data: InstructionData) -> Inst { + let inst = self.dfg.make_inst(data); + inst + } +} + +// Include code generated by `meta/gen_instr.py`. This file includes `Builder` methods per +// instruction format and per opcode for inserting instructions. +include!(concat!(env!("OUT_DIR"), "/builder.rs")); diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 90581d5f12..36d17c29aa 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -12,14 +12,16 @@ pub mod layout; pub mod function; mod funcname; mod extfunc; +mod builder; pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension}; pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; -pub use ir::instructions::{Opcode, InstructionData}; +pub use ir::instructions::{Opcode, InstructionData, VariableArgs}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::Layout; pub use ir::function::Function; +pub use ir::builder::Builder; From d650d30b882a051287dadcc88e1211594a14ded8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Oct 2016 14:36:34 -0700 Subject: [PATCH 351/968] Add a LAyout Cursor data structure. A layout cursor can be used instead of an iterator to keep track of a position in a basic block *and* permitting instructions to be manipulated. --- src/libcretonne/ir/layout.rs | 319 ++++++++++++++++++++++++++++++++++- src/libcretonne/ir/mod.rs | 2 +- 2 files changed, 319 insertions(+), 2 deletions(-) diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index a872efa5d8..1b78386100 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -250,9 +250,273 @@ impl<'a> Iterator for Insts<'a> { } } + +/// Layout Cursor. +/// +/// A `Cursor` represents a position in a function layout where instructions can be inserted and +/// removed. It can be used to iterate through the instructions of a function while editing them at +/// the same time. A normal instruction iterator can't do this since it holds an immutable refernce +/// to the Layout. +/// +/// When new instructions are added, the cursor can either apend them to an EBB or insert them +/// before the current instruction. +pub struct Cursor<'a> { + layout: &'a mut Layout, + pos: CursorPosition, +} + +/// The possible positions of a cursor. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum CursorPosition { + /// Cursor is not pointing anywhere. No instructions can be inserted. + Nowhere, + /// Cursor is pointing at an existing instruction. + /// New instructions will be inserted *before* the current instruction. + At(Inst), + /// Cursor is before the beginning of an EBB. No instructions can be inserted. Calling + /// `next_inst()` wil move to the first instruction in the EBB. + Before(Ebb), + /// Cursor is pointing after the end of an EBB. + /// New instructions will be appended to the EBB. + After(Ebb), +} + +impl<'a> Cursor<'a> { + /// Create a new `Cursor` for `layout`. + /// The cursor holds a mutable reference to `layout` for its entire lifetime. + pub fn new(layout: &'a mut Layout) -> Cursor { + Cursor { + layout: layout, + pos: CursorPosition::Nowhere, + } + } + + /// Get the current position. + pub fn position(&self) -> CursorPosition { + self.pos + } + + /// Get the EBB corresponding to the current position. + pub fn current_ebb(&self) -> Option { + use self::CursorPosition::*; + match self.pos { + Nowhere => None, + At(inst) => self.layout.inst_ebb(inst), + Before(ebb) | After(ebb) => Some(ebb), + } + } + + /// Go to a specific instruction which must be inserted in the layout. + /// New instructions will be inserted before `inst`. + pub fn goto_inst(&mut self, inst: Inst) { + assert!(self.layout.inst_ebb(inst).is_some()); + self.pos = CursorPosition::At(inst); + } + + /// Go to the top of `ebb` which must be inserted into the layout. + /// At this position, instructions cannot be inserted, but `next_inst()` will move to the first + /// instruction in `ebb`. + pub fn goto_top(&mut self, ebb: Ebb) { + assert!(self.layout.is_ebb_inserted(ebb)); + self.pos = CursorPosition::Before(ebb); + } + + /// Go to the bottom of `ebb` which must be inserted into the layout. + /// At this position, inserted instructions will be appended to `ebb`. + pub fn goto_bottom(&mut self, ebb: Ebb) { + assert!(self.layout.is_ebb_inserted(ebb)); + self.pos = CursorPosition::After(ebb); + } + + /// Go to the top of the next EBB in layout order and return it. + /// + /// - If the cursor wasn't pointing at anything, go to the top of the first EBB in the + /// function. + /// - If there are no more EBBs, leave the cursor pointing at nothing and return `None`. + /// + /// # Examples + /// + /// The `next_ebb()` method is intended for iterating over the EBBs in layout order: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb}; + /// # use cretonne::ir::layout::Cursor; + /// fn edit_func(func: &mut Function) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// while let Some(ebb) = cursor.next_ebb() { + /// // Edit ebb. + /// } + /// } + /// ``` + pub fn next_ebb(&mut self) -> Option { + let next = if let Some(ebb) = self.current_ebb() { + self.layout.ebbs[ebb].next.wrap() + } else { + self.layout.first_ebb + }; + self.pos = match next { + Some(ebb) => CursorPosition::Before(ebb), + None => CursorPosition::Nowhere, + }; + next + } + + /// Go to the bottom of the previous EBB in layout order and return it. + /// + /// - If the cursor wasn't pointing at anything, go to the bottom of the last EBB in the + /// function. + /// - If there are no more EBBs, leave the cursor pointing at nothing and return `None`. + /// + /// # Examples + /// + /// The `prev_ebb()` method is intended for iterating over the EBBs in backwards layout order: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb}; + /// # use cretonne::ir::layout::Cursor; + /// fn edit_func(func: &mut Function) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// while let Some(ebb) = cursor.prev_ebb() { + /// // Edit ebb. + /// } + /// } + /// ``` + pub fn prev_ebb(&mut self) -> Option { + let prev = if let Some(ebb) = self.current_ebb() { + self.layout.ebbs[ebb].prev.wrap() + } else { + self.layout.last_ebb + }; + self.pos = match prev { + Some(ebb) => CursorPosition::After(ebb), + None => CursorPosition::Nowhere, + }; + prev + } + + /// Move to the next instruction in the same EBB and return it. + /// + /// - If the cursor was positioned before an EBB, go to the first instruction in that EBB. + /// - If there are no more instructions in the EBB, go to the `After(ebb)` position and return + /// `None`. + /// - If the cursor wasn't pointing anywhere, keep doing that. + /// + /// This method will never move the cursor to a different EBB. + /// + /// # Examples + /// + /// The `next_inst()` method is intended for iterating over the instructions in an EBB like + /// this: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb}; + /// # use cretonne::ir::layout::Cursor; + /// fn edit_ebb(func: &mut Function, ebb: Ebb) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// cursor.goto_top(ebb); + /// while let Some(inst) = cursor.next_inst() { + /// // Edit instructions... + /// } + /// } + /// ``` + /// The loop body can insert and remove instructions via the cursor. + /// + /// Iterating over all the instructions in a function looks like this: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb}; + /// # use cretonne::ir::layout::Cursor; + /// fn edit_func(func: &mut Function) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// while let Some(ebb) = cursor.next_ebb() { + /// while let Some(inst) = cursor.next_inst() { + /// // Edit instructions... + /// } + /// } + /// } + /// ``` + pub fn next_inst(&mut self) -> Option { + use self::CursorPosition::*; + match self.pos { + Nowhere | After(..) => None, + At(inst) => { + if let Some(next) = self.layout.insts[inst].next.wrap() { + self.pos = At(next); + Some(next) + } else { + self.pos = + After(self.layout.inst_ebb(inst).expect("current instruction removed?")); + None + } + } + Before(ebb) => { + if let Some(next) = self.layout.ebbs[ebb].first_inst.wrap() { + self.pos = At(next); + Some(next) + } else { + self.pos = After(ebb); + None + } + } + } + } + + /// Move to the previous instruction in the same EBB and return it. + /// + /// - If the cursor was positioned after an EBB, go to the last instruction in that EBB. + /// - If there are no more instructions in the EBB, go to the `Before(ebb)` position and return + /// `None`. + /// - If the cursor wasn't pointing anywhere, keep doing that. + /// + /// This method will never move the cursor to a different EBB. + /// + /// # Examples + /// + /// The `prev_inst()` method is intended for iterating backwards over the instructions in an + /// EBB like this: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb}; + /// # use cretonne::ir::layout::Cursor; + /// fn edit_ebb(func: &mut Function, ebb: Ebb) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// cursor.goto_bottom(ebb); + /// while let Some(inst) = cursor.prev_inst() { + /// // Edit instructions... + /// } + /// } + /// ``` + pub fn prev_inst(&mut self) -> Option { + use self::CursorPosition::*; + match self.pos { + Nowhere | Before(..) => None, + At(inst) => { + if let Some(prev) = self.layout.insts[inst].prev.wrap() { + self.pos = At(prev); + Some(prev) + } else { + self.pos = + Before(self.layout.inst_ebb(inst).expect("current instruction removed?")); + None + } + } + After(ebb) => { + if let Some(prev) = self.layout.ebbs[ebb].last_inst.wrap() { + self.pos = At(prev); + Some(prev) + } else { + self.pos = Before(ebb); + None + } + } + } + } +} + + #[cfg(test)] mod tests { - use super::Layout; + use super::{Layout, Cursor, CursorPosition}; use entity_map::EntityRef; use ir::{Ebb, Inst}; @@ -301,6 +565,35 @@ mod tests { } assert_eq!(v, [e1, e2, e0]); } + + // Test cursor positioning. + let mut cur = Cursor::new(&mut layout); + assert_eq!(cur.position(), CursorPosition::Nowhere); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.position(), CursorPosition::Nowhere); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.position(), CursorPosition::Nowhere); + + assert_eq!(cur.next_ebb(), Some(e1)); + assert_eq!(cur.position(), CursorPosition::Before(e1)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.position(), CursorPosition::After(e1)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.position(), CursorPosition::After(e1)); + assert_eq!(cur.next_ebb(), Some(e2)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.position(), CursorPosition::Before(e2)); + assert_eq!(cur.next_ebb(), Some(e0)); + assert_eq!(cur.next_ebb(), None); + assert_eq!(cur.position(), CursorPosition::Nowhere); + + // Backwards through the EBBs. + assert_eq!(cur.prev_ebb(), Some(e0)); + assert_eq!(cur.position(), CursorPosition::After(e0)); + assert_eq!(cur.prev_ebb(), Some(e2)); + assert_eq!(cur.prev_ebb(), Some(e1)); + assert_eq!(cur.prev_ebb(), None); + assert_eq!(cur.position(), CursorPosition::Nowhere); } #[test] @@ -378,6 +671,30 @@ mod tests { assert_eq!(layout.inst_ebb(i2), Some(e1)); let v: Vec = layout.ebb_insts(e1).collect(); assert_eq!(v, [i1, i2, i0]); + + // Test cursor positioning. + let mut cur = Cursor::new(&mut layout); + cur.goto_top(e1); + assert_eq!(cur.position(), CursorPosition::Before(e1)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.position(), CursorPosition::Before(e1)); + assert_eq!(cur.next_inst(), Some(i1)); + assert_eq!(cur.position(), CursorPosition::At(i1)); + assert_eq!(cur.next_inst(), Some(i2)); + assert_eq!(cur.next_inst(), Some(i0)); + assert_eq!(cur.prev_inst(), Some(i2)); + assert_eq!(cur.position(), CursorPosition::At(i2)); + assert_eq!(cur.next_inst(), Some(i0)); + assert_eq!(cur.position(), CursorPosition::At(i0)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.position(), CursorPosition::After(e1)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.position(), CursorPosition::After(e1)); + assert_eq!(cur.prev_inst(), Some(i0)); + assert_eq!(cur.prev_inst(), Some(i2)); + assert_eq!(cur.prev_inst(), Some(i1)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.position(), CursorPosition::Before(e1)); } #[test] diff --git a/src/libcretonne/ir/mod.rs b/src/libcretonne/ir/mod.rs index 36d17c29aa..bbbc6d26ab 100644 --- a/src/libcretonne/ir/mod.rs +++ b/src/libcretonne/ir/mod.rs @@ -22,6 +22,6 @@ pub use ir::instructions::{Opcode, InstructionData, VariableArgs}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; pub use ir::dfg::{DataFlowGraph, ValueDef}; -pub use ir::layout::Layout; +pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; pub use ir::builder::Builder; From 6f68673e8a65702daab5611934a0c2513335edc7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 10:09:57 -0700 Subject: [PATCH 352/968] Add Cursor::insert_inst(). Insert an instruction at thecurrent cursor position. --- src/libcretonne/ir/layout.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index 1b78386100..48943ffc1c 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -511,6 +511,24 @@ impl<'a> Cursor<'a> { } } } + + /// Insert an instruction at the current position. + /// + /// - If pointing at an instruction, the new instruction is inserted before the current + /// instruction. + /// - If pointing at the bottom of an EBB, the new instruction is appended to the EBB. + /// - Otherwise panic. + /// + /// In either case, the cursor is not moved, such that repeates calls to `insert_inst()` causes + /// instructions to appear in insertion order in the EBB. + pub fn insert_inst(&mut self, inst: Inst) { + use self::CursorPosition::*; + match self.pos { + Nowhere | Before(..) => panic!("Invalid insert_inst position"), + At(cur) => self.layout.insert_inst(inst, cur), + After(ebb) => self.layout.append_inst(inst, ebb), + } + } } From 10c579e0cd6daa5b6ddbcd5adf2628c9b2183a27 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 12:33:54 -0700 Subject: [PATCH 353/968] Add Layout::split_ebb(). This method splits an EBB in two and moves trailing instructions to a new EBB. --- src/libcretonne/ir/layout.rs | 140 +++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index 48943ffc1c..b8d10739e5 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -221,6 +221,71 @@ impl Layout { next: self.ebbs[ebb].first_inst.wrap(), } } + + /// Split the EBB containing `before` in two. + /// + /// Insert `new_ebb` after the old EBB and move `before` and the following instructions to + /// `new_ebb`: + /// + /// ```text + /// old_ebb: + /// i1 + /// i2 + /// i3 << before + /// i4 + /// ``` + /// becomes: + /// + /// ```text + /// old_ebb: + /// i1 + /// i2 + /// new_ebb: + /// i3 << before + /// i4 + /// ``` + pub fn split_ebb(&mut self, new_ebb: Ebb, before: Inst) { + let old_ebb = self.inst_ebb(before) + .expect("The `before` instruction must be in the layout"); + assert!(!self.is_ebb_inserted(new_ebb)); + + // Insert new_ebb after old_ebb. + let next_ebb = self.ebbs[old_ebb].next; + let last_inst = self.ebbs[old_ebb].last_inst; + { + let node = self.ebbs.ensure(new_ebb); + node.prev = old_ebb; + node.next = next_ebb; + node.first_inst = before; + node.last_inst = last_inst; + } + self.ebbs[old_ebb].next = new_ebb; + + // Fix backwards link. + if Some(old_ebb) == self.last_ebb { + self.last_ebb = Some(new_ebb); + } else { + self.ebbs[next_ebb].prev = new_ebb; + } + + // Disconnect the instruction links. + let prev_inst = self.insts[before].prev; + self.insts[before].prev = NO_INST; + self.ebbs[old_ebb].last_inst = prev_inst; + if prev_inst == NO_INST { + self.ebbs[old_ebb].first_inst = NO_INST; + } else { + self.insts[prev_inst].next = NO_INST; + } + + // Fix the instruction -> ebb pointers. + let mut i = before; + while i != NO_INST { + debug_assert_eq!(self.insts[i].ebb, old_ebb); + self.insts[i].ebb = new_ebb; + i = self.insts[i].next; + } + } } #[derive(Clone, Debug, Default)] @@ -782,4 +847,79 @@ mod tests { assert_eq!(v0, [i0, i1]); assert_eq!(v1, [i2, i3]); } + + #[test] + fn split_ebb() { + let mut layout = Layout::new(); + + let e0 = Ebb::new(0); + let e1 = Ebb::new(1); + let e2 = Ebb::new(2); + + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let i3 = Inst::new(3); + + layout.append_ebb(e0); + layout.append_inst(i0, e0); + assert_eq!(layout.inst_ebb(i0), Some(e0)); + layout.split_ebb(e1, i0); + assert_eq!(layout.inst_ebb(i0), Some(e1)); + + { + let mut cur = Cursor::new(&mut layout); + assert_eq!(cur.next_ebb(), Some(e0)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.next_ebb(), Some(e1)); + assert_eq!(cur.next_inst(), Some(i0)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.next_ebb(), None); + + // Check backwards links. + assert_eq!(cur.prev_ebb(), Some(e1)); + assert_eq!(cur.prev_inst(), Some(i0)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.prev_ebb(), Some(e0)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.prev_ebb(), None); + } + + layout.append_inst(i1, e0); + layout.append_inst(i2, e0); + layout.append_inst(i3, e0); + layout.split_ebb(e2, i2); + + assert_eq!(layout.inst_ebb(i0), Some(e1)); + assert_eq!(layout.inst_ebb(i1), Some(e0)); + assert_eq!(layout.inst_ebb(i2), Some(e2)); + assert_eq!(layout.inst_ebb(i3), Some(e2)); + + { + let mut cur = Cursor::new(&mut layout); + assert_eq!(cur.next_ebb(), Some(e0)); + assert_eq!(cur.next_inst(), Some(i1)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.next_ebb(), Some(e2)); + assert_eq!(cur.next_inst(), Some(i2)); + assert_eq!(cur.next_inst(), Some(i3)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.next_ebb(), Some(e1)); + assert_eq!(cur.next_inst(), Some(i0)); + assert_eq!(cur.next_inst(), None); + assert_eq!(cur.next_ebb(), None); + + assert_eq!(cur.prev_ebb(), Some(e1)); + assert_eq!(cur.prev_inst(), Some(i0)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.prev_ebb(), Some(e2)); + assert_eq!(cur.prev_inst(), Some(i3)); + assert_eq!(cur.prev_inst(), Some(i2)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.prev_ebb(), Some(e0)); + assert_eq!(cur.prev_inst(), Some(i1)); + assert_eq!(cur.prev_inst(), None); + assert_eq!(cur.prev_ebb(), None); + } + } } From 8592979fc9ca2e030f70d82418aeebd0ba8a2f88 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 13:54:26 -0700 Subject: [PATCH 354/968] Add Layout::insert_ebb_after() method. Symmetrical with the existing insert_ebb(). --- src/libcretonne/ir/layout.rs | 91 ++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index b8d10739e5..b1263a5b47 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -102,6 +102,26 @@ impl Layout { } } + /// Insert `ebb` in the layout *after* the existing EBB `after`. + pub fn insert_ebb_after(&mut self, ebb: Ebb, after: Ebb) { + assert!(!self.is_ebb_inserted(ebb), + "Cannot insert EBB that is already in the layout"); + assert!(self.is_ebb_inserted(after), + "EBB Insertion point not in the layout"); + let before = self.ebbs[after].next; + { + let node = self.ebbs.ensure(ebb); + node.next = before; + node.prev = after; + } + self.ebbs[after].next = ebb; + if before == NO_EBB { + self.last_ebb = Some(ebb); + } else { + self.ebbs[before].prev = ebb; + } + } + /// Return an iterator over all EBBs in layout order. pub fn ebbs<'a>(&'a self) -> Ebbs<'a> { Ebbs { @@ -603,6 +623,37 @@ mod tests { use entity_map::EntityRef; use ir::{Ebb, Inst}; + fn verify(layout: &mut Layout, ebbs: &[(Ebb, &[Inst])]) { + // Check that EBBs are inserted and instructions belong the right places. + // Check forward linkage with iterators. + { + let mut ebb_iter = layout.ebbs(); + for &(ebb, insts) in ebbs { + assert!(layout.is_ebb_inserted(ebb)); + assert_eq!(ebb_iter.next(), Some(ebb)); + + let mut inst_iter = layout.ebb_insts(ebb); + for &inst in insts { + assert_eq!(layout.inst_ebb(inst), Some(ebb)); + assert_eq!(inst_iter.next(), Some(inst)); + } + assert_eq!(inst_iter.next(), None); + } + assert_eq!(ebb_iter.next(), None); + } + + // Check backwards linkage with a cursor. + let mut cur = Cursor::new(layout); + for &(ebb, insts) in ebbs.into_iter().rev() { + assert_eq!(cur.prev_ebb(), Some(ebb)); + for &inst in insts.into_iter().rev() { + assert_eq!(cur.prev_inst(), Some(inst)); + } + assert_eq!(cur.prev_inst(), None); + } + assert_eq!(cur.prev_ebb(), None); + } + #[test] fn append_ebb() { let mut layout = Layout::new(); @@ -614,10 +665,8 @@ mod tests { let imm = &layout; assert!(!imm.is_ebb_inserted(e0)); assert!(!imm.is_ebb_inserted(e1)); - - let v: Vec = layout.ebbs().collect(); - assert_eq!(v, []); } + verify(&mut layout, &[]); layout.append_ebb(e1); assert!(!layout.is_ebb_inserted(e0)); @@ -699,22 +748,34 @@ mod tests { assert!(!layout.is_ebb_inserted(e0)); assert!(layout.is_ebb_inserted(e1)); assert!(!layout.is_ebb_inserted(e2)); - let v: Vec = layout.ebbs().collect(); - assert_eq!(v, [e1]); + verify(&mut layout, &[(e1, &[])]); layout.insert_ebb(e2, e1); assert!(!layout.is_ebb_inserted(e0)); assert!(layout.is_ebb_inserted(e1)); assert!(layout.is_ebb_inserted(e2)); - let v: Vec = layout.ebbs().collect(); - assert_eq!(v, [e2, e1]); + verify(&mut layout, &[(e2, &[]), (e1, &[])]); layout.insert_ebb(e0, e1); assert!(layout.is_ebb_inserted(e0)); assert!(layout.is_ebb_inserted(e1)); assert!(layout.is_ebb_inserted(e2)); - let v: Vec = layout.ebbs().collect(); - assert_eq!(v, [e2, e0, e1]); + verify(&mut layout, &[(e2, &[]), (e0, &[]), (e1, &[])]); + } + + #[test] + fn insert_ebb_after() { + let mut layout = Layout::new(); + let e0 = Ebb::new(0); + let e1 = Ebb::new(1); + let e2 = Ebb::new(2); + + layout.append_ebb(e1); + layout.insert_ebb_after(e2, e1); + verify(&mut layout, &[(e1, &[]), (e2, &[])]); + + layout.insert_ebb_after(e0, e1); + verify(&mut layout, &[(e1, &[]), (e0, &[]), (e2, &[])]); } #[test] @@ -749,11 +810,7 @@ mod tests { assert_eq!(v, [i1, i2]); layout.append_inst(i0, e1); - assert_eq!(layout.inst_ebb(i0), Some(e1)); - assert_eq!(layout.inst_ebb(i1), Some(e1)); - assert_eq!(layout.inst_ebb(i2), Some(e1)); - let v: Vec = layout.ebb_insts(e1).collect(); - assert_eq!(v, [i1, i2, i0]); + verify(&mut layout, &[(e1, &[i1, i2, i0])]); // Test cursor positioning. let mut cur = Cursor::new(&mut layout); @@ -812,11 +869,7 @@ mod tests { assert_eq!(v, [i2, i1]); layout.insert_inst(i0, i1); - assert_eq!(layout.inst_ebb(i0), Some(e1)); - assert_eq!(layout.inst_ebb(i1), Some(e1)); - assert_eq!(layout.inst_ebb(i2), Some(e1)); - let v: Vec = layout.ebb_insts(e1).collect(); - assert_eq!(v, [i2, i0, i1]); + verify(&mut layout, &[(e1, &[i2, i0, i1])]); } #[test] From eb5cea7c6ae23ab0ea2c4932ba4c4c114d2e47fc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 14:01:49 -0700 Subject: [PATCH 355/968] Add Cursor::insert_ebb() method. Insert an EBB at the current position and switch to inserting instructions there. Call the relevant Layout methods to do so. --- src/libcretonne/ir/layout.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/libcretonne/ir/layout.rs b/src/libcretonne/ir/layout.rs index b1263a5b47..c6543a88e0 100644 --- a/src/libcretonne/ir/layout.rs +++ b/src/libcretonne/ir/layout.rs @@ -614,6 +614,36 @@ impl<'a> Cursor<'a> { After(ebb) => self.layout.append_inst(inst, ebb), } } + + /// Insert an EBB at the current position and switch to it. + /// + /// As far as possible, this method behaves as if the EBB header were an instruction inserted + /// at the current position. + /// + /// - If the cursor is pointing at an existing instruction, *the current EBB is split in two* + /// and the current instruction becomes the first instruction in the inserted EBB. + /// - If the cursor points at the bottom of an EBB, the new EBB is inserted after the current + /// one, and moved to the bottom of the new EBB where instructions can be appended. + /// - If the cursor points to the top of an EBB, the new EBB is inserted above the current one. + /// - If the cursor is not pointing at anything, the new EBB is placed last in the layout. + /// + /// This means that is is always valid to call this method, and it always leaves the cursor in + /// a state that will insert instructions into the new EBB. + pub fn insert_ebb(&mut self, new_ebb: Ebb) { + use self::CursorPosition::*; + match self.pos { + At(inst) => { + self.layout.split_ebb(new_ebb, inst); + // All other cases move to `After(ebb)`, but in this case we we'll stay `At(inst)`. + return; + } + Nowhere => self.layout.append_ebb(new_ebb), + Before(ebb) => self.layout.insert_ebb(new_ebb, ebb), + After(ebb) => self.layout.insert_ebb_after(new_ebb, ebb), + } + // For everything but `At(inst)` we end up appending to the new EBB. + self.pos = After(new_ebb); + } } From d4197ca73184c8b917f295afa052b747adc33b85 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 15:02:58 -0700 Subject: [PATCH 356/968] Give Builder a Cursor. The Builder keeps track of a position in the layout and inserts new instructions there. Add insert_ebb() and ebb() methods to Builder. Use Builder in the cfg tests. --- src/libcretonne/cfg.rs | 32 ++++++++++++++++++-------------- src/libcretonne/ir/builder.rs | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/libcretonne/cfg.rs b/src/libcretonne/cfg.rs index 8397ca5166..aab576ae82 100644 --- a/src/libcretonne/cfg.rs +++ b/src/libcretonne/cfg.rs @@ -139,9 +139,7 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { use super::*; - use ir::Function; - - use test_utils::make_inst; + use ir::{Function, Builder, Cursor, VariableArgs, types}; #[test] fn empty() { @@ -176,23 +174,29 @@ mod tests { fn branches_and_jumps() { let mut func = Function::new(); let ebb0 = func.dfg.make_ebb(); + let cond = func.dfg.append_ebb_arg(ebb0, types::I32); let ebb1 = func.dfg.make_ebb(); let ebb2 = func.dfg.make_ebb(); - func.layout.append_ebb(ebb0); - func.layout.append_ebb(ebb1); - func.layout.append_ebb(ebb2); - let br_ebb0_ebb2 = make_inst::branch(&mut func, ebb2); - func.layout.append_inst(br_ebb0_ebb2, ebb0); + let br_ebb0_ebb2; + let br_ebb1_ebb1; + let jmp_ebb0_ebb1; + let jmp_ebb1_ebb2; - let jmp_ebb0_ebb1 = make_inst::jump(&mut func, ebb1); - func.layout.append_inst(jmp_ebb0_ebb1, ebb0); + { + let mut cursor = Cursor::new(&mut func.layout); + let mut b = Builder::new(&mut func.dfg, &mut cursor); - let br_ebb1_ebb1 = make_inst::branch(&mut func, ebb1); - func.layout.append_inst(br_ebb1_ebb1, ebb1); + b.insert_ebb(ebb0); + br_ebb0_ebb2 = b.brnz(cond, ebb2, VariableArgs::new()); + jmp_ebb0_ebb1 = b.jump(ebb1, VariableArgs::new()); - let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); - func.layout.append_inst(jmp_ebb1_ebb2, ebb1); + b.insert_ebb(ebb1); + br_ebb1_ebb1 = b.brnz(cond, ebb1, VariableArgs::new()); + jmp_ebb1_ebb2 = b.jump(ebb2, VariableArgs::new()); + + b.insert_ebb(ebb2); + } let cfg = ControlFlowGraph::new(&func); diff --git a/src/libcretonne/ir/builder.rs b/src/libcretonne/ir/builder.rs index 4e81027e9b..e31be0f2ca 100644 --- a/src/libcretonne/ir/builder.rs +++ b/src/libcretonne/ir/builder.rs @@ -4,20 +4,48 @@ //! function. Many of its methods are generated from the meta language instruction definitions. use ir::{types, instructions}; -use ir::{InstructionData, DataFlowGraph}; +use ir::{InstructionData, DataFlowGraph, Cursor}; use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, FuncRef}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::{IntCC, FloatCC}; +/// Instruction builder. +/// +/// A `Builder` holds mutable references to a data flow graph and a layout cursor. It provides +/// convenience method for creating and inserting instructions at the current cursor position. pub struct Builder<'a> { - dfg: &'a mut DataFlowGraph, + pub dfg: &'a mut DataFlowGraph, + pub pos: &'a mut Cursor<'a>, } impl<'a> Builder<'a> { + /// Create a new builder which inserts instructions at `pos`. + /// The `dfg` and `pos.layout` references should be from the same `Function`. + pub fn new(dfg: &'a mut DataFlowGraph, pos: &'a mut Cursor<'a>) -> Builder<'a> { + Builder { + dfg: dfg, + pos: pos, + } + } + + /// Create and insert an EBB. Further instructions will be inserted into the new EBB. + pub fn ebb(&mut self) -> Ebb { + let ebb = self.dfg.make_ebb(); + self.insert_ebb(ebb); + ebb + } + + /// Insert an existing EBB at the current position. Further instructions will be inserted into + /// the new EBB. + pub fn insert_ebb(&mut self, ebb: Ebb) { + self.pos.insert_ebb(ebb); + } + // Create and insert an instruction. // This method is used by the generated format-specific methods. fn insert_inst(&mut self, data: InstructionData) -> Inst { let inst = self.dfg.make_inst(data); + self.pos.insert_inst(inst); inst } } From af8f8d98e6f4ff8edb937525b215ea212cebc526 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 15:16:29 -0700 Subject: [PATCH 357/968] Switch domtree tests to using Builder. --- src/libcretonne/dominator_tree.rs | 35 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/libcretonne/dominator_tree.rs b/src/libcretonne/dominator_tree.rs index c741b5992d..0e34a2c021 100644 --- a/src/libcretonne/dominator_tree.rs +++ b/src/libcretonne/dominator_tree.rs @@ -116,10 +116,9 @@ impl DominatorTree { #[cfg(test)] mod test { use super::*; - use ir::Function; + use ir::{Function, Builder, Cursor, VariableArgs, types}; use ir::entities::NO_INST; use cfg::ControlFlowGraph; - use test_utils::make_inst; #[test] fn empty() { @@ -133,23 +132,31 @@ mod test { fn non_zero_entry_block() { let mut func = Function::new(); let ebb3 = func.dfg.make_ebb(); + let cond = func.dfg.append_ebb_arg(ebb3, types::I32); let ebb1 = func.dfg.make_ebb(); let ebb2 = func.dfg.make_ebb(); let ebb0 = func.dfg.make_ebb(); - func.layout.append_ebb(ebb3); - func.layout.append_ebb(ebb1); - func.layout.append_ebb(ebb2); - func.layout.append_ebb(ebb0); - let jmp_ebb3_ebb1 = make_inst::jump(&mut func, ebb1); - let br_ebb1_ebb0 = make_inst::branch(&mut func, ebb0); - let jmp_ebb1_ebb2 = make_inst::jump(&mut func, ebb2); - let jmp_ebb2_ebb0 = make_inst::jump(&mut func, ebb0); + let jmp_ebb3_ebb1; + let br_ebb1_ebb0; + let jmp_ebb1_ebb2; - func.layout.append_inst(br_ebb1_ebb0, ebb1); - func.layout.append_inst(jmp_ebb1_ebb2, ebb1); - func.layout.append_inst(jmp_ebb2_ebb0, ebb2); - func.layout.append_inst(jmp_ebb3_ebb1, ebb3); + { + let mut cursor = Cursor::new(&mut func.layout); + let mut b = Builder::new(&mut func.dfg, &mut cursor); + + b.insert_ebb(ebb3); + jmp_ebb3_ebb1 = b.jump(ebb1, VariableArgs::new()); + + b.insert_ebb(ebb1); + br_ebb1_ebb0 = b.brnz(cond, ebb0, VariableArgs::new()); + jmp_ebb1_ebb2 = b.jump(ebb2, VariableArgs::new()); + + b.insert_ebb(ebb2); + b.jump(ebb0, VariableArgs::new()); + + b.insert_ebb(ebb0); + } let cfg = ControlFlowGraph::new(&func); let dt = DominatorTree::new(&cfg); From 8480879f3ef3228e102575f33d88c857a2441119 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 14 Oct 2016 15:17:34 -0700 Subject: [PATCH 358/968] Remove test_utils. These test utilities have been subsumed by ir::Builder. --- src/libcretonne/lib.rs | 3 -- src/libcretonne/test_utils/make_inst.rs | 37 ------------------------- src/libcretonne/test_utils/mod.rs | 3 -- 3 files changed, 43 deletions(-) delete mode 100644 src/libcretonne/test_utils/make_inst.rs delete mode 100644 src/libcretonne/test_utils/mod.rs diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 26a87bd223..ae25157248 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -23,6 +23,3 @@ mod write; mod constant_hash; mod predicates; mod legalizer; - -#[cfg(test)] -pub mod test_utils; diff --git a/src/libcretonne/test_utils/make_inst.rs b/src/libcretonne/test_utils/make_inst.rs deleted file mode 100644 index 9ab4429b76..0000000000 --- a/src/libcretonne/test_utils/make_inst.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Helper functions for generating dummy instructions. - -use ir::{Function, Ebb, Inst, Opcode}; -use ir::entities::NO_VALUE; -use ir::instructions::{InstructionData, ReturnData, VariableArgs, JumpData, BranchData}; -use ir::types; - -pub fn jump(func: &mut Function, dest: Ebb) -> Inst { - func.dfg.make_inst(InstructionData::Jump { - opcode: Opcode::Jump, - ty: types::VOID, - data: Box::new(JumpData { - destination: dest, - varargs: VariableArgs::new(), - }), - }) -} - -pub fn branch(func: &mut Function, dest: Ebb) -> Inst { - func.dfg.make_inst(InstructionData::Branch { - opcode: Opcode::Brz, - ty: types::VOID, - data: Box::new(BranchData { - arg: NO_VALUE, - destination: dest, - varargs: VariableArgs::new(), - }), - }) -} - -pub fn ret(func: &mut Function) -> Inst { - func.dfg.make_inst(InstructionData::Return { - opcode: Opcode::Return, - ty: types::VOID, - data: Box::new(ReturnData { varargs: VariableArgs::new() }), - }) -} diff --git a/src/libcretonne/test_utils/mod.rs b/src/libcretonne/test_utils/mod.rs deleted file mode 100644 index 6273573d6d..0000000000 --- a/src/libcretonne/test_utils/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Test utility functions. - -pub mod make_inst; From e7f30a40b4f539b800ba32cc57fe834e57702a2f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Oct 2016 14:16:43 -0700 Subject: [PATCH 359/968] Move the 'meta' dir to 'lib/cretonne/meta'. The 'lib/cretonne' directory will be the new root of a stand-alone cretonne crate containg both Python and Rust sources. This is in preparation for publishing crates on crates.io. --- docs/Makefile | 2 +- docs/conf.py | 2 +- docs/metaref.rst | 16 +++++++++------- {meta => lib/cretonne/meta}/build.py | 0 {meta => lib/cretonne/meta}/check.sh | 0 {meta => lib/cretonne/meta}/constant_hash.py | 0 {meta => lib/cretonne/meta}/cretonne/__init__.py | 0 {meta => lib/cretonne/meta}/cretonne/ast.py | 0 {meta => lib/cretonne/meta}/cretonne/base.py | 0 {meta => lib/cretonne/meta}/cretonne/entities.py | 0 {meta => lib/cretonne/meta}/cretonne/formats.py | 0 .../cretonne/meta}/cretonne/immediates.py | 0 {meta => lib/cretonne/meta}/cretonne/legalize.py | 0 .../cretonne/meta}/cretonne/predicates.py | 0 {meta => lib/cretonne/meta}/cretonne/settings.py | 0 {meta => lib/cretonne/meta}/cretonne/test_ast.py | 0 .../cretonne/meta}/cretonne/test_typevar.py | 0 .../cretonne/meta}/cretonne/test_xform.py | 0 {meta => lib/cretonne/meta}/cretonne/types.py | 0 {meta => lib/cretonne/meta}/cretonne/typevar.py | 0 {meta => lib/cretonne/meta}/cretonne/xform.py | 0 {meta => lib/cretonne/meta}/gen_build_deps.py | 0 {meta => lib/cretonne/meta}/gen_encoding.py | 0 {meta => lib/cretonne/meta}/gen_instr.py | 0 {meta => lib/cretonne/meta}/gen_settings.py | 0 {meta => lib/cretonne/meta}/gen_types.py | 0 {meta => lib/cretonne/meta}/isa/__init__.py | 0 .../cretonne/meta}/isa/riscv/__init__.py | 0 {meta => lib/cretonne/meta}/isa/riscv/defs.py | 0 .../cretonne/meta}/isa/riscv/encodings.py | 0 {meta => lib/cretonne/meta}/isa/riscv/recipes.py | 0 .../cretonne/meta}/isa/riscv/settings.py | 0 {meta => lib/cretonne/meta}/srcgen.py | 0 .../cretonne/meta}/test_constant_hash.py | 0 {meta => lib/cretonne/meta}/test_srcgen.py | 0 {meta => lib/cretonne/meta}/unique_table.py | 0 src/libcretonne/build.rs | 2 +- 37 files changed, 12 insertions(+), 10 deletions(-) rename {meta => lib/cretonne/meta}/build.py (100%) rename {meta => lib/cretonne/meta}/check.sh (100%) rename {meta => lib/cretonne/meta}/constant_hash.py (100%) rename {meta => lib/cretonne/meta}/cretonne/__init__.py (100%) rename {meta => lib/cretonne/meta}/cretonne/ast.py (100%) rename {meta => lib/cretonne/meta}/cretonne/base.py (100%) rename {meta => lib/cretonne/meta}/cretonne/entities.py (100%) rename {meta => lib/cretonne/meta}/cretonne/formats.py (100%) rename {meta => lib/cretonne/meta}/cretonne/immediates.py (100%) rename {meta => lib/cretonne/meta}/cretonne/legalize.py (100%) rename {meta => lib/cretonne/meta}/cretonne/predicates.py (100%) rename {meta => lib/cretonne/meta}/cretonne/settings.py (100%) rename {meta => lib/cretonne/meta}/cretonne/test_ast.py (100%) rename {meta => lib/cretonne/meta}/cretonne/test_typevar.py (100%) rename {meta => lib/cretonne/meta}/cretonne/test_xform.py (100%) rename {meta => lib/cretonne/meta}/cretonne/types.py (100%) rename {meta => lib/cretonne/meta}/cretonne/typevar.py (100%) rename {meta => lib/cretonne/meta}/cretonne/xform.py (100%) rename {meta => lib/cretonne/meta}/gen_build_deps.py (100%) rename {meta => lib/cretonne/meta}/gen_encoding.py (100%) rename {meta => lib/cretonne/meta}/gen_instr.py (100%) rename {meta => lib/cretonne/meta}/gen_settings.py (100%) rename {meta => lib/cretonne/meta}/gen_types.py (100%) rename {meta => lib/cretonne/meta}/isa/__init__.py (100%) rename {meta => lib/cretonne/meta}/isa/riscv/__init__.py (100%) rename {meta => lib/cretonne/meta}/isa/riscv/defs.py (100%) rename {meta => lib/cretonne/meta}/isa/riscv/encodings.py (100%) rename {meta => lib/cretonne/meta}/isa/riscv/recipes.py (100%) rename {meta => lib/cretonne/meta}/isa/riscv/settings.py (100%) rename {meta => lib/cretonne/meta}/srcgen.py (100%) rename {meta => lib/cretonne/meta}/test_constant_hash.py (100%) rename {meta => lib/cretonne/meta}/test_srcgen.py (100%) rename {meta => lib/cretonne/meta}/unique_table.py (100%) diff --git a/docs/Makefile b/docs/Makefile index 56137e8b2c..b2443f68b7 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -58,7 +58,7 @@ html: @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." autohtml: html - $(SPHINXABUILD) -z ../meta --ignore '*.swp' -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXABUILD) -z ../lib/cretonne/meta --ignore '*.swp' -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml diff --git a/docs/conf.py b/docs/conf.py index 5602561828..0603a27bfd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ sys.path.insert(0, os.path.abspath('.')) # Also add the meta directory to sys.path so autodoc can find the Cretonne meta # language definitions. -sys.path.insert(0, os.path.abspath('../meta')) +sys.path.insert(0, os.path.abspath('../lib/cretonne/meta')) # -- General configuration ------------------------------------------------ diff --git a/docs/metaref.rst b/docs/metaref.rst index 4edf708041..cc6d7173d6 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -10,8 +10,9 @@ The Cretonne meta language is used to define instructions for Cretonne. It is a domain specific language embedded in Python. This document describes the Python modules that form the embedded DSL. -The meta language descriptions are Python modules under the :file:`meta` -top-level directory. The descriptions are processed in two steps: +The meta language descriptions are Python modules under the +:file:`lib/cretonne/meta` directory. The descriptions are processed in two +steps: 1. The Python modules are imported. This has the effect of building static data structures in global variables in the modules. These static data structures @@ -22,8 +23,9 @@ top-level directory. The descriptions are processed in two steps: constant tables. The main driver for this source code generation process is the -:file:`meta/build.py` script which is invoked as part of the build process if -anything in the :file:`meta` directory has changed since the last build. +:file:`lib/cretonne/meta/build.py` script which is invoked as part of the build +process if anything in the :file:`lib/cretonne/meta` directory has changed +since the last build. Settings @@ -33,8 +35,8 @@ Settings are used by the environment embedding Cretonne to control the details of code generation. Each setting is defined in the meta language so a compact and consistent Rust representation can be generated. Shared settings are defined in the :mod:`cretonne.settings` module. Some settings are specific to a target -ISA, and defined in a `settings` module under the appropriate :file:`meta/isa/*` -directory. +ISA, and defined in a `settings` module under the appropriate +:file:`lib/cretonne/meta/isa/*` directory. Settings can take boolean on/off values, small numbers, or explicitly enumerated symbolic values. Each type is represented by a sub-class of :class:`Setting`: @@ -343,7 +345,7 @@ architectures. Each ISA is represented by a :py:class:`cretonne.TargetISA` insta .. autoclass:: TargetISA The definitions for each supported target live in a package under -:file:`meta/isa`. +:file:`lib/cretonne/meta/isa`. .. automodule:: isa :members: diff --git a/meta/build.py b/lib/cretonne/meta/build.py similarity index 100% rename from meta/build.py rename to lib/cretonne/meta/build.py diff --git a/meta/check.sh b/lib/cretonne/meta/check.sh similarity index 100% rename from meta/check.sh rename to lib/cretonne/meta/check.sh diff --git a/meta/constant_hash.py b/lib/cretonne/meta/constant_hash.py similarity index 100% rename from meta/constant_hash.py rename to lib/cretonne/meta/constant_hash.py diff --git a/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py similarity index 100% rename from meta/cretonne/__init__.py rename to lib/cretonne/meta/cretonne/__init__.py diff --git a/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py similarity index 100% rename from meta/cretonne/ast.py rename to lib/cretonne/meta/cretonne/ast.py diff --git a/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py similarity index 100% rename from meta/cretonne/base.py rename to lib/cretonne/meta/cretonne/base.py diff --git a/meta/cretonne/entities.py b/lib/cretonne/meta/cretonne/entities.py similarity index 100% rename from meta/cretonne/entities.py rename to lib/cretonne/meta/cretonne/entities.py diff --git a/meta/cretonne/formats.py b/lib/cretonne/meta/cretonne/formats.py similarity index 100% rename from meta/cretonne/formats.py rename to lib/cretonne/meta/cretonne/formats.py diff --git a/meta/cretonne/immediates.py b/lib/cretonne/meta/cretonne/immediates.py similarity index 100% rename from meta/cretonne/immediates.py rename to lib/cretonne/meta/cretonne/immediates.py diff --git a/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py similarity index 100% rename from meta/cretonne/legalize.py rename to lib/cretonne/meta/cretonne/legalize.py diff --git a/meta/cretonne/predicates.py b/lib/cretonne/meta/cretonne/predicates.py similarity index 100% rename from meta/cretonne/predicates.py rename to lib/cretonne/meta/cretonne/predicates.py diff --git a/meta/cretonne/settings.py b/lib/cretonne/meta/cretonne/settings.py similarity index 100% rename from meta/cretonne/settings.py rename to lib/cretonne/meta/cretonne/settings.py diff --git a/meta/cretonne/test_ast.py b/lib/cretonne/meta/cretonne/test_ast.py similarity index 100% rename from meta/cretonne/test_ast.py rename to lib/cretonne/meta/cretonne/test_ast.py diff --git a/meta/cretonne/test_typevar.py b/lib/cretonne/meta/cretonne/test_typevar.py similarity index 100% rename from meta/cretonne/test_typevar.py rename to lib/cretonne/meta/cretonne/test_typevar.py diff --git a/meta/cretonne/test_xform.py b/lib/cretonne/meta/cretonne/test_xform.py similarity index 100% rename from meta/cretonne/test_xform.py rename to lib/cretonne/meta/cretonne/test_xform.py diff --git a/meta/cretonne/types.py b/lib/cretonne/meta/cretonne/types.py similarity index 100% rename from meta/cretonne/types.py rename to lib/cretonne/meta/cretonne/types.py diff --git a/meta/cretonne/typevar.py b/lib/cretonne/meta/cretonne/typevar.py similarity index 100% rename from meta/cretonne/typevar.py rename to lib/cretonne/meta/cretonne/typevar.py diff --git a/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py similarity index 100% rename from meta/cretonne/xform.py rename to lib/cretonne/meta/cretonne/xform.py diff --git a/meta/gen_build_deps.py b/lib/cretonne/meta/gen_build_deps.py similarity index 100% rename from meta/gen_build_deps.py rename to lib/cretonne/meta/gen_build_deps.py diff --git a/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py similarity index 100% rename from meta/gen_encoding.py rename to lib/cretonne/meta/gen_encoding.py diff --git a/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py similarity index 100% rename from meta/gen_instr.py rename to lib/cretonne/meta/gen_instr.py diff --git a/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py similarity index 100% rename from meta/gen_settings.py rename to lib/cretonne/meta/gen_settings.py diff --git a/meta/gen_types.py b/lib/cretonne/meta/gen_types.py similarity index 100% rename from meta/gen_types.py rename to lib/cretonne/meta/gen_types.py diff --git a/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py similarity index 100% rename from meta/isa/__init__.py rename to lib/cretonne/meta/isa/__init__.py diff --git a/meta/isa/riscv/__init__.py b/lib/cretonne/meta/isa/riscv/__init__.py similarity index 100% rename from meta/isa/riscv/__init__.py rename to lib/cretonne/meta/isa/riscv/__init__.py diff --git a/meta/isa/riscv/defs.py b/lib/cretonne/meta/isa/riscv/defs.py similarity index 100% rename from meta/isa/riscv/defs.py rename to lib/cretonne/meta/isa/riscv/defs.py diff --git a/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py similarity index 100% rename from meta/isa/riscv/encodings.py rename to lib/cretonne/meta/isa/riscv/encodings.py diff --git a/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py similarity index 100% rename from meta/isa/riscv/recipes.py rename to lib/cretonne/meta/isa/riscv/recipes.py diff --git a/meta/isa/riscv/settings.py b/lib/cretonne/meta/isa/riscv/settings.py similarity index 100% rename from meta/isa/riscv/settings.py rename to lib/cretonne/meta/isa/riscv/settings.py diff --git a/meta/srcgen.py b/lib/cretonne/meta/srcgen.py similarity index 100% rename from meta/srcgen.py rename to lib/cretonne/meta/srcgen.py diff --git a/meta/test_constant_hash.py b/lib/cretonne/meta/test_constant_hash.py similarity index 100% rename from meta/test_constant_hash.py rename to lib/cretonne/meta/test_constant_hash.py diff --git a/meta/test_srcgen.py b/lib/cretonne/meta/test_srcgen.py similarity index 100% rename from meta/test_srcgen.py rename to lib/cretonne/meta/test_srcgen.py diff --git a/meta/unique_table.py b/lib/cretonne/meta/unique_table.py similarity index 100% rename from meta/unique_table.py rename to lib/cretonne/meta/unique_table.py diff --git a/src/libcretonne/build.rs b/src/libcretonne/build.rs index c275e33e53..2cb13ac959 100644 --- a/src/libcretonne/build.rs +++ b/src/libcretonne/build.rs @@ -29,7 +29,7 @@ fn main() { let top_dir = cur_dir.as_path(); // Scripts are in $top_dir/meta. - let meta_dir = top_dir.join("meta"); + let meta_dir = top_dir.join("lib/cretonne/meta"); let build_script = meta_dir.join("build.py"); // Launch build script with Python. We'll just find python in the path. From 0764df28b54ee3bf5a1910f6ed0e278df08fab29 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Oct 2016 14:44:43 -0700 Subject: [PATCH 360/968] Move library crates under 'lib/'. Give these crates each a more standard directory layout with sources in a 'src' sub-sirectory and Cargo.toml in the top lib/foo directory. Add license and description fields to each. The build script for the cretonne crate now lives in 'lib/cretonne/build.rs' separating it from the normal library sources under 'lib/cretonne/src'. --- {src/libcretonne => lib/cretonne}/Cargo.toml | 4 +- lib/cretonne/build.rs | 41 +++++++++++++++++ {src/libcretonne => lib/cretonne/src}/cfg.rs | 0 .../cretonne/src}/constant_hash.rs | 0 .../cretonne/src}/dominator_tree.rs | 0 .../cretonne/src}/entity_map.rs | 0 .../cretonne/src}/ir/builder.rs | 0 .../cretonne/src}/ir/condcodes.rs | 0 .../cretonne/src}/ir/dfg.rs | 0 .../cretonne/src}/ir/entities.rs | 0 .../cretonne/src}/ir/extfunc.rs | 0 .../cretonne/src}/ir/funcname.rs | 0 .../cretonne/src}/ir/function.rs | 0 .../cretonne/src}/ir/immediates.rs | 0 .../cretonne/src}/ir/instructions.rs | 0 .../cretonne/src}/ir/jumptable.rs | 0 .../cretonne/src}/ir/layout.rs | 0 .../cretonne/src}/ir/mod.rs | 0 .../cretonne/src}/ir/stackslot.rs | 0 .../cretonne/src}/ir/types.rs | 0 .../cretonne/src}/isa/enc_tables.rs | 0 .../cretonne/src}/isa/encoding.rs | 0 .../cretonne/src}/isa/mod.rs | 0 .../cretonne/src}/isa/riscv/enc_tables.rs | 0 .../cretonne/src}/isa/riscv/mod.rs | 0 .../cretonne/src}/isa/riscv/settings.rs | 0 .../cretonne/src}/legalizer.rs | 0 {src/libcretonne => lib/cretonne/src}/lib.rs | 0 .../cretonne/src}/predicates.rs | 0 .../cretonne/src}/settings.rs | 0 .../cretonne/src}/verifier.rs | 0 .../libcretonne => lib/cretonne/src}/write.rs | 0 .../libfilecheck => lib/filecheck}/Cargo.toml | 4 +- .../filecheck/src}/checker.rs | 0 .../filecheck/src}/error.rs | 0 .../filecheck/src}/explain.rs | 0 .../libfilecheck => lib/filecheck/src}/lib.rs | 0 .../filecheck/src}/pattern.rs | 0 .../filecheck/src}/tests/basic.rs | 0 .../filecheck/src}/variable.rs | 0 lib/reader/Cargo.toml | 15 ++++++ {src/libreader => lib/reader/src}/error.rs | 0 {src/libreader => lib/reader/src}/isaspec.rs | 0 {src/libreader => lib/reader/src}/lexer.rs | 0 {src/libreader => lib/reader/src}/lib.rs | 0 {src/libreader => lib/reader/src}/parser.rs | 0 .../libreader => lib/reader/src}/sourcemap.rs | 0 .../reader/src}/testcommand.rs | 0 {src/libreader => lib/reader/src}/testfile.rs | 0 src/libcretonne/build.rs | 46 ------------------- src/libreader/Cargo.toml | 12 ----- src/tools/Cargo.toml | 6 +-- 52 files changed, 64 insertions(+), 64 deletions(-) rename {src/libcretonne => lib/cretonne}/Cargo.toml (91%) create mode 100644 lib/cretonne/build.rs rename {src/libcretonne => lib/cretonne/src}/cfg.rs (100%) rename {src/libcretonne => lib/cretonne/src}/constant_hash.rs (100%) rename {src/libcretonne => lib/cretonne/src}/dominator_tree.rs (100%) rename {src/libcretonne => lib/cretonne/src}/entity_map.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/builder.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/condcodes.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/dfg.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/entities.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/extfunc.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/funcname.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/function.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/immediates.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/instructions.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/jumptable.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/layout.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/mod.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/stackslot.rs (100%) rename {src/libcretonne => lib/cretonne/src}/ir/types.rs (100%) rename {src/libcretonne => lib/cretonne/src}/isa/enc_tables.rs (100%) rename {src/libcretonne => lib/cretonne/src}/isa/encoding.rs (100%) rename {src/libcretonne => lib/cretonne/src}/isa/mod.rs (100%) rename {src/libcretonne => lib/cretonne/src}/isa/riscv/enc_tables.rs (100%) rename {src/libcretonne => lib/cretonne/src}/isa/riscv/mod.rs (100%) rename {src/libcretonne => lib/cretonne/src}/isa/riscv/settings.rs (100%) rename {src/libcretonne => lib/cretonne/src}/legalizer.rs (100%) rename {src/libcretonne => lib/cretonne/src}/lib.rs (100%) rename {src/libcretonne => lib/cretonne/src}/predicates.rs (100%) rename {src/libcretonne => lib/cretonne/src}/settings.rs (100%) rename {src/libcretonne => lib/cretonne/src}/verifier.rs (100%) rename {src/libcretonne => lib/cretonne/src}/write.rs (100%) rename {src/libfilecheck => lib/filecheck}/Cargo.toml (52%) rename {src/libfilecheck => lib/filecheck/src}/checker.rs (100%) rename {src/libfilecheck => lib/filecheck/src}/error.rs (100%) rename {src/libfilecheck => lib/filecheck/src}/explain.rs (100%) rename {src/libfilecheck => lib/filecheck/src}/lib.rs (100%) rename {src/libfilecheck => lib/filecheck/src}/pattern.rs (100%) rename {src/libfilecheck => lib/filecheck/src}/tests/basic.rs (100%) rename {src/libfilecheck => lib/filecheck/src}/variable.rs (100%) create mode 100644 lib/reader/Cargo.toml rename {src/libreader => lib/reader/src}/error.rs (100%) rename {src/libreader => lib/reader/src}/isaspec.rs (100%) rename {src/libreader => lib/reader/src}/lexer.rs (100%) rename {src/libreader => lib/reader/src}/lib.rs (100%) rename {src/libreader => lib/reader/src}/parser.rs (100%) rename {src/libreader => lib/reader/src}/sourcemap.rs (100%) rename {src/libreader => lib/reader/src}/testcommand.rs (100%) rename {src/libreader => lib/reader/src}/testfile.rs (100%) delete mode 100644 src/libcretonne/build.rs delete mode 100644 src/libreader/Cargo.toml diff --git a/src/libcretonne/Cargo.toml b/lib/cretonne/Cargo.toml similarity index 91% rename from src/libcretonne/Cargo.toml rename to lib/cretonne/Cargo.toml index 174fb2e0bf..60e8eb2be9 100644 --- a/src/libcretonne/Cargo.toml +++ b/lib/cretonne/Cargo.toml @@ -3,6 +3,7 @@ authors = ["The Cretonne Project Developers"] name = "cretonne" version = "0.0.0" description = "Low-level code generator library" +license = "Apache-2.0" documentation = "https://cretonne.readthedocs.io/" repository = "https://github.com/stoklund/cretonne" publish = false @@ -10,10 +11,9 @@ build = "build.rs" [lib] name = "cretonne" -path = "lib.rs" [dependencies] # It is a goal of the cretonne crate to have minimal external dependencies. # Please don't add any unless they are essential to the task of creating binary # machine code. Integration tests that need external dependencies can be -# accomodated in src/tools/tests. +# accomodated in `tests`. diff --git a/lib/cretonne/build.rs b/lib/cretonne/build.rs new file mode 100644 index 0000000000..47e2890217 --- /dev/null +++ b/lib/cretonne/build.rs @@ -0,0 +1,41 @@ +// Build script. +// +// This program is run by Cargo when building lib/cretonne. It is used to generate Rust code from +// the language definitions in the lib/cretonne/meta directory. +// +// Environment: +// +// OUT_DIR +// Directory where generated files should be placed. +// +// The build script expects to be run from the directory where this build.rs file lives. The +// current directory is used to find the sources. + + +use std::env; +use std::process; + +fn main() { + let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); + + println!("Build script generating files in {}", out_dir); + + let cur_dir = env::current_dir().expect("Can't access current working directory"); + let crate_dir = cur_dir.as_path(); + + // Scripts are in `$crate_dir/meta`. + let meta_dir = crate_dir.join("meta"); + let build_script = meta_dir.join("build.py"); + + // Launch build script with Python. We'll just find python in the path. + let status = process::Command::new("python") + .current_dir(crate_dir) + .arg(build_script) + .arg("--out-dir") + .arg(out_dir) + .status() + .expect("Failed to launch second-level build script"); + if !status.success() { + process::exit(status.code().unwrap()); + } +} diff --git a/src/libcretonne/cfg.rs b/lib/cretonne/src/cfg.rs similarity index 100% rename from src/libcretonne/cfg.rs rename to lib/cretonne/src/cfg.rs diff --git a/src/libcretonne/constant_hash.rs b/lib/cretonne/src/constant_hash.rs similarity index 100% rename from src/libcretonne/constant_hash.rs rename to lib/cretonne/src/constant_hash.rs diff --git a/src/libcretonne/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs similarity index 100% rename from src/libcretonne/dominator_tree.rs rename to lib/cretonne/src/dominator_tree.rs diff --git a/src/libcretonne/entity_map.rs b/lib/cretonne/src/entity_map.rs similarity index 100% rename from src/libcretonne/entity_map.rs rename to lib/cretonne/src/entity_map.rs diff --git a/src/libcretonne/ir/builder.rs b/lib/cretonne/src/ir/builder.rs similarity index 100% rename from src/libcretonne/ir/builder.rs rename to lib/cretonne/src/ir/builder.rs diff --git a/src/libcretonne/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs similarity index 100% rename from src/libcretonne/ir/condcodes.rs rename to lib/cretonne/src/ir/condcodes.rs diff --git a/src/libcretonne/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs similarity index 100% rename from src/libcretonne/ir/dfg.rs rename to lib/cretonne/src/ir/dfg.rs diff --git a/src/libcretonne/ir/entities.rs b/lib/cretonne/src/ir/entities.rs similarity index 100% rename from src/libcretonne/ir/entities.rs rename to lib/cretonne/src/ir/entities.rs diff --git a/src/libcretonne/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs similarity index 100% rename from src/libcretonne/ir/extfunc.rs rename to lib/cretonne/src/ir/extfunc.rs diff --git a/src/libcretonne/ir/funcname.rs b/lib/cretonne/src/ir/funcname.rs similarity index 100% rename from src/libcretonne/ir/funcname.rs rename to lib/cretonne/src/ir/funcname.rs diff --git a/src/libcretonne/ir/function.rs b/lib/cretonne/src/ir/function.rs similarity index 100% rename from src/libcretonne/ir/function.rs rename to lib/cretonne/src/ir/function.rs diff --git a/src/libcretonne/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs similarity index 100% rename from src/libcretonne/ir/immediates.rs rename to lib/cretonne/src/ir/immediates.rs diff --git a/src/libcretonne/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs similarity index 100% rename from src/libcretonne/ir/instructions.rs rename to lib/cretonne/src/ir/instructions.rs diff --git a/src/libcretonne/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs similarity index 100% rename from src/libcretonne/ir/jumptable.rs rename to lib/cretonne/src/ir/jumptable.rs diff --git a/src/libcretonne/ir/layout.rs b/lib/cretonne/src/ir/layout.rs similarity index 100% rename from src/libcretonne/ir/layout.rs rename to lib/cretonne/src/ir/layout.rs diff --git a/src/libcretonne/ir/mod.rs b/lib/cretonne/src/ir/mod.rs similarity index 100% rename from src/libcretonne/ir/mod.rs rename to lib/cretonne/src/ir/mod.rs diff --git a/src/libcretonne/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs similarity index 100% rename from src/libcretonne/ir/stackslot.rs rename to lib/cretonne/src/ir/stackslot.rs diff --git a/src/libcretonne/ir/types.rs b/lib/cretonne/src/ir/types.rs similarity index 100% rename from src/libcretonne/ir/types.rs rename to lib/cretonne/src/ir/types.rs diff --git a/src/libcretonne/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs similarity index 100% rename from src/libcretonne/isa/enc_tables.rs rename to lib/cretonne/src/isa/enc_tables.rs diff --git a/src/libcretonne/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs similarity index 100% rename from src/libcretonne/isa/encoding.rs rename to lib/cretonne/src/isa/encoding.rs diff --git a/src/libcretonne/isa/mod.rs b/lib/cretonne/src/isa/mod.rs similarity index 100% rename from src/libcretonne/isa/mod.rs rename to lib/cretonne/src/isa/mod.rs diff --git a/src/libcretonne/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs similarity index 100% rename from src/libcretonne/isa/riscv/enc_tables.rs rename to lib/cretonne/src/isa/riscv/enc_tables.rs diff --git a/src/libcretonne/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs similarity index 100% rename from src/libcretonne/isa/riscv/mod.rs rename to lib/cretonne/src/isa/riscv/mod.rs diff --git a/src/libcretonne/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs similarity index 100% rename from src/libcretonne/isa/riscv/settings.rs rename to lib/cretonne/src/isa/riscv/settings.rs diff --git a/src/libcretonne/legalizer.rs b/lib/cretonne/src/legalizer.rs similarity index 100% rename from src/libcretonne/legalizer.rs rename to lib/cretonne/src/legalizer.rs diff --git a/src/libcretonne/lib.rs b/lib/cretonne/src/lib.rs similarity index 100% rename from src/libcretonne/lib.rs rename to lib/cretonne/src/lib.rs diff --git a/src/libcretonne/predicates.rs b/lib/cretonne/src/predicates.rs similarity index 100% rename from src/libcretonne/predicates.rs rename to lib/cretonne/src/predicates.rs diff --git a/src/libcretonne/settings.rs b/lib/cretonne/src/settings.rs similarity index 100% rename from src/libcretonne/settings.rs rename to lib/cretonne/src/settings.rs diff --git a/src/libcretonne/verifier.rs b/lib/cretonne/src/verifier.rs similarity index 100% rename from src/libcretonne/verifier.rs rename to lib/cretonne/src/verifier.rs diff --git a/src/libcretonne/write.rs b/lib/cretonne/src/write.rs similarity index 100% rename from src/libcretonne/write.rs rename to lib/cretonne/src/write.rs diff --git a/src/libfilecheck/Cargo.toml b/lib/filecheck/Cargo.toml similarity index 52% rename from src/libfilecheck/Cargo.toml rename to lib/filecheck/Cargo.toml index 4f85778d92..f7cfa926e8 100644 --- a/src/libfilecheck/Cargo.toml +++ b/lib/filecheck/Cargo.toml @@ -2,11 +2,13 @@ authors = ["The Cretonne Project Developers"] name = "filecheck" version = "0.0.0" +description = "Library for matching test outputs against filecheck directives" +license = "Apache-2.0" +repository = "https://github.com/stoklund/cretonne" publish = false [lib] name = "filecheck" -path = "lib.rs" [dependencies] regex = "0.1.71" diff --git a/src/libfilecheck/checker.rs b/lib/filecheck/src/checker.rs similarity index 100% rename from src/libfilecheck/checker.rs rename to lib/filecheck/src/checker.rs diff --git a/src/libfilecheck/error.rs b/lib/filecheck/src/error.rs similarity index 100% rename from src/libfilecheck/error.rs rename to lib/filecheck/src/error.rs diff --git a/src/libfilecheck/explain.rs b/lib/filecheck/src/explain.rs similarity index 100% rename from src/libfilecheck/explain.rs rename to lib/filecheck/src/explain.rs diff --git a/src/libfilecheck/lib.rs b/lib/filecheck/src/lib.rs similarity index 100% rename from src/libfilecheck/lib.rs rename to lib/filecheck/src/lib.rs diff --git a/src/libfilecheck/pattern.rs b/lib/filecheck/src/pattern.rs similarity index 100% rename from src/libfilecheck/pattern.rs rename to lib/filecheck/src/pattern.rs diff --git a/src/libfilecheck/tests/basic.rs b/lib/filecheck/src/tests/basic.rs similarity index 100% rename from src/libfilecheck/tests/basic.rs rename to lib/filecheck/src/tests/basic.rs diff --git a/src/libfilecheck/variable.rs b/lib/filecheck/src/variable.rs similarity index 100% rename from src/libfilecheck/variable.rs rename to lib/filecheck/src/variable.rs diff --git a/lib/reader/Cargo.toml b/lib/reader/Cargo.toml new file mode 100644 index 0000000000..030d20ff9f --- /dev/null +++ b/lib/reader/Cargo.toml @@ -0,0 +1,15 @@ +[package] +authors = ["The Cretonne Project Developers"] +name = "cretonne-reader" +version = "0.0.0" +description = "Cretonne textual IL reader" +license = "Apache-2.0" +documentation = "https://cretonne.readthedocs.io/" +repository = "https://github.com/stoklund/cretonne" +publish = false + +[lib] +name = "cton_reader" + +[dependencies] +cretonne = { path = "../cretonne" } diff --git a/src/libreader/error.rs b/lib/reader/src/error.rs similarity index 100% rename from src/libreader/error.rs rename to lib/reader/src/error.rs diff --git a/src/libreader/isaspec.rs b/lib/reader/src/isaspec.rs similarity index 100% rename from src/libreader/isaspec.rs rename to lib/reader/src/isaspec.rs diff --git a/src/libreader/lexer.rs b/lib/reader/src/lexer.rs similarity index 100% rename from src/libreader/lexer.rs rename to lib/reader/src/lexer.rs diff --git a/src/libreader/lib.rs b/lib/reader/src/lib.rs similarity index 100% rename from src/libreader/lib.rs rename to lib/reader/src/lib.rs diff --git a/src/libreader/parser.rs b/lib/reader/src/parser.rs similarity index 100% rename from src/libreader/parser.rs rename to lib/reader/src/parser.rs diff --git a/src/libreader/sourcemap.rs b/lib/reader/src/sourcemap.rs similarity index 100% rename from src/libreader/sourcemap.rs rename to lib/reader/src/sourcemap.rs diff --git a/src/libreader/testcommand.rs b/lib/reader/src/testcommand.rs similarity index 100% rename from src/libreader/testcommand.rs rename to lib/reader/src/testcommand.rs diff --git a/src/libreader/testfile.rs b/lib/reader/src/testfile.rs similarity index 100% rename from src/libreader/testfile.rs rename to lib/reader/src/testfile.rs diff --git a/src/libcretonne/build.rs b/src/libcretonne/build.rs deleted file mode 100644 index 2cb13ac959..0000000000 --- a/src/libcretonne/build.rs +++ /dev/null @@ -1,46 +0,0 @@ - -// Build script. -// -// This program is run by Cargo when building libcretonne. It is used to generate Rust code from -// the language definitions in the meta directory. -// -// Environment: -// -// OUT_DIR -// Directory where generated files should be placed. -// -// The build script expects to be run from the directory where this build.rs file lives. The -// current directory is used to find the sources. - - -use std::env; -use std::process; - -fn main() { - let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); - - println!("Build script generating files in {}", out_dir); - - let mut cur_dir = env::current_dir().expect("Can't access current working directory"); - - // We're in src/libcretonne. Find the top-level directory. - assert!(cur_dir.pop(), "No parent 'src' directory"); - assert!(cur_dir.pop(), "No top-level directory"); - let top_dir = cur_dir.as_path(); - - // Scripts are in $top_dir/meta. - let meta_dir = top_dir.join("lib/cretonne/meta"); - let build_script = meta_dir.join("build.py"); - - // Launch build script with Python. We'll just find python in the path. - let status = process::Command::new("python") - .current_dir(top_dir) - .arg(build_script) - .arg("--out-dir") - .arg(out_dir) - .status() - .expect("Failed to launch second-level build script"); - if !status.success() { - process::exit(status.code().unwrap()); - } -} diff --git a/src/libreader/Cargo.toml b/src/libreader/Cargo.toml deleted file mode 100644 index 478ee3b586..0000000000 --- a/src/libreader/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -authors = ["The Cretonne Project Developers"] -name = "cretonne-reader" -version = "0.0.0" -publish = false - -[lib] -name = "cton_reader" -path = "lib.rs" - -[dependencies] -cretonne = { path = "../libcretonne" } diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml index 3a1a8979a7..930256d8bb 100644 --- a/src/tools/Cargo.toml +++ b/src/tools/Cargo.toml @@ -10,9 +10,9 @@ name = "cton-util" path = "main.rs" [dependencies] -cretonne = { path = "../libcretonne" } -cretonne-reader = { path = "../libreader" } -filecheck = { path = "../libfilecheck" } +cretonne = { path = "../../lib/cretonne" } +cretonne-reader = { path = "../../lib/reader" } +filecheck = { path = "../../lib/filecheck" } docopt = "0.6.80" rustc-serialize = "0.3.19" num_cpus = "1.1.0" From a8a79df6206d928c3c91c0b268b70c4bd0dde2cf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Oct 2016 14:57:42 -0700 Subject: [PATCH 361/968] Promote the src/tools crate to the top-level workspace. The 'src' and 'tests' top-level directories now contain tools sources and integration tests for any of the library crates. --- Cargo.toml | 24 +++++++++++++++++++-- src/format-all.sh => format-all.sh | 0 src/{tools => }/cat.rs | 0 src/{tools/main.rs => cton-util.rs} | 0 src/{tools => }/filetest/concurrent.rs | 0 src/{tools => }/filetest/domtree.rs | 0 src/{tools => }/filetest/legalizer.rs | 0 src/{tools => }/filetest/mod.rs | 0 src/{tools => }/filetest/runner.rs | 0 src/{tools => }/filetest/runone.rs | 0 src/{tools => }/filetest/subtest.rs | 0 src/{tools => }/filetest/verifier.rs | 0 src/{tools => }/print_cfg.rs | 0 src/{tools => }/rsfilecheck.rs | 0 src/tools/Cargo.toml | 18 ---------------- src/{tools => }/utils.rs | 0 test-all.sh | 6 +++--- {src/tools/tests => tests}/cfg_traversal.rs | 0 18 files changed, 25 insertions(+), 23 deletions(-) rename src/format-all.sh => format-all.sh (100%) rename src/{tools => }/cat.rs (100%) rename src/{tools/main.rs => cton-util.rs} (100%) rename src/{tools => }/filetest/concurrent.rs (100%) rename src/{tools => }/filetest/domtree.rs (100%) rename src/{tools => }/filetest/legalizer.rs (100%) rename src/{tools => }/filetest/mod.rs (100%) rename src/{tools => }/filetest/runner.rs (100%) rename src/{tools => }/filetest/runone.rs (100%) rename src/{tools => }/filetest/subtest.rs (100%) rename src/{tools => }/filetest/verifier.rs (100%) rename src/{tools => }/print_cfg.rs (100%) rename src/{tools => }/rsfilecheck.rs (100%) delete mode 100644 src/tools/Cargo.toml rename src/{tools => }/utils.rs (100%) rename {src/tools/tests => tests}/cfg_traversal.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 63597ad428..d0da28f918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,23 @@ -# Phantom workspace manifest for all Cretonne crates. +[package] +name = "cretonne-tools" +authors = ["The Cretonne Project Developers"] +version = "0.0.0" +description = "Binaries for testing the Cretonne library" +license = "Apache-2.0" +documentation = "https://cretonne.readthedocs.io/" +repository = "https://github.com/stoklund/cretonne" +publish = false + +[[bin]] +name = "cton-util" +path = "src/cton-util.rs" + +[dependencies] +cretonne = { path = "lib/cretonne" } +cretonne-reader = { path = "lib/reader" } +filecheck = { path = "lib/filecheck" } +docopt = "0.6.86" +rustc-serialize = "0.3.19" +num_cpus = "1.1.0" + [workspace] -members = ["src/tools"] diff --git a/src/format-all.sh b/format-all.sh similarity index 100% rename from src/format-all.sh rename to format-all.sh diff --git a/src/tools/cat.rs b/src/cat.rs similarity index 100% rename from src/tools/cat.rs rename to src/cat.rs diff --git a/src/tools/main.rs b/src/cton-util.rs similarity index 100% rename from src/tools/main.rs rename to src/cton-util.rs diff --git a/src/tools/filetest/concurrent.rs b/src/filetest/concurrent.rs similarity index 100% rename from src/tools/filetest/concurrent.rs rename to src/filetest/concurrent.rs diff --git a/src/tools/filetest/domtree.rs b/src/filetest/domtree.rs similarity index 100% rename from src/tools/filetest/domtree.rs rename to src/filetest/domtree.rs diff --git a/src/tools/filetest/legalizer.rs b/src/filetest/legalizer.rs similarity index 100% rename from src/tools/filetest/legalizer.rs rename to src/filetest/legalizer.rs diff --git a/src/tools/filetest/mod.rs b/src/filetest/mod.rs similarity index 100% rename from src/tools/filetest/mod.rs rename to src/filetest/mod.rs diff --git a/src/tools/filetest/runner.rs b/src/filetest/runner.rs similarity index 100% rename from src/tools/filetest/runner.rs rename to src/filetest/runner.rs diff --git a/src/tools/filetest/runone.rs b/src/filetest/runone.rs similarity index 100% rename from src/tools/filetest/runone.rs rename to src/filetest/runone.rs diff --git a/src/tools/filetest/subtest.rs b/src/filetest/subtest.rs similarity index 100% rename from src/tools/filetest/subtest.rs rename to src/filetest/subtest.rs diff --git a/src/tools/filetest/verifier.rs b/src/filetest/verifier.rs similarity index 100% rename from src/tools/filetest/verifier.rs rename to src/filetest/verifier.rs diff --git a/src/tools/print_cfg.rs b/src/print_cfg.rs similarity index 100% rename from src/tools/print_cfg.rs rename to src/print_cfg.rs diff --git a/src/tools/rsfilecheck.rs b/src/rsfilecheck.rs similarity index 100% rename from src/tools/rsfilecheck.rs rename to src/rsfilecheck.rs diff --git a/src/tools/Cargo.toml b/src/tools/Cargo.toml deleted file mode 100644 index 930256d8bb..0000000000 --- a/src/tools/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -authors = ["The Cretonne Project Developers"] -name = "cretonne-tools" -version = "0.0.0" -description = "Binaries for testing the Cretonne library" -publish = false - -[[bin]] -name = "cton-util" -path = "main.rs" - -[dependencies] -cretonne = { path = "../../lib/cretonne" } -cretonne-reader = { path = "../../lib/reader" } -filecheck = { path = "../../lib/filecheck" } -docopt = "0.6.80" -rustc-serialize = "0.3.19" -num_cpus = "1.1.0" diff --git a/src/tools/utils.rs b/src/utils.rs similarity index 100% rename from src/tools/utils.rs rename to src/utils.rs diff --git a/test-all.sh b/test-all.sh index 843b1e82be..0ad58aa114 100755 --- a/test-all.sh +++ b/test-all.sh @@ -34,14 +34,14 @@ RUSTFMT_VERSION="0.6.2" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" - $topdir/src/format-all.sh --write-mode=diff + $topdir/format-all.sh --write-mode=diff else echo "Please install rustfmt v$RUSTFMT_VERSION to verify formatting." echo "If a newer version of rustfmt is available, update this script." fi PKGS="cretonne cretonne-reader cretonne-tools filecheck" -cd "$topdir/src/tools" +cd "$topdir" for PKG in $PKGS do banner "Rust $PKG unit tests" @@ -49,7 +49,7 @@ do done # Build cton-util for parser testing. -cd "$topdir/src/tools" +cd "$topdir" banner "Rust documentation" echo "open $topdir/target/doc/cretonne/index.html" cargo doc diff --git a/src/tools/tests/cfg_traversal.rs b/tests/cfg_traversal.rs similarity index 100% rename from src/tools/tests/cfg_traversal.rs rename to tests/cfg_traversal.rs From 45cf655ce45d0e33d95717a25d109013c4c4a691 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 09:31:19 -0700 Subject: [PATCH 362/968] Generalize def_inst() to def_entity(). Use this source map method for assigning a location to any entity whose source number is not exposed. This could be - Instructions. - Signatures defined implicitly by function decls. These entities only appear in the location map, not the entity number maps. --- lib/reader/src/parser.rs | 2 +- lib/reader/src/sourcemap.rs | 28 ++++++++++------------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 5c86e4e21a..89786ed2b3 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -837,7 +837,7 @@ impl<'a> Parser<'a> { let inst = ctx.function.dfg.make_inst(inst_data); let num_results = ctx.function.dfg.make_inst_results(inst, ctrl_typevar); ctx.function.layout.append_inst(inst, ebb); - ctx.map.def_inst(inst, &opcode_loc).expect("duplicate inst references created"); + ctx.map.def_entity(inst.into(), &opcode_loc).expect("duplicate inst references created"); if results.len() != num_results { return err!(self.loc, diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 2bff8e4b81..5808670fa4 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -8,7 +8,7 @@ //! clients. use std::collections::HashMap; -use cretonne::ir::{StackSlot, JumpTable, Ebb, Value, Inst}; +use cretonne::ir::{StackSlot, JumpTable, Ebb, Value}; use cretonne::ir::entities::AnyEntity; use error::{Result, Location}; use lexer::split_entity_name; @@ -128,9 +128,9 @@ pub trait MutableSourceMap { fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()>; fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()>; - /// Define an instruction. Since instruction numbers never appear in source, only the location - /// is recorded. - fn def_inst(&mut self, entity: Inst, loc: &Location) -> Result<()>; + /// Define an entity without an associated source number. This can be used for instructions + /// whose numbers never appear in source, or implicitly defined signatures. + fn def_entity(&mut self, entity: AnyEntity, loc: &Location) -> Result<()>; } impl MutableSourceMap for SourceMap { @@ -147,45 +147,37 @@ impl MutableSourceMap for SourceMap { fn def_value(&mut self, src: Value, entity: Value, loc: &Location) -> Result<()> { if self.values.insert(src, entity).is_some() { err!(loc, "duplicate value: {}", src) - } else if self.locations.insert(entity.into(), loc.clone()).is_some() { - err!(loc, "duplicate entity: {}", entity) } else { - Ok(()) + self.def_entity(entity.into(), loc) } } fn def_ebb(&mut self, src: Ebb, entity: Ebb, loc: &Location) -> Result<()> { if self.ebbs.insert(src, entity).is_some() { err!(loc, "duplicate EBB: {}", src) - } else if self.locations.insert(entity.into(), loc.clone()).is_some() { - err!(loc, "duplicate entity: {}", entity) } else { - Ok(()) + self.def_entity(entity.into(), loc) } } fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()> { if self.stack_slots.insert(src_num, entity).is_some() { err!(loc, "duplicate stack slot: ss{}", src_num) - } else if self.locations.insert(entity.into(), loc.clone()).is_some() { - err!(loc, "duplicate entity: {}", entity) } else { - Ok(()) + self.def_entity(entity.into(), loc) } } fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()> { if self.jump_tables.insert(src_num, entity).is_some() { err!(loc, "duplicate jump table: jt{}", src_num) - } else if self.locations.insert(entity.into(), loc.clone()).is_some() { - err!(loc, "duplicate entity: {}", entity) } else { - Ok(()) + self.def_entity(entity.into(), loc) } } - fn def_inst(&mut self, entity: Inst, loc: &Location) -> Result<()> { - if self.locations.insert(entity.into(), loc.clone()).is_some() { + fn def_entity(&mut self, entity: AnyEntity, loc: &Location) -> Result<()> { + if self.locations.insert(entity, loc.clone()).is_some() { err!(loc, "duplicate entity: {}", entity) } else { Ok(()) From 8055ac681ca13498e22078723e45e0d6c46335d0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 09:43:20 -0700 Subject: [PATCH 363/968] Track signatures and function references in the source map. --- lib/reader/src/sourcemap.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 5808670fa4..26d874817d 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -8,7 +8,7 @@ //! clients. use std::collections::HashMap; -use cretonne::ir::{StackSlot, JumpTable, Ebb, Value}; +use cretonne::ir::{StackSlot, JumpTable, Ebb, Value, SigRef, FuncRef}; use cretonne::ir::entities::AnyEntity; use error::{Result, Location}; use lexer::split_entity_name; @@ -19,6 +19,8 @@ pub struct SourceMap { values: HashMap, // vNN, vxNN ebbs: HashMap, // ebbNN stack_slots: HashMap, // ssNN + signatures: HashMap, // sigNN + functions: HashMap, // fnNN jump_tables: HashMap, // jtNN // Store locations for entities, including instructions. @@ -42,6 +44,16 @@ impl SourceMap { self.stack_slots.get(&src_num).cloned() } + /// Look up a signature entity by its source number. + pub fn get_sig(&self, src_num: u32) -> Option { + self.signatures.get(&src_num).cloned() + } + + /// Look up a function entity by its source number. + pub fn get_fn(&self, src_num: u32) -> Option { + self.functions.get(&src_num).cloned() + } + /// Look up a jump table entity by its source number. pub fn get_jt(&self, src_num: u32) -> Option { self.jump_tables.get(&src_num).cloned() @@ -64,6 +76,8 @@ impl SourceMap { } "ebb" => Ebb::with_number(num).and_then(|e| self.get_ebb(e)).map(AnyEntity::Ebb), "ss" => self.get_ss(num).map(AnyEntity::StackSlot), + "sig" => self.get_sig(num).map(AnyEntity::SigRef), + "fn" => self.get_fn(num).map(AnyEntity::FuncRef), "jt" => self.get_jt(num).map(AnyEntity::JumpTable), _ => None, } @@ -126,6 +140,8 @@ pub trait MutableSourceMap { fn def_value(&mut self, src: Value, entity: Value, loc: &Location) -> Result<()>; fn def_ebb(&mut self, src: Ebb, entity: Ebb, loc: &Location) -> Result<()>; fn def_ss(&mut self, src_num: u32, entity: StackSlot, loc: &Location) -> Result<()>; + fn def_sig(&mut self, src_num: u32, entity: SigRef, loc: &Location) -> Result<()>; + fn def_fn(&mut self, src_num: u32, entity: FuncRef, loc: &Location) -> Result<()>; fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()>; /// Define an entity without an associated source number. This can be used for instructions @@ -139,6 +155,8 @@ impl MutableSourceMap for SourceMap { values: HashMap::new(), ebbs: HashMap::new(), stack_slots: HashMap::new(), + signatures: HashMap::new(), + functions: HashMap::new(), jump_tables: HashMap::new(), locations: HashMap::new(), } @@ -168,6 +186,22 @@ impl MutableSourceMap for SourceMap { } } + fn def_sig(&mut self, src_num: u32, entity: SigRef, loc: &Location) -> Result<()> { + if self.signatures.insert(src_num, entity).is_some() { + err!(loc, "duplicate signature: sig{}", src_num) + } else { + self.def_entity(entity.into(), loc) + } + } + + fn def_fn(&mut self, src_num: u32, entity: FuncRef, loc: &Location) -> Result<()> { + if self.functions.insert(src_num, entity).is_some() { + err!(loc, "duplicate function: fn{}", src_num) + } else { + self.def_entity(entity.into(), loc) + } + } + fn def_jt(&mut self, src_num: u32, entity: JumpTable, loc: &Location) -> Result<()> { if self.jump_tables.insert(src_num, entity).is_some() { err!(loc, "duplicate jump table: jt{}", src_num) From e4e1c30f872fb0a73495ee6a3ab75f7d7b276513 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 09:48:05 -0700 Subject: [PATCH 364/968] Add call and call_indirect instructions. Add a new IndirectCall instruction format which has a value callee as well as the call arguments. Define call and call_indirect instructions. --- docs/langref.rst | 22 ++++--------------- lib/cretonne/meta/cretonne/__init__.py | 5 ++++- lib/cretonne/meta/cretonne/base.py | 30 ++++++++++++++++++++++++++ lib/cretonne/meta/cretonne/formats.py | 5 ++++- lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/instructions.rs | 21 +++++++++++++----- lib/cretonne/src/write.rs | 5 ++++- lib/reader/src/parser.rs | 14 +++++++++--- 8 files changed, 74 insertions(+), 30 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index b2ced79300..8acc2b4ef2 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -373,22 +373,15 @@ platform. When calling other Cretonne functions, the flags are not necessary. Functions that are called directly must be declared in the :term:`function preamble`: -.. inst:: F = function NAME signature +.. inst:: FN = function NAME signature Declare a function so it can be called directly. :arg NAME: Name of the function, passed to the linker for resolution. :arg signature: Function signature. See below. - :result F: A function identifier that can be used with :inst:`call`. - -.. inst:: a, b, ... = call F(args...) - - Direct function call. - - :arg F: Function identifier to call, declared by :inst:`function`. - :arg args...: Function arguments matching the signature of F. - :result a,b,...: Return values matching the signature of F. + :result FN: A function identifier that can be used with :inst:`call`. +.. autoinst:: call .. autoinst:: x_return This simple example illustrates direct function calls and signatures:: @@ -414,14 +407,7 @@ Indirect function calls use a signature declared in the preamble. :arg signature: Function signature. See :token:`signature`. :result SIG: A signature identifier. -.. inst:: a, b, ... = call_indirect SIG, x(args...) - - Indirect function call. - - :arg SIG: A function signature identifier declared with :inst:`signature`. - :arg iPtr x: The address of the function to call. - :arg args...: Function arguments matching SIG. - :result a,b,...: Return values matching SIG. +.. autoinst:: call_indirect .. todo:: Define safe indirect function calls. diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index a6cef5397b..a38126f98f 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -712,7 +712,10 @@ class InstructionFormat(object): :py:class:`Instruction` arguments of the same name, except they must be tuples of :py:`Operand` objects. """ - multiple_results = len(outs) > 1 + if len(outs) == 1: + multiple_results = outs[0].kind == variable_args + else: + multiple_results = len(outs) > 1 sig = (multiple_results,) + tuple(op.kind for op in ins) if sig not in InstructionFormat._registry: raise RuntimeError( diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index b665cf5d71..f507526626 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -15,6 +15,7 @@ instructions = InstructionGroup("base", "Shared base instruction set") Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) iB = TypeVar('iB', 'A scalar integer type', ints=True) +iPtr = TypeVar('iB', 'An integer address type', ints=(32, 64)) Testable = TypeVar( 'Testable', 'A scalar boolean or integer type', ints=True, bools=True) @@ -109,6 +110,35 @@ x_return = Instruction( """, ins=rvals) +FN = Operand( + 'FN', + entities.func_ref, + doc='function to call, declared by :inst:`function`') +args = Operand('args', variable_args, doc='call arguments') + +call = Instruction( + 'call', r""" + Direct function call. + + Call a function which has been declared in the preamble. The argument + types must match the function's signature. + """, + ins=(FN, args), + outs=rvals) + +SIG = Operand('SIG', entities.sig_ref, doc='function signature') +callee = Operand('callee', iPtr, doc='address of function to call') + +call_indirect = Instruction( + 'call_indirect', r""" + Indirect function call. + + Call the function pointed to by `callee` with the given arguments. The + called function must match the soecified signature. + """, + ins=(SIG, callee, args), + outs=rvals) + # # Materializing constants. # diff --git a/lib/cretonne/meta/cretonne/formats.py b/lib/cretonne/meta/cretonne/formats.py index 8e5d4e25cb..cb8aed3f98 100644 --- a/lib/cretonne/meta/cretonne/formats.py +++ b/lib/cretonne/meta/cretonne/formats.py @@ -8,7 +8,7 @@ in this module. from __future__ import absolute_import from . import InstructionFormat, value, variable_args from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc -from .entities import ebb, func_ref, jump_table +from .entities import ebb, sig_ref, func_ref, jump_table Nullary = InstructionFormat() @@ -47,6 +47,9 @@ BranchTable = InstructionFormat(value, jump_table) Call = InstructionFormat( func_ref, variable_args, multiple_results=True, boxed_storage=True) +IndirectCall = InstructionFormat( + sig_ref, value, variable_args, + multiple_results=True, boxed_storage=True) Return = InstructionFormat(variable_args, boxed_storage=True) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index e31be0f2ca..f8510bb7f4 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,7 +5,7 @@ use ir::{types, instructions}; use ir::{InstructionData, DataFlowGraph, Cursor}; -use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, FuncRef}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, SigRef, FuncRef}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::{IntCC, FloatCC}; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 37b1106d36..f7fb6f3d94 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -10,7 +10,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use ir::{Value, Type, Ebb, JumpTable, FuncRef}; +use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::*; use ir::types; @@ -207,6 +207,12 @@ pub enum InstructionData { second_result: Value, data: Box, }, + IndirectCall { + opcode: Opcode, + ty: Type, + second_result: Value, + data: Box, + }, Return { opcode: Opcode, ty: Type, @@ -342,10 +348,15 @@ pub struct CallData { pub varargs: VariableArgs, } -impl Display for CallData { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "TBD({})", self.varargs) - } +/// Payload of an indirect call instruction. +#[derive(Clone, Debug)] +pub struct IndirectCallData { + /// Callee function. + pub arg: Value, + pub sig_ref: SigRef, + + /// Dynamically sized array containing call argument values. + pub varargs: VariableArgs, } /// Payload of a return instruction. diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 3ef72d7178..dfae305933 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -199,7 +199,10 @@ fn write_instruction(w: &mut Write, Jump { ref data, .. } => writeln!(w, " {}", data), Branch { ref data, .. } => writeln!(w, " {}", data), BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), - Call { ref data, .. } => writeln!(w, " {}", data), + Call { ref data, .. } => writeln!(w, " {}({})", data.func_ref, data.varargs), + IndirectCall { ref data, .. } => { + writeln!(w, " {}, {}({})", data.sig_ref, data.arg, data.varargs) + } Return { ref data, .. } => { if data.varargs.is_empty() { writeln!(w, "") diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 89786ed2b3..186fd6df1e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -158,6 +158,11 @@ impl Context { try!(self.map.rewrite_values(&mut data.varargs, loc)); } + InstructionData::IndirectCall { ref mut data, .. } => { + try!(self.map.rewrite_value(&mut data.arg, loc)); + try!(self.map.rewrite_values(&mut data.varargs, loc)); + } + InstructionData::Return { ref mut data, .. } => { try!(self.map.rewrite_values(&mut data.varargs, loc)); } @@ -1171,6 +1176,12 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } + InstructionFormat::Call => { + unimplemented!(); + } + InstructionFormat::IndirectCall => { + unimplemented!(); + } InstructionFormat::Return => { let args = try!(self.parse_value_list()); InstructionData::Return { @@ -1190,9 +1201,6 @@ impl<'a> Parser<'a> { table: table, } } - InstructionFormat::Call => { - unimplemented!(); - } }) } } From c961e89fdc1554be81b8a54674c28fca2fb1b4c2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 10:07:44 -0700 Subject: [PATCH 365/968] Add signatures and ext_funcs tables to DataFlowGraph. These two tables are used to keep track of type signatures of function calls as well as external function references used in direct function calls. Also add an ExtFuncData struct representing an external function that can be called directly. --- lib/cretonne/src/ir/dfg.rs | 14 +++++++++++++- lib/cretonne/src/ir/extfunc.rs | 17 ++++++++++++++++- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/write.rs | 11 +++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 35bb9c8f7e..b913f505e6 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,8 +1,9 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use ir::{Ebb, Inst, Value, Type}; +use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef}; use ir::entities::{NO_VALUE, ExpandedValue}; use ir::instructions::InstructionData; +use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; use std::ops::{Index, IndexMut}; @@ -33,10 +34,19 @@ pub struct DataFlowGraph { /// This is implemented directly with a `Vec` rather than an `EntityMap` because /// the Value entity references can refer to two things -- an instruction or an extended value. extended_values: Vec, + + /// Function signature table. These signatures are referenced by indirect call instructions as + /// well as the external function references. + pub signatures: EntityMap, + + /// External function references. These are functions that can be called directly. + pub ext_funcs: EntityMap, } impl PrimaryEntityData for InstructionData {} impl PrimaryEntityData for EbbData {} +impl PrimaryEntityData for Signature {} +impl PrimaryEntityData for ExtFuncData {} impl DataFlowGraph { /// Create a new empty `DataFlowGraph`. @@ -45,6 +55,8 @@ impl DataFlowGraph { insts: EntityMap::new(), ebbs: EntityMap::new(), extended_values: Vec::new(), + signatures: EntityMap::new(), + ext_funcs: EntityMap::new(), } } diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 78b723398e..6f89d5d307 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -6,7 +6,7 @@ //! This module declares the data types used to represent external functions and call signatures. use std::fmt::{self, Display, Formatter}; -use ir::Type; +use ir::{Type, FunctionName, SigRef}; /// Function signature. /// @@ -104,6 +104,21 @@ pub enum ArgumentExtension { Sext, } +/// An external function. +/// +/// Information about a function that can be called directly with a direct `call` instruction. +#[derive(Clone, Debug)] +pub struct ExtFuncData { + pub name: FunctionName, + pub signature: SigRef, +} + +impl Display for ExtFuncData { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} {}", self.signature, self.name) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index bbbc6d26ab..5056d9b29e 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -15,7 +15,7 @@ mod extfunc; mod builder; pub use ir::funcname::FunctionName; -pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension}; +pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs}; diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index dfae305933..ac487c6f37 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -43,6 +43,17 @@ fn write_preamble(w: &mut Write, func: &Function) -> result::Result try!(writeln!(w, " {} = {}", ss, func.stack_slots[ss])); } + // Write out all signatures before functions since function decls can refer to signatures. + for sig in func.dfg.signatures.keys() { + any = true; + try!(writeln!(w, " {} = signature{}", sig, func.dfg.signatures[sig])); + } + + for fnref in func.dfg.ext_funcs.keys() { + any = true; + try!(writeln!(w, " {} = {}", fnref, func.dfg.ext_funcs[fnref])); + } + for jt in func.jump_tables.keys() { any = true; try!(writeln!(w, " {} = {}", jt, func.jump_tables[jt])); From 7d7a7875d54231819df3cd8d4e0ec45ab66da6a0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 12:31:37 -0700 Subject: [PATCH 366/968] Add result values to call instructions too. The make_inst_results() method now understands direct and indirect calls, and can allocate result values matching the return types of the function call. --- lib/cretonne/src/ir/dfg.rs | 52 +++++++++++++++++++++++------ lib/cretonne/src/ir/instructions.rs | 28 ++++++++++++++++ 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index b913f505e6..35b0af4dc6 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -2,7 +2,7 @@ use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef}; use ir::entities::{NO_VALUE, ExpandedValue}; -use ir::instructions::InstructionData; +use ir::instructions::{InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; @@ -213,6 +213,7 @@ impl DataFlowGraph { pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { let constraints = self.insts[inst].opcode().constraints(); let fixed_results = constraints.fixed_results(); + let mut total_results = fixed_results; // Additional values form a linked list starting from the second result value. Generate // the list backwards so we don't have to modify value table entries in place. (This @@ -220,20 +221,41 @@ impl DataFlowGraph { // choice, but since it is only visible in extremely rare instructions with 3+ results, // we don't care). let mut head = NO_VALUE; - let mut first_type = Type::default(); + let mut first_type = None; + let mut rev_num = 1; - // TBD: Function call return values for direct and indirect function calls. + // Get the call signature if this is a function call. + if let Some(sig) = self.call_signature(inst) { + // Create result values corresponding to the call return types. + let var_results = self.signatures[sig].return_types.len(); + total_results += var_results; - if fixed_results > 0 { - for res_idx in (1..fixed_results).rev() { + for res_idx in (0..var_results).rev() { + if let Some(ty) = first_type { + head = self.make_value(ValueData::Inst { + ty: ty, + num: (total_results - rev_num) as u16, + inst: inst, + next: head, + }); + rev_num += 1; + } + first_type = Some(self.signatures[sig].return_types[res_idx].value_type); + } + } + + // Then the fixed results whic will appear at the front of the list. + for res_idx in (0..fixed_results).rev() { + if let Some(ty) = first_type { head = self.make_value(ValueData::Inst { - ty: constraints.result_type(res_idx, ctrl_typevar), - num: res_idx as u16, + ty: ty, + num: (total_results - rev_num) as u16, inst: inst, next: head, }); + rev_num += 1; } - first_type = constraints.result_type(0, ctrl_typevar); + first_type = Some(constraints.result_type(res_idx, ctrl_typevar)); } // Update the second_result pointer in `inst`. @@ -242,9 +264,9 @@ impl DataFlowGraph { .second_result_mut() .expect("instruction format doesn't allow multiple results") = head; } - *self.insts[inst].first_type_mut() = first_type; + *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); - fixed_results + total_results } /// Get the first result of an instruction. @@ -265,6 +287,16 @@ impl DataFlowGraph { }, } } + + /// Get the call signature of a direct or indirect call instruction. + /// Returns `None` if `inst` is not a call instruction. + pub fn call_signature(&self, inst: Inst) -> Option { + match self.insts[inst].analyze_call() { + CallInfo::NotACall => None, + CallInfo::Direct(f, _) => Some(self.ext_funcs[f].signature), + CallInfo::Indirect(s, _) => Some(s), + } + } } /// Allow immutable access to instructions via indexing. diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index f7fb6f3d94..daab56d09c 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -388,6 +388,21 @@ impl InstructionData { } } + /// Return information about a call instruction. + /// + /// Any instruction that can call another function reveals its call signature here. + pub fn analyze_call<'a>(&'a self) -> CallInfo<'a> { + match self { + &InstructionData::Call { ref data, .. } => { + CallInfo::Direct(data.func_ref, &data.varargs) + } + &InstructionData::IndirectCall { ref data, .. } => { + CallInfo::Indirect(data.sig_ref, &data.varargs) + } + _ => CallInfo::NotACall, + } + } + /// Return true if an instruction is terminating, or false otherwise. pub fn is_terminating<'a>(&'a self) -> bool { match self { @@ -413,6 +428,19 @@ pub enum BranchInfo<'a> { Table(JumpTable), } +/// Information about call instructions. +pub enum CallInfo<'a> { + /// This is not a call instruction. + NotACall, + + /// This is a direct call to an external function declared in the preamble. See + /// `DataFlowGraph.ext_funcs`. + Direct(FuncRef, &'a [Value]), + + /// This is an indirect call with the specified signature. See `DataFlowGraph.signatures`. + Indirect(SigRef, &'a [Value]), +} + /// Value type constraints for a given opcode. /// /// The `InstructionFormat` determines the constraints on most operands, but `Value` operands and From d85225c537d456f3d134d35651b9ca9fd1a00f3c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 10:58:59 -0700 Subject: [PATCH 367/968] Parse signature and function declarations. Also add support for parsing call and call_indirect instructions. --- filetests/parser/call.cton | 46 ++++++++++++ lib/reader/src/parser.rs | 145 ++++++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 4 deletions(-) diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton index 71c7c1efb0..72a61b6e21 100644 --- a/filetests/parser/call.cton +++ b/filetests/parser/call.cton @@ -22,3 +22,49 @@ ebb1: ; nextln: v1 = f32const 0.0 ; nextln: return v0, v1 ; nextln: } + +function signatures() { + sig10 = signature() + sig11 = signature(i32, f64) -> i32, b1 + fn5 = sig11 foo + fn8 = function bar(i32) -> b1 +} +; sameln: function signatures() { +; nextln: $sig10 = signature() +; nextln: $sig11 = signature(i32, f64) -> i32, b1 +; nextln: sig2 = signature(i32) -> b1 +; nextln: $fn5 = $sig11 foo +; nextln: $fn8 = sig2 bar +; nextln: } + +function direct() { + fn0 = function none() + fn1 = function one() -> i32 + fn2 = function two() -> i32, f32 + +ebb0: + call fn0() + v1 = call fn1() + v2, v3 = call fn2() + return +} +; check: call $fn0() +; check: $v1 = call $fn1() +; check: $v2, $v3 = call $fn2() +; check: return + +function indirect(i64) { + sig0 = signature(i64) + sig1 = signature() -> i32 + sig2 = signature() -> i32, f32 + +ebb0(v0: i64): + v1 = call_indirect sig1, v0() + call_indirect sig0, v1(v0) + v3, v4 = call_indirect sig2, v1() + return +} +; check: $v1 = call_indirect $sig1, $v0() +; check: call_indirect $sig0, $v1($v0) +; check: $v3, $v4 = call_indirect $sig2, $v1() +; check: return diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 186fd6df1e..7b16abe272 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -9,12 +9,14 @@ use std::str::FromStr; use std::u32; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, - JumpTableData, Signature, ArgumentType, ArgumentExtension}; + JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, + FuncRef}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, - TernaryOverflowData, JumpData, BranchData, ReturnData}; + TernaryOverflowData, JumpData, BranchData, CallData, + IndirectCallData, ReturnData}; use cretonne::isa; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -84,6 +86,32 @@ impl Context { self.map.def_ss(number, self.function.stack_slots.push(data), loc) } + // Allocate a new signature and add a mapping number -> SigRef. + fn add_sig(&mut self, number: u32, data: Signature, loc: &Location) -> Result<()> { + self.map.def_sig(number, self.function.dfg.signatures.push(data), loc) + } + + // Resolve a reference to a signature. + fn get_sig(&self, number: u32, loc: &Location) -> Result { + match self.map.get_sig(number) { + Some(sig) => Ok(sig), + None => err!(loc, "undefined signature sig{}", number), + } + } + + // Allocate a new external function and add a mapping number -> FuncRef. + fn add_fn(&mut self, number: u32, data: ExtFuncData, loc: &Location) -> Result<()> { + self.map.def_fn(number, self.function.dfg.ext_funcs.push(data), loc) + } + + // Resolve a reference to a function. + fn get_fn(&self, number: u32, loc: &Location) -> Result { + match self.map.get_fn(number) { + Some(fnref) => Ok(fnref), + None => err!(loc, "undefined function fn{}", number), + } + } + // Allocate a new jump table and add a mapping number -> JumpTable. fn add_jt(&mut self, number: u32, data: JumpTableData, loc: &Location) -> Result<()> { self.map.def_jt(number, self.function.jump_tables.push(data), loc) @@ -305,6 +333,26 @@ impl<'a> Parser<'a> { } } + // Match and consume a function reference. + fn match_fn(&mut self, err_msg: &str) -> Result { + if let Some(Token::FuncRef(fnref)) = self.token() { + self.consume(); + Ok(fnref) + } else { + err!(self.loc, err_msg) + } + } + + // Match and consume a signature reference. + fn match_sig(&mut self, err_msg: &str) -> Result { + if let Some(Token::SigRef(sigref)) = self.token() { + self.consume(); + Ok(sigref) + } else { + err!(self.loc, err_msg) + } + } + // Match and consume a jump table reference. fn match_jt(&mut self) -> Result { if let Some(Token::JumpTable(jt)) = self.token() { @@ -628,6 +676,16 @@ impl<'a> Parser<'a> { self.parse_stack_slot_decl() .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) } + Some(Token::SigRef(..)) => { + self.gather_comments(ctx.function.dfg.signatures.next_key()); + self.parse_signature_decl() + .and_then(|(num, dat)| ctx.add_sig(num, dat, &self.loc)) + } + Some(Token::FuncRef(..)) => { + self.gather_comments(ctx.function.dfg.ext_funcs.next_key()); + self.parse_function_decl(ctx) + .and_then(|(num, dat)| ctx.add_fn(num, dat, &self.loc)) + } Some(Token::JumpTable(..)) => { self.gather_comments(ctx.function.jump_tables.next_key()); self.parse_jump_table_decl() @@ -661,6 +719,56 @@ impl<'a> Parser<'a> { Ok((number, data)) } + // Parse a signature decl. + // + // signature-decl ::= SigRef(sigref) "=" "signature" signature + // + fn parse_signature_decl(&mut self) -> Result<(u32, Signature)> { + let number = try!(self.match_sig("expected signature number: sig«n»")); + try!(self.match_token(Token::Equal, "expected '=' in signature decl")); + try!(self.match_identifier("signature", "expected 'signature'")); + let data = try!(self.parse_signature()); + Ok((number, data)) + } + + // Parse a function decl. + // + // Two variants: + // + // function-decl ::= FuncRef(fnref) "=" function-spec + // FuncRef(fnref) "=" SigRef(sig) name + // + // The first variant allocates a new signature reference. The second references an existing + // signature which must be declared first. + // + fn parse_function_decl(&mut self, ctx: &mut Context) -> Result<(u32, ExtFuncData)> { + let number = try!(self.match_fn("expected function number: fn«n»")); + try!(self.match_token(Token::Equal, "expected '=' in function decl")); + + let data = match self.token() { + Some(Token::Identifier("function")) => { + let (loc, name, sig) = try!(self.parse_function_spec()); + let sigref = ctx.function.dfg.signatures.push(sig); + ctx.map.def_entity(sigref.into(), &loc).expect("duplicate SigRef entities created"); + ExtFuncData { + name: name, + signature: sigref, + } + } + Some(Token::SigRef(sig_src)) => { + let sig = try!(ctx.get_sig(sig_src, &self.loc)); + self.consume(); + let name = try!(self.parse_function_name()); + ExtFuncData { + name: name, + signature: sig, + } + } + _ => return err!(self.loc, "expected 'function' or sig«n» in function decl"), + }; + Ok((number, data)) + } + // Parse a jump table decl. // // jump-table-decl ::= * JumpTable(jt) "=" "jump_table" jt-entry {"," jt-entry} @@ -1177,10 +1285,39 @@ impl<'a> Parser<'a> { } } InstructionFormat::Call => { - unimplemented!(); + let func_ref = try!(self.match_fn("expected function reference") + .and_then(|num| ctx.get_fn(num, &self.loc))); + try!(self.match_token(Token::LPar, "expected '(' before arguments")); + let args = try!(self.parse_value_list()); + try!(self.match_token(Token::RPar, "expected ')' after arguments")); + InstructionData::Call { + opcode: opcode, + ty: VOID, + second_result: NO_VALUE, + data: Box::new(CallData { + func_ref: func_ref, + varargs: args, + }), + } } InstructionFormat::IndirectCall => { - unimplemented!(); + let sig_ref = try!(self.match_sig("expected signature reference") + .and_then(|num| ctx.get_sig(num, &self.loc))); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let callee = try!(self.match_value("expected SSA value callee operand")); + try!(self.match_token(Token::LPar, "expected '(' before arguments")); + let args = try!(self.parse_value_list()); + try!(self.match_token(Token::RPar, "expected ')' after arguments")); + InstructionData::IndirectCall { + opcode: opcode, + ty: VOID, + second_result: NO_VALUE, + data: Box::new(IndirectCallData { + sig_ref: sig_ref, + arg: callee, + varargs: args, + }), + } } InstructionFormat::Return => { let args = try!(self.parse_value_list()); From 0b674fb28b1f99e7af6651205f8264fb6d1bc1ae Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Oct 2016 13:28:38 -0700 Subject: [PATCH 368/968] Bump filecheck version to 0.0.1, allow publication. --- lib/filecheck/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/filecheck/Cargo.toml b/lib/filecheck/Cargo.toml index f7cfa926e8..ee31a8fa42 100644 --- a/lib/filecheck/Cargo.toml +++ b/lib/filecheck/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ["The Cretonne Project Developers"] name = "filecheck" -version = "0.0.0" +version = "0.0.1" description = "Library for matching test outputs against filecheck directives" license = "Apache-2.0" repository = "https://github.com/stoklund/cretonne" -publish = false +documentation = "https://docs.rs/filecheck" [lib] name = "filecheck" From b068bace9d9f175aadbd19e59f50cad8101ca687 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 11:12:09 -0700 Subject: [PATCH 369/968] Add more expansion patterns. RISC-V does not have a flags register, and thus no add-with-carry instructions. Neither does MIPS. Add expansions of these instructions in terms of iadd and icmp. --- lib/cretonne/meta/cretonne/legalize.py | 76 +++++++++++++++++++++++++- lib/cretonne/meta/cretonne/xform.py | 3 +- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py index 14eb879abd..9d7b218755 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/cretonne/legalize.py @@ -7,19 +7,43 @@ patterns that describe how base instructions can be transformed to other base instructions that are legal. """ from __future__ import absolute_import -from .base import iadd, iadd_cout, iadd_cin, isplit_lohi, iconcat_lohi -from .base import isub, isub_bin, isub_bout +from .base import iadd, iadd_cout, iadd_cin, iadd_carry +from .base import isub, isub_bin, isub_bout, isub_borrow +from .base import bor, isplit_lohi, iconcat_lohi +from .base import icmp from .ast import Var from .xform import Rtl, XFormGroup -narrow = XFormGroup() +narrow = XFormGroup(""" + Legalize instructions by narrowing. + + The transformations in the 'narrow' group work by expressing + instructions in terms of smaller types. Operations on vector types are + expressed in terms of vector types with fewer lanes, and integer + operations are expressed in terms of smaller integer types. + """) + +expand = XFormGroup(""" + Legalize instructions by expansion. + + Rewrite instructions in terms of other instructions, generally + operating on the same types as the original instructions. + """) x = Var('x') y = Var('y') a = Var('a') +a1 = Var('a1') +a2 = Var('a2') b = Var('b') +b1 = Var('b1') +b2 = Var('b2') +b_in = Var('b_in') c = Var('c') +c1 = Var('c1') +c2 = Var('c2') +c_in = Var('c_in') xl = Var('xl') xh = Var('xh') yl = Var('yl') @@ -46,3 +70,49 @@ narrow.legalize( ah << isub_bin(xh, yh, b), a << iconcat_lohi(al, ah) )) + +# Expand integer operations with carry for RISC architectures that don't have +# the flags. +expand.legalize( + (a, c) << iadd_cout(x, y), + Rtl( + a << iadd(x, y), + c << icmp('ult', a, x) + )) + +expand.legalize( + (a, b) << isub_bout(x, y), + Rtl( + a << isub(x, y), + b << icmp('ugt', a, x) + )) + +expand.legalize( + a << iadd_cin(x, y, c), + Rtl( + a1 << iadd(x, y), + a << iadd(a1, c) + )) + +expand.legalize( + a << isub_bin(x, y, b), + Rtl( + a1 << isub(x, y), + a << isub(a1, b) + )) + +expand.legalize( + (a, c) << iadd_carry(x, y, c_in), + Rtl( + (a1, c1) << iadd_cout(x, y), + (a, c2) << iadd_cout(a1, c_in), + c << bor(c1, c2) + )) + +expand.legalize( + (a, b) << isub_borrow(x, y, b_in), + Rtl( + (a1, b1) << isub_bout(x, y), + (a, b2) << isub_bout(a1, b_in), + c << bor(c1, c2) + )) diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 16a9eae870..8015ee04b0 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -169,8 +169,9 @@ class XFormGroup(object): A group of related transformations. """ - def __init__(self): + def __init__(self, doc): self.xforms = list() + self.__doc__ = doc def legalize(self, src, dst): """ From cba5cdd22e8fda8f975b778c42538495cba97a82 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 12:03:50 -0700 Subject: [PATCH 370/968] Also rebuild if build.rs itself changes. We already have all the meta/**.py as dependencies. --- lib/cretonne/build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/cretonne/build.rs b/lib/cretonne/build.rs index 47e2890217..2067a37afd 100644 --- a/lib/cretonne/build.rs +++ b/lib/cretonne/build.rs @@ -23,6 +23,11 @@ fn main() { let cur_dir = env::current_dir().expect("Can't access current working directory"); let crate_dir = cur_dir.as_path(); + // Make sure we rebuild is this build script changes. + // I guess that won't happen if you have non-UTF8 bytes in your path names. + // The `build.py` script prints out its own dependencies. + println!("cargo:rerun-if-changed={}", crate_dir.join("build.rs").to_string_lossy()); + // Scripts are in `$crate_dir/meta`. let meta_dir = crate_dir.join("meta"); let build_script = meta_dir.join("build.py"); From c8f28e590214efff7b22d040ee9a5295fcd1c4e9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 16:23:08 -0700 Subject: [PATCH 371/968] Rename lifetimes in layout.rs to 'f These lifetimes all represent the lifetime of the Function. --- lib/cretonne/src/ir/layout.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index c6543a88e0..864420c4f5 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -123,7 +123,7 @@ impl Layout { } /// Return an iterator over all EBBs in layout order. - pub fn ebbs<'a>(&'a self) -> Ebbs<'a> { + pub fn ebbs<'f>(&'f self) -> Ebbs<'f> { Ebbs { layout: self, next: self.first_ebb, @@ -146,12 +146,12 @@ struct EbbNode { } /// Iterate over EBBs in layout order. See `Layout::ebbs()`. -pub struct Ebbs<'a> { - layout: &'a Layout, +pub struct Ebbs<'f> { + layout: &'f Layout, next: Option, } -impl<'a> Iterator for Ebbs<'a> { +impl<'f> Iterator for Ebbs<'f> { type Item = Ebb; fn next(&mut self) -> Option { @@ -166,11 +166,11 @@ impl<'a> Iterator for Ebbs<'a> { } /// Use a layout reference in a for loop. -impl<'a> IntoIterator for &'a Layout { +impl<'f> IntoIterator for &'f Layout { type Item = Ebb; - type IntoIter = Ebbs<'a>; + type IntoIter = Ebbs<'f>; - fn into_iter(self) -> Ebbs<'a> { + fn into_iter(self) -> Ebbs<'f> { self.ebbs() } } @@ -235,7 +235,7 @@ impl Layout { } /// Iterate over the instructions in `ebb` in layout order. - pub fn ebb_insts<'a>(&'a self, ebb: Ebb) -> Insts<'a> { + pub fn ebb_insts<'f>(&'f self, ebb: Ebb) -> Insts<'f> { Insts { layout: self, next: self.ebbs[ebb].first_inst.wrap(), @@ -316,12 +316,12 @@ struct InstNode { } /// Iterate over instructions in an EBB in layout order. See `Layout::ebb_insts()`. -pub struct Insts<'a> { - layout: &'a Layout, +pub struct Insts<'f> { + layout: &'f Layout, next: Option, } -impl<'a> Iterator for Insts<'a> { +impl<'f> Iterator for Insts<'f> { type Item = Inst; fn next(&mut self) -> Option { @@ -345,8 +345,8 @@ impl<'a> Iterator for Insts<'a> { /// /// When new instructions are added, the cursor can either apend them to an EBB or insert them /// before the current instruction. -pub struct Cursor<'a> { - layout: &'a mut Layout, +pub struct Cursor<'f> { + layout: &'f mut Layout, pos: CursorPosition, } @@ -366,10 +366,10 @@ pub enum CursorPosition { After(Ebb), } -impl<'a> Cursor<'a> { +impl<'f> Cursor<'f> { /// Create a new `Cursor` for `layout`. /// The cursor holds a mutable reference to `layout` for its entire lifetime. - pub fn new(layout: &'a mut Layout) -> Cursor { + pub fn new(layout: &'f mut Layout) -> Cursor { Cursor { layout: layout, pos: CursorPosition::Nowhere, From 58168bcd07ff7a206f6a9929b5a8bfdb3aecdb96 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 18:13:37 -0700 Subject: [PATCH 372/968] Generate an InstBuilder trait. All of the instruction format an opcode methods are emitted as an InstBuilder trait instead of adding them to the Bulder struct directly. The methods only make use of the InstBuilderBase methods to create new instructions. This makes it possible to reuse the InstBuilder trait for different ways of inserting instructions. --- lib/cretonne/meta/cretonne/__init__.py | 14 ++++++- lib/cretonne/meta/gen_instr.py | 56 +++++++++++++------------ lib/cretonne/src/cfg.rs | 2 +- lib/cretonne/src/dominator_tree.rs | 2 +- lib/cretonne/src/ir/builder.rs | 57 ++++++++++++++++++++++---- lib/cretonne/src/ir/mod.rs | 2 +- 6 files changed, 96 insertions(+), 37 deletions(-) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index a38126f98f..d9005d6572 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -795,7 +795,19 @@ class Instruction(object): InstructionGroup.append(self) def __str__(self): - return self.name + prefix = ', '.join(o.name for o in self.outs) + if prefix: + prefix = prefix + ' = ' + suffix = ', '.join(o.name for o in self.ins) + return '{}{} {}'.format(prefix, self.name, suffix) + + def blurb(self): + """Get the first line of the doc comment""" + for line in self.__doc__.split('\n'): + line = line.strip() + if line: + return line + return "" def _verify_polymorphic(self): """ diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 8ae6d47ef7..4e854fe32c 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -177,14 +177,7 @@ def gen_opcodes(groups, fmt): for i in g.instructions: instrs.append(i) i.number = len(instrs) - # Build a doc comment. - prefix = ', '.join(o.name for o in i.outs) - if prefix: - prefix = prefix + ' = ' - suffix = ', '.join(o.name for o in i.ins) - fmt.doc_comment( - '`{}{} {}`. ({})' - .format(prefix, i.name, suffix, i.format.name)) + fmt.doc_comment('`{}`. ({})'.format(i, i.format.name)) # Document polymorphism. if i.is_polymorphic: if i.use_typevar_operand: @@ -370,7 +363,7 @@ def gen_format_constructor(iform, fmt): proto = '{}({}) -> Inst'.format(iform.name, ', '.join(args)) fmt.line('#[allow(non_snake_case)]') - with fmt.indented('pub fn {} {{'.format(proto), '}'): + with fmt.indented('fn {} {{'.format(proto), '}'): # Generate the instruction data. with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): @@ -388,11 +381,9 @@ def gen_format_constructor(iform, fmt): # Create result values if necessary. if iform.multiple_results: - fmt.line('let inst = self.insert_inst(data);') - fmt.line('self.dfg.make_inst_results(inst, ctrl_typevar);') - fmt.line('inst') + fmt.line('self.complex_instruction(data, ctrl_typevar)') else: - fmt.line('self.insert_inst(data)') + fmt.line('self.simple_instruction(data)') def gen_member_inits(iform, fmt): @@ -452,8 +443,8 @@ def gen_inst_builder(inst, fmt): method = inst.name if method == 'return': - # Avoid Rust keywords - method = '_' + method + # Avoid Rust keywords by appending '_'. + method += '_' if len(tmpl_types) > 0: tmpl = '<{}>'.format(', '.join(tmpl_types)) @@ -461,8 +452,9 @@ def gen_inst_builder(inst, fmt): tmpl = '' proto = '{}{}({}) -> {}'.format(method, tmpl, ', '.join(args), rtype) + fmt.doc_comment('`{}`\n\n{}'.format(inst, inst.blurb())) fmt.line('#[allow(non_snake_case)]') - with fmt.indented('pub fn {} {{'.format(proto), '}'): + with fmt.indented('fn {} {{'.format(proto), '}'): # Convert all of the `Into<>` arguments. for arg in into_args: fmt.line('let {} = {}.into();'.format(arg, arg)) @@ -478,7 +470,7 @@ def gen_inst_builder(inst, fmt): elif inst.is_polymorphic: # Infer the controlling type variable from the input operands. fmt.line( - 'let ctrl_typevar = self.dfg.value_type({});' + 'let ctrl_typevar = self.data_flow_graph().value_type({});' .format(inst.ins[inst.format.typevar_operand].name)) args.append('ctrl_typevar') else: @@ -494,9 +486,11 @@ def gen_inst_builder(inst, fmt): if len(inst.value_results) == 0: fmt.line('inst') elif len(inst.value_results) == 1: - fmt.line('self.dfg.first_result(inst)') + fmt.line('self.data_flow_graph().first_result(inst)') else: - fmt.line('let mut results = self.dfg.inst_results(inst);') + fmt.line( + 'let mut results = ' + + 'self.data_flow_graph().inst_results(inst);') fmt.line('({})'.format(', '.join( len(inst.value_results) * ['results.next().unwrap()']))) @@ -505,16 +499,26 @@ def gen_builder(insts, fmt): """ Generate a Builder trait with methods for all instructions. """ - fmt.doc_comment( - 'Methods for inserting instructions by instruction format.') - with fmt.indented("impl<'a> Builder<'a> {", '}'): - for f in cretonne.InstructionFormat.all_formats: - gen_format_constructor(f, fmt) + fmt.doc_comment(""" + Convenience methods for building instructions. - fmt.doc_comment('Methods for inserting instructions by opcode.') - with fmt.indented("impl<'a> Builder<'a> {", '}'): + The `InstrBuilder` trait has one method per instruction opcode for + conveniently constructing the instruction with minimum arguments. + Polymorphic instructions infer their result types from the input + arguments when possible. In some cases, an explicit `result_type` + or `ctrl_typevar` argument is required. + + The opcode methods return the new instruction's result values, or + the `Inst` itself for instructions that don't have any results. + + There is also a method per instruction format. These methods all + return an `Inst`. + """) + with fmt.indented("pub trait InstBuilder: InstBuilderBase {", '}'): for inst in insts: gen_inst_builder(inst, fmt) + for f in cretonne.InstructionFormat.all_formats: + gen_format_constructor(f, fmt) def generate(isas, out_dir): diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index aab576ae82..59ced57d82 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -139,7 +139,7 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { use super::*; - use ir::{Function, Builder, Cursor, VariableArgs, types}; + use ir::{Function, Builder, InstBuilder, Cursor, VariableArgs, types}; #[test] fn empty() { diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 0e34a2c021..85d224060d 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -116,7 +116,7 @@ impl DominatorTree { #[cfg(test)] mod test { use super::*; - use ir::{Function, Builder, Cursor, VariableArgs, types}; + use ir::{Function, Builder, InstBuilder, Cursor, VariableArgs, types}; use ir::entities::NO_INST; use cfg::ControlFlowGraph; diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index f8510bb7f4..e8646d0dd1 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -9,6 +9,42 @@ use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, SigRef, FuncRe use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::{IntCC, FloatCC}; +/// Base trait for instruction builders. +/// +/// The `InstBuilderBase` trait provides the basic functionality required by the methods of the +/// generated `InstBuilder` trait. These methods should not normally be used directly. Use the +/// methods in the `InstBuilder trait instead. +/// +/// Any data type that implements `InstBuilderBase` also gets all the methods of the `InstBuilder` +/// trait. +pub trait InstBuilderBase { + /// Get an immutable reference to the data flow graph that will hold the constructed + /// instructions. + fn data_flow_graph(&self) -> &DataFlowGraph; + + /// Insert a simple instruction and return a reference to it. + /// + /// A 'simple' instruction has at most one result, and the `data.ty` field must contain the + /// result type or `VOID` for an instruction with no result values. + fn simple_instruction(&mut self, data: InstructionData) -> Inst; + + /// Insert a simple instruction and return a reference to it. + /// + /// A 'complex' instruction may produce multiple results, and the result types may depend on a + /// controlling type variable. For non-polymorphic instructions with multiple results, pass + /// `VOID` for the `ctrl_typevar` argument. + fn complex_instruction(&mut self, data: InstructionData, ctrl_typevar: Type) -> Inst; +} + +// Include trait code generated by `meta/gen_instr.py`. +// +// This file defines the `InstBuilder` trait as an extension of `InstBuilderBase` with methods per +// instruction format and per opcode. +include!(concat!(env!("OUT_DIR"), "/builder.rs")); + +/// Any type implementing `InstBuilderBase` gets all the `InstBuilder` methods for free. +impl InstBuilder for T {} + /// Instruction builder. /// /// A `Builder` holds mutable references to a data flow graph and a layout cursor. It provides @@ -40,16 +76,23 @@ impl<'a> Builder<'a> { pub fn insert_ebb(&mut self, ebb: Ebb) { self.pos.insert_ebb(ebb); } +} - // Create and insert an instruction. - // This method is used by the generated format-specific methods. - fn insert_inst(&mut self, data: InstructionData) -> Inst { +impl<'a> InstBuilderBase for Builder<'a> { + fn data_flow_graph(&self) -> &DataFlowGraph { + self.dfg + } + + fn simple_instruction(&mut self, data: InstructionData) -> Inst { let inst = self.dfg.make_inst(data); self.pos.insert_inst(inst); inst } -} -// Include code generated by `meta/gen_instr.py`. This file includes `Builder` methods per -// instruction format and per opcode for inserting instructions. -include!(concat!(env!("OUT_DIR"), "/builder.rs")); + fn complex_instruction(&mut self, data: InstructionData, ctrl_typevar: Type) -> Inst { + let inst = self.dfg.make_inst(data); + self.dfg.make_inst_results(inst, ctrl_typevar); + self.pos.insert_inst(inst); + inst + } +} diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 5056d9b29e..0812496032 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -24,4 +24,4 @@ pub use ir::jumptable::JumpTableData; pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; -pub use ir::builder::Builder; +pub use ir::builder::{Builder, InstBuilder}; From 368b12e7cdf563f9b94fb86d33666e8bd0b88a06 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 18:27:22 -0700 Subject: [PATCH 373/968] Use more precise lifetimes for Builder. Distinguish the lifetime of the Cursor and its referenced function layout. Use two separate function lifetimes: 'fc and 'fd. The borrow checker seems to get confused if we don't. --- lib/cretonne/src/ir/builder.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index e8646d0dd1..a360263138 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -49,15 +49,15 @@ impl InstBuilder for T {} /// /// A `Builder` holds mutable references to a data flow graph and a layout cursor. It provides /// convenience method for creating and inserting instructions at the current cursor position. -pub struct Builder<'a> { - pub dfg: &'a mut DataFlowGraph, - pub pos: &'a mut Cursor<'a>, +pub struct Builder<'c, 'fc: 'c, 'fd> { + pub pos: &'c mut Cursor<'fc>, + pub dfg: &'fd mut DataFlowGraph, } -impl<'a> Builder<'a> { +impl<'c, 'fc, 'fd> Builder<'c, 'fc, 'fd> { /// Create a new builder which inserts instructions at `pos`. /// The `dfg` and `pos.layout` references should be from the same `Function`. - pub fn new(dfg: &'a mut DataFlowGraph, pos: &'a mut Cursor<'a>) -> Builder<'a> { + pub fn new(dfg: &'fd mut DataFlowGraph, pos: &'c mut Cursor<'fc>) -> Builder<'c, 'fc, 'fd> { Builder { dfg: dfg, pos: pos, @@ -78,7 +78,7 @@ impl<'a> Builder<'a> { } } -impl<'a> InstBuilderBase for Builder<'a> { +impl<'c, 'fc, 'fd> InstBuilderBase for Builder<'c, 'fc, 'fd> { fn data_flow_graph(&self) -> &DataFlowGraph { self.dfg } From ab910b3f58afe70fd05c57c9edafc8bac60ae691 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 18:50:11 -0700 Subject: [PATCH 374/968] Add a Cursor::ins() method which constructs a Builder. Rewrite Builder uses in test cases to use this method and construct a new builder for each instruction. This pattern allows us to change the InstBuilder trait to a one-shot implementation that can only create a single instruction. Don't re-export the Builder struct, it is less important than the InstBuilder trait, and we may get more implementations. --- lib/cretonne/src/cfg.rs | 20 ++++++++++---------- lib/cretonne/src/dominator_tree.rs | 22 +++++++++++----------- lib/cretonne/src/ir/layout.rs | 7 +++++++ lib/cretonne/src/ir/mod.rs | 2 +- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index 59ced57d82..a50d4ff89f 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -139,7 +139,7 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { use super::*; - use ir::{Function, Builder, InstBuilder, Cursor, VariableArgs, types}; + use ir::{Function, InstBuilder, Cursor, VariableArgs, types}; #[test] fn empty() { @@ -184,18 +184,18 @@ mod tests { let jmp_ebb1_ebb2; { - let mut cursor = Cursor::new(&mut func.layout); - let mut b = Builder::new(&mut func.dfg, &mut cursor); + let mut cur = Cursor::new(&mut func.layout); + let dfg = &mut func.dfg; - b.insert_ebb(ebb0); - br_ebb0_ebb2 = b.brnz(cond, ebb2, VariableArgs::new()); - jmp_ebb0_ebb1 = b.jump(ebb1, VariableArgs::new()); + cur.insert_ebb(ebb0); + br_ebb0_ebb2 = cur.ins(dfg).brnz(cond, ebb2, VariableArgs::new()); + jmp_ebb0_ebb1 = cur.ins(dfg).jump(ebb1, VariableArgs::new()); - b.insert_ebb(ebb1); - br_ebb1_ebb1 = b.brnz(cond, ebb1, VariableArgs::new()); - jmp_ebb1_ebb2 = b.jump(ebb2, VariableArgs::new()); + cur.insert_ebb(ebb1); + br_ebb1_ebb1 = cur.ins(dfg).brnz(cond, ebb1, VariableArgs::new()); + jmp_ebb1_ebb2 = cur.ins(dfg).jump(ebb2, VariableArgs::new()); - b.insert_ebb(ebb2); + cur.insert_ebb(ebb2); } let cfg = ControlFlowGraph::new(&func); diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 85d224060d..d109cc9f9f 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -116,7 +116,7 @@ impl DominatorTree { #[cfg(test)] mod test { use super::*; - use ir::{Function, Builder, InstBuilder, Cursor, VariableArgs, types}; + use ir::{Function, InstBuilder, Cursor, VariableArgs, types}; use ir::entities::NO_INST; use cfg::ControlFlowGraph; @@ -142,20 +142,20 @@ mod test { let jmp_ebb1_ebb2; { - let mut cursor = Cursor::new(&mut func.layout); - let mut b = Builder::new(&mut func.dfg, &mut cursor); + let mut cur = Cursor::new(&mut func.layout); + let dfg = &mut func.dfg; - b.insert_ebb(ebb3); - jmp_ebb3_ebb1 = b.jump(ebb1, VariableArgs::new()); + cur.insert_ebb(ebb3); + jmp_ebb3_ebb1 = cur.ins(dfg).jump(ebb1, VariableArgs::new()); - b.insert_ebb(ebb1); - br_ebb1_ebb0 = b.brnz(cond, ebb0, VariableArgs::new()); - jmp_ebb1_ebb2 = b.jump(ebb2, VariableArgs::new()); + cur.insert_ebb(ebb1); + br_ebb1_ebb0 = cur.ins(dfg).brnz(cond, ebb0, VariableArgs::new()); + jmp_ebb1_ebb2 = cur.ins(dfg).jump(ebb2, VariableArgs::new()); - b.insert_ebb(ebb2); - b.jump(ebb0, VariableArgs::new()); + cur.insert_ebb(ebb2); + cur.ins(dfg).jump(ebb0, VariableArgs::new()); - b.insert_ebb(ebb0); + cur.insert_ebb(ebb0); } let cfg = ControlFlowGraph::new(&func); diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 864420c4f5..3997b93349 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -6,6 +6,8 @@ use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; +use ir::dfg::DataFlowGraph; +use ir::builder::Builder; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -615,6 +617,11 @@ impl<'f> Cursor<'f> { } } + /// Create a builder for inserting an instruction at the current position. + pub fn ins<'c, 'fd>(&'c mut self, dfg: &'fd mut DataFlowGraph) -> Builder<'c, 'f, 'fd> { + Builder::new(dfg, self) + } + /// Insert an EBB at the current position and switch to it. /// /// As far as possible, this method behaves as if the EBB header were an instruction inserted diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 0812496032..b8cda7711c 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -24,4 +24,4 @@ pub use ir::jumptable::JumpTableData; pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; -pub use ir::builder::{Builder, InstBuilder}; +pub use ir::builder::InstBuilder; From 6db94bb9809c318a91f841f08c3709beaa9fe34c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Oct 2016 19:25:28 -0700 Subject: [PATCH 375/968] Switch InstrBuilder to the one-shot builder pattern. All the InstrBuilder methods now consume the builder, and the non-leaf methods return the dfg mutable reference they were holding. This makes it possible to construct instruction builders that are only safe to use once because they are doing more advanced value rewriting. --- lib/cretonne/meta/gen_instr.py | 34 ++++++++++++++++++++-------------- lib/cretonne/src/ir/builder.rs | 24 +++++++++++++++--------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 4e854fe32c..21bd8bcd85 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -347,7 +347,7 @@ def gen_format_constructor(iform, fmt): """ # Construct method arguments. - args = ['&mut self', 'opcode: Opcode'] + args = ['self', 'opcode: Opcode'] if iform.multiple_results: args.append('ctrl_typevar: Type') @@ -361,7 +361,9 @@ def gen_format_constructor(iform, fmt): for idx, kind in enumerate(iform.kinds): args.append('op{}: {}'.format(idx, kind.rust_type)) - proto = '{}({}) -> Inst'.format(iform.name, ', '.join(args)) + proto = '{}({})'.format(iform.name, ', '.join(args)) + proto += " -> (Inst, &'f mut DataFlowGraph)" + fmt.line('#[allow(non_snake_case)]') with fmt.indented('fn {} {{'.format(proto), '}'): # Generate the instruction data. @@ -414,7 +416,7 @@ def gen_inst_builder(inst, fmt): """ # Construct method arguments. - args = ['&mut self'] + args = ['self'] # The controlling type variable will be inferred from the input values if # possible. Otherwise, it is the first method argument. @@ -481,18 +483,21 @@ def gen_inst_builder(inst, fmt): args.extend(op.name for op in inst.ins) args = ', '.join(args) - fmt.line('let inst = self.{}({});'.format(inst.format.name, args)) + # Call to the format constructor, + fcall = 'self.{}({})'.format(inst.format.name, args) if len(inst.value_results) == 0: - fmt.line('inst') - elif len(inst.value_results) == 1: - fmt.line('self.data_flow_graph().first_result(inst)') - else: - fmt.line( - 'let mut results = ' + - 'self.data_flow_graph().inst_results(inst);') - fmt.line('({})'.format(', '.join( - len(inst.value_results) * ['results.next().unwrap()']))) + fmt.line(fcall + '.0') + return + + if len(inst.value_results) == 1: + fmt.line('Value::new_direct({}.0)'.format(fcall)) + return + + fmt.line('let (inst, dfg) = {};'.format(fcall)) + fmt.line('let mut results = dfg.inst_results(inst);') + fmt.line('({})'.format(', '.join( + len(inst.value_results) * ['results.next().unwrap()']))) def gen_builder(insts, fmt): @@ -514,7 +519,8 @@ def gen_builder(insts, fmt): There is also a method per instruction format. These methods all return an `Inst`. """) - with fmt.indented("pub trait InstBuilder: InstBuilderBase {", '}'): + with fmt.indented( + "pub trait InstBuilder<'f>: InstBuilderBase<'f> {", '}'): for inst in insts: gen_inst_builder(inst, fmt) for f in cretonne.InstructionFormat.all_formats: diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index a360263138..6ad50d9ab8 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -17,7 +17,7 @@ use ir::condcodes::{IntCC, FloatCC}; /// /// Any data type that implements `InstBuilderBase` also gets all the methods of the `InstBuilder` /// trait. -pub trait InstBuilderBase { +pub trait InstBuilderBase<'f>: Sized { /// Get an immutable reference to the data flow graph that will hold the constructed /// instructions. fn data_flow_graph(&self) -> &DataFlowGraph; @@ -26,14 +26,17 @@ pub trait InstBuilderBase { /// /// A 'simple' instruction has at most one result, and the `data.ty` field must contain the /// result type or `VOID` for an instruction with no result values. - fn simple_instruction(&mut self, data: InstructionData) -> Inst; + fn simple_instruction(self, data: InstructionData) -> (Inst, &'f mut DataFlowGraph); /// Insert a simple instruction and return a reference to it. /// /// A 'complex' instruction may produce multiple results, and the result types may depend on a /// controlling type variable. For non-polymorphic instructions with multiple results, pass /// `VOID` for the `ctrl_typevar` argument. - fn complex_instruction(&mut self, data: InstructionData, ctrl_typevar: Type) -> Inst; + fn complex_instruction(self, + data: InstructionData, + ctrl_typevar: Type) + -> (Inst, &'f mut DataFlowGraph); } // Include trait code generated by `meta/gen_instr.py`. @@ -43,7 +46,7 @@ pub trait InstBuilderBase { include!(concat!(env!("OUT_DIR"), "/builder.rs")); /// Any type implementing `InstBuilderBase` gets all the `InstBuilder` methods for free. -impl InstBuilder for T {} +impl<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {} /// Instruction builder. /// @@ -78,21 +81,24 @@ impl<'c, 'fc, 'fd> Builder<'c, 'fc, 'fd> { } } -impl<'c, 'fc, 'fd> InstBuilderBase for Builder<'c, 'fc, 'fd> { +impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for Builder<'c, 'fc, 'fd> { fn data_flow_graph(&self) -> &DataFlowGraph { self.dfg } - fn simple_instruction(&mut self, data: InstructionData) -> Inst { + fn simple_instruction(self, data: InstructionData) -> (Inst, &'fd mut DataFlowGraph) { let inst = self.dfg.make_inst(data); self.pos.insert_inst(inst); - inst + (inst, self.dfg) } - fn complex_instruction(&mut self, data: InstructionData, ctrl_typevar: Type) -> Inst { + fn complex_instruction(self, + data: InstructionData, + ctrl_typevar: Type) + -> (Inst, &'fd mut DataFlowGraph) { let inst = self.dfg.make_inst(data); self.dfg.make_inst_results(inst, ctrl_typevar); self.pos.insert_inst(inst); - inst + (inst, self.dfg) } } From 634de933379c1073ba2aec34fbc169b583606340 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Oct 2016 11:16:58 -0700 Subject: [PATCH 376/968] Rename Builder to InsertBuilder. This instruction builder inserts an instruction at the cursor position. We'll add other kinds of builders shortly. --- lib/cretonne/src/ir/builder.rs | 36 ++++++++++++---------------------- lib/cretonne/src/ir/layout.rs | 6 +++--- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 6ad50d9ab8..e9f859cd7b 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -48,40 +48,30 @@ include!(concat!(env!("OUT_DIR"), "/builder.rs")); /// Any type implementing `InstBuilderBase` gets all the `InstBuilder` methods for free. impl<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {} -/// Instruction builder. +/// Builder that inserts an instruction at the current cursor position. /// -/// A `Builder` holds mutable references to a data flow graph and a layout cursor. It provides -/// convenience method for creating and inserting instructions at the current cursor position. -pub struct Builder<'c, 'fc: 'c, 'fd> { - pub pos: &'c mut Cursor<'fc>, - pub dfg: &'fd mut DataFlowGraph, +/// An `InsertBuilder` holds mutable references to a data flow graph and a layout cursor. It +/// provides convenience methods for creating and inserting instructions at the current cursor +/// position. +pub struct InsertBuilder<'c, 'fc: 'c, 'fd> { + pos: &'c mut Cursor<'fc>, + dfg: &'fd mut DataFlowGraph, } -impl<'c, 'fc, 'fd> Builder<'c, 'fc, 'fd> { +impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { /// Create a new builder which inserts instructions at `pos`. /// The `dfg` and `pos.layout` references should be from the same `Function`. - pub fn new(dfg: &'fd mut DataFlowGraph, pos: &'c mut Cursor<'fc>) -> Builder<'c, 'fc, 'fd> { - Builder { + pub fn new(dfg: &'fd mut DataFlowGraph, + pos: &'c mut Cursor<'fc>) + -> InsertBuilder<'c, 'fc, 'fd> { + InsertBuilder { dfg: dfg, pos: pos, } } - - /// Create and insert an EBB. Further instructions will be inserted into the new EBB. - pub fn ebb(&mut self) -> Ebb { - let ebb = self.dfg.make_ebb(); - self.insert_ebb(ebb); - ebb - } - - /// Insert an existing EBB at the current position. Further instructions will be inserted into - /// the new EBB. - pub fn insert_ebb(&mut self, ebb: Ebb) { - self.pos.insert_ebb(ebb); - } } -impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for Builder<'c, 'fc, 'fd> { +impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { fn data_flow_graph(&self) -> &DataFlowGraph { self.dfg } diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 3997b93349..ae37899f5e 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -7,7 +7,7 @@ use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; use ir::dfg::DataFlowGraph; -use ir::builder::Builder; +use ir::builder::InsertBuilder; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -618,8 +618,8 @@ impl<'f> Cursor<'f> { } /// Create a builder for inserting an instruction at the current position. - pub fn ins<'c, 'fd>(&'c mut self, dfg: &'fd mut DataFlowGraph) -> Builder<'c, 'f, 'fd> { - Builder::new(dfg, self) + pub fn ins<'c, 'fd>(&'c mut self, dfg: &'fd mut DataFlowGraph) -> InsertBuilder<'c, 'f, 'fd> { + InsertBuilder::new(dfg, self) } /// Insert an EBB at the current position and switch to it. From bb0bb1e91c401f08da8a387b80d90d3bba898aee Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Oct 2016 13:09:37 -0700 Subject: [PATCH 377/968] Add a ReplaceBuilder instruction builder. The DataFlowGraph::replace(inst) method returns an instruction builder that will replace an instruction in-place. This will be used when transforming instructions, replacing an old instruction with a new (legal) way of computing its primary value. Since primary result values are essentially instruction pointers, this is the only way of replacing the definition of a value. If secondary result values match the old instruction in both number and types, they can be reused. If not, added a detach_secondary_results() method for detaching old secondary values. --- lib/cretonne/src/ir/builder.rs | 88 ++++++++++++++++++++++++++++++++++ lib/cretonne/src/ir/dfg.rs | 55 ++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index e9f859cd7b..7df5a6aa3c 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -92,3 +92,91 @@ impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { (inst, self.dfg) } } + +/// Instruction builder that replaces an existing instruction. +/// +/// The inserted instruction will have the same `Inst` number as the old one. This is the only way +/// of rewriting the first result value of an instruction since this is a `ExpandedValue::Direct` +/// variant which encodes the instruction number directly. +/// +/// If the old instruction produced a value, the same value number will refer to the new +/// instruction's first result, so if that value has any uses the type should stay the same. +/// +/// If the old instruction still has secondary result values attached, it is assumed that the new +/// instruction produces the same number and types of results. The old secondary values are +/// preserved. If the replacemant instruction format does not support multiple results, the builder +/// panics. It is a bug to leave result values dangling. +/// +/// If the old instruction was capable of producing secondary results, but the values have been +/// detached, new result values are generated by calling `DataFlowGraph::make_inst_results()`. +pub struct ReplaceBuilder<'f> { + dfg: &'f mut DataFlowGraph, + inst: Inst, +} + +impl<'f> ReplaceBuilder<'f> { + /// Create a `ReplaceBuilder` that will overwrite `inst`. + pub fn new(dfg: &'f mut DataFlowGraph, inst: Inst) -> ReplaceBuilder { + ReplaceBuilder { + dfg: dfg, + inst: inst, + } + } +} + +impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { + fn data_flow_graph(&self) -> &DataFlowGraph { + self.dfg + } + + fn simple_instruction(self, data: InstructionData) -> (Inst, &'f mut DataFlowGraph) { + // The replacement instruction cannot generate multiple results, so verify that the old + // instruction's secondary results have been detached. + let old_second_value = self.dfg[self.inst].second_result().unwrap_or_default(); + assert_eq!(old_second_value, + Value::default(), + "Secondary result values {:?} would be left dangling by replacing {} with {}", + self.dfg.inst_results(self.inst).collect::>(), + self.dfg[self.inst].opcode(), + data.opcode()); + + // Splat the new instruction on top of the old one. + self.dfg[self.inst] = data; + (self.inst, self.dfg) + } + + fn complex_instruction(self, + data: InstructionData, + ctrl_typevar: Type) + -> (Inst, &'f mut DataFlowGraph) { + // If the old instruction still has secondary results attached, we'll keep them. + let old_second_value = self.dfg[self.inst].second_result().unwrap_or_default(); + + // Splat the new instruction on top of the old one. + self.dfg[self.inst] = data; + + if old_second_value == Value::default() { + // The old secondary values were either detached or non-existent. + // Construct new ones and set the first result type too. + self.dfg.make_inst_results(self.inst, ctrl_typevar); + } else { + // Reattach the old secondary values. + if let Some(val_ref) = self.dfg[self.inst].second_result_mut() { + // Don't check types here. Leave that to the verifier. + *val_ref = old_second_value; + } else { + // Actually, this instruction format should have called `simple_instruction()`, but + // we don't have a rule against calling `complex_instruction()` even when it is + // overkill. + panic!("Secondary result values left dangling"); + } + + // Normally, make_inst_results() would also set the first result type, but we're not + // going to call that, so set it manually. + *self.dfg[self.inst].first_type_mut() = + self.dfg.compute_result_type(self.inst, 0, ctrl_typevar).unwrap_or_default(); + } + + (self.inst, self.dfg) + } +} diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 35b0af4dc6..7b81c8deed 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -5,7 +5,9 @@ use ir::entities::{NO_VALUE, ExpandedValue}; use ir::instructions::{InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; +use ir::builder::ReplaceBuilder; +use std::mem; use std::ops::{Index, IndexMut}; use std::u16; @@ -269,6 +271,27 @@ impl DataFlowGraph { total_results } + /// Create a `ReplaceBuilder` that will replace `inst` with a new instruction in place. + pub fn replace(&mut self, inst: Inst) -> ReplaceBuilder { + ReplaceBuilder::new(self, inst) + } + + /// Detach secondary instruction results, and return them as an iterator. + /// + /// If `inst` produces two or more results, detach these secondary result values from `inst`, + /// and return an iterator that will enumerate them. The first result value cannot be detached. + /// + /// Use this method to detach secondary values before using `replace(inst)` to provide an + /// alternate instruction for computing the primary result value. + pub fn detach_secondary_results(&mut self, inst: Inst) -> Values { + let second_result = + self[inst].second_result_mut().map(|r| mem::replace(r, NO_VALUE)).unwrap_or_default(); + Values { + dfg: self, + cur: second_result, + } + } + /// Get the first result of an instruction. /// /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. @@ -277,7 +300,7 @@ impl DataFlowGraph { } /// Iterate through all the results of an instruction. - pub fn inst_results<'a>(&'a self, inst: Inst) -> Values<'a> { + pub fn inst_results(&self, inst: Inst) -> Values { Values { dfg: self, cur: if self.insts[inst].first_type().is_void() { @@ -297,6 +320,34 @@ impl DataFlowGraph { CallInfo::Indirect(s, _) => Some(s), } } + + /// Compute the type of an instruction result from opcode constraints and call signatures. + /// + /// This computes the same sequence of result types that `make_inst_results()` above would + /// assign to the created result values, but it does not depend on `make_inst_results()` being + /// called first. + /// + /// Returns `None` if asked about a result index that is too large. + pub fn compute_result_type(&self, + inst: Inst, + result_idx: usize, + ctrl_typevar: Type) + -> Option { + let constraints = self.insts[inst].opcode().constraints(); + let fixed_results = constraints.fixed_results(); + + if result_idx < fixed_results { + return Some(constraints.result_type(result_idx, ctrl_typevar)); + } + + // Not a fixed result, try to extract a return type from the call signature. + self.call_signature(inst).and_then(|sigref| { + self.signatures[sigref] + .return_types + .get(result_idx - fixed_results) + .map(|&arg| arg.value_type) + }) + } } /// Allow immutable access to instructions via indexing. @@ -369,7 +420,7 @@ impl DataFlowGraph { } /// Iterate through the arguments to an EBB. - pub fn ebb_args<'a>(&'a self, ebb: Ebb) -> Values<'a> { + pub fn ebb_args(&self, ebb: Ebb) -> Values { Values { dfg: self, cur: self.ebbs[ebb].first_arg, From 84172ddf98718b0952aadb0271bc4ed4184aae19 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Oct 2016 15:34:16 -0700 Subject: [PATCH 378/968] Define live range splitting instructions. The copy/spill/fill instructions will be used by the register allocator for splitting live ranges. The copy instruction is also useful when rewriting values: If a primary value is rewritten as a secondary result, a copy instruction can be used instead: a = foo x => t, vx1 = call ... a = copy vx1 Since a primary value must be the first value of an instruction, this doesn't work: a = foo x => t, a = call ... --- docs/langref.rst | 26 ++++++++++++++++++++++- lib/cretonne/meta/cretonne/base.py | 34 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/docs/langref.rst b/docs/langref.rst index 8acc2b4ef2..6fbe3610de 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -633,11 +633,35 @@ A few instructions have variants that take immediate operands (e.g., :inst:`band` / :inst:`band_imm`), but in general an instruction is required to load a constant into an SSA value. +.. autoinst:: select + +Constant materialization +------------------------ + .. autoinst:: iconst .. autoinst:: f32const .. autoinst:: f64const .. autoinst:: vconst -.. autoinst:: select + +Live range splitting +-------------------- + +Cretonne's register allocator assigns each SSA value to a register or a spill +slot on the stack for its entire live range. Since the live range of an SSA +value can be quite large, it is sometimes beneficial to split the live range +into smaller parts. + +A live range is split by creating new SSA values that are copies or the +original value or each other. The copies are created by inserting :inst:`copy`, +:inst:`spill`, or :inst:`fill` instructions, depending on whether the values +are assigned to registers or stack slots. + +This approach permits SSA form to be preserved throughout the register +allocation pass and beyond. + +.. autoinst:: copy +.. autoinst:: spill +.. autoinst:: fill Vector operations ----------------- diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index f507526626..1edbce9ecd 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -204,6 +204,40 @@ select = Instruction( """, ins=(c, x, y), outs=a) +x = Operand('x', Any) + +copy = Instruction( + 'copy', r""" + Register-register copy. + + This instruction copies its input, preserving the value type. + + A pure SSA-form program does not need to copy values, but this + instruction is useful for representing intermediate stages during + instruction transformations, and the register allocator needs a way of + representing register copies. + """, + ins=x, outs=a) + +spill = Instruction( + 'spill', r""" + Spill a register value to a stack slot. + + This instruction behaves exactly like :inst:`copy`, but the result + value is assigned to a spill slot. + """, + ins=x, outs=a) + +fill = Instruction( + 'fill', r""" + Load a register value from a stack slot. + + This instruction behaves exactly like :inst:`copy`, but the input + value is assigned to a spill slot. + """, + ins=x, outs=a) + + # # Vector operations # From 6aa3e4594af497f3b74e003fa91186c50e1bc1fc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Oct 2016 09:44:24 -0700 Subject: [PATCH 379/968] Move the 'ins' method to DataFlowGraph. This given us better symmetry between the replace and insert builder operations: dfg.replace(inst).iadd(x, y) dfg.ins(cursor).imul(x, y) --- lib/cretonne/src/cfg.rs | 10 +++++----- lib/cretonne/src/dominator_tree.rs | 10 +++++----- lib/cretonne/src/ir/dfg.rs | 10 +++++++++- lib/cretonne/src/ir/layout.rs | 7 ------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index a50d4ff89f..76ccc27965 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -184,16 +184,16 @@ mod tests { let jmp_ebb1_ebb2; { - let mut cur = Cursor::new(&mut func.layout); let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); cur.insert_ebb(ebb0); - br_ebb0_ebb2 = cur.ins(dfg).brnz(cond, ebb2, VariableArgs::new()); - jmp_ebb0_ebb1 = cur.ins(dfg).jump(ebb1, VariableArgs::new()); + br_ebb0_ebb2 = dfg.ins(cur).brnz(cond, ebb2, VariableArgs::new()); + jmp_ebb0_ebb1 = dfg.ins(cur).jump(ebb1, VariableArgs::new()); cur.insert_ebb(ebb1); - br_ebb1_ebb1 = cur.ins(dfg).brnz(cond, ebb1, VariableArgs::new()); - jmp_ebb1_ebb2 = cur.ins(dfg).jump(ebb2, VariableArgs::new()); + br_ebb1_ebb1 = dfg.ins(cur).brnz(cond, ebb1, VariableArgs::new()); + jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, VariableArgs::new()); cur.insert_ebb(ebb2); } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index d109cc9f9f..c81e579f6b 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -142,18 +142,18 @@ mod test { let jmp_ebb1_ebb2; { - let mut cur = Cursor::new(&mut func.layout); let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); cur.insert_ebb(ebb3); - jmp_ebb3_ebb1 = cur.ins(dfg).jump(ebb1, VariableArgs::new()); + jmp_ebb3_ebb1 = dfg.ins(cur).jump(ebb1, VariableArgs::new()); cur.insert_ebb(ebb1); - br_ebb1_ebb0 = cur.ins(dfg).brnz(cond, ebb0, VariableArgs::new()); - jmp_ebb1_ebb2 = cur.ins(dfg).jump(ebb2, VariableArgs::new()); + br_ebb1_ebb0 = dfg.ins(cur).brnz(cond, ebb0, VariableArgs::new()); + jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, VariableArgs::new()); cur.insert_ebb(ebb2); - cur.ins(dfg).jump(ebb0, VariableArgs::new()); + dfg.ins(cur).jump(ebb0, VariableArgs::new()); cur.insert_ebb(ebb0); } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 7b81c8deed..903fd48ddc 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -5,7 +5,8 @@ use ir::entities::{NO_VALUE, ExpandedValue}; use ir::instructions::{InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; -use ir::builder::ReplaceBuilder; +use ir::builder::{InsertBuilder, ReplaceBuilder}; +use ir::layout::Cursor; use std::mem; use std::ops::{Index, IndexMut}; @@ -271,6 +272,13 @@ impl DataFlowGraph { total_results } + /// Create an `InsertBuilder` that will insert an instruction at the cursor's current position. + pub fn ins<'c, 'fc: 'c, 'fd>(&'fd mut self, + at: &'c mut Cursor<'fc>) + -> InsertBuilder<'c, 'fc, 'fd> { + InsertBuilder::new(self, at) + } + /// Create a `ReplaceBuilder` that will replace `inst` with a new instruction in place. pub fn replace(&mut self, inst: Inst) -> ReplaceBuilder { ReplaceBuilder::new(self, inst) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index ae37899f5e..864420c4f5 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -6,8 +6,6 @@ use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; -use ir::dfg::DataFlowGraph; -use ir::builder::InsertBuilder; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -617,11 +615,6 @@ impl<'f> Cursor<'f> { } } - /// Create a builder for inserting an instruction at the current position. - pub fn ins<'c, 'fd>(&'c mut self, dfg: &'fd mut DataFlowGraph) -> InsertBuilder<'c, 'f, 'fd> { - InsertBuilder::new(dfg, self) - } - /// Insert an EBB at the current position and switch to it. /// /// As far as possible, this method behaves as if the EBB header were an instruction inserted From 853e995c99be2c6d5c1e8c8ab2f3e9b3c136204f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Oct 2016 09:56:55 -0700 Subject: [PATCH 380/968] Implement From for Imm64. This makes it possible to use literal integers as arguments to InstBuilder methods. --- lib/cretonne/src/ir/immediates.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index e8d4a20773..0395d13f26 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -28,6 +28,12 @@ impl Into for Imm64 { } } +impl From for Imm64 { + fn from(x: i64) -> Self { + Imm64(x) + } +} + impl Display for Imm64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let x = self.0; From 20aabcd1c763144d31dcdde7e8594791828112ba Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Oct 2016 10:39:05 -0700 Subject: [PATCH 381/968] Make Type::as_bool() less pedantic for scalars. All scalar types are mapped to b1 which is usually what you want for a scalar. Vector types have their lanes mapped to the wider boolean types. Add an as_bool_pedantic() methos that produces the scalar sized boolean types as before. Also add a friendlier Debug implementation for Type. --- lib/cretonne/src/ir/types.rs | 49 +++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index 99390ee22a..f1e0998403 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -1,7 +1,7 @@ //! Common types for the Cretonne code generator. use std::default::Default; -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display, Debug, Formatter}; // ====--------------------------------------------------------------------------------------====// // @@ -23,7 +23,7 @@ use std::fmt::{self, Display, Formatter}; /// /// SIMD vector types have power-of-two lanes, up to 256. Lanes can be any int/float/bool type. /// -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq)] pub struct Type(u8); /// No type. Used for functions without a return value. Can't be loaded or stored. Can't be part of @@ -31,7 +31,7 @@ pub struct Type(u8); pub const VOID: Type = Type(0); // Include code generated by `meta/gen_types.py`. This file contains constant definitions for all -// the scalar types as well as common vector types for 64, 128, 256, and 512-bit SID vectors. +// the scalar types as well as common vector types for 64, 128, 256, and 512-bit SIMD vectors. include!(concat!(env!("OUT_DIR"), "/types.rs")); impl Type { @@ -68,7 +68,10 @@ impl Type { /// Get a type with the same number of lanes as this type, but with the lanes replaced by /// booleans of the same size. - pub fn as_bool(self) -> Type { + /// + /// Scalar types are treated as vectors with one lane, so they are converted to the multi-bit + /// boolean types. + pub fn as_bool_pedantic(self) -> Type { // Replace the low 4 bits with the boolean version, preserve the high 4 bits. let lane = match self.lane_type() { B8 | I8 => B8, @@ -80,6 +83,18 @@ impl Type { Type(lane.0 | (self.0 & 0xf0)) } + /// Get a type with the same number of lanes as this type, but with the lanes replaced by + /// booleans of the same size. + /// + /// Scalar types are all converted to `b1` which is usually what you want. + pub fn as_bool(self) -> Type { + if self.is_scalar() { + B1 + } else { + self.as_bool_pedantic() + } + } + /// Get a type with the same number of lanes as this type, but with lanes that are half the /// number of bits. pub fn half_width(self) -> Option { @@ -222,6 +237,24 @@ impl Display for Type { } } +impl Debug for Type { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if self.is_void() { + write!(f, "types::VOID") + } else if self.is_bool() { + write!(f, "types::B{}", self.lane_bits()) + } else if self.is_int() { + write!(f, "types::I{}", self.lane_bits()) + } else if self.is_float() { + write!(f, "types::F{}", self.lane_bits()) + } else if !self.is_scalar() { + write!(f, "{:?}X{}", self.lane_type(), self.lane_count()) + } else { + write!(f, "Type(0x{:x})", self.0) + } + } +} + impl Default for Type { fn default() -> Type { VOID @@ -339,4 +372,12 @@ mod tests { assert_eq!(I8.by(512), None); assert_eq!(VOID.by(4), None); } + + #[test] + fn as_bool() { + assert_eq!(I32X4.as_bool(), B32X4); + assert_eq!(I32.as_bool(), B1); + assert_eq!(I32X4.as_bool_pedantic(), B32X4); + assert_eq!(I32.as_bool_pedantic(), B32); + } } From 61f5eeee2fd9c9ed7aba21c0d1dc475681bc2d0d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Oct 2016 11:02:53 -0700 Subject: [PATCH 382/968] Properly infer result type for single-result instructions. Polymorphic single-result instructions don't always return the controlling type variable as their first result. They may use a derived type variable, as for example icmp does. --- lib/cretonne/meta/gen_instr.py | 16 +++++++++++++++- lib/cretonne/src/ir/builder.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 21bd8bcd85..8a7e813223 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -474,7 +474,21 @@ def gen_inst_builder(inst, fmt): fmt.line( 'let ctrl_typevar = self.data_flow_graph().value_type({});' .format(inst.ins[inst.format.typevar_operand].name)) - args.append('ctrl_typevar') + if inst.format.multiple_results: + # The format constructor will resolve the result types from the + # type var. + args.append('ctrl_typevar') + elif inst.outs[inst.value_results[0]].typ == inst.ctrl_typevar: + # The format constructor expects a simple result type. + # No type transformation needed from the controlling type + # variable. + args.append('ctrl_typevar') + else: + # The format constructor expects a simple result type. + # TODO: This formula could be resolved ahead of time. + args.append( + 'Opcode::{}.constraints().result_type(0, ctrl_typevar)' + .format(inst.camel_name)) else: # This non-polymorphic instruction has a fixed result type. args.append( diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 7df5a6aa3c..575d36cd0b 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -180,3 +180,32 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { (self.inst, self.dfg) } } + +#[cfg(test)] +mod tests { + use ir::{Function, Cursor, InstBuilder}; + use ir::types::*; + use ir::condcodes::*; + + #[test] + fn types() { + let mut func = Function::new(); + let dfg = &mut func.dfg; + let ebb0 = dfg.make_ebb(); + let arg0 = dfg.append_ebb_arg(ebb0, I32); + let pos = &mut Cursor::new(&mut func.layout); + pos.insert_ebb(ebb0); + + // Explicit types. + let v0 = dfg.ins(pos).iconst(I32, 3); + assert_eq!(dfg.value_type(v0), I32); + + // Inferred from inputs. + let v1 = dfg.ins(pos).iadd(arg0, v0); + assert_eq!(dfg.value_type(v1), I32); + + // Formula. + let cmp = dfg.ins(pos).icmp(IntCC::Equal, arg0, v0); + assert_eq!(dfg.value_type(cmp), B1); + } +} From 84faddbf6555586f61657d46d6377efb30066456 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Oct 2016 17:24:14 -0700 Subject: [PATCH 383/968] Introduce value aliases. A extended value can now be changed to a third form: An alias of another value. This is like a copy instruction, but implicit in the value table. Value aliases are used in lieu of use-def chains which would be used to implement replace-all-uses-with. Added new DFG methods: - change_to_alias() changes an existing extended value into an alias. Primay values can't be changed, replace their definition with a copy instruction instead. - resolve_aliases() find the original non-alias value. - resolve_copies() like resolve_aliases(), but also sees through copy/spill/fill instructions. --- lib/cretonne/src/ir/dfg.rs | 150 ++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 903fd48ddc..0d24bc2d1a 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -2,7 +2,7 @@ use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef}; use ir::entities::{NO_VALUE, ExpandedValue}; -use ir::instructions::{InstructionData, CallInfo}; +use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; use ir::builder::{InsertBuilder, ReplaceBuilder}; @@ -100,6 +100,7 @@ impl DataFlowGraph { match self.extended_values[i] { ValueData::Inst { ty, .. } => ty, ValueData::Arg { ty, .. } => ty, + ValueData::Alias { ty, .. } => ty, } } None => panic!("NO_VALUE has no type"), @@ -118,11 +119,104 @@ impl DataFlowGraph { match self.extended_values[idx] { ValueData::Inst { inst, num, .. } => ValueDef::Res(inst, num as usize), ValueData::Arg { ebb, num, .. } => ValueDef::Arg(ebb, num as usize), + ValueData::Alias { original, .. } => { + // Make sure we only recurse one level. `resolve_aliases` has safeguards to + // detect alias loops without overrunning the stack. + self.value_def(self.resolve_aliases(original)) + } } } None => panic!("NO_VALUE has no def"), } } + + /// Resolve value aliases. + /// + /// Find the original SSA value that `value` aliases. + pub fn resolve_aliases(&self, value: Value) -> Value { + use ir::entities::ExpandedValue::Table; + let mut v = value; + + for _ in 0..self.extended_values.len() { + v = match v.expand() { + Table(idx) => { + match self.extended_values[idx] { + ValueData::Alias { original, .. } => { + // Follow alias values. + original + } + _ => return v, + } + } + _ => return v, + }; + } + panic!("Value alias loop detected for {}", value); + } + + /// Resolve value copies. + /// + /// Find the original definition of a value, looking through value aliases as well as + /// copy/spill/fill instructions. + pub fn resolve_copies(&self, value: Value) -> Value { + use ir::entities::ExpandedValue::Direct; + let mut v = self.resolve_aliases(value); + + for _ in 0..self.insts.len() { + v = self.resolve_aliases(match v.expand() { + Direct(inst) => { + match self[inst] { + InstructionData::Unary { opcode, arg, .. } => { + match opcode { + Opcode::Copy | Opcode::Spill | Opcode::Fill => arg, + _ => return v, + } + } + _ => return v, + } + } + _ => return v, + }); + } + panic!("Copy loop detected for {}", value); + } + + /// Turn a value into an alias of another. + /// + /// Change the `dest` value to behave as an alias of `src`. This means that all uses of `dest` + /// will behave as if they used that value `src`. + /// + /// The `dest` value cannot be a direct value defined as the first result of an instruction. To + /// replace a direct value with `src`, its defining instruction should be replaced with a + /// `copy src` instruction. See `replace()`. + pub fn change_to_alias(&mut self, dest: Value, src: Value) { + use ir::entities::ExpandedValue::Table; + + // Try to create short alias chains by finding the original source value. + // This also avoids the creation of loops. + let original = self.resolve_aliases(src); + assert!(dest != original, + "Aliasing {} to {} would create a loop", + dest, + src); + let ty = self.value_type(original); + assert_eq!(self.value_type(dest), + ty, + "Aliasing {} to {} would change its type {} to {}", + dest, + src, + self.value_type(dest), + ty); + + if let Table(idx) = dest.expand() { + self.extended_values[idx] = ValueData::Alias { + ty: ty, + original: original, + }; + } else { + panic!("Cannot change dirrect value {} into an alias", dest); + } + } } /// Where did a value come from? @@ -152,6 +246,11 @@ enum ValueData { ebb: Ebb, next: Value, // Next argument to `ebb`. }, + + // Value is an alias of another value. + // An alias value can't be linked as an instruction result or EBB argument. It is used as a + // placeholder when the original instruction or EBB has been rewritten or modified. + Alias { ty: Type, original: Value }, } /// Iterate through a list of related value references, such as: @@ -178,6 +277,9 @@ impl<'a> Iterator for Values<'a> { match self.dfg.extended_values[index] { ValueData::Inst { next, .. } => next, ValueData::Arg { next, .. } => next, + ValueData::Alias { .. } => { + panic!("Alias value {} appeared in value list", prev) + } } } ExpandedValue::None => return None, @@ -466,7 +568,7 @@ impl EbbData { mod tests { use super::*; use ir::types; - use ir::{Opcode, InstructionData}; + use ir::{Function, Cursor, Opcode, InstructionData}; #[test] fn make_inst() { @@ -543,4 +645,48 @@ mod tests { assert_eq!(dfg.value_type(arg1), types::F32); assert_eq!(dfg.value_type(arg2), types::I16); } + + #[test] + fn aliases() { + use ir::InstBuilder; + use ir::entities::ExpandedValue::Direct; + use ir::condcodes::IntCC; + + let mut func = Function::new(); + let dfg = &mut func.dfg; + let ebb0 = dfg.make_ebb(); + let arg0 = dfg.append_ebb_arg(ebb0, types::I32); + let pos = &mut Cursor::new(&mut func.layout); + pos.insert_ebb(ebb0); + + // Build a little test program. + let v1 = dfg.ins(pos).iconst(types::I32, 42); + let (s, c) = dfg.ins(pos).iadd_cout(v1, arg0); + let iadd = match s.expand() { + Direct(i) => i, + _ => panic!(), + }; + + // Detach the 'c' value from iadd. + { + let mut vals = dfg.detach_secondary_results(iadd); + assert_eq!(vals.next(), Some(c)); + assert_eq!(vals.next(), None); + } + + // Replace iadd_cout with a normal iadd and an icmp. + dfg.replace(iadd).iadd(v1, arg0); + let c2 = dfg.ins(pos).icmp(IntCC::UnsignedLessThan, s, v1); + dfg.change_to_alias(c, c2); + + assert_eq!(dfg.resolve_aliases(c2), c2); + assert_eq!(dfg.resolve_aliases(c), c2); + + // Make a copy of the alias. + let c3 = dfg.ins(pos).copy(c); + // This does not see through copies. + assert_eq!(dfg.resolve_aliases(c3), c3); + // But this goes through both copies and aliases. + assert_eq!(dfg.resolve_copies(c3), c2); + } } From b6ff2621f988834e6b1fa7947d5561e30b428f70 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 24 Oct 2016 13:27:10 -0700 Subject: [PATCH 384/968] File stale path references. After rearranging the directory layout, some paths in documentation needed updating. Fix some typos too. --- docs/testing.rst | 25 ++++++++++++++----------- lib/cretonne/meta/build.py | 2 +- lib/cretonne/meta/gen_build_deps.py | 2 +- lib/cretonne/meta/gen_types.py | 2 +- lib/cretonne/src/constant_hash.rs | 6 +++--- lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/instructions.rs | 6 +++--- lib/cretonne/src/ir/types.rs | 5 +++-- lib/cretonne/src/isa/enc_tables.rs | 2 +- lib/cretonne/src/isa/riscv/settings.rs | 5 +++-- lib/cretonne/src/predicates.rs | 2 +- 11 files changed, 32 insertions(+), 27 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index c902dc70f6..0f0527bf06 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -23,7 +23,7 @@ Unit tests Unit test live in a ``tests`` sub-module of the code they are testing:: - pub fn add(x: u32, y: u32) { + pub fn add(x: u32, y: u32) -> u32 { x + y } @@ -62,19 +62,21 @@ tested:: These tests are useful for demonstrating how to use an API, and running them regularly makes sure that they stay up to date. Documentation tests are not -appropriate for lots of assertions, use unit tests for that. +appropriate for lots of assertions; use unit tests for that. Integration tests ----------------- -Integration tests are Rust source files thar are compiled and linked +Integration tests are Rust source files that are compiled and linked individually. They are used to exercise the external API of the crates under test. -These tests are usually found in the :file:`src/tools/tests` sub-directory where they -have access to all the crates in the Cretonne repository. The -:file:`libcretonne` and :file:`libreader` crates have no external dependencies -which can make testing tedious. +These tests are usually found in the :file:`tests` top-level directory where +they have access to all the crates in the Cretonne repository. The +:file:`lib/cretonne` and :file:`lib/reader` crates have no external +dependencies, which can make testing tedious. Integration tests that don't need +to depend on other crates can be placed in :file:`lib/cretonne/tests` and +:file:`lib/reader/tests`. File tests ========== @@ -107,7 +109,7 @@ header: isa_spec : "isa" isa_name { `option` } "\n" The options given on the ``isa`` line modify the ISA-specific settings defined in -:file:`meta/isa/*/setings.py`. +:file:`lib/cretonne/meta/isa/*/settings.py`. All types of tests allow shared Cretonne settings to be modified: @@ -117,7 +119,7 @@ All types of tests allow shared Cretonne settings to be modified: option : flag | setting "=" value The shared settings available for all target ISAs are defined in -:file:`meta/cretonne/settings.py`. +:file:`lib/cretonne/meta/cretonne/settings.py`. The ``set`` lines apply settings cumulatively:: @@ -139,7 +141,8 @@ Filecheck Many of the test commands bescribed below use *filecheck* to verify their output. Filecheck is a Rust implementation of the LLVM tool of the same name. -See the :file:`libfilecheck` documentation for details of its syntax. +See the :file:`lib/filecheck` `documentation `_ for +details of its syntax. Comments in :file:`.cton` files are associated with the entity they follow. This typically means and instruction or the whole function. Those tests that @@ -225,7 +228,7 @@ reported location of the error is verified:: return } -This example test passed if the verifier fails with an error message containing +This example test passes if the verifier fails with an error message containing the sub-string ``"terminator"`` *and* the error is reported for the ``jump`` instruction. diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index 730d1c9b3f..cfda7ae862 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -1,6 +1,6 @@ # Second-level build script. # -# This script is run from src/libcretonne/build.rs to generate Rust files. +# This script is run from lib/cretonne/build.rs to generate Rust files. from __future__ import absolute_import import argparse diff --git a/lib/cretonne/meta/gen_build_deps.py b/lib/cretonne/meta/gen_build_deps.py index 32ae2f7238..191113a759 100644 --- a/lib/cretonne/meta/gen_build_deps.py +++ b/lib/cretonne/meta/gen_build_deps.py @@ -1,7 +1,7 @@ """ Generate build dependencies for Cargo. -The `build.py` script is invoked by cargo when building libcretonne to +The `build.py` script is invoked by cargo when building lib/cretonne to generate Rust code from the instruction descriptions. Cargo needs to know when it is necessary to rerun the build script. diff --git a/lib/cretonne/meta/gen_types.py b/lib/cretonne/meta/gen_types.py index 51607a61f9..2b284d135b 100644 --- a/lib/cretonne/meta/gen_types.py +++ b/lib/cretonne/meta/gen_types.py @@ -2,7 +2,7 @@ Generate sources with type info. This generates a `types.rs` file which is included in -`libcretonne/ir/types/rs`. The file provides constant definitions for the most +`lib/cretonne/ir/types.rs`. The file provides constant definitions for the most commonly used types, including all of the scalar types. This ensures that Python and Rust use the same type numbering. diff --git a/lib/cretonne/src/constant_hash.rs b/lib/cretonne/src/constant_hash.rs index fd8115c7fa..f76fde7fb7 100644 --- a/lib/cretonne/src/constant_hash.rs +++ b/lib/cretonne/src/constant_hash.rs @@ -1,7 +1,7 @@ //! Runtime support for precomputed constant hash tables. //! -//! The `meta/constant_hash.py` Python module can generate constant hash tables using open -//! addressing and quadratic probing. The hash tables are arrays that are guaranteed to: +//! The `lib/cretonne/meta/constant_hash.py` Python module can generate constant hash tables using +//! open addressing and quadratic probing. The hash tables are arrays that are guaranteed to: //! //! - Have a power-of-two size. //! - Contain at least one empty slot. @@ -52,7 +52,7 @@ pub fn probe + ?Sized>(table: &T, key: K, hash: usize) } /// A primitive hash function for matching opcodes. -/// Must match `meta/constant_hash.py`. +/// Must match `lib/cretonne/meta/constant_hash.py`. pub fn simple_hash(s: &str) -> usize { let mut h: u32 = 5381; for c in s.chars() { diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 575d36cd0b..695bc8e5a3 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -39,7 +39,7 @@ pub trait InstBuilderBase<'f>: Sized { -> (Inst, &'f mut DataFlowGraph); } -// Include trait code generated by `meta/gen_instr.py`. +// Include trait code generated by `lib/cretonne/meta/gen_instr.py`. // // This file defines the `InstBuilder` trait as an extension of `InstBuilderBase` with methods per // instruction format and per opcode. diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index daab56d09c..103dcd4fe5 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -15,7 +15,7 @@ use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::*; use ir::types; -// Include code generated by `meta/gen_instr.py`. This file contains: +// Include code generated by `lib/cretonne/meta/gen_instr.py`. This file contains: // // - The `pub enum InstructionFormat` enum with all the instruction formats. // - The `pub enum Opcode` definition with all known opcodes, @@ -54,9 +54,9 @@ impl Opcode { } } -// This trait really belongs in libreader where it is used by the .cton file parser, but since it +// This trait really belongs in lib/reader where it is used by the .cton file parser, but since it // critically depends on the `opcode_name()` function which is needed here anyway, it lives in this -// module. This also saves us from runing the build script twice to generate code for the two +// module. This also saves us from running the build script twice to generate code for the two // separate crates. impl FromStr for Opcode { type Err = &'static str; diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index f1e0998403..c001c9f3c7 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -30,8 +30,9 @@ pub struct Type(u8); /// a SIMD vector. pub const VOID: Type = Type(0); -// Include code generated by `meta/gen_types.py`. This file contains constant definitions for all -// the scalar types as well as common vector types for 64, 128, 256, and 512-bit SIMD vectors. +// Include code generated by `lib/cretonne/meta/gen_types.py`. This file contains constant +// definitions for all the scalar types as well as common vector types for 64, 128, 256, and +// 512-bit SIMD vectors. include!(concat!(env!("OUT_DIR"), "/types.rs")); impl Type { diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 62aa54e445..185e162f7f 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -1,7 +1,7 @@ //! Support types for generated encoding tables. //! //! This module contains types and functions for working with the encoding tables generated by -//! `meta/gen_encoding.py`. +//! `lib/cretonne/meta/gen_encoding.py`. use ir::{Type, Opcode}; use isa::Encoding; use constant_hash::{Table, probe}; diff --git a/lib/cretonne/src/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs index 57d8689ed1..c070612d08 100644 --- a/lib/cretonne/src/isa/riscv/settings.rs +++ b/lib/cretonne/src/isa/riscv/settings.rs @@ -3,8 +3,9 @@ use settings::{self, detail, Builder}; use std::fmt; -// Include code generated by `meta/gen_settings.py`. This file contains a public `Flags` struct -// with an impl for all of the settings defined in `meta/cretonne/settings.py`. +// Include code generated by `lib/cretonne/meta/gen_settings.py`. This file contains a public +// `Flags` struct with an impl for all of the settings defined in +// `lib/cretonne/meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs")); #[cfg(test)] diff --git a/lib/cretonne/src/predicates.rs b/lib/cretonne/src/predicates.rs index 83676be5a0..a0e50402bf 100644 --- a/lib/cretonne/src/predicates.rs +++ b/lib/cretonne/src/predicates.rs @@ -1,7 +1,7 @@ //! Predicate functions for testing instruction fields. //! //! This module defines functions that are used by the instruction predicates defined by -//! `meta/cretonne/predicates.py` classes. +//! `lib/cretonne/meta/cretonne/predicates.py` classes. //! //! The predicates the operate on integer fields use `Into` as a shared trait bound. This //! bound is implemented by all the native integer types as well as `Imm64`. From 67488179859821adea188713cb00ab7f5f86a1b0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Oct 2016 12:19:55 -0700 Subject: [PATCH 385/968] Add PEP 484 type annotations to a bunch of Python code. Along with the mypy tool, this helps find bugs in the Python code handling the instruction definition data structures. --- lib/cretonne/meta/build.py | 2 +- lib/cretonne/meta/cretonne/__init__.py | 60 ++++++++++++++++++-------- lib/cretonne/meta/cretonne/ast.py | 54 ++++++++++++++++++----- lib/cretonne/meta/cretonne/legalize.py | 6 +-- lib/cretonne/meta/cretonne/xform.py | 32 ++++++++++---- lib/cretonne/meta/isa/__init__.py | 2 + lib/cretonne/meta/isa/riscv/recipes.py | 7 +++ lib/cretonne/meta/srcgen.py | 19 +++++++- 8 files changed, 140 insertions(+), 42 deletions(-) diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index cfda7ae862..1554b6a54c 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -15,7 +15,7 @@ parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') args = parser.parse_args() -out_dir = args.out_dir +out_dir = args.out_dir # type: ignore isas = isa.all_isas() diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index d9005d6572..e8a82f650e 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -9,14 +9,23 @@ import re import math import importlib from collections import OrderedDict -from .predicates import And -from .ast import Apply +from .predicates import And, Predicate, FieldPredicate # noqa + +# The typing module is only required by mypy, and we don't use these imports +# outside type comments. +try: + from typing import Tuple, Union, Any, Iterable, Sequence # noqa + MaybeBoundInst = Union['Instruction', 'BoundInstruction'] + AnyPredicate = Union['Predicate', 'FieldPredicate'] +except ImportError: + pass camel_re = re.compile('(^|_)([a-z])') def camel_case(s): + # type: (str) -> str """Convert the string s to CamelCase""" return camel_re.sub(lambda m: m.group(2).upper(), s) @@ -133,7 +142,7 @@ class SettingGroup(object): """ # The currently open setting group. - _current = None + _current = None # type: SettingGroup def __init__(self, name, parent=None): self.name = name @@ -175,7 +184,6 @@ class SettingGroup(object): .format(self, SettingGroup._current)) SettingGroup._current = None if globs: - from .predicates import Predicate for name, obj in globs.iteritems(): if isinstance(obj, Setting): assert obj.name is None, obj.name @@ -381,10 +389,10 @@ class ValueType(object): """ # Map name -> ValueType. - _registry = dict() + _registry = dict() # type: Dict[str, ValueType] # List of all the scalar types. - all_scalars = list() + all_scalars = list() # type: List[ValueType] def __init__(self, name, membytes, doc): self.name = name @@ -534,7 +542,7 @@ class InstructionGroup(object): """ # The currently open instruction group. - _current = None + _current = None # type: InstructionGroup def open(self): """ @@ -644,15 +652,17 @@ class InstructionFormat(object): """ # Map (multiple_results, kind, kind, ...) -> InstructionFormat - _registry = dict() + _registry = dict() # type: Dict[Tuple, InstructionFormat] # All existing formats. - all_formats = list() + all_formats = list() # type: List[InstructionFormat] def __init__(self, *kinds, **kwargs): - self.name = kwargs.get('name', None) + # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa + self.name = kwargs.get('name', None) # type: str self.multiple_results = kwargs.get('multiple_results', False) self.boxed_storage = kwargs.get('boxed_storage', False) + self.members = list() # type: List[str] self.kinds = tuple(self._process_member_names(kinds)) # Which of self.kinds are `value`? @@ -660,7 +670,7 @@ class InstructionFormat(object): i for i, k in enumerate(self.kinds) if k is value) # The typevar_operand argument must point to a 'value' operand. - self.typevar_operand = kwargs.get('typevar_operand', None) + self.typevar_operand = kwargs.get('typevar_operand', None) # type: int if self.typevar_operand is not None: assert self.kinds[self.typevar_operand] is value, \ "typevar_operand must indicate a 'value' operand" @@ -678,6 +688,7 @@ class InstructionFormat(object): InstructionFormat.all_formats.append(self) def _process_member_names(self, kinds): + # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[OperandKind] # noqa """ Extract names of all the immediate operands in the kinds tuple. @@ -687,14 +698,14 @@ class InstructionFormat(object): Yields the operand kinds. """ - self.members = list() - for i, k in enumerate(kinds): - if isinstance(k, tuple): - member, k = k + for arg in kinds: + if isinstance(arg, OperandKind): + member = arg.default_member + k = arg else: - member = k.default_member - yield k + member, k = arg self.members.append(member) + yield k # Create `FormatField` instances for the immediates. if isinstance(k, ImmediateKind): @@ -704,6 +715,7 @@ class InstructionFormat(object): @staticmethod def lookup(ins, outs): + # type: (Sequence[Operand], Sequence[Operand]) -> InstructionFormat """ Find an existing instruction format that matches the given lists of instruction inputs and outputs. @@ -750,6 +762,7 @@ class FormatField(object): """ def __init__(self, format, operand, name): + # type: (InstructionFormat, int, str) -> None self.format = format self.operand = operand self.name = name @@ -758,6 +771,7 @@ class FormatField(object): return '{}.{}'.format(self.format.name, self.name) def rust_name(self): + # type: () -> str if self.format.boxed_storage: return 'data.' + self.name else: @@ -782,6 +796,7 @@ class Instruction(object): """ def __init__(self, name, doc, ins=(), outs=(), **kwargs): + # type: (str, str, Union[Sequence[Operand], Operand], Union[Sequence[Operand], Operand], **Any) -> None # noqa self.name = name self.camel_name = camel_case(name) self.__doc__ = doc @@ -898,6 +913,7 @@ class Instruction(object): @staticmethod def _to_operand_tuple(x): + # type: (Union[Sequence[Operand], Operand]) -> Tuple[Operand, ...] # Allow a single Operand instance instead of the awkward singleton # tuple syntax. if isinstance(x, Operand): @@ -909,6 +925,7 @@ class Instruction(object): return x def bind(self, *args): + # type: (*ValueType) -> BoundInstruction """ Bind a polymorphic instruction to a concrete list of type variable values. @@ -917,6 +934,7 @@ class Instruction(object): return BoundInstruction(self, args) def __getattr__(self, name): + # type: (str) -> BoundInstruction """ Bind a polymorphic instruction to a single type variable with dot syntax: @@ -926,6 +944,7 @@ class Instruction(object): return self.bind(ValueType.by_name(name)) def fully_bound(self): + # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] """ Verify that all typevars have been bound, and return a `(inst, typevars)` pair. @@ -941,6 +960,7 @@ class Instruction(object): Create an `ast.Apply` AST node representing the application of this instruction to the arguments. """ + from .ast import Apply return Apply(self, args) @@ -950,6 +970,7 @@ class BoundInstruction(object): """ def __init__(self, inst, typevars): + # type: (Instruction, Tuple[ValueType, ...]) -> None self.inst = inst self.typevars = typevars assert len(typevars) <= 1 + len(inst.other_typevars) @@ -958,12 +979,14 @@ class BoundInstruction(object): return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) def bind(self, *args): + # type: (*ValueType) -> BoundInstruction """ Bind additional typevars. """ return BoundInstruction(self.inst, self.typevars + args) def __getattr__(self, name): + # type: (str) -> BoundInstruction """ Bind an additional typevar dot syntax: @@ -972,6 +995,7 @@ class BoundInstruction(object): return self.bind(ValueType.by_name(name)) def fully_bound(self): + # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] """ Verify that all typevars have been bound, and return a `(inst, typevars)` pair. @@ -989,6 +1013,7 @@ class BoundInstruction(object): Create an `ast.Apply` AST node representing the application of this instruction to the arguments. """ + from .ast import Apply return Apply(self, args) @@ -1139,6 +1164,7 @@ class Encoding(object): """ def __init__(self, cpumode, inst, recipe, encbits, instp=None, isap=None): + # type: (CPUMode, MaybeBoundInst, EncRecipe, int, AnyPredicate, AnyPredicate) -> None # noqa assert isinstance(cpumode, CPUMode) assert isinstance(recipe, EncRecipe) self.inst, self.typevars = inst.fully_bound() diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py index 8c9c8576cb..693a0b5a2b 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cretonne/ast.py @@ -5,6 +5,12 @@ This module defines classes that can be used to create abstract syntax trees for patern matching an rewriting of cretonne instructions. """ from __future__ import absolute_import +from . import Instruction, BoundInstruction + +try: + from typing import Union, Tuple # noqa +except ImportError: + pass class Def(object): @@ -29,10 +35,12 @@ class Def(object): """ def __init__(self, defs, expr): + # type: (Union[Var, Tuple[Var, ...]], Apply) -> None if not isinstance(defs, tuple): - defs = (defs,) - assert isinstance(expr, Expr) - self.defs = defs + self.defs = (defs,) # type: Tuple[Var, ...] + else: + self.defs = defs + assert isinstance(expr, Apply) self.expr = expr def __repr__(self): @@ -42,7 +50,18 @@ class Def(object): if len(self.defs) == 1: return "{!s} << {!s}".format(self.defs[0], self.expr) else: - return "({}) << {!s}".format(", ".join(self.defs), self.expr) + return "({}) << {!s}".format( + ', '.join(map(str, self.defs)), self.expr) + + def root_inst(self): + # type: () -> Instruction + """Get the instruction at the root of this tree.""" + return self.expr.root_inst() + + def defs_expr(self): + # type: () -> Tuple[Tuple[Var, ...], Apply] + """Split into a defs tuple and an Apply expr.""" + return (self.defs, self.expr) class Expr(object): @@ -50,12 +69,6 @@ class Expr(object): An AST expression. """ - def __rlshift__(self, other): - """ - Define variables using `var << expr` or `(v1, v2) << expr`. - """ - return Def(other, self) - class Var(Expr): """ @@ -63,6 +76,7 @@ class Var(Expr): """ def __init__(self, name): + # type: (str) -> None self.name = name # Bitmask of contexts where this variable is defined. # See XForm._rewrite_defs(). @@ -98,16 +112,24 @@ class Apply(Expr): """ def __init__(self, inst, args): - from . import BoundInstruction + # type: (Union[Instruction, BoundInstruction], Tuple[Expr, ...]) -> None # noqa if isinstance(inst, BoundInstruction): self.inst = inst.inst self.typevars = inst.typevars else: + assert isinstance(inst, Instruction) self.inst = inst self.typevars = () self.args = args assert len(self.inst.ins) == len(args) + def __rlshift__(self, other): + # type: (Union[Var, Tuple[Var, ...]]) -> Def + """ + Define variables using `var << expr` or `(v1, v2) << expr`. + """ + return Def(other, self) + def instname(self): i = self.inst.name for t in self.typevars: @@ -120,3 +142,13 @@ class Apply(Expr): def __str__(self): args = ', '.join(map(str, self.args)) return '{}({})'.format(self.instname(), args) + + def root_inst(self): + # type: () -> Instruction + """Get the instruction at the root of this tree.""" + return self.inst + + def defs_expr(self): + # type: () -> Tuple[Tuple[Var, ...], Apply] + """Split into a defs tuple and an Apply expr.""" + return ((), self) diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py index 9d7b218755..21073d0aec 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/cretonne/legalize.py @@ -15,7 +15,7 @@ from .ast import Var from .xform import Rtl, XFormGroup -narrow = XFormGroup(""" +narrow = XFormGroup('narrow', """ Legalize instructions by narrowing. The transformations in the 'narrow' group work by expressing @@ -24,7 +24,7 @@ narrow = XFormGroup(""" operations are expressed in terms of smaller integer types. """) -expand = XFormGroup(""" +expand = XFormGroup('expand', """ Legalize instructions by expansion. Rewrite instructions in terms of other instructions, generally @@ -114,5 +114,5 @@ expand.legalize( Rtl( (a1, b1) << isub_bout(x, y), (a, b2) << isub_bout(a1, b_in), - c << bor(c1, c2) + c << bor(b1, b2) )) diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 8015ee04b0..1100e4721f 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -2,7 +2,13 @@ Instruction transformations. """ from __future__ import absolute_import -from .ast import Def, Var, Apply +from .ast import Def, Var, Apply, Expr # noqa + +try: + from typing import Union, Iterator, Sequence, Iterable # noqa + DefApply = Union[Def, Apply] +except ImportError: + pass SRCCTX = 1 @@ -21,9 +27,11 @@ class Rtl(object): """ def __init__(self, *args): + # type: (*DefApply) -> None self.rtl = args def __iter__(self): + # type: () -> Iterator[DefApply] return iter(self.rtl) @@ -55,16 +63,17 @@ class XForm(object): """ def __init__(self, src, dst): + # type: (Rtl, Rtl) -> None self.src = src self.dst = dst # Variables that are inputs to the source pattern. - self.inputs = list() + self.inputs = list() # type: List[Var] # Variables defined in either src or dst. - self.defs = list() + self.defs = list() # type: List[Var] # Rewrite variables in src and dst RTL lists to our own copies. # Map name -> private Var. - symtab = dict() + symtab = dict() # type: Dict[str, Var] self._rewrite_rtl(src, symtab, SRCCTX) num_src_inputs = len(self.inputs) self._rewrite_rtl(dst, symtab, DSTCTX) @@ -90,7 +99,8 @@ class XForm(object): return s def _rewrite_rtl(self, rtl, symtab, context): - for line in rtl: + # type: (Rtl, Dict[str, Var], int) -> None + for line in rtl.rtl: if isinstance(line, Def): line.defs = tuple( self._rewrite_defs(line.defs, symtab, context)) @@ -100,6 +110,7 @@ class XForm(object): self._rewrite_expr(expr, symtab, context) def _rewrite_expr(self, expr, symtab, context): + # type: (Apply, Dict[str, Var], int) -> None """ Find all uses of variables in `expr` and replace them with our own local symbols. @@ -113,6 +124,7 @@ class XForm(object): self._rewrite_uses(expr, stack, symtab, context)) def _rewrite_defs(self, defs, symtab, context): + # type: (Sequence[Var], Dict[str, Var], int) -> Iterable[Var] """ Given a tuple of symbols defined in a Def, rewrite them to local symbols. Yield the new locals. @@ -131,6 +143,7 @@ class XForm(object): yield var def _rewrite_uses(self, expr, stack, symtab, context): + # type: (Apply, List[Apply], Dict[str, Var], int) -> Iterable[Expr] """ Given an `Apply` expr, rewrite all uses in its arguments to local variables. Yield a sequence of new arguments. @@ -140,7 +153,7 @@ class XForm(object): for arg, operand in zip(expr.args, expr.inst.ins): # Nested instructions are allowed. Visit recursively. if isinstance(arg, Apply): - stack.push(arg) + stack.append(arg) yield arg continue if not isinstance(arg, Var): @@ -169,11 +182,14 @@ class XFormGroup(object): A group of related transformations. """ - def __init__(self, doc): - self.xforms = list() + def __init__(self, name, doc): + # type: (str, str) -> None + self.xforms = list() # type: List[XForm] + self.name = name self.__doc__ = doc def legalize(self, src, dst): + # type: (Union[Def, Apply], Rtl) -> None """ Add a legalization pattern to this group. diff --git a/lib/cretonne/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py index addc70b8a9..9412f6502c 100644 --- a/lib/cretonne/meta/isa/__init__.py +++ b/lib/cretonne/meta/isa/__init__.py @@ -7,9 +7,11 @@ architecture supported by Cretonne. """ from __future__ import absolute_import from . import riscv +from cretonne import TargetISA # noqa def all_isas(): + # type: () -> List[TargetISA] """ Get a list of all the supported target ISAs. Each target ISA is represented as a :py:class:`cretonne.TargetISA` instance. diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index db6d7021c8..375b0e2150 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -22,37 +22,44 @@ from cretonne.predicates import IsSignedInt def LOAD(funct3): + # type: (int) -> int assert funct3 <= 0b111 return 0b00000 | (funct3 << 5) def STORE(funct3): + # type: (int) -> int assert funct3 <= 0b111 return 0b01000 | (funct3 << 5) def BRANCH(funct3): + # type: (int) -> int assert funct3 <= 0b111 return 0b11000 | (funct3 << 5) def OPIMM(funct3, funct7=0): + # type: (int, int) -> int assert funct3 <= 0b111 return 0b00100 | (funct3 << 5) | (funct7 << 8) def OPIMM32(funct3, funct7=0): + # type: (int, int) -> int assert funct3 <= 0b111 return 0b00110 | (funct3 << 5) | (funct7 << 8) def OP(funct3, funct7): + # type: (int, int) -> int assert funct3 <= 0b111 assert funct7 <= 0b1111111 return 0b01100 | (funct3 << 5) | (funct7 << 8) def OP32(funct3, funct7): + # type: (int, int) -> int assert funct3 <= 0b111 assert funct7 <= 0b1111111 return 0b01110 | (funct3 << 5) | (funct7 << 8) diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py index 962a881436..75b102f534 100644 --- a/lib/cretonne/meta/srcgen.py +++ b/lib/cretonne/meta/srcgen.py @@ -10,6 +10,11 @@ import sys import os import re +try: + from typing import Any # noqa +except ImportError: + pass + class Formatter(object): """ @@ -38,19 +43,23 @@ class Formatter(object): shiftwidth = 4 def __init__(self): + # type: () -> None self.indent = '' - self.lines = [] + self.lines = [] # type: List[str] def indent_push(self): + # type: () -> None """Increase current indentation level by one.""" self.indent += ' ' * self.shiftwidth def indent_pop(self): + # type: () -> None """Decrease indentation by one level.""" assert self.indent != '', 'Already at top level indentation' self.indent = self.indent[0:-self.shiftwidth] def line(self, s=None): + # type: (str) -> None """Add an indented line.""" if s: self.lines.append('{}{}\n'.format(self.indent, s)) @@ -58,6 +67,7 @@ class Formatter(object): self.lines.append('\n') def outdented_line(self, s): + # type: (str) -> None """ Emit a line outdented one level. @@ -67,12 +77,14 @@ class Formatter(object): self.lines.append('{}{}\n'.format(self.indent[0:-self.shiftwidth], s)) def writelines(self, f=None): + # type: (Any) -> None """Write all lines to `f`.""" if not f: f = sys.stdout f.writelines(self.lines) def update_file(self, filename, directory): + # type: (str, str) -> None if directory is not None: filename = os.path.join(directory, filename) with open(filename, 'w') as f: @@ -80,10 +92,12 @@ class Formatter(object): class _IndentedScope(object): def __init__(self, fmt, after): + # type: (Formatter, str) -> None self.fmt = fmt self.after = after def __enter__(self): + # type: () -> None self.fmt.indent_push() def __exit__(self, t, v, tb): @@ -92,6 +106,7 @@ class Formatter(object): self.fmt.line(self.after) def indented(self, before=None, after=None): + # type: (str, str) -> Formatter._IndentedScope """ Return a scope object for use with a `with` statement: @@ -108,7 +123,7 @@ class Formatter(object): """ if before: self.line(before) - return self._IndentedScope(self, after) + return Formatter._IndentedScope(self, after) def format(self, fmt, *args): self.line(fmt.format(*args)) From 7722ba91ffe68f5a300b1f6a107a2d9b8bc8306b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Oct 2016 12:24:16 -0700 Subject: [PATCH 386/968] Create FormatField attributes on demand. The InstructionFormat objects make their non-value operands available as FormatField attributes for use by predicates etc. Compute these on demand instead of up front. This makes it possible for the mypy tool to infer the types of these attributes from the __getattr__ signature. --- lib/cretonne/meta/cretonne/__init__.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index e8a82f650e..a8865e1966 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -707,11 +707,24 @@ class InstructionFormat(object): self.members.append(member) yield k - # Create `FormatField` instances for the immediates. - if isinstance(k, ImmediateKind): - assert not hasattr(self, member), "Duplicate member name" - field = FormatField(self, i, member) - setattr(self, member, field) + def __getattr__(self, attr): + # type: (str) -> FormatField + """ + Make instruction format members available as attributes. + + Each non-value format member becomes a corresponding `FormatField` + attribute. + """ + try: + i = self.members.index(attr) + except ValueError: + raise AttributeError( + '{} is neither a {} member or a ' + .format(attr, self.name) + + 'normal InstructionFormat attribute') + field = FormatField(self, i, attr) + setattr(self, attr, field) + return field @staticmethod def lookup(ins, outs): From 5dc9ea1de498f884bd94e87f741a3eafd36b0f59 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Oct 2016 12:26:23 -0700 Subject: [PATCH 387/968] Run mypy if it's available. The Python tools pyliint, flake8, and mypy are only run if they exist in $PATH. --- lib/cretonne/meta/check.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh index f01653b57e..072066ebec 100755 --- a/lib/cretonne/meta/check.sh +++ b/lib/cretonne/meta/check.sh @@ -2,11 +2,21 @@ set -e cd $(dirname "$0") -# Run unit tests. +echo "=== Python unit tests ===" python -m unittest discover +runif() { + if command -v "$1" > /dev/null; then + echo "=== $1 ===" + "$@" + else + echo "$1 not found" + fi +} + # Check Python sources for Python 3 compatibility using pylint. # # Install pylint with 'pip install pylint'. -pylint --py3k --reports=no -- *.py cretonne isa -flake8 . +runif pylint --py3k --reports=no -- *.py cretonne isa +runif flake8 . +runif mypy --py2 build.py From bcd5bc559c14aae60c5e025d76a1599909b876ed Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 25 Oct 2016 16:52:15 -0700 Subject: [PATCH 388/968] Begin generating code for the legalizer. This is a work in progress. The 'legalizer.rs' file generated by gen_legalizer.py is not used for anything yet. Add PEP 484 type annotations to a bunch of Python code. --- lib/cretonne/meta/build.py | 2 + lib/cretonne/meta/gen_legalizer.py | 121 +++++++++++++++++++++++++++++ lib/cretonne/src/ir/layout.rs | 9 +++ 3 files changed, 132 insertions(+) create mode 100644 lib/cretonne/meta/gen_legalizer.py diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index 1554b6a54c..51f8e7c056 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -10,6 +10,7 @@ import gen_instr import gen_settings import gen_build_deps import gen_encoding +import gen_legalizer parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') @@ -23,4 +24,5 @@ gen_types.generate(out_dir) gen_instr.generate(isas, out_dir) gen_settings.generate(isas, out_dir) gen_encoding.generate(isas, out_dir) +gen_legalizer.generate(isas, out_dir) gen_build_deps.generate() diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py new file mode 100644 index 0000000000..0fdfc921af --- /dev/null +++ b/lib/cretonne/meta/gen_legalizer.py @@ -0,0 +1,121 @@ +""" +Generate legalizer transformations. + +The transformations defined in the `cretonne.legalize` module are all of the +macro-expansion form where the input pattern is a single instruction. We +generate a Rust function for each `XFormGroup` which takes a `Cursor` pointing +at the instruction to be legalized. The expanded destination pattern replaces +the input instruction. +""" +from __future__ import absolute_import +from srcgen import Formatter +import cretonne.legalize as legalize +from cretonne.ast import Def, Apply # noqa +from cretonne.xform import XForm, XFormGroup # noqa + +try: + from typing import Union # noqa + DefApply = Union[Def, Apply] +except ImportError: + pass + + +def unwrap_inst(iref, node, fmt): + # type: (str, DefApply, Formatter) -> None + """ + Given a `Def` or `Apply` node, emit code that extracts all the instruction + fields from `dfg[iref]`. + + Create local variables named after the `Var` instances in `node`. + + :param iref: Name of the `Inst` reference to unwrap. + :param node: `Def` or `Apply` node providing variable names. + + """ + fmt.comment('Unwrap {}'.format(node)) + defs, expr = node.defs_expr() + iform = expr.inst.format + nvops = len(iform.value_operands) + + # The tuple of locals we're extracting is `expr.args`. + with fmt.indented( + 'let ({}) = if let InstructionData::{} {{' + .format(', '.join(map(str, expr.args)), iform.name), '};'): + if iform.boxed_storage: + # This format indirects to a largish `data` struct. + fmt.line('ref data,') + else: + # Fields are encoded directly. + for m in iform.members: + if m: + fmt.line('{},'.format(m)) + if nvops == 1: + fmt.line('arg,') + elif nvops > 1: + fmt.line('args,') + fmt.line('..') + fmt.outdented_line('} = dfg[inst] {') + # Generate the values for the tuple. + outs = list() + prefix = 'data.' if iform.boxed_storage else '' + for i, m in enumerate(iform.members): + if m: + outs.append(prefix + m) + else: + # This is a value operand. + if nvops == 1: + outs.append(prefix + 'arg') + else: + outs.append( + '{}args[{}]'.format( + prefix, iform.value_operands.index(i))) + fmt.line('({})'.format(', '.join(outs))) + fmt.outdented_line('} else {') + fmt.line('unimplemented!("bad instruction format")') + + +def gen_xform(xform, fmt): + # type: (XForm, Formatter) -> None + """ + Emit code for `xform`, assuming the the opcode of xform's root instruction + has already been matched. + + `inst: Inst` is the variable to be replaced. It is pointed to by `pos: + Cursor`. + `dfg: DataFlowGraph` is available and mutable. + """ + unwrap_inst('inst', xform.src.rtl[0], fmt) + + +def gen_xform_group(xgrp, fmt): + # type: (XFormGroup, Formatter) -> None + fmt.doc_comment(""" + Legalize the instruction pointed to by `pos`. + + Return the first instruction in the expansion, and leave `pos` pointing + at the last instruction in the expansion. + """) + with fmt.indented( + 'fn ' + xgrp.name + + '(pos: &mut Cursor, dfg: &mut DataFlowGraph) -> ' + + 'Option {{', + '}'): + # Gen the instruction to be legalized. The cursor we're passed must be + # pointing at an instruction. + fmt.line('let inst = pos.current_inst().expect("need instruction");') + + with fmt.indented('match dfg[inst].opcode() {', '}'): + for xform in xgrp.xforms: + inst = xform.src.rtl[0].root_inst() + with fmt.indented( + 'Opcode::{} => {{'.format(inst.camel_name), '}'): + gen_xform(xform, fmt) + # We'll assume there are uncovered opcodes. + fmt.line('_ => None,') + + +def generate(isas, out_dir): + fmt = Formatter() + gen_xform_group(legalize.narrow, fmt) + gen_xform_group(legalize.expand, fmt) + fmt.update_file('legalizer.rs', out_dir) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 864420c4f5..fdd2305747 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -391,6 +391,15 @@ impl<'f> Cursor<'f> { } } + /// Get the instruction corresponding to the current position, if any. + pub fn current_inst(&self) -> Option { + use self::CursorPosition::*; + match self.pos { + At(inst) => Some(inst), + _ => None, + } + } + /// Go to a specific instruction which must be inserted in the layout. /// New instructions will be inserted before `inst`. pub fn goto_inst(&mut self, inst: Inst) { From 77cc9623802d3371b6cf123b705b1eac10f40a61 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Oct 2016 17:34:45 -0700 Subject: [PATCH 389/968] Require documentation on filecheck public items. --- lib/filecheck/src/lib.rs | 2 ++ lib/filecheck/src/variable.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/filecheck/src/lib.rs b/lib/filecheck/src/lib.rs index f6c53c5dcf..66fe6036ab 100644 --- a/lib/filecheck/src/lib.rs +++ b/lib/filecheck/src/lib.rs @@ -234,6 +234,8 @@ //! This will match `"one, two"` , but not `"one,two"`. Without the `$()`, trailing whitespace //! would be trimmed from the pattern. +#![deny(missing_docs)] + pub use error::{Error, Result}; pub use variable::{VariableMap, Value, NO_VARIABLES}; pub use checker::{Checker, CheckerBuilder}; diff --git a/lib/filecheck/src/variable.rs b/lib/filecheck/src/variable.rs index 51bc38fc07..1a43f1428a 100644 --- a/lib/filecheck/src/variable.rs +++ b/lib/filecheck/src/variable.rs @@ -19,7 +19,9 @@ pub fn varname_prefix(s: &str) -> usize { /// A variable can contain either a regular expression or plain text. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Value<'a> { + /// Verbatim text. Text(Cow<'a, str>), + /// Regular expression. Regex(Cow<'a, str>), } From 7b3160dbbbabcf593c21d23841cf912366a804ae Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Oct 2016 17:43:18 -0700 Subject: [PATCH 390/968] Require documentation on reader public items. --- lib/reader/src/error.rs | 4 ++++ lib/reader/src/lib.rs | 2 ++ lib/reader/src/testcommand.rs | 10 ++++++++++ lib/reader/src/testfile.rs | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/lib/reader/src/error.rs b/lib/reader/src/error.rs index cf8bd12f08..c49e523b72 100644 --- a/lib/reader/src/error.rs +++ b/lib/reader/src/error.rs @@ -8,13 +8,16 @@ use std::result; /// The location of a `Token` or `Error`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Location { + /// Line number, starting from 1. pub line_number: usize, } /// A parse error is returned when the parse failed. #[derive(Debug)] pub struct Error { + /// Location of the error. pub location: Location, + /// Error message. pub message: String, } @@ -24,6 +27,7 @@ impl fmt::Display for Error { } } +/// Result of a parser operation. The `Error` variant includes a location. pub type Result = result::Result; // Create an `Err` variant of `Result` from a location and `format!` args. diff --git a/lib/reader/src/lib.rs b/lib/reader/src/lib.rs index ef81e7f210..25aa25aa98 100644 --- a/lib/reader/src/lib.rs +++ b/lib/reader/src/lib.rs @@ -3,6 +3,8 @@ //! The cton_reader library supports reading .cton files. This functionality is needed for testing //! Cretonne, but is not essential for a JIT compiler. +#![deny(missing_docs)] + extern crate cretonne; pub use error::{Location, Result, Error}; diff --git a/lib/reader/src/testcommand.rs b/lib/reader/src/testcommand.rs index 3eba0d0a53..012c7033e1 100644 --- a/lib/reader/src/testcommand.rs +++ b/lib/reader/src/testcommand.rs @@ -14,19 +14,27 @@ use std::fmt::{self, Display, Formatter}; +/// A command appearing in a test file. #[derive(Clone, PartialEq, Eq, Debug)] pub struct TestCommand<'a> { + /// The command name as a string. pub command: &'a str, + /// The options following the command name. pub options: Vec>, } +/// An option on a test command. #[derive(Clone, PartialEq, Eq, Debug)] pub enum TestOption<'a> { + /// Single identifier flag: `foo`. Flag(&'a str), + /// A value assigned to an identifier: `foo=bar`. Value(&'a str, &'a str), } impl<'a> TestCommand<'a> { + /// Create a new TestCommand by parsing `s`. + /// The returned command contains references into `s`. pub fn new(s: &'a str) -> TestCommand<'a> { let mut parts = s.split_whitespace(); let cmd = parts.next().unwrap_or(""); @@ -48,6 +56,8 @@ impl<'a> Display for TestCommand<'a> { } impl<'a> TestOption<'a> { + /// Create a new TestOption by parsing `s`. + /// The returned option contains references into `s`. pub fn new(s: &'a str) -> TestOption<'a> { match s.find('=') { None => TestOption::Flag(s), diff --git a/lib/reader/src/testfile.rs b/lib/reader/src/testfile.rs index 30ed59074b..deeaf04e5a 100644 --- a/lib/reader/src/testfile.rs +++ b/lib/reader/src/testfile.rs @@ -20,6 +20,7 @@ pub struct TestFile<'a> { pub commands: Vec>, /// `isa bar ...` lines. pub isa_spec: IsaSpec, + /// Parsed functions and additional details about each function. pub functions: Vec<(Function, Details<'a>)>, } @@ -28,8 +29,12 @@ pub struct TestFile<'a> { /// The details to not affect the semantics of the function. #[derive(Debug)] pub struct Details<'a> { + /// Location of the `function` keyword that begins this function. pub location: Location, + /// Annotation comments that appeared inside or after the function. pub comments: Vec>, + /// Mapping of source entity numbers to parsed entity numbers. + /// Source locations of parsed entities. pub map: SourceMap, } From 3da569de06f5d8e6c9e02b6e03361e50c49f004e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Oct 2016 18:41:39 -0700 Subject: [PATCH 391/968] Require documentation on cretonne public items. --- lib/cretonne/meta/cretonne/__init__.py | 5 +++ lib/cretonne/meta/gen_instr.py | 3 ++ lib/cretonne/meta/gen_settings.py | 11 ++++-- lib/cretonne/src/cfg.rs | 13 ++----- lib/cretonne/src/dominator_tree.rs | 3 +- lib/cretonne/src/ir/condcodes.rs | 54 +++++++++++++++++++------- lib/cretonne/src/ir/entities.rs | 23 ++++++++--- lib/cretonne/src/ir/extfunc.rs | 8 ++++ lib/cretonne/src/ir/funcname.rs | 1 + lib/cretonne/src/ir/immediates.rs | 3 ++ lib/cretonne/src/ir/instructions.rs | 15 ++++++- lib/cretonne/src/isa/mod.rs | 1 + lib/cretonne/src/isa/riscv/mod.rs | 1 + lib/cretonne/src/lib.rs | 8 ++-- lib/cretonne/src/settings.rs | 11 +++++- lib/cretonne/src/verifier.rs | 8 +++- 16 files changed, 124 insertions(+), 44 deletions(-) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index a8865e1966..6672d6d869 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -707,6 +707,11 @@ class InstructionFormat(object): self.members.append(member) yield k + def __str__(self): + args = ', '.join('{}: {}'.format(m, k) if m else str(k) + for m, k in zip(self.members, self.kinds)) + return '{}({})'.format(self.name, args) + def __getattr__(self, attr): # type: (str) -> FormatField """ diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 8a7e813223..bb14d36375 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -19,6 +19,7 @@ def gen_formats(fmt): fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') with fmt.indented('pub enum InstructionFormat {', '}'): for f in cretonne.InstructionFormat.all_formats: + fmt.doc_comment(str(f)) fmt.line(f.name + ',') fmt.line() @@ -172,6 +173,7 @@ def gen_opcodes(groups, fmt): fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') instrs = [] with fmt.indented('pub enum Opcode {', '}'): + fmt.doc_comment('An invalid opcode.') fmt.line('NotAnOpcode,') for g in groups: for i in g.instructions: @@ -364,6 +366,7 @@ def gen_format_constructor(iform, fmt): proto = '{}({})'.format(iform.name, ', '.join(args)) proto += " -> (Inst, &'f mut DataFlowGraph)" + fmt.doc_comment(str(iform)) fmt.line('#[allow(non_snake_case)]') with fmt.indented('fn {} {{'.format(proto), '}'): # Generate the instruction data. diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index c3c4710ea3..8e35d05909 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -16,10 +16,12 @@ def gen_enum_types(sgrp, fmt): if not isinstance(setting, EnumSetting): continue ty = camel_case(setting.name) + fmt.doc_comment('Values for {}.'.format(setting)) fmt.line('#[derive(Debug, PartialEq, Eq)]') - fmt.line( - 'pub enum {} {{ {} }}' - .format(ty, ", ".join(camel_case(v) for v in setting.values))) + with fmt.indented('pub enum {} {{'.format(ty), '}'): + for v in setting.values: + fmt.doc_comment('`{}`.'.format(v)) + fmt.line(camel_case(v) + ',') def gen_getter(setting, sgrp, fmt): @@ -70,7 +72,7 @@ def gen_getters(sgrp, fmt): """ fmt.doc_comment("User-defined settings.") with fmt.indented('impl Flags {', '}'): - # Dynamic numbered predicate getter. + fmt.doc_comment('Dynamic numbered predicate getter.') with fmt.indented( 'pub fn numbered_predicate(&self, p: usize) -> bool {', '}'): fmt.line( @@ -187,6 +189,7 @@ def gen_constructor(sgrp, parent, fmt): if sgrp.parent: p = sgrp.parent args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args) + fmt.doc_comment('Create flags {} settings group.'.format(sgrp.name)) with fmt.indented( 'pub fn new({}) -> Flags {{'.format(args), '}'): fmt.line('let bvec = builder.state_for("{}");'.format(sgrp.name)) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index 76ccc27965..f68d8bcbf4 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -33,19 +33,12 @@ pub type BasicBlock = (Ebb, Inst); /// A container for the successors and predecessors of some Ebb. #[derive(Debug, Clone, Default)] pub struct CFGNode { + /// EBBs that are the targets of branches and jumps in this EBB. pub successors: Vec, + /// Basic blocks that can branch or jump to this EBB. pub predecessors: Vec, } -impl CFGNode { - pub fn new() -> CFGNode { - CFGNode { - successors: Vec::new(), - predecessors: Vec::new(), - } - } -} - /// The Control Flow Graph maintains a mapping of ebbs to their predecessors /// and successors where predecessors are basic blocks and successors are /// extended basic blocks. @@ -88,10 +81,12 @@ impl ControlFlowGraph { self.data[to].predecessors.push(from); } + /// Get the CFG predecessor basic blocks to `ebb`. pub fn get_predecessors(&self, ebb: Ebb) -> &Vec { &self.data[ebb].predecessors } + /// Get the CFG successors to `ebb`. pub fn get_successors(&self, ebb: Ebb) -> &Vec { &self.data[ebb].successors } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index c81e579f6b..e331f5657b 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -1,10 +1,11 @@ -/// ! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. +//! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. use cfg::*; use ir::Ebb; use ir::entities::NO_INST; use entity_map::EntityMap; +/// The dominator tree for a single function. pub struct DominatorTree { data: EntityMap>, } diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs index 692ecda96f..dfa6b054f5 100644 --- a/lib/cretonne/src/ir/condcodes.rs +++ b/lib/cretonne/src/ir/condcodes.rs @@ -29,15 +29,25 @@ pub trait CondCode: Copy { /// difference. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum IntCC { + /// `==`. Equal, + /// `!=`. NotEqual, + /// Signed `<`. SignedLessThan, + /// Signed `>=`. SignedGreaterThanOrEqual, + /// Signed `>`. SignedGreaterThan, + /// Signed `<=`. SignedLessThanOrEqual, + /// Unsigned `<`. UnsignedLessThan, + /// Unsigned `>=`. UnsignedGreaterThanOrEqual, + /// Unsigned `>`. UnsignedGreaterThan, + /// Unsigned `<=`. UnsignedLessThanOrEqual, } @@ -131,24 +141,38 @@ impl FromStr for IntCC { /// except the impossible `!UN & !EQ & !LT & !GT` and the always true `UN | EQ | LT | GT`. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum FloatCC { - Ordered, // EQ | LT | GT - Unordered, // UN + /// EQ | LT | GT + Ordered, + /// UN + Unordered, - Equal, // EQ - // The C '!=' operator is the inverse of '==': NotEqual. - NotEqual, // UN | LT | GT - OrderedNotEqual, // LT | GT - UnorderedOrEqual, // UN | EQ + /// EQ + Equal, + /// The C '!=' operator is the inverse of '==': `NotEqual`. + /// UN | LT | GT + NotEqual, + /// LT | GT + OrderedNotEqual, + /// UN | EQ + UnorderedOrEqual, - LessThan, // LT - LessThanOrEqual, // LT | EQ - GreaterThan, // GT - GreaterThanOrEqual, // GT | EQ + /// LT + LessThan, + /// LT | EQ + LessThanOrEqual, + /// GT + GreaterThan, + /// GT | EQ + GreaterThanOrEqual, - UnorderedOrLessThan, // UN | LT - UnorderedOrLessThanOrEqual, // UN | LT | EQ - UnorderedOrGreaterThan, // UN | GT - UnorderedOrGreaterThanOrEqual, // UN | GT | EQ + /// UN | LT + UnorderedOrLessThan, + /// UN | LT | EQ + UnorderedOrLessThanOrEqual, + /// UN | GT + UnorderedOrGreaterThan, + /// UN | GT | EQ + UnorderedOrGreaterThanOrEqual, } impl CondCode for FloatCC { diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index bd8913e5bf..384e4d3ae9 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -98,16 +98,16 @@ impl Default for Inst { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Value(u32); -// Value references can either reference an instruction directly, or they can refer to the extended -// value table. +/// Value references can either reference an instruction directly, or they can refer to the +/// extended value table. pub enum ExpandedValue { - // This is the first value produced by the referenced instruction. + /// This is the first value produced by the referenced instruction. Direct(Inst), - // This value is described in the extended value table. + /// This value is described in the extended value table. Table(usize), - // This is NO_VALUE. + /// This is NO_VALUE. None, } @@ -135,19 +135,23 @@ impl Value { None } } + /// Create a `Direct` value corresponding to the first value produced by `i`. pub fn new_direct(i: Inst) -> Value { let encoding = i.index() * 2; assert!(encoding < u32::MAX as usize); Value(encoding as u32) } + /// Create a `Table` value referring to entry `i` in the `DataFlowGraph.extended_values` table. + /// This constructor should not be used directly. Use the public `DataFlowGraph` methods to + /// manipulate values. pub fn new_table(index: usize) -> Value { let encoding = index * 2 + 1; assert!(encoding < u32::MAX as usize); Value(encoding as u32) } - // Expand the internal representation into something useful. + /// Expand the internal representation into something useful. pub fn expand(&self) -> ExpandedValue { use self::ExpandedValue::*; if *self == NO_VALUE { @@ -312,12 +316,19 @@ impl Default for SigRef { pub enum AnyEntity { /// The whole function. Function, + /// An extended basic block. Ebb(Ebb), + /// An instruction. Inst(Inst), + /// An SSA value. Value(Value), + /// A stack slot. StackSlot(StackSlot), + /// A jump table. JumpTable(JumpTable), + /// An external function. FuncRef(FuncRef), + /// A function call signature. SigRef(SigRef), } diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 6f89d5d307..a47ff4ad7a 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -14,11 +14,14 @@ use ir::{Type, FunctionName, SigRef}; /// details that are needed to call a function correctly. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Signature { + /// Types of the arguments passed to the function. pub argument_types: Vec, + /// Types returned from the function. pub return_types: Vec, } impl Signature { + /// Create a new blank signature. pub fn new() -> Signature { Signature { argument_types: Vec::new(), @@ -59,13 +62,16 @@ impl Display for Signature { /// how the argument is passed. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct ArgumentType { + /// Type of the argument value. pub value_type: Type, + /// Method for extending argument to a full register. pub extension: ArgumentExtension, /// Place this argument in a register if possible. pub inreg: bool, } impl ArgumentType { + /// Create an argument type with default flags. pub fn new(vt: Type) -> ArgumentType { ArgumentType { value_type: vt, @@ -109,7 +115,9 @@ pub enum ArgumentExtension { /// Information about a function that can be called directly with a direct `call` instruction. #[derive(Clone, Debug)] pub struct ExtFuncData { + /// Name of the external function. pub name: FunctionName, + /// Call signature of function. pub signature: SigRef, } diff --git a/lib/cretonne/src/ir/funcname.rs b/lib/cretonne/src/ir/funcname.rs index 0f6f106365..d84b25ac2b 100644 --- a/lib/cretonne/src/ir/funcname.rs +++ b/lib/cretonne/src/ir/funcname.rs @@ -14,6 +14,7 @@ use std::ascii::AsciiExt; pub struct FunctionName(String); impl FunctionName { + /// Create new function name equal to `s`. pub fn new>(s: S) -> FunctionName { FunctionName(s.into()) } diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 0395d13f26..e2fc0c446d 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -17,6 +17,7 @@ use std::str::FromStr; pub struct Imm64(i64); impl Imm64 { + /// Create a new `Imm64` representing the signed number `x`. pub fn new(x: i64) -> Imm64 { Imm64(x) } @@ -374,6 +375,7 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { } impl Ieee32 { + /// Create a new `Ieee32` representing the number `x`. pub fn new(x: f32) -> Ieee32 { Ieee32(x) } @@ -403,6 +405,7 @@ impl FromStr for Ieee32 { } impl Ieee64 { + /// Create a new `Ieee64` representing the number `x`. pub fn new(x: f64) -> Ieee64 { Ieee64(x) } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 103dcd4fe5..f601169beb 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -93,6 +93,7 @@ impl FromStr for Opcode { /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a /// `Box` to store the additional information out of line. #[derive(Clone, Debug)] +#[allow(missing_docs)] pub enum InstructionData { Nullary { opcode: Opcode, ty: Type }, Unary { @@ -226,14 +227,17 @@ pub enum InstructionData { pub struct VariableArgs(Vec); impl VariableArgs { + /// Create an empty argument list. pub fn new() -> VariableArgs { VariableArgs(Vec::new()) } + /// Add an argument to the end. pub fn push(&mut self, v: Value) { self.0.push(v) } + /// Check if the list is empty. pub fn is_empty(&self) -> bool { self.0.is_empty() } @@ -276,6 +280,7 @@ impl Default for VariableArgs { /// Payload data for `vconst`. #[derive(Clone, Debug)] pub struct UnaryImmVectorData { + /// Raw vector data. pub imm: ImmVector, } @@ -292,6 +297,7 @@ impl Display for UnaryImmVectorData { /// Payload data for ternary instructions with multiple results, such as `iadd_carry`. #[derive(Clone, Debug)] pub struct TernaryOverflowData { + /// Value arguments. pub args: [Value; 3], } @@ -305,7 +311,9 @@ impl Display for TernaryOverflowData { /// in the allowed InstructionData size. #[derive(Clone, Debug)] pub struct JumpData { + /// Jump destination EBB. pub destination: Ebb, + /// Arguments passed to destination EBB. pub varargs: VariableArgs, } @@ -323,8 +331,11 @@ impl Display for JumpData { /// in the allowed InstructionData size. #[derive(Clone, Debug)] pub struct BranchData { + /// Value argument controlling the branch. pub arg: Value, + /// Branch destination EBB. pub destination: Ebb, + /// Arguments passed to destination EBB. pub varargs: VariableArgs, } @@ -353,6 +364,8 @@ pub struct CallData { pub struct IndirectCallData { /// Callee function. pub arg: Value, + + /// Signature of the callee function. pub sig_ref: SigRef, /// Dynamically sized array containing call argument values. @@ -362,7 +375,7 @@ pub struct IndirectCallData { /// Payload of a return instruction. #[derive(Clone, Debug)] pub struct ReturnData { - // Dynamically sized array containing return values. + /// Dynamically sized array containing return values. pub varargs: VariableArgs, } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 67d5a91c29..5705fe3a55 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -87,6 +87,7 @@ impl settings::Configurable for Builder { } } +/// Methods that are specialized to a target ISA. pub trait TargetIsa { /// Get the name of this ISA. fn name(&self) -> &'static str; diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index cdc49f6973..06a9c890d0 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -16,6 +16,7 @@ struct Isa { cpumode: &'static [shared_enc_tables::Level1Entry], } +/// Get an ISA builder for creating RISC-V targets. pub fn isa_builder() -> IsaBuilder { IsaBuilder { setup: settings::builder(), diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index ae25157248..aef83bd433 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -1,14 +1,12 @@ +//! Cretonne code generation library. -// ====------------------------------------------------------------------------------------==== // -// -// Cretonne code generation library. -// -// ====------------------------------------------------------------------------------------==== // +#![deny(missing_docs)] pub use verifier::verify_function; pub use write::write_function; pub use legalizer::legalize_function; +/// Version number of the cretonne crate. pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod ir; diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 37d09d2ba8..291211b1b3 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -144,6 +144,7 @@ pub enum Error { BadValue, } +/// A result returned when changing a setting. pub type Result = result::Result; /// Implementation details for generated code. @@ -156,10 +157,15 @@ pub mod detail { /// An instruction group template. pub struct Template { + /// Name of the instruction group. pub name: &'static str, + /// List of setting descriptors. pub descriptors: &'static [Descriptor], + /// Union of all enumerators. pub enumerators: &'static [&'static str], + /// Hash table of settings. pub hash_table: &'static [u16], + /// Default values. pub defaults: &'static [u8], } @@ -227,7 +233,10 @@ pub mod detail { #[derive(Clone, Copy)] pub enum Detail { /// A boolean setting only uses one bit, numbered from LSB. - Bool { bit: u8 }, + Bool { + /// 0-7. + bit: u8, + }, /// A numerical setting uses the whole byte. Num, diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 2b544967e2..9e044a738a 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -61,7 +61,9 @@ use std::result; /// A verifier error. #[derive(Debug, PartialEq, Eq)] pub struct Error { + /// The entity causing the verifier error. pub location: AnyEntity, + /// Error message. pub message: String, } @@ -71,6 +73,7 @@ impl Display for Error { } } +/// Verifier result. pub type Result = result::Result; // Create an `Err` variant of `Result` from a location and `format!` args. @@ -90,11 +93,12 @@ macro_rules! err { }; } +/// Verify `func`. pub fn verify_function(func: &Function) -> Result<()> { Verifier::new(func).run() } -pub struct Verifier<'a> { +struct Verifier<'a> { func: &'a Function, } @@ -165,7 +169,7 @@ impl<'a> Verifier<'a> { #[cfg(test)] mod tests { - use super::*; + use super::{Verifier, Error}; use ir::Function; use ir::instructions::{InstructionData, Opcode}; use ir::types; From 2fa707bc4fa782bca4dc5807fe92c30c001c3c11 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Oct 2016 13:40:51 -0700 Subject: [PATCH 392/968] Add a document comparing Cretonne and LLVM. --- docs/compare-llvm.rst | 199 ++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + docs/langref.rst | 1 + 3 files changed, 201 insertions(+) create mode 100644 docs/compare-llvm.rst diff --git a/docs/compare-llvm.rst b/docs/compare-llvm.rst new file mode 100644 index 0000000000..550c6c6f5c --- /dev/null +++ b/docs/compare-llvm.rst @@ -0,0 +1,199 @@ +************************* +Cretonne compared to LLVM +************************* + +`LLVM `_ is a collection of compiler components implemented as +a set of C++ libraries. It can be used to build both JIT compilers and static +compilers like `Clang `_, and it is deservedly very +popular. `Chris Lattner's chapter about LLVM +`_ in the `Architecture of Open Source +Applications `_ book gives an excellent +overview of the architecture and design of LLVM. + +Cretonne and LLVM are superficially similar projects, so it is worth +highlighting some of the differences and similarities. Both projects: + +- Use an ISA-agnostic input language in order to mostly abstract away the + differences between target instruction set architectures. +- Depend extensively on SSA form. +- Have both textual and in-memory forms of their primary intermediate language. + (LLVM also has a binary bitcode format; Cretonne doesn't.) +- Can target multiple ISAs. +- Can cross-compile by default without rebuilding the code generator. + +Cretonne's scope is much smaller than that of LLVM. The classical three main +parts of a compiler are: + +1. The language-dependent front end parses and type-checks the input program. +2. Common optimizations that are independent of both the input language and the + target ISA. +3. The code generator which depends strongly on the target ISA. + +LLVM provides both common optimizations *and* a code generator. Cretonne only +provides the last part, the code generator. LLVM additionally provides +infrastructure for building assemblers and disassemblers. Cretonne does not +handle assembly at all---it only generates binary machine code. + +Intermediate representations +============================ + +LLVM uses multiple intermediate representations as it translates a program to +binary machine code: + +`LLVM IR `_ + This is the primary intermediate language which has textual, binary, and + in-memory representations. It serves two main purposes: + + - An ISA-agnostic, stable(ish) input language that front ends can generate + easily. + - Intermediate representation for common mid-level optimizations. A large + library of code analysis and transformation passes operate on LLVM IR. + +`SelectionDAG `_ + A graph-based representation of the code in a single basic block is used by + the instruction selector. It has both ISA-agnostic and ISA-specific + opcodes. These main passes are run on the SelectionDAG representation: + + - Type legalization eliminates all value types that don't have a + representation in the target ISA registers. + - Operation legalization eliminates all opcodes that can't be mapped to + target ISA instructions. + - DAG-combine cleans up redundant code after the legalization passes. + - Instruction selection translates ISA-agnostic expressions to ISA-specific + instructions. + + The SelectionDAG representation automatically eliminates common + subexpressions and dead code. + +`MachineInstr `_ + A linear representation of ISA-specific instructions that initially is in + SSA form, but it can also represent non-SSA form during and after register + allocation. Many low-level optimizations run on MI code. The most important + passes are: + + - Scheduling. + - Register allocation. + +`MC `_ + MC serves as the output abstraction layer and is the basis for LLVM's + integrated assembler. It is used for: + + - Branch relaxation. + - Emitting assembly or binary object code. + - Assemblers. + - Disassemblers. + +There is an ongoing "global instruction selection" project to replace the +SelectionDAG representation with ISA-agnostic opcodes on the MachineInstr +representation. Some target ISAs have a fast instruction selector that can +translate simple code directly to MachineInstrs, bypassing SelectionDAG when +possible. + +:doc:`Cretonne ` uses a single intermediate language to cover these +levels of abstraction. This is possible in part because of Cretonne's smaller +scope. + +- Cretonne does not provide assemblers and disassemblers, so it is not + necessary to be able to represent every weird instruction in an ISA. Only + those instructions that the code generator emits have a representation. +- Cretonne's opcodes are ISA-agnostic, but after legalization / instruction + selection, each instruction is annotated with an ISA-specific encoding which + represents a native instruction. +- SSA form is preserved throughout. After register allocation, each SSA value + is annotated with an assigned ISA register or stack slot. + +The Cretonne intermediate language is similar to LLVM IR, but at a slightly +lower level of abstraction. + +Program structure +----------------- + +In LLVM IR, the largest representable unit is the *module* which corresponds +more or less to a C translation unit. It is a collection of functions and +global variables that may contain references to external symbols too. + +In Cretonne IL, the largest representable unit is the *function*. This is so +that functions can easily be compiled in parallel without worrying about +references to shared data structures. Cretonne does not have any +inter-procedural optimizations like inlining. + +An LLVM IR function is a graph of *basic blocks*. A Cretonne IL function is a +graph of *extended basic blocks* that may contain internal branch instructions. +The main difference is that an LLVM conditional branch instruction has two +target basic blocks---a true and a false edge. A Cretonne branch instruction +only has a single target and falls through to the next instruction when its +condition is false. The Cretonne representation is closer to how machine code +works; LLVM's representation is more abstract. + +LLVM uses `phi instructions +`_ in its SSA +representation. Cretonne passes arguments to EBBs instead. The two +representations are equivalent, but the EBB arguments are better suited to +handle EBBs that main contain multiple branches to the same destination block +with different arguments. Passing arguments to an EBB looks a lot like passing +arguments to a function call, and the register allocator treats them very +similarly. Arguments are assigned to registers or stack locations. + +Value types +----------- + +:ref:`Cretonne's type system ` is mostly a subset of LLVM's type +system. It is less abstract and closer to the types that common ISA registers +can hold. + +- Integer types are limited to powers of two from :cton:type:`i8` to + :cton:type:`i64`. LLVM can represent integer types of arbitrary bit width. +- Floating point types are limited to :cton:type:`f32` and :cton:type:`f64` + which is what WebAssembly provides. It is possible that 16-bit and 128-bit + types will be added in the future/ +- Addresses are represented as integers---There are no Cretonne pointer types. + LLVM currently has rich pointer types that include the pointee type. It may + move to a simpler 'address' type in the future. Cretonne may add a single + address type too. +- SIMD vector types are limited to a power-of-two number of vector lanes up to + 256. LLVM allows an arbitrary number of SIMD lanes. +- Cretonne has no aggregrate types. LLVM has named and anonymous struct types as + well as array types. + +Cretonne has multiple boolean types, whereas LLVM simply uses `i1`. The sized +Cretonne boolean types are used to represent SIMD vector masks like ``b32x4`` +where each lane is either all 0 or all 1 bits. + +Cretonne instructions and function calls can return multiple result values. LLVM +instead models this by returning a single value of an aggregrate type. + +Instruction set +--------------- + +LLVM has a small well-defined basic instruction set and a large number of +intrinsics, some of which are ISA-specific. Cretonne has a larger instruction +set and no intrinsics. Some Cretonne instructions are ISA-specific. + +Since Cretonne instructions are used all the way until the binary machine code +is emitted, there are opcodes for every native instruction that can be +generated. There is a lot of overlap between different ISAs, so for example the +:cton:inst:`iadd_imm` instruction is used by every ISA that can add an +immediate integer to a register. A simle RISC ISA like RISC-V can be defined +with only shared instructions, while an Intel ISA needs a number of specific +instructions to model addressing modes. + +Undefined behavior +================== + +Cretonne does not generally exploit undefined behavior in its optimizations. +LLVM's mid-level optimizations do, but it should be noted that LLVM's low-level code +generator rarely needs to make use of undefined behavior either. + +LLVM provides ``nsw`` and ``nuw`` flags for its arithmetic that invoke +undefined behavior on overflow. Cretonne does not provide this functionality. +Its arithmetic instructions either produce a value or a trap. + +LLVM has an ``unreachable`` instruction which is used to indicate impossible +code paths. Cretonne only has an explicit :cton:inst:`trap` instruction. + +Cretonne does make assumptions about aliasing. For example, it assumes that it +has full control of the stack objects in a function, and that they can only be +modified by function calls if their address have escaped. It is quite likely +that Cretonne will admit more detailed aliasing annotations on load/store +instructions in the future. When these annotations are incorrect, undefined +behavior ensues. diff --git a/docs/index.rst b/docs/index.rst index b5a23ab28b..8df4042db3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Contents: langref metaref testing + compare-llvm Indices and tables ================== diff --git a/docs/langref.rst b/docs/langref.rst index 6fbe3610de..f8b59e560c 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -83,6 +83,7 @@ instructions which behave more like variable accesses in a typical programming language. Cretonne can perform the necessary dataflow analysis to convert stack slots to SSA form. +.. _value-types: Value types =========== From 2cb126341b2b12861857e568642b866c7284fca9 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Fri, 28 Oct 2016 16:04:43 +0200 Subject: [PATCH 393/968] Add build instructions in the REAME; --- README.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.rst b/README.rst index a80a23a283..1eb43c6981 100644 --- a/README.rst +++ b/README.rst @@ -36,6 +36,26 @@ Predictable performance the quirks of the target architecture. There are no advanced optimizations that sometimes work, somtimes fail. +Building Cretonne +----------------- + +Cretonne is using the Cargo package manager format. First, ensure you have +installed `rust 1.12.0` or above. Then, change the workind directory to your +clone of cretonne and run:: + + cargo build + +This will create a *target/debug* directory where you can find the generated +binary. + +To build the optimized binary for release:: + + cargo build --release + +You can then run tests with:: + + ./test-all.sh + Building the documentation -------------------------- From 72479b53ff48a81e24e69d684166c76915bd97e9 Mon Sep 17 00:00:00 2001 From: Joel Gallant Date: Fri, 28 Oct 2016 08:16:28 -0600 Subject: [PATCH 394/968] Typo fix --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1eb43c6981..3ceef14248 100644 --- a/README.rst +++ b/README.rst @@ -34,7 +34,7 @@ Scalable performance Predictable performance When optimizing, Cretonne focuses on adapting the target-independent IL to the quirks of the target architecture. There are no advanced optimizations - that sometimes work, somtimes fail. + that sometimes work, sometimes fail. Building Cretonne ----------------- From 409ce915130747279a7531d22047e8abe7b6aed8 Mon Sep 17 00:00:00 2001 From: Sean Gillespie Date: Thu, 27 Oct 2016 23:12:35 -0700 Subject: [PATCH 395/968] Use `items` instead of `iteritems` for Python 3 compatibility --- lib/cretonne/meta/cretonne/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 6672d6d869..a3f0634b94 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -184,7 +184,7 @@ class SettingGroup(object): .format(self, SettingGroup._current)) SettingGroup._current = None if globs: - for name, obj in globs.iteritems(): + for name, obj in globs.items(): if isinstance(obj, Setting): assert obj.name is None, obj.name obj.name = name @@ -761,7 +761,7 @@ class InstructionFormat(object): all the InstructionFormat objects and set their name from the dict key. This is used to name a bunch of global variables in a module. """ - for name, obj in globs.iteritems(): + for name, obj in globs.items(): if isinstance(obj, InstructionFormat): assert obj.name is None obj.name = name From de109103242b6f93b62a8761f77d4bddb879db0c Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Fri, 28 Oct 2016 18:56:52 +0200 Subject: [PATCH 396/968] Fix a few typos in the docs; --- docs/compare-llvm.rst | 6 +++--- docs/langref.rst | 4 ++-- docs/metaref.rst | 6 +++--- docs/testing.rst | 4 ++-- lib/cretonne/meta/cretonne/base.py | 10 +++++----- lib/cretonne/meta/cretonne/entities.py | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/compare-llvm.rst b/docs/compare-llvm.rst index 550c6c6f5c..dc19a64983 100644 --- a/docs/compare-llvm.rst +++ b/docs/compare-llvm.rst @@ -129,7 +129,7 @@ LLVM uses `phi instructions `_ in its SSA representation. Cretonne passes arguments to EBBs instead. The two representations are equivalent, but the EBB arguments are better suited to -handle EBBs that main contain multiple branches to the same destination block +handle EBBs that may contain multiple branches to the same destination block with different arguments. Passing arguments to an EBB looks a lot like passing arguments to a function call, and the register allocator treats them very similarly. Arguments are assigned to registers or stack locations. @@ -145,7 +145,7 @@ can hold. :cton:type:`i64`. LLVM can represent integer types of arbitrary bit width. - Floating point types are limited to :cton:type:`f32` and :cton:type:`f64` which is what WebAssembly provides. It is possible that 16-bit and 128-bit - types will be added in the future/ + types will be added in the future. - Addresses are represented as integers---There are no Cretonne pointer types. LLVM currently has rich pointer types that include the pointee type. It may move to a simpler 'address' type in the future. Cretonne may add a single @@ -173,7 +173,7 @@ Since Cretonne instructions are used all the way until the binary machine code is emitted, there are opcodes for every native instruction that can be generated. There is a lot of overlap between different ISAs, so for example the :cton:inst:`iadd_imm` instruction is used by every ISA that can add an -immediate integer to a register. A simle RISC ISA like RISC-V can be defined +immediate integer to a register. A simple RISC ISA like RISC-V can be defined with only shared instructions, while an Intel ISA needs a number of specific instructions to model addressing modes. diff --git a/docs/langref.rst b/docs/langref.rst index f8b59e560c..164e6590bb 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -514,7 +514,7 @@ simply represent a contiguous sequence of bytes in the stack frame. :arg SS: Stack slot declared with :inst:`stack_slot`. :arg Offset: Immediate non-negative offset. -The dedicated stack access instructions are easy ofr the compiler to reason +The dedicated stack access instructions are easy for the compiler to reason about because stack slots and offsets are fixed at compile time. For example, the alignment of these stack memory accesses can be inferred from the offsets and stack slot alignments. @@ -583,7 +583,7 @@ than the native pointer size, for example unsigned :type:`i32` offsets on a Trap if the heap access would be out of bounds. - :arg H: Heap indetifier created by :inst:`heap`. + :arg H: Heap identifier created by :inst:`heap`. :arg T x: Value to be stored. :arg iN p: Unsigned base address in heap. :arg Offset: Immediate signed offset. diff --git a/docs/metaref.rst b/docs/metaref.rst index cc6d7173d6..fa3226c51f 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -139,7 +139,7 @@ indicated with an instance of :class:`ImmediateKind`. Entity references ----------------- -Instruction operands can also refer to other entties in the same function. This +Instruction operands can also refer to other entities in the same function. This can be extended basic blocks, or entities declared in the function preamble. .. autoclass:: EntityRefKind @@ -248,7 +248,7 @@ Encodings ========= Encodings describe how Cretonne instructions are mapped to binary machine code -for the target architecture. After the lealization pass, all remaining +for the target architecture. After the legalization pass, all remaining instructions are expected to map 1-1 to native instruction encodings. Cretonne instructions that can't be encoded for the current architecture are called :term:`illegal instruction`\s. @@ -256,7 +256,7 @@ instructions that can't be encoded for the current architecture are called Some instruction set architectures have different :term:`CPU mode`\s with incompatible encodings. For example, a modern ARMv8 CPU might support three different CPU modes: *A64* where instructions are encoded in 32 bits, *A32* -where all instuctions are 32 bits, and *T32* which has a mix of 16-bit and +where all instructions are 32 bits, and *T32* which has a mix of 16-bit and 32-bit instruction encodings. These are incompatible encoding spaces, and while an :cton:inst:`iadd` instruction can be encoded in 32 bits in each of them, it's not the same 32 bits. It's a judgement call if CPU modes should be modelled as diff --git a/docs/testing.rst b/docs/testing.rst index 0f0527bf06..419f51ff22 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -145,10 +145,10 @@ See the :file:`lib/filecheck` `documentation `_ for details of its syntax. Comments in :file:`.cton` files are associated with the entity they follow. -This typically means and instruction or the whole function. Those tests that +This typically means an instruction or the whole function. Those tests that use filecheck will extract comments associated with each function (or its entities) and scan them for filecheck directives. The test output for each -function is then matched againts the filecheck directives for that function. +function is then matched against the filecheck directives for that function. Note that LLVM's file tests don't separate filecheck directives by their associated function. It verifies the concatenated output against all filecheck diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index 1edbce9ecd..27a8be9ac6 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -134,7 +134,7 @@ call_indirect = Instruction( Indirect function call. Call the function pointed to by `callee` with the given arguments. The - called function must match the soecified signature. + called function must match the specified signature. """, ins=(SIG, callee, args), outs=rvals) @@ -392,9 +392,9 @@ srem = Instruction( .. todo:: Integer remainder vs modulus. - Clarify whether the result has the sign of the divisor or the dividend. - Should we add a ``smod`` instruction for the case where the result has - the same sign as the divisor? + Clarify whether the result has the sign of the divisor or the dividend. + Should we add a ``smod`` instruction for the case where the result has + the same sign as the divisor? """, ins=(x, y), outs=a) @@ -900,7 +900,7 @@ fma = Instruction( 'fma', r""" Floating point fused multiply-and-add. - Computes :math:`a := xy+z` wihtout any intermediate rounding of the + Computes :math:`a := xy+z` without any intermediate rounding of the product. """, ins=(x, y, z), outs=a) diff --git a/lib/cretonne/meta/cretonne/entities.py b/lib/cretonne/meta/cretonne/entities.py index 317006f79f..afeeea7e98 100644 --- a/lib/cretonne/meta/cretonne/entities.py +++ b/lib/cretonne/meta/cretonne/entities.py @@ -1,6 +1,6 @@ """ The `cretonne.entities` module predefines all the Cretonne entity reference -operand types. Thee are corresponding definitions in the `cretonne.entities` +operand types. There are corresponding definitions in the `cretonne.entities` Rust module. """ from __future__ import absolute_import From c2dc400572a69fae75e782ed54ebfb24e22ef72c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Oct 2016 11:49:35 -0700 Subject: [PATCH 397/968] Typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3ceef14248..8befcf42e2 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Building Cretonne ----------------- Cretonne is using the Cargo package manager format. First, ensure you have -installed `rust 1.12.0` or above. Then, change the workind directory to your +installed `rust 1.12.0` or above. Then, change the working directory to your clone of cretonne and run:: cargo build From b7634ae5c323fb93e5e6b1a97bda5e67070eeecb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 1 Nov 2016 11:55:07 -0700 Subject: [PATCH 398/968] Run unittests with Python 3 if it is available. The check.sh script always runs the Python unittests with 'python', but if 'python3' is in the path, run it with that too. Fix a Python 3 compat issue and avoid passing None to max() and min(). Use an explicit intersect() function instead to intersect intervals. --- lib/cretonne/meta/check.sh | 9 +++++ lib/cretonne/meta/cretonne/base.py | 6 +-- lib/cretonne/meta/cretonne/typevar.py | 55 +++++++++++++++++++-------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh index 072066ebec..e1e8b1f0b2 100755 --- a/lib/cretonne/meta/check.sh +++ b/lib/cretonne/meta/check.sh @@ -18,5 +18,14 @@ runif() { # # Install pylint with 'pip install pylint'. runif pylint --py3k --reports=no -- *.py cretonne isa + +# Then run the unit tests again with Python 3. +# We get deprecation warnings about assertRaisesRegexp which was renamed in +# Python 3, but there doesn't seem to be an easy workaround. +runif python3 -Wignore:Deprecation -m unittest discover + +# Style linting. runif flake8 . + +# Type checking. runif mypy --py2 build.py diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index 27a8be9ac6..670d85cc0c 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -392,9 +392,9 @@ srem = Instruction( .. todo:: Integer remainder vs modulus. - Clarify whether the result has the sign of the divisor or the dividend. - Should we add a ``smod`` instruction for the case where the result has - the same sign as the divisor? + Clarify whether the result has the sign of the divisor or the + dividend. Should we add a ``smod`` instruction for the case where + the result has the same sign as the divisor? """, ins=(x, y), outs=a) diff --git a/lib/cretonne/meta/cretonne/typevar.py b/lib/cretonne/meta/cretonne/typevar.py index a29548af38..f4b227fd7e 100644 --- a/lib/cretonne/meta/cretonne/typevar.py +++ b/lib/cretonne/meta/cretonne/typevar.py @@ -8,19 +8,45 @@ from __future__ import absolute_import import math from . import value +try: + from typing import Tuple # noqa + Interval = Tuple[int, int] +except ImportError: + pass MAX_LANES = 256 MAX_BITS = 64 def is_power_of_two(x): + # type: (int) -> bool return x > 0 and x & (x-1) == 0 def int_log2(x): + # type: (int) -> int return int(math.log(x, 2)) +def intersect(a, b): + # type: (Interval, Interval) -> Interval + """ + Given two `(min, max)` inclusive intervals, compute their intersection. + + Use `(None, None)` to represent the empty interval on input and output. + """ + if a[0] is None or b[0] is None: + return (None, None) + lo = max(a[0], b[0]) + assert lo is not None + hi = min(a[1], b[1]) + assert hi is not None + if lo <= hi: + return (lo, hi) + else: + return (None, None) + + class TypeSet(object): """ A set of types. @@ -69,6 +95,7 @@ class TypeSet(object): """ def __init__(self, lanes=None, ints=None, floats=None, bools=None): + # type: (Interval, Interval, Interval, Interval) -> None if lanes: if lanes is True: lanes = (1, MAX_LANES) @@ -119,6 +146,7 @@ class TypeSet(object): self.max_bool = None def typeset_key(self): + # type: () -> Tuple[int, int, int, int, int, int, int, int] """Key tuple used for hashing and equality.""" return (self.min_lanes, self.max_lanes, self.min_int, self.max_int, @@ -126,6 +154,7 @@ class TypeSet(object): self.min_bool, self.max_bool) def __hash__(self): + # type: () -> int h = hash(self.typeset_key()) assert h == getattr(self, 'prev_hash', h), "TypeSet changed!" self.prev_hash = h @@ -135,6 +164,7 @@ class TypeSet(object): return self.typeset_key() == other.typeset_key() def __repr__(self): + # type: () -> str s = 'TypeSet(lanes=({}, {})'.format(self.min_lanes, self.max_lanes) if self.min_int is not None: s += ', ints=({}, {})'.format(self.min_int, self.max_int) @@ -161,6 +191,7 @@ class TypeSet(object): field, int_log2(max_val) + 1)) def __iand__(self, other): + # type: (TypeSet) -> TypeSet """ Intersect self with other type set. @@ -181,23 +212,17 @@ class TypeSet(object): self.min_lanes = max(self.min_lanes, other.min_lanes) self.max_lanes = min(self.max_lanes, other.max_lanes) - self.min_int = max(self.min_int, other.min_int) - self.max_int = min(self.max_int, other.max_int) - if self.min_int > self.max_int: - self.min_int = None - self.max_int = None + self.min_int, self.max_int = intersect( + (self.min_int, self.max_int), + (other.min_int, other.max_int)) - self.min_float = max(self.min_float, other.min_float) - self.max_float = min(self.max_float, other.max_float) - if self.min_float > self.max_float: - self.min_float = None - self.max_float = None + self.min_float, self.max_float = intersect( + (self.min_float, self.max_float), + (other.min_float, other.max_float)) - self.min_bool = max(self.min_bool, other.min_bool) - self.max_bool = min(self.max_bool, other.max_bool) - if self.min_bool > self.max_bool: - self.min_bool = None - self.max_bool = None + self.min_bool, self.max_bool = intersect( + (self.min_bool, self.max_bool), + (other.min_bool, other.max_bool)) return self From bfeb9e483970abfe0bac578745d8efe0142c95a1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 1 Nov 2016 14:23:41 -0700 Subject: [PATCH 399/968] Add type annotations to TypeVar --- lib/cretonne/meta/cretonne/typevar.py | 109 ++++++++++++-------------- 1 file changed, 48 insertions(+), 61 deletions(-) diff --git a/lib/cretonne/meta/cretonne/typevar.py b/lib/cretonne/meta/cretonne/typevar.py index f4b227fd7e..fa0179304b 100644 --- a/lib/cretonne/meta/cretonne/typevar.py +++ b/lib/cretonne/meta/cretonne/typevar.py @@ -6,11 +6,13 @@ polymorphic by using type variables. """ from __future__ import absolute_import import math -from . import value +from . import OperandKind, value # noqa try: - from typing import Tuple # noqa + from typing import Tuple, Union # noqa Interval = Tuple[int, int] + # An Interval where `True` means 'everything' + BoolInterval = Union[bool, Interval] except ImportError: pass @@ -47,6 +49,34 @@ def intersect(a, b): return (None, None) +def decode_interval(intv, full_range, default=None): + # type: (BoolInterval, Interval, int) -> Interval + """ + Decode an interval specification which can take the following values: + + True + Use the `full_range`. + `False` or `None` + An empty interval + (lo, hi) + An explicit interval + """ + if isinstance(intv, tuple): + # mypy buig here: 'builtins.None' object is not iterable + lo, hi = intv # type: ignore + assert is_power_of_two(lo) + assert is_power_of_two(hi) + assert lo <= hi + assert lo >= full_range[0] + assert hi <= full_range[1] + return intv + + if intv: + return full_range + else: + return (default, default) + + class TypeSet(object): """ A set of types. @@ -95,55 +125,12 @@ class TypeSet(object): """ def __init__(self, lanes=None, ints=None, floats=None, bools=None): - # type: (Interval, Interval, Interval, Interval) -> None - if lanes: - if lanes is True: - lanes = (1, MAX_LANES) - self.min_lanes, self.max_lanes = lanes - assert is_power_of_two(self.min_lanes) - assert is_power_of_two(self.max_lanes) - assert self.max_lanes <= MAX_LANES - else: - self.min_lanes = 1 - self.max_lanes = 1 - assert self.min_lanes <= self.max_lanes - - if ints: - if ints is True: - ints = (8, MAX_BITS) - self.min_int, self.max_int = ints - assert is_power_of_two(self.min_int) - assert is_power_of_two(self.max_int) - assert self.max_int <= MAX_BITS - assert self.min_int <= self.max_int - else: - self.min_int = None - self.max_int = None - - if floats: - if floats is True: - floats = (32, 64) - self.min_float, self.max_float = floats - assert is_power_of_two(self.min_float) - assert self.min_float >= 32 - assert is_power_of_two(self.max_float) - assert self.max_float <= 64 - assert self.min_float <= self.max_float - else: - self.min_float = None - self.max_float = None - - if bools: - if bools is True: - bools = (1, MAX_BITS) - self.min_bool, self.max_bool = bools - assert is_power_of_two(self.min_bool) - assert is_power_of_two(self.max_bool) - assert self.max_bool <= MAX_BITS - assert self.min_bool <= self.max_bool - else: - self.min_bool = None - self.max_bool = None + # type: (BoolInterval, BoolInterval, BoolInterval, BoolInterval) -> None # noqa + self.min_lanes, self.max_lanes = decode_interval( + lanes, (1, MAX_LANES), 1) + self.min_int, self.max_int = decode_interval(ints, (8, MAX_BITS)) + self.min_float, self.max_float = decode_interval(floats, (32, 64)) + self.min_bool, self.max_bool = decode_interval(bools, (1, MAX_BITS)) def typeset_key(self): # type: () -> Tuple[int, int, int, int, int, int, int, int] @@ -253,6 +240,7 @@ class TypeVar(object): ints=False, floats=False, bools=False, scalars=True, simd=False, base=None, derived_func=None): + # type: (str, str, BoolInterval, BoolInterval, BoolInterval, bool, BoolInterval, TypeVar, str) -> None # noqa self.name = name self.__doc__ = doc self.is_derived = isinstance(base, TypeVar) @@ -264,25 +252,19 @@ class TypeVar(object): self.name = '{}({})'.format(derived_func, base.name) else: min_lanes = 1 if scalars else 2 - if simd: - if simd is True: - max_lanes = MAX_LANES - else: - min_lanes, max_lanes = simd - assert not scalars or min_lanes <= 2 - else: - max_lanes = 1 - + lanes = decode_interval(simd, (min_lanes, MAX_LANES), 1) self.type_set = TypeSet( - lanes=(min_lanes, max_lanes), + lanes=lanes, ints=ints, floats=floats, bools=bools) def __str__(self): + # type: () -> str return "`{}`".format(self.name) def lane_of(self): + # type: () -> TypeVar """ Return a derived type variable that is the scalar lane type of this type variable. @@ -293,6 +275,7 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='LaneOf') def as_bool(self): + # type: () -> TypeVar """ Return a derived type variable that has the same vector geometry as this type variable, but with boolean lanes. Scalar types map to `b1`. @@ -300,6 +283,7 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='AsBool') def half_width(self): + # type: () -> TypeVar """ Return a derived type variable that has the same number of vector lanes as this one, but the lanes are half the width. @@ -315,6 +299,7 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='HalfWidth') def double_width(self): + # type: () -> TypeVar """ Return a derived type variable that has the same number of vector lanes as this one, but the lanes are double the width. @@ -330,12 +315,14 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='DoubleWidth') def operand_kind(self): + # type: () -> OperandKind # When a `TypeVar` object is used to describe the type of an `Operand` # in an instruction definition, the kind of that operand is an SSA # value. return value def free_typevar(self): + # type: () -> TypeVar if self.is_derived: return self.base else: From 0b7010e3670352554ecdda433974fa9e9df851d6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Nov 2016 09:58:02 -0700 Subject: [PATCH 400/968] Add glossary entries for IL and IR. --- docs/langref.rst | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 164e6590bb..5dcf80a893 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -5,10 +5,10 @@ Cretonne Language Reference .. default-domain:: cton .. highlight:: cton -The Cretonne intermediate language has two equivalent representations: an -*in-memory data structure* that the code generator library is using, and -a *text format* which is used for test cases and debug output. Files containing -Cretonne textual IL have the ``.cton`` filename extension. +The Cretonne intermediate language (:term:`IL`) has two equivalent +representations: an *in-memory data structure* that the code generator library +is using, and a *text format* which is used for test cases and debug output. +Files containing Cretonne textual IL have the ``.cton`` filename extension. This reference uses the text format to describe IL semantics but glosses over the finer details of the lexical and syntactic structure of the format. @@ -76,9 +76,9 @@ variable during each iteration. Finally, ``v12`` is computed as the induction variable value for the next iteration. It can be difficult to generate correct SSA form if the program being converted -into Cretonne IL contains multiple assignments to the same variables. Such -variables can be presented to Cretonne as :term:`stack slot`\s instead. Stack -slots are accessed with the :inst:`stack_store` and :inst:`stack_load` +into Cretonne :term:`IL` contains multiple assignments to the same variables. +Such variables can be presented to Cretonne as :term:`stack slot`\s instead. +Stack slots are accessed with the :inst:`stack_store` and :inst:`stack_load` instructions which behave more like variable accesses in a typical programming language. Cretonne can perform the necessary dataflow analysis to convert stack slots to SSA form. @@ -275,11 +275,11 @@ indicate the different kinds of immediate operands on an instruction. A floating point condition code. See the :inst:`fcmp` instruction for details. The two IEEE floating point immediate types :type:`ieee32` and :type:`ieee64` -are displayed as hexadecimal floating point literals in the textual IL format. -Decimal floating point literals are not allowed because some computer systems -can round differently when converting to binary. The hexadecimal floating point -format is mostly the same as the one used by C99, but extended to represent all -NaN bit patterns: +are displayed as hexadecimal floating point literals in the textual :term:`IL` +format. Decimal floating point literals are not allowed because some computer +systems can round differently when converting to binary. The hexadecimal +floating point format is mostly the same as the one used by C99, but extended +to represent all NaN bit patterns: Normal numbers Compatible with C99: ``-0x1.Tpe`` where ``T`` are the trailing @@ -859,6 +859,20 @@ Glossary .. glossary:: + intermediate language + IL + The language used to describe functions to Cretonne. This reference + describes the syntax and semantics of the Cretonne IL. The IL has two + forms: Textual and an in-memory intermediate representation + (:term:`IR`). + + intermediate representation + IR + The in-memory representation of :term:`IL`. The data structures + Cretonne uses to represent a program internally are called the + intermediate representation. Cretonne's IR can be converted to text + losslessly. + function signature A function signature describes how to call a function. It consists of: From 22e75b127156dce933f15a44fd6515508a3288d9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Nov 2016 10:45:59 -0700 Subject: [PATCH 401/968] Fix inconsistent instruction name. The 'fpromote' instruction was renamed from 'fcvt_ftof', but the name argument was not changed. --- lib/cretonne/meta/cretonne/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index 670d85cc0c..adbbf68d28 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -1089,7 +1089,7 @@ x = Operand('x', Float) a = Operand('a', FloatTo) fpromote = Instruction( - 'fcvt_ftof', r""" + 'fpromote', r""" Convert `x` to a larger floating point format. Each lane in `x` is converted to the destination floating point format. From 88cbd5a43b89832f1b7fc930a0ee39fe7ed23b6c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Nov 2016 14:28:37 -0700 Subject: [PATCH 402/968] Canonicalize the objects in an RTL list. Any Apply objects in the input are converted to Defs with empty def lists. --- lib/cretonne/meta/cretonne/xform.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 1100e4721f..5149c02991 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -15,12 +15,25 @@ SRCCTX = 1 DSTCTX = 2 +def canonicalize_defapply(node): + # type: (DefApply) -> Def + """ + Canonicalize a `Def` or `Apply` node into a `Def`. + + An `Apply` becomes a `Def` with an empty list of defs. + """ + if isinstance(node, Apply): + return Def((), node) + else: + return node + + class Rtl(object): """ Register Transfer Language list. An RTL object contains a list of register assignments in the form of `Def` - objects and/or Apply objects for side-effecting instructions. + objects. An RTL list can represent both a source pattern to be matched, or a destination pattern to be inserted. @@ -28,10 +41,10 @@ class Rtl(object): def __init__(self, *args): # type: (*DefApply) -> None - self.rtl = args + self.rtl = tuple(map(canonicalize_defapply, args)) def __iter__(self): - # type: () -> Iterator[DefApply] + # type: () -> Iterator[Def] return iter(self.rtl) From 1ba8e9e05b71db73ab86b296ccd4bab10a8b6bef Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 1 Nov 2016 16:10:38 -0700 Subject: [PATCH 403/968] Classify Vars in patterns. There's 4 classes of variables, depending on whether they have defs in the source and destination patterns. Add more XForm verification: In a legalize XForm, all source defs must be outputs. Fix a legalize pattern bug caught by this. --- lib/cretonne/meta/check.sh | 17 +++++----- lib/cretonne/meta/cretonne/ast.py | 45 ++++++++++++++++++++++++++ lib/cretonne/meta/cretonne/legalize.py | 2 +- lib/cretonne/meta/cretonne/xform.py | 30 ++++++++++++----- lib/cretonne/meta/gen_legalizer.py | 2 +- 5 files changed, 78 insertions(+), 18 deletions(-) diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh index e1e8b1f0b2..b0a6c853db 100755 --- a/lib/cretonne/meta/check.sh +++ b/lib/cretonne/meta/check.sh @@ -2,9 +2,6 @@ set -e cd $(dirname "$0") -echo "=== Python unit tests ===" -python -m unittest discover - runif() { if command -v "$1" > /dev/null; then echo "=== $1 ===" @@ -19,13 +16,17 @@ runif() { # Install pylint with 'pip install pylint'. runif pylint --py3k --reports=no -- *.py cretonne isa -# Then run the unit tests again with Python 3. -# We get deprecation warnings about assertRaisesRegexp which was renamed in -# Python 3, but there doesn't seem to be an easy workaround. -runif python3 -Wignore:Deprecation -m unittest discover - # Style linting. runif flake8 . # Type checking. runif mypy --py2 build.py + +echo "=== Python unit tests ===" +python -m unittest discover + +# Then run the unit tests again with Python 3. +# We get deprecation warnings about assertRaisesRegexp which was renamed in +# Python 3, but there doesn't seem to be an easy workaround. +runif python3 -Wignore:Deprecation -m unittest discover + diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py index 693a0b5a2b..a43ddfadcb 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cretonne/ast.py @@ -73,6 +73,24 @@ class Expr(object): class Var(Expr): """ A free variable. + + When variables are used in `XForms` with source ans destination patterns, + they are classified as follows: + + Input values + Uses in the source pattern with no preceding def. These may appear as + inputs in the destination pattern too, but no new inputs can be + introduced. + Output values + Variables that are defined in both the source and destination pattern. + These values may have uses outside the source pattern, and the + destination pattern must compute the same value. + Intermediate values + Values that are defined in the source pattern, but not in the + destination pattern. These may have uses outside the source pattern, so + the defining instruction can't be deleted immediately. + Temporary values + Values that are defined only in the destination pattern. """ def __init__(self, name): @@ -82,15 +100,42 @@ class Var(Expr): # See XForm._rewrite_defs(). self.defctx = 0 + # Context bits for `defctx` indicating which pattern has defines of this + # var. + SRCCTX = 1 + DSTCTX = 2 + def __str__(self): + # type: () -> str return self.name def __repr__(self): + # type: () -> str s = self.name if self.defctx: s += ", d={:02b}".format(self.defctx) return "Var({})".format(s) + def is_input(self): + # type: () -> bool + """Is this an input value to the source pattern?""" + return self.defctx == 0 + + def is_output(self): + """Is this an output value, defined in both src and dest patterns?""" + # type: () -> bool + return self.defctx == self.SRCCTX | self.DSTCTX + + def is_intermediate(self): + """Is this an intermediate value, defined only in the src pattern?""" + # type: () -> bool + return self.defctx == self.SRCCTX + + def is_temp(self): + """Is this a temp value, defined only in the dest pattern?""" + # type: () -> bool + return self.defctx == self.DSTCTX + class Apply(Expr): """ diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py index 21073d0aec..8dfa2e6b97 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/cretonne/legalize.py @@ -114,5 +114,5 @@ expand.legalize( Rtl( (a1, b1) << isub_bout(x, y), (a, b2) << isub_bout(a1, b_in), - c << bor(b1, b2) + b << bor(b1, b2) )) diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 5149c02991..2888b9c26c 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -11,10 +11,6 @@ except ImportError: pass -SRCCTX = 1 -DSTCTX = 2 - - def canonicalize_defapply(node): # type: (DefApply) -> Def """ @@ -87,13 +83,13 @@ class XForm(object): # Rewrite variables in src and dst RTL lists to our own copies. # Map name -> private Var. symtab = dict() # type: Dict[str, Var] - self._rewrite_rtl(src, symtab, SRCCTX) + self._rewrite_rtl(src, symtab, Var.SRCCTX) num_src_inputs = len(self.inputs) - self._rewrite_rtl(dst, symtab, DSTCTX) + self._rewrite_rtl(dst, symtab, Var.DSTCTX) # Check for inconsistently used inputs. for i in self.inputs: - if i.defctx: + if not i.is_input(): raise AssertionError( "'{}' used as both input and def".format(i)) @@ -189,6 +185,22 @@ class XForm(object): self.inputs.append(var) yield var + def verify_legalize(self): + # type: () -> None + """ + Verify that this is a valid legalization XForm. + + - The source pattern must describe a single instruction. + - All values defined in the output pattern must be defined in the + destination pattern. + """ + assert len(self.src.rtl) == 1, "Legalize needs single instruction." + defs, expr = self.src.rtl[0].defs_expr() + for d in defs: + if not d.is_output(): + raise AssertionError( + '{} not defined in dest pattern'.format(d)) + class XFormGroup(object): """ @@ -209,4 +221,6 @@ class XFormGroup(object): :param src: Single `Def` or `Apply` to be legalized. :param dst: `Rtl` list of replacement instructions. """ - self.xforms.append(XForm(Rtl(src), dst)) + xform = XForm(Rtl(src), dst) + xform.verify_legalize() + self.xforms.append(xform) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 0fdfc921af..2a2387723f 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -14,7 +14,7 @@ from cretonne.ast import Def, Apply # noqa from cretonne.xform import XForm, XFormGroup # noqa try: - from typing import Union # noqa + from typing import Union DefApply = Union[Def, Apply] except ImportError: pass From f2c7a1d57b192420bd41aa6c6bfce97c84b4e6ee Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Nov 2016 14:41:30 -0700 Subject: [PATCH 404/968] Set expectations. --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 8befcf42e2..b98a4893fa 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,8 @@ Cretonne Code Generator Cretonne is a low-level retargetable code generator. It translates a target-independent intermediate language into executable machine code. +*This is a work in progress that is not yet functional.* + .. image:: https://readthedocs.org/projects/cretonne/badge/?version=latest :target: https://cretonne.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status From 026c899042ded4b58d41c50b9f49b12686348c1d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Nov 2016 11:24:04 -0700 Subject: [PATCH 405/968] Save a reference from a Var to its src and dst defs. When a Var is used in an XForm, it can be defined in the src or dst or both patterns, and it is classified accordingly. When a Var is defined, it is also useful to be able to find the `Def` that defined it. Add src_def and dst_def reference members to Var, and initialize them in the private Var copies that XForm creates for itself. These two members also replace the defctx bitmask. --- lib/cretonne/meta/cretonne/ast.py | 63 ++++++++++++++++++++--------- lib/cretonne/meta/cretonne/xform.py | 18 ++++----- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py index a43ddfadcb..1269a31668 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cretonne/ast.py @@ -74,7 +74,7 @@ class Var(Expr): """ A free variable. - When variables are used in `XForms` with source ans destination patterns, + When variables are used in `XForms` with source and destination patterns, they are classified as follows: Input values @@ -96,14 +96,10 @@ class Var(Expr): def __init__(self, name): # type: (str) -> None self.name = name - # Bitmask of contexts where this variable is defined. - # See XForm._rewrite_defs(). - self.defctx = 0 - - # Context bits for `defctx` indicating which pattern has defines of this - # var. - SRCCTX = 1 - DSTCTX = 2 + # The `Def` defining this variable in a source pattern. + self.src_def = None # type: Def + # The `Def` defining this variable in a destination pattern. + self.dst_def = None # type: Def def __str__(self): # type: () -> str @@ -112,29 +108,60 @@ class Var(Expr): def __repr__(self): # type: () -> str s = self.name - if self.defctx: - s += ", d={:02b}".format(self.defctx) + if self.src_def: + s += ", src" + if self.dst_def: + s += ", dst" return "Var({})".format(s) + # Context bits for `set_def` indicating which pattern has defines of this + # var. + SRCCTX = 1 + DSTCTX = 2 + + def set_def(self, context, d): + # type: (int, Def) -> None + """ + Set the `Def` that defines this variable in the given context. + + The `context` must be one of `SRCCTX` or `DSTCTX` + """ + if context == self.SRCCTX: + self.src_def = d + else: + self.dst_def = d + + def get_def(self, context): + # type: (int) -> Def + """ + Get the def of this variable in context. + + The `context` must be one of `SRCCTX` or `DSTCTX` + """ + if context == self.SRCCTX: + return self.src_def + else: + return self.dst_def + def is_input(self): # type: () -> bool - """Is this an input value to the source pattern?""" - return self.defctx == 0 + """Is this an input value to the src pattern?""" + return not self.src_def and not self.dst_def def is_output(self): - """Is this an output value, defined in both src and dest patterns?""" + """Is this an output value, defined in both src and dst patterns?""" # type: () -> bool - return self.defctx == self.SRCCTX | self.DSTCTX + return self.src_def and self.dst_def def is_intermediate(self): """Is this an intermediate value, defined only in the src pattern?""" # type: () -> bool - return self.defctx == self.SRCCTX + return self.src_def and not self.dst_def def is_temp(self): - """Is this a temp value, defined only in the dest pattern?""" + """Is this a temp value, defined only in the dst pattern?""" # type: () -> bool - return self.defctx == self.DSTCTX + return not self.src_def and self.dst_def class Apply(Expr): diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 2888b9c26c..4c2f3c9daf 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -63,7 +63,7 @@ class XForm(object): ... Rtl(c << iconst(v), ... a << iadd(x, c)), ... Rtl(a << iadd_imm(x, v))) - XForm(inputs=[Var(v), Var(x)], defs=[Var(c, d=01), Var(a, d=11)], + XForm(inputs=[Var(v), Var(x)], defs=[Var(c, src), Var(a, src, dst)], c << iconst(v) a << iadd(x, c) => @@ -112,7 +112,7 @@ class XForm(object): for line in rtl.rtl: if isinstance(line, Def): line.defs = tuple( - self._rewrite_defs(line.defs, symtab, context)) + self._rewrite_defs(line, symtab, context)) expr = line.expr else: expr = line @@ -132,23 +132,23 @@ class XForm(object): expr.args = tuple( self._rewrite_uses(expr, stack, symtab, context)) - def _rewrite_defs(self, defs, symtab, context): - # type: (Sequence[Var], Dict[str, Var], int) -> Iterable[Var] + def _rewrite_defs(self, line, symtab, context): + # type: (Def, Dict[str, Var], int) -> Iterable[Var] """ Given a tuple of symbols defined in a Def, rewrite them to local symbols. Yield the new locals. """ - for sym in defs: + for sym in line.defs: name = str(sym) if name in symtab: var = symtab[name] - if var.defctx & context: + if var.get_def(context): raise AssertionError("'{}' multiply defined".format(name)) else: var = Var(name) symtab[name] = var self.defs.append(var) - var.defctx |= context + var.set_def(context, line) yield var def _rewrite_uses(self, expr, stack, symtab, context): @@ -173,8 +173,8 @@ class XForm(object): name = str(arg) if name in symtab: var = symtab[name] - # The variable must be used consistenty as a def or input. - if var.defctx and (var.defctx & context) == 0: + # The variable must be used consistently as a def or input. + if not var.is_input() and not var.get_def(context): raise AssertionError( "'{}' used as both input and def" .format(name)) From 3bb6efba6e127525f60e6dc5bfca5263725d65e3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Nov 2016 17:55:41 -0700 Subject: [PATCH 406/968] Add a Value::unwrap_direct() method. When it is known that a value is the first result of an instruction, it is safe to unwrap the instruction reference. --- lib/cretonne/src/ir/entities.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 384e4d3ae9..df7d81988f 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -164,6 +164,19 @@ impl Value { Table(index) } } + + /// Assuming that this is a direct value, get the referenced instruction. + /// + /// # Panics + /// + /// If this is not a value created with `new_direct()`. + pub fn unwrap_direct(&self) -> Inst { + if let ExpandedValue::Direct(inst) = self.expand() { + inst + } else { + panic!("{} is not a direct value", self) + } + } } /// Display a `Value` reference as "v7" or "v2x". From 125fe6412130d69268361bbfd1f49eaaeefee6ba Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Nov 2016 12:56:34 -0700 Subject: [PATCH 407/968] Legalization pattern emission WIP. Begin emitting legalization patterns in the form of two functions, 'expand' and 'narrow' that are included in legalizer.rs. The generated code compiles, but it is not fully working yet. We need to deal with the special cases of instructions producing multiple results. --- lib/cretonne/meta/cretonne/__init__.py | 12 ++++ lib/cretonne/meta/cretonne/ast.py | 28 +++----- lib/cretonne/meta/cretonne/legalize.py | 4 +- lib/cretonne/meta/cretonne/xform.py | 3 +- lib/cretonne/meta/gen_instr.py | 8 +-- lib/cretonne/meta/gen_legalizer.py | 90 ++++++++++++++++++++++---- lib/cretonne/src/legalizer.rs | 9 ++- 7 files changed, 113 insertions(+), 41 deletions(-) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index a3f0634b94..8a46b2d08f 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -834,6 +834,18 @@ class Instruction(object): suffix = ', '.join(o.name for o in self.ins) return '{}{} {}'.format(prefix, self.name, suffix) + def snake_name(self): + # type: () -> str + """ + Get the snake_case name of this instruction. + + Keywords in Rust and Python are altered by appending a '_' + """ + if self.name == 'return': + return 'return_' + else: + return self.name + def blurb(self): """Get the first line of the doc comment""" for line in self.__doc__.split('\n'): diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py index 1269a31668..ac21809317 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cretonne/ast.py @@ -53,16 +53,6 @@ class Def(object): return "({}) << {!s}".format( ', '.join(map(str, self.defs)), self.expr) - def root_inst(self): - # type: () -> Instruction - """Get the instruction at the root of this tree.""" - return self.expr.root_inst() - - def defs_expr(self): - # type: () -> Tuple[Tuple[Var, ...], Apply] - """Split into a defs tuple and an Apply expr.""" - return (self.defs, self.expr) - class Expr(object): """ @@ -215,12 +205,12 @@ class Apply(Expr): args = ', '.join(map(str, self.args)) return '{}({})'.format(self.instname(), args) - def root_inst(self): - # type: () -> Instruction - """Get the instruction at the root of this tree.""" - return self.inst - - def defs_expr(self): - # type: () -> Tuple[Tuple[Var, ...], Apply] - """Split into a defs tuple and an Apply expr.""" - return ((), self) + def rust_builder(self): + # type: () -> str + """ + Return a Rust Builder method call for instantiating this instruction + application. + """ + args = ', '.join(map(str, self.args)) + method = self.inst.snake_name() + return '{}({})'.format(method, args) diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py index 8dfa2e6b97..102df5f6a5 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/cretonne/legalize.py @@ -77,14 +77,14 @@ expand.legalize( (a, c) << iadd_cout(x, y), Rtl( a << iadd(x, y), - c << icmp('ult', a, x) + c << icmp('IntCC::UnsignedLessThan', a, x) )) expand.legalize( (a, b) << isub_bout(x, y), Rtl( a << isub(x, y), - b << icmp('ugt', a, x) + b << icmp('IntCC::UnsignedGreaterThan', a, x) )) expand.legalize( diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 4c2f3c9daf..6b9b37f513 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -195,8 +195,7 @@ class XForm(object): destination pattern. """ assert len(self.src.rtl) == 1, "Legalize needs single instruction." - defs, expr = self.src.rtl[0].defs_expr() - for d in defs: + for d in self.src.rtl[0].defs: if not d.is_output(): raise AssertionError( '{} not defined in dest pattern'.format(d)) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index bb14d36375..8b4555d9b2 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -446,16 +446,12 @@ def gen_inst_builder(inst, fmt): rvals = ', '.join(len(inst.value_results) * ['Value']) rtype = '({})'.format(rvals) - method = inst.name - if method == 'return': - # Avoid Rust keywords by appending '_'. - method += '_' - if len(tmpl_types) > 0: tmpl = '<{}>'.format(', '.join(tmpl_types)) else: tmpl = '' - proto = '{}{}({}) -> {}'.format(method, tmpl, ', '.join(args), rtype) + proto = '{}{}({}) -> {}'.format( + inst.snake_name(), tmpl, ', '.join(args), rtype) fmt.doc_comment('`{}`\n\n{}'.format(inst, inst.blurb())) fmt.line('#[allow(non_snake_case)]') diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 2a2387723f..d6b0f5eb2c 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -10,30 +10,29 @@ the input instruction. from __future__ import absolute_import from srcgen import Formatter import cretonne.legalize as legalize -from cretonne.ast import Def, Apply # noqa +from cretonne.ast import Def # noqa from cretonne.xform import XForm, XFormGroup # noqa try: - from typing import Union - DefApply = Union[Def, Apply] + from typing import Sequence # noqa except ImportError: pass def unwrap_inst(iref, node, fmt): - # type: (str, DefApply, Formatter) -> None + # type: (str, Def, Formatter) -> None """ - Given a `Def` or `Apply` node, emit code that extracts all the instruction - fields from `dfg[iref]`. + Given a `Def` node, emit code that extracts all the instruction fields from + `dfg[iref]`. Create local variables named after the `Var` instances in `node`. :param iref: Name of the `Inst` reference to unwrap. - :param node: `Def` or `Apply` node providing variable names. + :param node: `Def` node providing variable names. """ fmt.comment('Unwrap {}'.format(node)) - defs, expr = node.defs_expr() + expr = node.expr iform = expr.inst.format nvops = len(iform.value_operands) @@ -71,7 +70,64 @@ def unwrap_inst(iref, node, fmt): prefix, iform.value_operands.index(i))) fmt.line('({})'.format(', '.join(outs))) fmt.outdented_line('} else {') - fmt.line('unimplemented!("bad instruction format")') + fmt.line('unreachable!("bad instruction format")') + + # If the node has multiple results, detach the values. + # Place the secondary values in 'src_{}' locals. + if len(node.defs) > 1: + if node.defs == node.defs[0].dst_def.defs: + # Special case: The instruction replacing node defines the exact + # same values. + fmt.comment( + 'Multiple results handled by {}.' + .format(node.defs[0].dst_def)) + else: + fmt.comment('Detaching secondary results.') + # Boring case: Detach the secondary values, capture them in locals. + for d in node.defs[1:]: + fmt.line('let src_{};'.format(d)) + with fmt.indented('{', '}'): + fmt.line('let mut vals = dfg.detach_secondary_results(inst);') + for d in node.defs[1:]: + fmt.line('src_{} = vals.next().unwrap();'.format(d)) + fmt.line('assert_eq!(vals.next(), None);') + + +def wrap_tup(seq): + # type: (Sequence[object]) -> str + tup = tuple(map(str, seq)) + if len(tup) == 1: + return tup[0] + else: + return '({})'.format(', '.join(tup)) + + +def emit_dst_inst(node, fmt): + # type: (Def, Formatter) -> None + exact_replace = False + if len(node.defs) == 0: + # This node doesn't define any values, so just insert the new + # instruction. + builder = 'dfg.ins(pos)' + else: + src_def0 = node.defs[0].src_def + if src_def0 and node.defs[0] == src_def0.defs[0]: + # The primary result is replacing the primary result of the src + # pattern. + # Replace the whole instruction. + builder = 'let {} = dfg.replace(inst)'.format(wrap_tup(node.defs)) + # Secondary values weren't replaced if this is an exact replacement + # for all the src results. + exact_replace = (node.defs == src_def0.defs) + else: + # Insert a new instruction since its primary def doesn't match the + # src. + builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) + + fmt.line('{}.{};'.format(builder, node.expr.rust_builder())) + + if exact_replace: + fmt.comment('exactreplacement') def gen_xform(xform, fmt): @@ -83,9 +139,20 @@ def gen_xform(xform, fmt): `inst: Inst` is the variable to be replaced. It is pointed to by `pos: Cursor`. `dfg: DataFlowGraph` is available and mutable. + + Produce an `Option` result at the end. """ + # Unwrap the source instruction, create local variables for the input + # variables. unwrap_inst('inst', xform.src.rtl[0], fmt) + # Emit the destination pattern. + for dst in xform.dst.rtl: + emit_dst_inst(dst, fmt) + + # TODO: Return the first replacement instruction. + fmt.line('None') + def gen_xform_group(xgrp, fmt): # type: (XFormGroup, Formatter) -> None @@ -95,10 +162,11 @@ def gen_xform_group(xgrp, fmt): Return the first instruction in the expansion, and leave `pos` pointing at the last instruction in the expansion. """) + fmt.line('#[allow(unused_variables,unused_assignments)]') with fmt.indented( 'fn ' + xgrp.name + '(pos: &mut Cursor, dfg: &mut DataFlowGraph) -> ' + - 'Option {{', + 'Option {', '}'): # Gen the instruction to be legalized. The cursor we're passed must be # pointing at an instruction. @@ -106,7 +174,7 @@ def gen_xform_group(xgrp, fmt): with fmt.indented('match dfg[inst].opcode() {', '}'): for xform in xgrp.xforms: - inst = xform.src.rtl[0].root_inst() + inst = xform.src.rtl[0].expr.inst with fmt.indented( 'Opcode::{} => {{'.format(inst.camel_name), '}'): gen_xform(xform, fmt) diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 3358f77bb0..89ba8cd569 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -13,7 +13,8 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. -use ir::Function; +use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, Inst, InstBuilder}; +use ir::condcodes::IntCC; use isa::TargetIsa; /// Legalize `func` for `isa`. @@ -52,3 +53,9 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { } } } + +// Include legalization patterns that were generated by gen_legalizer.py from the XForms in +// meta/cretonne/legalize.py. +// +// Concretely, this defines private functions `narrow()`, and `expand()`. +include!(concat!(env!("OUT_DIR"), "/legalizer.rs")); From e24291fe4cfd7d836e3d7e098b2c73c798aab983 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Nov 2016 18:59:32 -0700 Subject: [PATCH 408/968] Return a Result from the TargetIsa::encode() method. When an instruction can't be encoded, provide a viable legalization action in the form of a Legalize enum. --- lib/cretonne/src/isa/enc_tables.rs | 21 +++++++----- lib/cretonne/src/isa/mod.rs | 15 ++++++++- lib/cretonne/src/isa/riscv/mod.rs | 17 +++++----- lib/cretonne/src/legalizer.rs | 51 ++++++++++++++++-------------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 185e162f7f..d0a371dbc4 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -3,7 +3,7 @@ //! This module contains types and functions for working with the encoding tables generated by //! `lib/cretonne/meta/gen_encoding.py`. use ir::{Type, Opcode}; -use isa::Encoding; +use isa::{Encoding, Legalize}; use constant_hash::{Table, probe}; /// Level 1 hash table entry. @@ -83,16 +83,21 @@ pub fn lookup_enclist(ctrl_typevar: Type, opcode: Opcode, level1_table: &[Level1Entry], level2_table: &[Level2Entry]) - -> Option + -> Result where OffT1: Into + Copy, OffT2: Into + Copy { - probe(level1_table, ctrl_typevar, ctrl_typevar.index()).and_then(|l1idx| { - let l1ent = &level1_table[l1idx]; - let l2off = l1ent.offset.into() as usize; - let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; - probe(l2tab, opcode, opcode as usize).map(|l2idx| l2tab[l2idx].offset.into() as usize) - }) + // TODO: The choice of legalization actions here is naive. This needs to be configurable. + probe(level1_table, ctrl_typevar, ctrl_typevar.index()) + .ok_or(Legalize::Narrow) + .and_then(|l1idx| { + let l1ent = &level1_table[l1idx]; + let l2off = l1ent.offset.into() as usize; + let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; + probe(l2tab, opcode, opcode as usize) + .map(|l2idx| l2tab[l2idx].offset.into() as usize) + .ok_or(Legalize::Expand) + }) } /// Encoding list entry. diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 5705fe3a55..aa7b9a76d8 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -87,6 +87,19 @@ impl settings::Configurable for Builder { } } +/// After determining that an instruction doesn't have an encoding, how should we proceed to +/// legalize it? +/// +/// These actions correspond to the transformation groups defined in `meta/cretonne/legalize.py`. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Legalize { + /// Legalize in terms of narrower types. + Narrow, + + /// Expanding in terms of other instructions using the same types. + Expand, +} + /// Methods that are specialized to a target ISA. pub trait TargetIsa { /// Get the name of this ISA. @@ -101,7 +114,7 @@ pub trait TargetIsa { /// Otherwise, return `None`. /// /// This is also the main entry point for determining if an instruction is legal. - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Option; + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result; /// Get a static array of names associated with encoding recipes in this ISA. Encoding recipes /// are numbered starting from 0, corresponding to indexes into th name array. diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 06a9c890d0..63ab5ccf89 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -6,7 +6,7 @@ mod enc_tables; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, Encoding}; +use isa::{TargetIsa, Encoding, Legalize}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -48,7 +48,7 @@ impl TargetIsa for Isa { &self.shared_flags } - fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Option { + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { lookup_enclist(inst.first_type(), inst.opcode(), self.cpumode, @@ -58,6 +58,7 @@ impl TargetIsa for Isa { &enc_tables::ENCLISTS[..], |instp| enc_tables::check_instp(inst, instp), |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) }) } @@ -109,7 +110,7 @@ mod tests { }; // Immediate is out of range for ADDI. - assert_eq!(isa.encode(&dfg, &inst64_large), None); + assert_eq!(isa.encode(&dfg, &inst64_large), Err(isa::Legalize::Expand)); // Create an iadd_imm.i32 which is encodable in RV64. let inst32 = InstructionData::BinaryImm { @@ -144,8 +145,8 @@ mod tests { imm: immediates::Imm64::new(-10), }; - // ADDI is I/0b00100 - assert_eq!(isa.encode(&dfg, &inst64), None); + // In 32-bit mode, an i64 bit add should be narrowed. + assert_eq!(isa.encode(&dfg, &inst64), Err(isa::Legalize::Narrow)); // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -155,8 +156,8 @@ mod tests { imm: immediates::Imm64::new(-10000), }; - // Immediate is out of range for ADDI. - assert_eq!(isa.encode(&dfg, &inst64_large), None); + // In 32-bit mode, an i64 bit add should be narrowed. + assert_eq!(isa.encode(&dfg, &inst64_large), Err(isa::Legalize::Narrow)); // Create an iadd_imm.i32 which is encodable in RV32. let inst32 = InstructionData::BinaryImm { @@ -176,7 +177,7 @@ mod tests { args: [arg32, arg32], }; - assert_eq!(isa.encode(&dfg, &mul32), None); + assert_eq!(isa.encode(&dfg, &mul32), Err(isa::Legalize::Expand)); } #[test] diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 89ba8cd569..2a84ee520c 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -15,7 +15,7 @@ use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, Inst, InstBuilder}; use ir::condcodes::IntCC; -use isa::TargetIsa; +use isa::{TargetIsa, Legalize}; /// Legalize `func` for `isa`. /// @@ -25,30 +25,35 @@ use isa::TargetIsa; pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { // TODO: This is very simplified and incomplete. func.encodings.resize(func.dfg.num_insts()); - for ebb in func.layout.ebbs() { - for inst in func.layout.ebb_insts(ebb) { + let mut pos = Cursor::new(&mut func.layout); + while let Some(_ebb) = pos.next_ebb() { + while let Some(inst) = pos.next_inst() { match isa.encode(&func.dfg, &func.dfg[inst]) { - Some(encoding) => func.encodings[inst] = encoding, - None => { - // TODO: We should transform the instruction into legal equivalents. - // Possible strategies are: - // 1. Expand instruction into sequence of legal instructions. Possibly - // iteratively. - // 2. Split the controlling type variable into high and low parts. This applies - // both to SIMD vector types which can be halved and to integer types such - // as `i64` used on a 32-bit ISA. - // 3. Promote the controlling type variable to a larger type. This typically - // means expressing `i8` and `i16` arithmetic in terms if `i32` operations - // on RISC targets. (It may or may not be beneficial to promote small vector - // types versus splitting them.) - // 4. Convert to library calls. For example, floating point operations on an - // ISA with no IEEE 754 support. - // - // The iteration scheme used here is not going to cut it. Transforming - // instructions involves changing `function.layout` which is impossiblr while - // it is referenced by the two iterators. We need a layout cursor that can - // maintain a position *and* permit inserting and replacing instructions. + Ok(encoding) => func.encodings[inst] = encoding, + Err(Legalize::Expand) => { + expand(&mut pos, &mut func.dfg); } + Err(Legalize::Narrow) => { + narrow(&mut pos, &mut func.dfg); + } + // TODO: We should transform the instruction into legal equivalents. + // Possible strategies are: + // 1. Expand instruction into sequence of legal instructions. Possibly + // iteratively. + // 2. Split the controlling type variable into high and low parts. This applies + // both to SIMD vector types which can be halved and to integer types such + // as `i64` used on a 32-bit ISA. + // 3. Promote the controlling type variable to a larger type. This typically + // means expressing `i8` and `i16` arithmetic in terms if `i32` operations + // on RISC targets. (It may or may not be beneficial to promote small vector + // types versus splitting them.) + // 4. Convert to library calls. For example, floating point operations on an + // ISA with no IEEE 754 support. + // + // The iteration scheme used here is not going to cut it. Transforming + // instructions involves changing `function.layout` which is impossiblr while + // it is referenced by the two iterators. We need a layout cursor that can + // maintain a position *and* permit inserting and replacing instructions. } } } From 9086c6c8f063cd4e724766e726ca40b2eb18eddc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 08:02:37 -0700 Subject: [PATCH 409/968] Add narrowing legalization patterns for bitwise ops. RISC-V 32-bit tests for band.i64, bor.i64, bxor.i64. --- filetests/isa/riscv/legalize-i64.cton | 42 ++++++++++++++++++++++++++ lib/cretonne/meta/cretonne/legalize.py | 13 +++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 filetests/isa/riscv/legalize-i64.cton diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton new file mode 100644 index 0000000000..2aa4be9e58 --- /dev/null +++ b/filetests/isa/riscv/legalize-i64.cton @@ -0,0 +1,42 @@ +; Test the legalization of i64 arithmetic instructions. +test legalizer +isa riscv supports_m=1 + +function bitwise_and(i64, i64) -> i64 { +ebb0(v1: i64, v2: i64): + v3 = band v1, v2 + return v3 +} +; regex: V=v\d+ +; regex: VX=vx\d+ +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v3l=$V) = band $v1l, $v2l +; check: $(v3h=$V) = band $v1h, $v2h +; check: $v3 = iconcat_lohi $v3l, $v3h + +function bitwise_or(i64, i64) -> i64 { +ebb0(v1: i64, v2: i64): + v3 = bor v1, v2 + return v3 +} +; regex: V=v\d+ +; regex: VX=vx\d+ +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v3l=$V) = bor $v1l, $v2l +; check: $(v3h=$V) = bor $v1h, $v2h +; check: $v3 = iconcat_lohi $v3l, $v3h + +function bitwise_xor(i64, i64) -> i64 { +ebb0(v1: i64, v2: i64): + v3 = bxor v1, v2 + return v3 +} +; regex: V=v\d+ +; regex: VX=vx\d+ +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v3l=$V) = bxor $v1l, $v2l +; check: $(v3h=$V) = bxor $v1h, $v2h +; check: $v3 = iconcat_lohi $v3l, $v3h diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py index 102df5f6a5..189685dce1 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/cretonne/legalize.py @@ -9,7 +9,7 @@ instructions that are legal. from __future__ import absolute_import from .base import iadd, iadd_cout, iadd_cin, iadd_carry from .base import isub, isub_bin, isub_bout, isub_borrow -from .base import bor, isplit_lohi, iconcat_lohi +from .base import band, bor, bxor, isplit_lohi, iconcat_lohi from .base import icmp from .ast import Var from .xform import Rtl, XFormGroup @@ -71,6 +71,17 @@ narrow.legalize( a << iconcat_lohi(al, ah) )) +for bitop in [band, bor, bxor]: + narrow.legalize( + a << bitop(x, y), + Rtl( + (xl, xh) << isplit_lohi(x), + (yl, yh) << isplit_lohi(y), + al << bitop(xl, yl), + ah << bitop(xh, yh), + a << iconcat_lohi(al, ah) + )) + # Expand integer operations with carry for RISC architectures that don't have # the flags. expand.legalize( From 63fc81541e90bc4256a2caacb8338ecd7e5edf79 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 08:44:01 -0700 Subject: [PATCH 410/968] Add Cursor::set_position. Make it possible to move a cursor to a new position. In the current implementation of Layout and Cursor, this is a trivial operation, but if we switch to a B-tree based function layout, this involves navigating the tree. --- lib/cretonne/src/ir/layout.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index fdd2305747..bdcf4cdd52 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -381,6 +381,11 @@ impl<'f> Cursor<'f> { self.pos } + /// Move the cursor to a new position. + pub fn set_position(&mut self, pos: CursorPosition) { + self.pos = pos; + } + /// Get the EBB corresponding to the current position. pub fn current_ebb(&self) -> Option { use self::CursorPosition::*; From 1641365f0198017f671cd9b4e11c89378b762f69 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 09:44:11 -0700 Subject: [PATCH 411/968] Revisit expanded instructions for legalization. When an illegal instruction is replaced with other instructions, back up and revisit the expanded instructions. The new instructions need to have encodings assigned too. This also allows for expansions to contain illegal instructions that need to be legalized themselves. --- filetests/isa/riscv/legalize-i64.cton | 18 +++++--- lib/cretonne/meta/gen_legalizer.py | 11 ++--- lib/cretonne/src/legalizer.rs | 60 ++++++++++++++++----------- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index 2aa4be9e58..916986fe68 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -11,8 +11,10 @@ ebb0(v1: i64, v2: i64): ; regex: VX=vx\d+ ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 ; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 -; check: $(v3l=$V) = band $v1l, $v2l -; check: $(v3h=$V) = band $v1h, $v2h +; check: [R#ec +; sameln: $(v3l=$V) = band $v1l, $v2l +; check: [R#ec +; sameln: $(v3h=$V) = band $v1h, $v2h ; check: $v3 = iconcat_lohi $v3l, $v3h function bitwise_or(i64, i64) -> i64 { @@ -24,8 +26,10 @@ ebb0(v1: i64, v2: i64): ; regex: VX=vx\d+ ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 ; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 -; check: $(v3l=$V) = bor $v1l, $v2l -; check: $(v3h=$V) = bor $v1h, $v2h +; check: [R#cc +; sameln: $(v3l=$V) = bor $v1l, $v2l +; check: [R#cc +; sameln: $(v3h=$V) = bor $v1h, $v2h ; check: $v3 = iconcat_lohi $v3l, $v3h function bitwise_xor(i64, i64) -> i64 { @@ -37,6 +41,8 @@ ebb0(v1: i64, v2: i64): ; regex: VX=vx\d+ ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 ; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 -; check: $(v3l=$V) = bxor $v1l, $v2l -; check: $(v3h=$V) = bxor $v1h, $v2h +; check: [R#8c +; sameln: $(v3l=$V) = bxor $v1l, $v2l +; check: [R#8c +; sameln: $(v3h=$V) = bxor $v1h, $v2h ; check: $v3 = iconcat_lohi $v3l, $v3h diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index d6b0f5eb2c..3efb120124 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -139,8 +139,6 @@ def gen_xform(xform, fmt): `inst: Inst` is the variable to be replaced. It is pointed to by `pos: Cursor`. `dfg: DataFlowGraph` is available and mutable. - - Produce an `Option` result at the end. """ # Unwrap the source instruction, create local variables for the input # variables. @@ -150,9 +148,6 @@ def gen_xform(xform, fmt): for dst in xform.dst.rtl: emit_dst_inst(dst, fmt) - # TODO: Return the first replacement instruction. - fmt.line('None') - def gen_xform_group(xgrp, fmt): # type: (XFormGroup, Formatter) -> None @@ -165,8 +160,7 @@ def gen_xform_group(xgrp, fmt): fmt.line('#[allow(unused_variables,unused_assignments)]') with fmt.indented( 'fn ' + xgrp.name + - '(pos: &mut Cursor, dfg: &mut DataFlowGraph) -> ' + - 'Option {', + '(pos: &mut Cursor, dfg: &mut DataFlowGraph) -> bool {', '}'): # Gen the instruction to be legalized. The cursor we're passed must be # pointing at an instruction. @@ -179,7 +173,8 @@ def gen_xform_group(xgrp, fmt): 'Opcode::{} => {{'.format(inst.camel_name), '}'): gen_xform(xform, fmt) # We'll assume there are uncovered opcodes. - fmt.line('_ => None,') + fmt.line('_ => return false,') + fmt.line('true') def generate(isas, out_dir): diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 2a84ee520c..a83485b5e2 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -13,7 +13,7 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. -use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, Inst, InstBuilder}; +use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; use ir::condcodes::IntCC; use isa::{TargetIsa, Legalize}; @@ -27,34 +27,44 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { func.encodings.resize(func.dfg.num_insts()); let mut pos = Cursor::new(&mut func.layout); while let Some(_ebb) = pos.next_ebb() { + // Keep track of the cursor position before the instruction being processed, so we can + // double back when replacing instructions. + let mut prev_pos = pos.position(); + while let Some(inst) = pos.next_inst() { match isa.encode(&func.dfg, &func.dfg[inst]) { - Ok(encoding) => func.encodings[inst] = encoding, - Err(Legalize::Expand) => { - expand(&mut pos, &mut func.dfg); + Ok(encoding) => *func.encodings.ensure(inst) = encoding, + Err(action) => { + // We should transform the instruction into legal equivalents. + // Possible strategies are: + // 1. Legalize::Expand: Expand instruction into sequence of legal instructions. + // Possibly iteratively. () + // 2. Legalize::Narrow: Split the controlling type variable into high and low + // parts. This applies both to SIMD vector types which can be halved and to + // integer types such as `i64` used on a 32-bit ISA. (). + // 3. TODO: Promote the controlling type variable to a larger type. This + // typically means expressing `i8` and `i16` arithmetic in terms if `i32` + // operations on RISC targets. (It may or may not be beneficial to promote + // small vector types versus splitting them.) + // 4. TODO: Convert to library calls. For example, floating point operations on + // an ISA with no IEEE 754 support. + let changed = match action { + Legalize::Expand => expand(&mut pos, &mut func.dfg), + Legalize::Narrow => narrow(&mut pos, &mut func.dfg), + }; + // If the current instruction was replaced, we need to double back and revisit + // the expanded sequence. This is both to assign encodings and possible to + // expand further. + // There's a risk of infinite looping here if the legalization patterns are + // unsound. Should we attempt to detect that? + if changed { + pos.set_position(prev_pos); + } } - Err(Legalize::Narrow) => { - narrow(&mut pos, &mut func.dfg); - } - // TODO: We should transform the instruction into legal equivalents. - // Possible strategies are: - // 1. Expand instruction into sequence of legal instructions. Possibly - // iteratively. - // 2. Split the controlling type variable into high and low parts. This applies - // both to SIMD vector types which can be halved and to integer types such - // as `i64` used on a 32-bit ISA. - // 3. Promote the controlling type variable to a larger type. This typically - // means expressing `i8` and `i16` arithmetic in terms if `i32` operations - // on RISC targets. (It may or may not be beneficial to promote small vector - // types versus splitting them.) - // 4. Convert to library calls. For example, floating point operations on an - // ISA with no IEEE 754 support. - // - // The iteration scheme used here is not going to cut it. Transforming - // instructions involves changing `function.layout` which is impossiblr while - // it is referenced by the two iterators. We need a layout cursor that can - // maintain a position *and* permit inserting and replacing instructions. } + + // Remember this position in case we need to double back. + prev_pos = pos.position(); } } } From c1b7080bf615bbb36db4e75bf1f4e417bf3220c8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 10:34:14 -0700 Subject: [PATCH 412/968] Gather comments in the preamble of a test file. Comments preceding the first function are not associated with any specific entity in the file. Put them in a TestFile::preamble_comments field. --- lib/reader/src/lib.rs | 2 +- lib/reader/src/parser.rs | 14 +++++++++++++- lib/reader/src/testfile.rs | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/reader/src/lib.rs b/lib/reader/src/lib.rs index 25aa25aa98..61473afe38 100644 --- a/lib/reader/src/lib.rs +++ b/lib/reader/src/lib.rs @@ -10,7 +10,7 @@ extern crate cretonne; pub use error::{Location, Result, Error}; pub use parser::{parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; -pub use testfile::{TestFile, Details}; +pub use testfile::{TestFile, Details, Comment}; pub use isaspec::IsaSpec; pub use sourcemap::SourceMap; diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 7b16abe272..c513e1a86a 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -38,9 +38,12 @@ pub fn parse_functions(text: &str) -> Result> { /// The returned `TestFile` contains direct references to substrings of `text`. pub fn parse_test<'a>(text: &'a str) -> Result> { let mut parser = Parser::new(text); + // Gather the preamble comments as 'Function'. + parser.gather_comments(AnyEntity::Function); Ok(TestFile { commands: parser.parse_test_commands(), isa_spec: try!(parser.parse_isa_specs()), + preamble_comments: parser.take_comments(), functions: try!(parser.parse_function_list()), }) } @@ -273,6 +276,11 @@ impl<'a> Parser<'a> { self.comment_entity = Some(entity.into()); } + // Get the comments gathered so far, clearing out the internal list. + fn take_comments(&mut self) -> Vec> { + mem::replace(&mut self.comments, Vec::new()) + } + // Rewrite the entity of the last added comments from `old` to `new`. // Also switch to collecting future comments for `new`. fn rewrite_last_comment_entities>(&mut self, old: E, new: E) { @@ -561,7 +569,7 @@ impl<'a> Parser<'a> { let details = Details { location: location, - comments: mem::replace(&mut self.comments, Vec::new()), + comments: self.take_comments(), map: ctx.map, }; @@ -1482,6 +1490,7 @@ mod tests { test cfg option=5 test verify set enable_float=false + ; still preamble function comment() {}") .unwrap(); assert_eq!(tf.commands.len(), 2); @@ -1491,6 +1500,9 @@ mod tests { IsaSpec::None(s) => assert!(!s.enable_float()), _ => panic!("unexpected ISAs"), } + assert_eq!(tf.preamble_comments.len(), 2); + assert_eq!(tf.preamble_comments[0].text, "; before"); + assert_eq!(tf.preamble_comments[1].text, "; still preamble"); assert_eq!(tf.functions.len(), 1); assert_eq!(tf.functions[0].0.name.to_string(), "comment"); } diff --git a/lib/reader/src/testfile.rs b/lib/reader/src/testfile.rs index deeaf04e5a..fcb11590e8 100644 --- a/lib/reader/src/testfile.rs +++ b/lib/reader/src/testfile.rs @@ -20,6 +20,9 @@ pub struct TestFile<'a> { pub commands: Vec>, /// `isa bar ...` lines. pub isa_spec: IsaSpec, + /// Comments appearing before the first function. + /// These are all tagged as 'Function' scope for lack of a better entity. + pub preamble_comments: Vec>, /// Parsed functions and additional details about each function. pub functions: Vec<(Function, Details<'a>)>, } @@ -47,6 +50,9 @@ pub struct Details<'a> { /// after the function are tagged as `AnyEntity::Function`. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Comment<'a> { + /// The entity this comment is attached to. + /// Comments always follow their entity. pub entity: AnyEntity, + /// Text of the comment, including the leading `;`. pub text: &'a str, } From a038279717cbb6d20c7a4cfa6c286929c4e80d01 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 10:49:09 -0700 Subject: [PATCH 413/968] TestFile preamble comments apply to all functions. Include the test file preamble comments when building a filecheck instance for every function in the file. This makes it possible to define common regex variables in the preamble and use these definitions for all the functions. --- docs/testing.rst | 4 ++++ filetests/isa/riscv/legalize-i64.cton | 9 +++------ src/filetest/runone.rs | 1 + src/filetest/subtest.rs | 17 ++++++++++++----- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 419f51ff22..d52b180c99 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -150,6 +150,10 @@ use filecheck will extract comments associated with each function (or its entities) and scan them for filecheck directives. The test output for each function is then matched against the filecheck directives for that function. +Comments appearing before the first function in a file apply to every function. +This is useful for defining common regular expression variables with the +``regex:`` directive, for example. + Note that LLVM's file tests don't separate filecheck directives by their associated function. It verifies the concatenated output against all filecheck directives in the test file. LLVM's :command:`FileCheck` command has a diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index 916986fe68..d4f3e6b05e 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -2,13 +2,14 @@ test legalizer isa riscv supports_m=1 +; regex: V=v\d+ +; regex: VX=vx\d+ + function bitwise_and(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 } -; regex: V=v\d+ -; regex: VX=vx\d+ ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 ; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 ; check: [R#ec @@ -22,8 +23,6 @@ ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 } -; regex: V=v\d+ -; regex: VX=vx\d+ ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 ; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 ; check: [R#cc @@ -37,8 +36,6 @@ ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 } -; regex: V=v\d+ -; regex: VX=vx\d+ ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 ; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 ; check: [R#8c diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index d0199c45cf..d84f0f7041 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -50,6 +50,7 @@ pub fn run(path: &Path) -> TestResult { for (func, details) in testfile.functions { let mut context = Context { + preamble_comments: &testfile.preamble_comments, details: details, verified: false, flags: flags, diff --git a/src/filetest/subtest.rs b/src/filetest/subtest.rs index fee9bbef85..f78ac7a6b1 100644 --- a/src/filetest/subtest.rs +++ b/src/filetest/subtest.rs @@ -5,13 +5,16 @@ use std::borrow::Cow; use cretonne::ir::Function; use cretonne::isa::TargetIsa; use cretonne::settings::Flags; -use cton_reader::Details; +use cton_reader::{Details, Comment}; use filecheck::{self, CheckerBuilder, Checker, Value as FCValue}; pub type Result = result::Result; /// Context for running a a test on a single function. pub struct Context<'a> { + /// Comments from the preamble f the test file. These apply to all functions. + pub preamble_comments: &'a [Comment<'a>], + /// Additional details about the function from the parser. pub details: Details<'a>, @@ -69,7 +72,7 @@ impl<'a> filecheck::VariableMap for Context<'a> { /// Run filecheck on `text`, using directives extracted from `context`. pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { - let checker = try!(build_filechecker(&context.details)); + let checker = try!(build_filechecker(context)); if try!(checker.check(&text, context).map_err(|e| format!("filecheck: {}", e))) { Ok(()) } else { @@ -80,10 +83,14 @@ pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { } } -/// Build a filechecker using the directives in the function's comments. -pub fn build_filechecker(details: &Details) -> Result { +/// Build a filechecker using the directives in the file preamble and the function's comments. +pub fn build_filechecker(context: &Context) -> Result { let mut builder = CheckerBuilder::new(); - for comment in &details.comments { + // Preamble comments apply to all functions. + for comment in context.preamble_comments { + try!(builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))); + } + for comment in &context.details.comments { try!(builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))); } let checker = builder.finish(); From 2b8732ae13ddb430e61596ba1c25e0af93649366 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 11:54:42 -0700 Subject: [PATCH 414/968] Advance the insertion cursor after replacinf an instruction. When expanding iadd_cout, the original instruction is replaced with an iadd, and an icmp is inserted after the iadd. Make sure we advance the insertion position after replacing iadd_cout so the icmp gets inserted *after* iadd. --- filetests/isa/riscv/expand-i32.cton | 18 ++++++++++++++++++ lib/cretonne/meta/gen_legalizer.py | 17 +++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 filetests/isa/riscv/expand-i32.cton diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton new file mode 100644 index 0000000000..b2473aa608 --- /dev/null +++ b/filetests/isa/riscv/expand-i32.cton @@ -0,0 +1,18 @@ +; Test the legalization of i32 instructions that don't have RISC-V versions. +test legalizer + +set is_64bit=0 +isa riscv supports_m=1 + +set is_64bit=1 +isa riscv supports_m=1 + +; regex: V=vx?\d+ + +function carry_out(i32, i32) -> i32, b1 { +ebb0(v1: i32, v2: i32): + v3, v4 = iadd_cout v1, v2 + return v3, v4 +} +; check: $v3 = iadd $v1, $v2 +; check: $(cout=$V) = icmp ult, $v3, $v1 diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 3efb120124..bc662dca8b 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -105,6 +105,7 @@ def wrap_tup(seq): def emit_dst_inst(node, fmt): # type: (Def, Formatter) -> None exact_replace = False + replaced_inst = None # type: str if len(node.defs) == 0: # This node doesn't define any values, so just insert the new # instruction. @@ -116,6 +117,7 @@ def emit_dst_inst(node, fmt): # pattern. # Replace the whole instruction. builder = 'let {} = dfg.replace(inst)'.format(wrap_tup(node.defs)) + replaced_inst = 'inst' # Secondary values weren't replaced if this is an exact replacement # for all the src results. exact_replace = (node.defs == src_def0.defs) @@ -126,6 +128,14 @@ def emit_dst_inst(node, fmt): fmt.line('{}.{};'.format(builder, node.expr.rust_builder())) + # If we just replaced an instruction, we need to bump the cursor so + # following instructions are inserted *after* the replaced insruction. + if replaced_inst: + with fmt.indented( + 'if pos.current_inst() == Some({}) {{' + .format(replaced_inst), '}'): + fmt.line('pos.next_inst();') + if exact_replace: fmt.comment('exactreplacement') @@ -151,12 +161,7 @@ def gen_xform(xform, fmt): def gen_xform_group(xgrp, fmt): # type: (XFormGroup, Formatter) -> None - fmt.doc_comment(""" - Legalize the instruction pointed to by `pos`. - - Return the first instruction in the expansion, and leave `pos` pointing - at the last instruction in the expansion. - """) + fmt.doc_comment("Legalize the instruction pointed to by `pos`.") fmt.line('#[allow(unused_variables,unused_assignments)]') with fmt.indented( 'fn ' + xgrp.name + From c995cb6f43c30abffcab991b0e776bdefac91e37 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 12:30:51 -0700 Subject: [PATCH 415/968] Add a ref_slice module. Utility functions for converting &T to an &[T] slice with a single element. --- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/ref_slice.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 lib/cretonne/src/ref_slice.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index aef83bd433..51a8d9b367 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -21,3 +21,4 @@ mod write; mod constant_hash; mod predicates; mod legalizer; +mod ref_slice; diff --git a/lib/cretonne/src/ref_slice.rs b/lib/cretonne/src/ref_slice.rs new file mode 100644 index 0000000000..b9dbd55dd1 --- /dev/null +++ b/lib/cretonne/src/ref_slice.rs @@ -0,0 +1,18 @@ +//! Functions for converting a reference into a singleton slice. +//! +//! See also the ref_slice crate on crates.io. +//! +//! We define the functions here to avoid external dependencies, and to ensure that they are +//! inlined in this crate. +//! +//! Despite their using an unsafe block, these functions are completely safe. + +use std::slice; + +pub fn ref_slice(s: &T) -> &[T] { + unsafe { slice::from_raw_parts(s, 1) } +} + +pub fn ref_slice_mut(s: &mut T) -> &mut [T] { + unsafe { slice::from_raw_parts_mut(s, 1) } +} From 152aabbfc0166865c9598eb8e4d3fe8da8a790cf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 13:25:40 -0700 Subject: [PATCH 416/968] Add arguments() and arguments_mut() methods. Provide a generic way of accessing the value arguments on an instruction. This is provided as two slice references. One for the fixed arguments and one for any VariableArgs. The arguments() methods return an array of two slices which is a bit awkward. Also provide an each_arg() method which passes each argument value to a closure. --- lib/cretonne/meta/gen_instr.py | 74 +++++++++++++++++++++++++++++ lib/cretonne/src/ir/instructions.rs | 52 +++++++++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 8b4555d9b2..569eca71b7 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -9,6 +9,7 @@ import cretonne def gen_formats(fmt): + # type: (srcgen.Formatter) -> None """Generate an instruction format enumeration""" fmt.doc_comment('An instruction format') @@ -38,7 +39,63 @@ def gen_formats(fmt): fmt.line() +def gen_arguments_method(fmt, is_mut): + # type: (srcgen.Formatter, bool) -> None + method = 'arguments' + mut = '' + rslice = 'ref_slice' + if is_mut: + method += '_mut' + mut = 'mut ' + rslice += '_mut' + + with fmt.indented( + 'pub fn {f}(&{m}self) -> [&{m}[Value]; 2] {{' + .format(f=method, m=mut), '}'): + with fmt.indented('match *self {', '}'): + for f in cretonne.InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + has_varargs = cretonne.variable_args in f.kinds + # Formats with both fixed and variable arguments delegate to + # the data struct. We need to work around borrow checker quirks + # when extracting two mutable references. + if has_varargs and len(f.value_operands) > 0: + fmt.line( + '{} {{ ref {}data, .. }} => data.{}(),' + .format(n, mut, method)) + continue + # Fixed args. + if len(f.value_operands) == 0: + arg = '&{}[]'.format(mut) + capture = '' + elif len(f.value_operands) == 1: + if f.boxed_storage: + capture = 'ref {}data, '.format(mut) + arg = '{}(&{}data.arg)'.format(rslice, mut) + else: + capture = 'ref {}arg, '.format(mut) + arg = '{}(arg)'.format(rslice) + else: + if f.boxed_storage: + capture = 'ref {}data, '.format(mut) + arg = '&{}data.args'.format(mut) + else: + capture = 'ref {}args, '.format(mut) + arg = 'args' + # Varargs. + if cretonne.variable_args in f.kinds: + varg = '&{}data.varargs'.format(mut) + capture = 'ref {}data, '.format(mut) + else: + varg = '&{}[]'.format(mut) + + fmt.line( + '{} {{ {} .. }} => [{}, {}],' + .format(n, capture, arg, varg)) + + def gen_instruction_data_impl(fmt): + # type: (srcgen.Formatter) -> None """ Generate the boring parts of the InstructionData implementation. @@ -49,6 +106,7 @@ def gen_instruction_data_impl(fmt): - `pub fn first_type(&self) -> Type` - `pub fn second_result(&self) -> Option` - `pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value>` + - `pub fn arguments(&self) -> (&[Value], &[Value])` """ # The `opcode` and `first_type` methods simply read the `opcode` and `ty` @@ -148,6 +206,22 @@ def gen_instruction_data_impl(fmt): ' {{ ref args, .. }} => Some(args[{}]),' .format(i)) + fmt.doc_comment( + """ + Get the value arguments to this instruction. + + This is returned as two `Value` slices. The first one + represents the fixed arguments, the second any variable + arguments. + """) + gen_arguments_method(fmt, False) + fmt.doc_comment( + """ + Get mutable references to the value arguments to this + instruction. + """) + gen_arguments_method(fmt, True) + def collect_instr_groups(isas): seen = set() diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index f601169beb..b8c5bdc4fa 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -15,6 +15,8 @@ use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::*; use ir::types; +use ref_slice::*; + // Include code generated by `lib/cretonne/meta/gen_instr.py`. This file contains: // // - The `pub enum InstructionFormat` enum with all the instruction formats. @@ -339,6 +341,18 @@ pub struct BranchData { pub varargs: VariableArgs, } +impl BranchData { + /// Get references to the arguments. + pub fn arguments(&self) -> [&[Value]; 2] { + [ref_slice(&self.arg), &self.varargs] + } + + /// Get mutable references to the arguments. + pub fn arguments_mut(&mut self) -> [&mut [Value]; 2] { + [ref_slice_mut(&mut self.arg), &mut self.varargs] + } +} + impl Display for BranchData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { try!(write!(f, "{}, {}", self.arg, self.destination)); @@ -372,6 +386,18 @@ pub struct IndirectCallData { pub varargs: VariableArgs, } +impl IndirectCallData { + /// Get references to the arguments. + pub fn arguments(&self) -> [&[Value]; 2] { + [ref_slice(&self.arg), &self.varargs] + } + + /// Get mutable references to the arguments. + pub fn arguments_mut(&mut self) -> [&mut [Value]; 2] { + [ref_slice_mut(&mut self.arg), &mut self.varargs] + } +} + /// Payload of a return instruction. #[derive(Clone, Debug)] pub struct ReturnData { @@ -381,9 +407,33 @@ pub struct ReturnData { /// Analyzing an instruction. /// -/// Avoid large matches on instruction formats by using the methods efined here to examine +/// Avoid large matches on instruction formats by using the methods defined here to examine /// instructions. impl InstructionData { + /// Execute a closure once for each argument to this instruction. + /// See also the `arguments()` method. + pub fn each_arg(&self, func: F) + where F: Fn(Value) + { + for part in &self.arguments() { + for &arg in part.iter() { + func(arg); + } + } + } + + /// Execute a closure with a mutable reference to each argument to this instruction. + /// See also the `arguments_mut()` method. + pub fn each_arg_mut(&mut self, func: F) + where F: Fn(&mut Value) + { + for part in &mut self.arguments_mut() { + for arg in part.iter_mut() { + func(arg); + } + } + } + /// Return information about the destination of a branch or jump instruction. /// /// Any instruction that can transfer control to another EBB reveals its possible destinations From 59fd74fb9abcd582b36bb751bac75b2f57575f29 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 15:27:34 -0700 Subject: [PATCH 417/968] Fix off-by-one in resolve_values. When the extended_values table is empty, the value to resolve is definitely not an alias, but we still need as least one trip in the loop to determine that. --- lib/cretonne/src/ir/dfg.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 0d24bc2d1a..7e843d0c87 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -137,7 +137,8 @@ impl DataFlowGraph { use ir::entities::ExpandedValue::Table; let mut v = value; - for _ in 0..self.extended_values.len() { + // Note that extended_values may be empty here. + for _ in 0..1 + self.extended_values.len() { v = match v.expand() { Table(idx) => { match self.extended_values[idx] { @@ -655,12 +656,16 @@ mod tests { let mut func = Function::new(); let dfg = &mut func.dfg; let ebb0 = dfg.make_ebb(); - let arg0 = dfg.append_ebb_arg(ebb0, types::I32); let pos = &mut Cursor::new(&mut func.layout); pos.insert_ebb(ebb0); // Build a little test program. let v1 = dfg.ins(pos).iconst(types::I32, 42); + + // Make sure we can resolve value aliases even when extended_values is empty. + assert_eq!(dfg.resolve_aliases(v1), v1); + + let arg0 = dfg.append_ebb_arg(ebb0, types::I32); let (s, c) = dfg.ins(pos).iadd_cout(v1, arg0); let iadd = match s.expand() { Direct(i) => i, From d34ec1bd06f3c27940b3e8cc31c3b79eadb86d1c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 15:19:44 -0700 Subject: [PATCH 418/968] Write out value aliases when writing instructions. If an instruction uses any values that are aliases of other values, print out the alias mappings on lines preceding the instruction. This is necessary to reconstruct the data flow graph. We don't make any attempt to only write out each alias mapping once. The parser does not yet support value aliases. --- lib/cretonne/src/write.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index ac487c6f37..d8db064cd6 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -145,11 +145,28 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { Some(rtype) } +// Write out any value aliases appearing in `inst`. +fn write_value_aliases(w: &mut Write, func: &Function, inst: Inst, indent: usize) -> Result { + for &arg in func.dfg[inst].arguments().iter().flat_map(|x| x.iter()) { + let resolved = func.dfg.resolve_aliases(arg); + if resolved != arg { + try!(writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)); + } + } + Ok(()) +} + fn write_instruction(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, inst: Inst) -> Result { + // Indent all instructions to col 24 if any encodings are present. + let indent = if func.encodings.is_empty() { 4 } else { 24 }; + + // Value aliases come out on lines before the instruction using them. + try!(write_value_aliases(w, func, inst, indent)); + // Write out encoding info. if let Some(enc) = func.encodings.get(inst).cloned() { let mut s = String::with_capacity(16); @@ -161,8 +178,8 @@ fn write_instruction(w: &mut Write, // Align instruction following ISA annotation to col 24. try!(write!(w, "{:23} ", s)); } else { - // No annotations, simply indent by 4. - try!(write!(w, " ")); + // No annotations, simply indent. + try!(write!(w, "{1:0$}", indent, "")); } // Write out the result values, if any. From 6f1a60366f2337f55dadb36f7bce269ab88ba01d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 14:49:31 -0700 Subject: [PATCH 419/968] Create value aliases when necessary. If a secondary value in the source pattern becomes a primary value in the destination pattern, it is not possible to overwrite the definition of the source value. Instead, change the original source value to an alias to the new promary value. --- filetests/isa/riscv/expand-i32.cton | 3 +++ lib/cretonne/meta/gen_legalizer.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index b2473aa608..c0ca7be56f 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -16,3 +16,6 @@ ebb0(v1: i32, v2: i32): } ; check: $v3 = iadd $v1, $v2 ; check: $(cout=$V) = icmp ult, $v3, $v1 +; It's possible the legalizer will rewrite these value aliases in the future. +; check: $v4 -> $cout +; check: return $v3, $v4 diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index bc662dca8b..6c154adc56 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -106,6 +106,7 @@ def emit_dst_inst(node, fmt): # type: (Def, Formatter) -> None exact_replace = False replaced_inst = None # type: str + fixup_first_result = False if len(node.defs) == 0: # This node doesn't define any values, so just insert the new # instruction. @@ -125,6 +126,7 @@ def emit_dst_inst(node, fmt): # Insert a new instruction since its primary def doesn't match the # src. builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) + fixup_first_result = node.defs[0].is_output() fmt.line('{}.{};'.format(builder, node.expr.rust_builder())) @@ -139,6 +141,14 @@ def emit_dst_inst(node, fmt): if exact_replace: fmt.comment('exactreplacement') + # Fix up any output vars. + if fixup_first_result: + # The first result of the instruction just inserted is an output var, + # but it was not a primary result in the source pattern. + # We need to change the original value to an alias of the primary one + # we just inserted. + fmt.line('dfg.change_to_alias(src_{0}, {0});'.format(node.defs[0])) + def gen_xform(xform, fmt): # type: (XForm, Formatter) -> None From 7bbbe97d2f843d1b149b78d4a6be2dc51780a948 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 15:54:58 -0700 Subject: [PATCH 420/968] Resolve value aliases when legalizing instructions. Since we're deconstructing an instruction anyway, go ahead and resolve any value aliases on its arguments before we construct the replacement instructions. --- filetests/isa/riscv/legalize-i64.cton | 20 ++++++++++++++++++++ lib/cretonne/meta/gen_legalizer.py | 8 ++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index d4f3e6b05e..41bc2e2947 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -43,3 +43,23 @@ ebb0(v1: i64, v2: i64): ; check: [R#8c ; sameln: $(v3h=$V) = bxor $v1h, $v2h ; check: $v3 = iconcat_lohi $v3l, $v3h + +function arith_add(i64, i64) -> i64 { +; Legalizing iadd.i64 requires two steps: +; 1. Narrow to iadd_cout.i32, then +; 2. Expand iadd_cout.i32 since RISC-V has no carry flag. +ebb0(v1: i64, v2: i64): + v3 = iadd v1, v2 + return v3 +} +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: [R#0c +; sameln: $(v3l=$V) = iadd $v1l, $v2l +; check: $(c=$V) = icmp ult, $v3l, $v1l +; check: [R#0c +; sameln: $(v3h1=$V) = iadd $v1h, $v2h +; TODO: This doesn't typecheck. We need to convert the b1 result to i32. +; check: [R#0c +; sameln: $(v3h=$V) = iadd $v3h1, $c +; check: $v3 = iconcat_lohi $v3l, $v3h diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 6c154adc56..ca7a81e069 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -63,11 +63,11 @@ def unwrap_inst(iref, node, fmt): else: # This is a value operand. if nvops == 1: - outs.append(prefix + 'arg') + arg = prefix + 'arg' else: - outs.append( - '{}args[{}]'.format( - prefix, iform.value_operands.index(i))) + arg = '{}args[{}]'.format( + prefix, iform.value_operands.index(i)) + outs.append('dfg.resolve_aliases({})'.format(arg)) fmt.line('({})'.format(', '.join(outs))) fmt.outdented_line('} else {') fmt.line('unreachable!("bad instruction format")') From 1e2f3c0ed48fe5bddf08f6a29d3ade4d0c16697b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Nov 2016 17:39:20 -0700 Subject: [PATCH 421/968] Check for unsupported value transformations. Add an assertion for the value placements that we don't support yet. 1. A primary result in the source pattern becomes a secondary result in the destination. 2. A secondary result becomes a secondary result, and the destination instruction is not exactly matching the source. --- lib/cretonne/meta/gen_legalizer.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index ca7a81e069..bd4ff926eb 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -138,9 +138,6 @@ def emit_dst_inst(node, fmt): .format(replaced_inst), '}'): fmt.line('pos.next_inst();') - if exact_replace: - fmt.comment('exactreplacement') - # Fix up any output vars. if fixup_first_result: # The first result of the instruction just inserted is an output var, @@ -149,6 +146,15 @@ def emit_dst_inst(node, fmt): # we just inserted. fmt.line('dfg.change_to_alias(src_{0}, {0});'.format(node.defs[0])) + if not exact_replace: + # We don't support secondary values as outputs yet. Depending on the + # source value, we would need to : + # 1. For a primary source value, replace with a copy instruction. + # 2. For a secondary source value, request that the builder reuses the + # value when making secondary result nodes. + for d in node.defs[1:]: + assert not d.is_output() + def gen_xform(xform, fmt): # type: (XForm, Formatter) -> None From 01494b1a47bf23c09a5bf9943f5d43fced501f0d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 08:52:21 -0800 Subject: [PATCH 422/968] Add more Python type annotations. --- lib/cretonne/meta/cretonne/__init__.py | 44 +++++++++++++++++++++++--- lib/cretonne/meta/cretonne/typevar.py | 2 +- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 8a46b2d08f..7463f47d69 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -14,11 +14,15 @@ from .predicates import And, Predicate, FieldPredicate # noqa # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: - from typing import Tuple, Union, Any, Iterable, Sequence # noqa + from typing import Tuple, Union, Any, Iterable, Sequence, TYPE_CHECKING # noqa MaybeBoundInst = Union['Instruction', 'BoundInstruction'] AnyPredicate = Union['Predicate', 'FieldPredicate'] + OperandSpec = Union['OperandKind', 'ValueType', 'TypeVar'] except ImportError: - pass + TYPE_CHECKING = False + +if TYPE_CHECKING: + from .typevar import TypeVar # noqa camel_re = re.compile('(^|_)([a-z])') @@ -299,6 +303,7 @@ class OperandKind(object): """ def __init__(self, name, doc, default_member=None, rust_type=None): + # type: (str, str, str, str) -> None self.name = name self.__doc__ = doc self.default_member = default_member @@ -307,12 +312,15 @@ class OperandKind(object): self.rust_type = rust_type or camel_case(name) def __str__(self): + # type: () -> str return self.name def __repr__(self): + # type: () -> str return 'OperandKind({})'.format(self.name) def operand_kind(self): + # type: () -> OperandKind """ An `OperandKind` instance can be used directly as the type of an `Operand` when defining an instruction. @@ -357,10 +365,12 @@ class ImmediateKind(OperandKind): """ def __init__(self, name, doc, default_member='imm', rust_type=None): + # type: (str, str, str, str) -> None super(ImmediateKind, self).__init__( name, doc, default_member, rust_type) def __repr__(self): + # type: () -> str return 'ImmediateKind({})'.format(self.name) @@ -372,10 +382,12 @@ class EntityRefKind(OperandKind): """ def __init__(self, name, doc, default_member=None, rust_type=None): + # type: (str, str, str, str) -> None super(EntityRefKind, self).__init__( name, doc, default_member or name, rust_type) def __repr__(self): + # type: () -> str return 'EntityRefKind({})'.format(self.name) @@ -395,6 +407,7 @@ class ValueType(object): all_scalars = list() # type: List[ValueType] def __init__(self, name, membytes, doc): + # type: (str, int, str) -> None self.name = name self.membytes = membytes self.__doc__ = doc @@ -402,9 +415,11 @@ class ValueType(object): ValueType._registry[name] = self def __str__(self): + # type: () -> str return self.name def operand_kind(self): + # type: () -> OperandKind """ When a `ValueType` object is used to describe the type of an `Operand` in an instruction definition, the kind of that operand is an SSA value. @@ -416,6 +431,7 @@ class ValueType(object): @staticmethod def by_name(name): + # type: (str) -> ValueType if name in ValueType._registry: return ValueType._registry[name] else: @@ -431,20 +447,24 @@ class ScalarType(ValueType): """ def __init__(self, name, membytes, doc): + # type: (str, int, str) -> None super(ScalarType, self).__init__(name, membytes, doc) - self._vectors = dict() + self._vectors = dict() # type: Dict[int, VectorType] # Assign numbers starting from 1. (0 is VOID). ValueType.all_scalars.append(self) self.number = len(ValueType.all_scalars) assert self.number < 16, 'Too many scalar types' def __repr__(self): + # type: () -> str return 'ScalarType({})'.format(self.name) def rust_name(self): + # type: () -> str return 'types::' + self.name.upper() def by(self, lanes): + # type: (int) -> VectorType """ Get a vector type with this type as the lane type. @@ -467,6 +487,7 @@ class VectorType(ValueType): """ def __init__(self, base, lanes): + # type: (ScalarType, int) -> None assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' super(VectorType, self).__init__( name='{}x{}'.format(base.name, lanes), @@ -479,6 +500,7 @@ class VectorType(ValueType): self.number = 16*int(math.log(lanes, 2)) + base.number def __repr__(self): + # type: () -> str return ('VectorType(base={}, lanes={})' .format(self.base.name, self.lanes)) @@ -487,6 +509,7 @@ class IntType(ScalarType): """A concrete scalar integer type.""" def __init__(self, bits): + # type: (int) -> None assert bits > 0, 'IntType must have positive number of bits' super(IntType, self).__init__( name='i{:d}'.format(bits), @@ -495,6 +518,7 @@ class IntType(ScalarType): self.bits = bits def __repr__(self): + # type: () -> str return 'IntType(bits={})'.format(self.bits) @@ -502,6 +526,7 @@ class FloatType(ScalarType): """A concrete scalar floating point type.""" def __init__(self, bits, doc): + # type: (int, str) -> None assert bits > 0, 'FloatType must have positive number of bits' super(FloatType, self).__init__( name='f{:d}'.format(bits), @@ -510,6 +535,7 @@ class FloatType(ScalarType): self.bits = bits def __repr__(self): + # type: () -> str return 'FloatType(bits={})'.format(self.bits) @@ -517,6 +543,7 @@ class BoolType(ScalarType): """A concrete scalar boolean type.""" def __init__(self, bits): + # type: (int) -> None assert bits > 0, 'BoolType must have positive number of bits' super(BoolType, self).__init__( name='b{:d}'.format(bits), @@ -525,6 +552,7 @@ class BoolType(ScalarType): self.bits = bits def __repr__(self): + # type: () -> str return 'BoolType(bits={})'.format(self.bits) @@ -545,6 +573,7 @@ class InstructionGroup(object): _current = None # type: InstructionGroup def open(self): + # type: () -> None """ Open this instruction group such that future new instructions are added to this group. @@ -555,6 +584,7 @@ class InstructionGroup(object): InstructionGroup._current = self def close(self): + # type: () -> None """ Close this instruction group. This function should be called before opening another instruction group. @@ -565,13 +595,15 @@ class InstructionGroup(object): InstructionGroup._current = None def __init__(self, name, doc): + # type: (str, str) -> None self.name = name self.__doc__ = doc - self.instructions = [] + self.instructions = [] # type: List[Instruction] self.open() @staticmethod def append(inst): + # type: (Instruction) -> None assert InstructionGroup._current, \ "Open an instruction group before defining instructions." InstructionGroup._current.instructions.append(inst) @@ -599,21 +631,25 @@ class Operand(object): """ def __init__(self, name, typ, doc=''): + # type: (str, OperandSpec, str) -> None self.name = name self.typ = typ self.__doc__ = doc self.kind = typ.operand_kind() def get_doc(self): + # type: () -> str if self.__doc__: return self.__doc__ else: return self.typ.__doc__ def __str__(self): + # type: () -> str return "`{}`".format(self.name) def is_value(self): + # type: () -> bool """ Is this an SSA value operand? """ diff --git a/lib/cretonne/meta/cretonne/typevar.py b/lib/cretonne/meta/cretonne/typevar.py index fa0179304b..18f1e6fa2c 100644 --- a/lib/cretonne/meta/cretonne/typevar.py +++ b/lib/cretonne/meta/cretonne/typevar.py @@ -319,7 +319,7 @@ class TypeVar(object): # When a `TypeVar` object is used to describe the type of an `Operand` # in an instruction definition, the kind of that operand is an SSA # value. - return value + return value # type: ignore def free_typevar(self): # type: () -> TypeVar From 1e1830aaa6108e57cf4c6c22fc1b6837c36d4edd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 09:04:40 -0800 Subject: [PATCH 423/968] Resolve import cycles. --- lib/cretonne/meta/cretonne/ast.py | 8 ++++---- lib/cretonne/meta/cretonne/typevar.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py index ac21809317..b437148435 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cretonne/ast.py @@ -5,7 +5,7 @@ This module defines classes that can be used to create abstract syntax trees for patern matching an rewriting of cretonne instructions. """ from __future__ import absolute_import -from . import Instruction, BoundInstruction +import cretonne try: from typing import Union, Tuple # noqa @@ -174,12 +174,12 @@ class Apply(Expr): """ def __init__(self, inst, args): - # type: (Union[Instruction, BoundInstruction], Tuple[Expr, ...]) -> None # noqa - if isinstance(inst, BoundInstruction): + # type: (Union[cretonne.Instruction, cretonne.BoundInstruction], Tuple[Expr, ...]) -> None # noqa + if isinstance(inst, cretonne.BoundInstruction): self.inst = inst.inst self.typevars = inst.typevars else: - assert isinstance(inst, Instruction) + assert isinstance(inst, cretonne.Instruction) self.inst = inst self.typevars = () self.args = args diff --git a/lib/cretonne/meta/cretonne/typevar.py b/lib/cretonne/meta/cretonne/typevar.py index 18f1e6fa2c..a6d8d9637f 100644 --- a/lib/cretonne/meta/cretonne/typevar.py +++ b/lib/cretonne/meta/cretonne/typevar.py @@ -6,7 +6,7 @@ polymorphic by using type variables. """ from __future__ import absolute_import import math -from . import OperandKind, value # noqa +import cretonne try: from typing import Tuple, Union # noqa @@ -315,11 +315,11 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='DoubleWidth') def operand_kind(self): - # type: () -> OperandKind + # type: () -> cretonne.OperandKind # When a `TypeVar` object is used to describe the type of an `Operand` # in an instruction definition, the kind of that operand is an SSA # value. - return value # type: ignore + return cretonne.value # type: ignore def free_typevar(self): # type: () -> TypeVar From f8545574b5bdd5ea7edd93a071d98e03e3adec93 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 09:40:19 -0800 Subject: [PATCH 424/968] Move ValueType into a new cdsl top-level module. We want to separate the Python classes that make up the DSL used to define the Cretonne language from the concrete definitions. - cdsl.types defines the ValueType class hierarchy. - base.types defines the concrete types. --- docs/cton_domain.py | 6 +- docs/metaref.rst | 25 +-- lib/cretonne/meta/base/__init__.py | 1 + lib/cretonne/meta/{cretonne => base}/types.py | 4 +- lib/cretonne/meta/cdsl/__init__.py | 6 + lib/cretonne/meta/cdsl/types.py | 160 ++++++++++++++ lib/cretonne/meta/check.sh | 2 +- lib/cretonne/meta/cretonne/__init__.py | 198 ++---------------- lib/cretonne/meta/cretonne/base.py | 2 +- lib/cretonne/meta/gen_instr.py | 5 +- lib/cretonne/meta/gen_types.py | 3 +- 11 files changed, 210 insertions(+), 202 deletions(-) create mode 100644 lib/cretonne/meta/base/__init__.py rename lib/cretonne/meta/{cretonne => base}/types.py (88%) create mode 100644 lib/cretonne/meta/cdsl/__init__.py create mode 100644 lib/cretonne/meta/cdsl/types.py diff --git a/docs/cton_domain.py b/docs/cton_domain.py index b45aa5e677..00242ff565 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -245,7 +245,7 @@ class TypeDocumenter(sphinx.ext.autodoc.Documenter): return False def resolve_name(self, modname, parents, path, base): - return 'cretonne.types', [base] + return 'base.types', [base] def add_content(self, more_content, no_docstring=False): super(TypeDocumenter, self).add_content(more_content, no_docstring) @@ -280,11 +280,11 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): op = inst.ins[0] sig += ' ' + op.name # If the first input is variable-args, this is 'return'. No parens. - if op.typ.operand_kind().name == 'variable_args': + if op.kind.name == 'variable_args': sig += '...'.format(op.name) for op in inst.ins[1:]: # This is a call or branch with args in (...). - if op.typ.operand_kind().name == 'variable_args': + if op.kind.name == 'variable_args': sig += '({}...)'.format(op.name) else: sig += ', ' + op.name diff --git a/docs/metaref.rst b/docs/metaref.rst index fa3226c51f..6d8d8ad2b1 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -4,7 +4,7 @@ Cretonne Meta Language Reference .. default-domain:: py .. highlight:: python -.. module:: cretonne +.. module:: cdsl The Cretonne meta language is used to define instructions for Cretonne. It is a domain specific language embedded in Python. This document describes the Python @@ -16,8 +16,8 @@ steps: 1. The Python modules are imported. This has the effect of building static data structures in global variables in the modules. These static data structures - use the classes in the :mod:`cretonne` module to describe instruction sets - and other properties. + in the :mod:`base` and :mod:`isa` packages use the classes in the + :mod:`cdsl` module to describe instruction sets and other properties. 2. The static data structures are processed to produce Rust source code and constant tables. @@ -41,6 +41,7 @@ ISA, and defined in a `settings` module under the appropriate Settings can take boolean on/off values, small numbers, or explicitly enumerated symbolic values. Each type is represented by a sub-class of :class:`Setting`: +.. currentmodule:: cretonne .. inheritance-diagram:: Setting BoolSetting NumSetting EnumSetting :parts: 1 @@ -125,36 +126,36 @@ Immediate operands Immediate instruction operands don't correspond to SSA values, but have values that are encoded directly in the instruction. Immediate operands don't -have types from the :class:`cretonne.ValueType` type system; they often have +have types from the :class:`cdsl.types.ValueType` type system; they often have enumerated values of a specific type. The type of an immediate operand is indicated with an instance of :class:`ImmediateKind`. +.. currentmodule:: cretonne .. autoclass:: ImmediateKind .. automodule:: cretonne.immediates :members: -.. currentmodule:: cretonne - Entity references ----------------- Instruction operands can also refer to other entities in the same function. This can be extended basic blocks, or entities declared in the function preamble. +.. currentmodule:: cretonne + .. autoclass:: EntityRefKind .. automodule:: cretonne.entities :members: -.. currentmodule:: cretonne - Value types ----------- -Concrete value types are represented as instances of :class:`cretonne.ValueType`. There are +Concrete value types are represented as instances of :class:`cdsl.types.ValueType`. There are subclasses to represent scalar and vector types. +.. currentmodule:: cdsl.types .. autoclass:: ValueType .. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType BoolType :parts: 1 @@ -169,11 +170,9 @@ subclasses to represent scalar and vector types. .. autoclass:: BoolType :members: -.. automodule:: cretonne.types +.. automodule:: base.types :members: -.. currentmodule:: cretonne - There are no predefined vector types, but they can be created as needed with the :func:`ScalarType.by` function. @@ -181,6 +180,8 @@ the :func:`ScalarType.by` function. Instruction representation ========================== +.. currentmodule:: cretonne + The Rust in-memory representation of instructions is derived from the instruction descriptions. Part of the representation is generated, and part is written as Rust code in the `cretonne.instructions` module. The instruction diff --git a/lib/cretonne/meta/base/__init__.py b/lib/cretonne/meta/base/__init__.py new file mode 100644 index 0000000000..132b469ee6 --- /dev/null +++ b/lib/cretonne/meta/base/__init__.py @@ -0,0 +1 @@ +"""Definitions for the base Cretonne language.""" diff --git a/lib/cretonne/meta/cretonne/types.py b/lib/cretonne/meta/base/types.py similarity index 88% rename from lib/cretonne/meta/cretonne/types.py rename to lib/cretonne/meta/base/types.py index 05dcb53e16..a2eb1054b9 100644 --- a/lib/cretonne/meta/cretonne/types.py +++ b/lib/cretonne/meta/base/types.py @@ -1,8 +1,8 @@ """ -The cretonne.types module predefines all the Cretonne scalar types. +The base.types module predefines all the Cretonne scalar types. """ from __future__ import absolute_import -from . import ScalarType, IntType, FloatType, BoolType +from cdsl.types import ScalarType, IntType, FloatType, BoolType #: Boolean. b1 = ScalarType( diff --git a/lib/cretonne/meta/cdsl/__init__.py b/lib/cretonne/meta/cdsl/__init__.py new file mode 100644 index 0000000000..417f800950 --- /dev/null +++ b/lib/cretonne/meta/cdsl/__init__.py @@ -0,0 +1,6 @@ +""" +Cretonne DSL classes. + +This module defines the classes that are used to define Cretonne instructions +and other entitties. +""" diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py new file mode 100644 index 0000000000..3d5a5dcd38 --- /dev/null +++ b/lib/cretonne/meta/cdsl/types.py @@ -0,0 +1,160 @@ +"""Cretonne ValueType hierarchy""" +from __future__ import absolute_import +import math + + +# ValueType instances (i8, i32, ...) are provided in the cretonne.types module. +class ValueType(object): + """ + A concrete SSA value type. + + All SSA values have a type that is described by an instance of `ValueType` + or one of its subclasses. + """ + + # Map name -> ValueType. + _registry = dict() # type: Dict[str, ValueType] + + # List of all the scalar types. + all_scalars = list() # type: List[ValueType] + + def __init__(self, name, membytes, doc): + # type: (str, int, str) -> None + self.name = name + self.membytes = membytes + self.__doc__ = doc + assert name not in ValueType._registry + ValueType._registry[name] = self + + def __str__(self): + # type: () -> str + return self.name + + def free_typevar(self): + return None + + @staticmethod + def by_name(name): + # type: (str) -> ValueType + if name in ValueType._registry: + return ValueType._registry[name] + else: + raise AttributeError("No type named '{}'".format(name)) + + +class ScalarType(ValueType): + """ + A concrete scalar (not vector) type. + + Also tracks a unique set of :py:class:`VectorType` instances with this type + as the lane type. + """ + + def __init__(self, name, membytes, doc): + # type: (str, int, str) -> None + super(ScalarType, self).__init__(name, membytes, doc) + self._vectors = dict() # type: Dict[int, VectorType] + # Assign numbers starting from 1. (0 is VOID). + ValueType.all_scalars.append(self) + self.number = len(ValueType.all_scalars) + assert self.number < 16, 'Too many scalar types' + + def __repr__(self): + # type: () -> str + return 'ScalarType({})'.format(self.name) + + def rust_name(self): + # type: () -> str + return 'types::' + self.name.upper() + + def by(self, lanes): + # type: (int) -> VectorType + """ + Get a vector type with this type as the lane type. + + For example, ``i32.by(4)`` returns the :obj:`i32x4` type. + """ + if lanes in self._vectors: + return self._vectors[lanes] + else: + v = VectorType(self, lanes) + self._vectors[lanes] = v + return v + + +class VectorType(ValueType): + """ + A concrete SIMD vector type. + + A vector type has a lane type which is an instance of :class:`ScalarType`, + and a positive number of lanes. + """ + + def __init__(self, base, lanes): + # type: (ScalarType, int) -> None + assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' + super(VectorType, self).__init__( + name='{}x{}'.format(base.name, lanes), + membytes=lanes*base.membytes, + doc=""" + A SIMD vector with {} lanes containing a `{}` each. + """.format(lanes, base.name)) + self.base = base + self.lanes = lanes + self.number = 16*int(math.log(lanes, 2)) + base.number + + def __repr__(self): + # type: () -> str + return ('VectorType(base={}, lanes={})' + .format(self.base.name, self.lanes)) + + +class IntType(ScalarType): + """A concrete scalar integer type.""" + + def __init__(self, bits): + # type: (int) -> None + assert bits > 0, 'IntType must have positive number of bits' + super(IntType, self).__init__( + name='i{:d}'.format(bits), + membytes=bits // 8, + doc="An integer type with {} bits.".format(bits)) + self.bits = bits + + def __repr__(self): + # type: () -> str + return 'IntType(bits={})'.format(self.bits) + + +class FloatType(ScalarType): + """A concrete scalar floating point type.""" + + def __init__(self, bits, doc): + # type: (int, str) -> None + assert bits > 0, 'FloatType must have positive number of bits' + super(FloatType, self).__init__( + name='f{:d}'.format(bits), + membytes=bits // 8, + doc=doc) + self.bits = bits + + def __repr__(self): + # type: () -> str + return 'FloatType(bits={})'.format(self.bits) + + +class BoolType(ScalarType): + """A concrete scalar boolean type.""" + + def __init__(self, bits): + # type: (int) -> None + assert bits > 0, 'BoolType must have positive number of bits' + super(BoolType, self).__init__( + name='b{:d}'.format(bits), + membytes=bits // 8, + doc="A boolean type with {} bits.".format(bits)) + self.bits = bits + + def __repr__(self): + # type: () -> str + return 'BoolType(bits={})'.format(self.bits) diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh index b0a6c853db..2077716eb7 100755 --- a/lib/cretonne/meta/check.sh +++ b/lib/cretonne/meta/check.sh @@ -14,7 +14,7 @@ runif() { # Check Python sources for Python 3 compatibility using pylint. # # Install pylint with 'pip install pylint'. -runif pylint --py3k --reports=no -- *.py cretonne isa +runif pylint --py3k --reports=no -- *.py cdsl base cretonne isa # Style linting. runif flake8 . diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 7463f47d69..b1cc0322bd 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -6,10 +6,10 @@ instructions. """ from __future__ import absolute_import import re -import math import importlib from collections import OrderedDict from .predicates import And, Predicate, FieldPredicate # noqa +import cdsl.types # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -17,7 +17,7 @@ try: from typing import Tuple, Union, Any, Iterable, Sequence, TYPE_CHECKING # noqa MaybeBoundInst = Union['Instruction', 'BoundInstruction'] AnyPredicate = Union['Predicate', 'FieldPredicate'] - OperandSpec = Union['OperandKind', 'ValueType', 'TypeVar'] + OperandSpec = Union['OperandKind', 'cdsl.types.ValueType', 'TypeVar'] except ImportError: TYPE_CHECKING = False @@ -391,171 +391,6 @@ class EntityRefKind(OperandKind): return 'EntityRefKind({})'.format(self.name) -# ValueType instances (i8, i32, ...) are provided in the cretonne.types module. -class ValueType(object): - """ - A concrete SSA value type. - - All SSA values have a type that is described by an instance of `ValueType` - or one of its subclasses. - """ - - # Map name -> ValueType. - _registry = dict() # type: Dict[str, ValueType] - - # List of all the scalar types. - all_scalars = list() # type: List[ValueType] - - def __init__(self, name, membytes, doc): - # type: (str, int, str) -> None - self.name = name - self.membytes = membytes - self.__doc__ = doc - assert name not in ValueType._registry - ValueType._registry[name] = self - - def __str__(self): - # type: () -> str - return self.name - - def operand_kind(self): - # type: () -> OperandKind - """ - When a `ValueType` object is used to describe the type of an `Operand` - in an instruction definition, the kind of that operand is an SSA value. - """ - return value - - def free_typevar(self): - return None - - @staticmethod - def by_name(name): - # type: (str) -> ValueType - if name in ValueType._registry: - return ValueType._registry[name] - else: - raise AttributeError("No type named '{}'".format(name)) - - -class ScalarType(ValueType): - """ - A concrete scalar (not vector) type. - - Also tracks a unique set of :py:class:`VectorType` instances with this type - as the lane type. - """ - - def __init__(self, name, membytes, doc): - # type: (str, int, str) -> None - super(ScalarType, self).__init__(name, membytes, doc) - self._vectors = dict() # type: Dict[int, VectorType] - # Assign numbers starting from 1. (0 is VOID). - ValueType.all_scalars.append(self) - self.number = len(ValueType.all_scalars) - assert self.number < 16, 'Too many scalar types' - - def __repr__(self): - # type: () -> str - return 'ScalarType({})'.format(self.name) - - def rust_name(self): - # type: () -> str - return 'types::' + self.name.upper() - - def by(self, lanes): - # type: (int) -> VectorType - """ - Get a vector type with this type as the lane type. - - For example, ``i32.by(4)`` returns the :obj:`i32x4` type. - """ - if lanes in self._vectors: - return self._vectors[lanes] - else: - v = VectorType(self, lanes) - self._vectors[lanes] = v - return v - - -class VectorType(ValueType): - """ - A concrete SIMD vector type. - - A vector type has a lane type which is an instance of :class:`ScalarType`, - and a positive number of lanes. - """ - - def __init__(self, base, lanes): - # type: (ScalarType, int) -> None - assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' - super(VectorType, self).__init__( - name='{}x{}'.format(base.name, lanes), - membytes=lanes*base.membytes, - doc=""" - A SIMD vector with {} lanes containing a `{}` each. - """.format(lanes, base.name)) - self.base = base - self.lanes = lanes - self.number = 16*int(math.log(lanes, 2)) + base.number - - def __repr__(self): - # type: () -> str - return ('VectorType(base={}, lanes={})' - .format(self.base.name, self.lanes)) - - -class IntType(ScalarType): - """A concrete scalar integer type.""" - - def __init__(self, bits): - # type: (int) -> None - assert bits > 0, 'IntType must have positive number of bits' - super(IntType, self).__init__( - name='i{:d}'.format(bits), - membytes=bits // 8, - doc="An integer type with {} bits.".format(bits)) - self.bits = bits - - def __repr__(self): - # type: () -> str - return 'IntType(bits={})'.format(self.bits) - - -class FloatType(ScalarType): - """A concrete scalar floating point type.""" - - def __init__(self, bits, doc): - # type: (int, str) -> None - assert bits > 0, 'FloatType must have positive number of bits' - super(FloatType, self).__init__( - name='f{:d}'.format(bits), - membytes=bits // 8, - doc=doc) - self.bits = bits - - def __repr__(self): - # type: () -> str - return 'FloatType(bits={})'.format(self.bits) - - -class BoolType(ScalarType): - """A concrete scalar boolean type.""" - - def __init__(self, bits): - # type: (int) -> None - assert bits > 0, 'BoolType must have positive number of bits' - super(BoolType, self).__init__( - name='b{:d}'.format(bits), - membytes=bits // 8, - doc="A boolean type with {} bits.".format(bits)) - self.bits = bits - - def __repr__(self): - # type: () -> str - return 'BoolType(bits={})'.format(self.bits) - - # Defining instructions. @@ -633,9 +468,12 @@ class Operand(object): def __init__(self, name, typ, doc=''): # type: (str, OperandSpec, str) -> None self.name = name - self.typ = typ self.__doc__ = doc - self.kind = typ.operand_kind() + self.typ = typ + if isinstance(typ, cdsl.types.ValueType): + self.kind = value + else: + self.kind = typ.operand_kind() def get_doc(self): # type: () -> str @@ -688,7 +526,7 @@ class InstructionFormat(object): """ # Map (multiple_results, kind, kind, ...) -> InstructionFormat - _registry = dict() # type: Dict[Tuple, InstructionFormat] + _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...]], InstructionFormat] # noqa # All existing formats. all_formats = list() # type: List[InstructionFormat] @@ -715,7 +553,7 @@ class InstructionFormat(object): self.typevar_operand = self.value_operands[0] # Compute a signature for the global registry. - sig = (self.multiple_results,) + self.kinds + sig = (self.multiple_results, self.kinds) if sig in InstructionFormat._registry: raise RuntimeError( "Format '{}' has the same signature as existing format '{}'" @@ -782,11 +620,11 @@ class InstructionFormat(object): multiple_results = outs[0].kind == variable_args else: multiple_results = len(outs) > 1 - sig = (multiple_results,) + tuple(op.kind for op in ins) + sig = (multiple_results, tuple(op.kind for op in ins)) if sig not in InstructionFormat._registry: raise RuntimeError( "No instruction format matches ins = ({}){}".format( - ", ".join(map(str, sig[1:])), + ", ".join(map(str, sig[1])), "[multiple results]" if multiple_results else "")) return InstructionFormat._registry[sig] @@ -991,7 +829,7 @@ class Instruction(object): return x def bind(self, *args): - # type: (*ValueType) -> BoundInstruction + # type: (*cdsl.types.ValueType) -> BoundInstruction """ Bind a polymorphic instruction to a concrete list of type variable values. @@ -1007,10 +845,10 @@ class Instruction(object): >>> iadd.i32 """ - return self.bind(ValueType.by_name(name)) + return self.bind(cdsl.types.ValueType.by_name(name)) def fully_bound(self): - # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] + # type: () -> Tuple[Instruction, Tuple[cdsl.types.ValueType, ...]] """ Verify that all typevars have been bound, and return a `(inst, typevars)` pair. @@ -1036,7 +874,7 @@ class BoundInstruction(object): """ def __init__(self, inst, typevars): - # type: (Instruction, Tuple[ValueType, ...]) -> None + # type: (Instruction, Tuple[cdsl.types.ValueType, ...]) -> None self.inst = inst self.typevars = typevars assert len(typevars) <= 1 + len(inst.other_typevars) @@ -1045,7 +883,7 @@ class BoundInstruction(object): return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) def bind(self, *args): - # type: (*ValueType) -> BoundInstruction + # type: (*cdsl.types.ValueType) -> BoundInstruction """ Bind additional typevars. """ @@ -1058,10 +896,10 @@ class BoundInstruction(object): >>> uext.i32.i8 """ - return self.bind(ValueType.by_name(name)) + return self.bind(cdsl.types.ValueType.by_name(name)) def fully_bound(self): - # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] + # type: () -> Tuple[Instruction, Tuple[cdsl.types.ValueType, ...]] """ Verify that all typevars have been bound, and return a `(inst, typevars)` pair. diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index adbbf68d28..d991228872 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -7,7 +7,7 @@ support. from __future__ import absolute_import from . import Operand, Instruction, InstructionGroup, variable_args from .typevar import TypeVar -from .types import i8, f32, f64, b1 +from base.types import i8, f32, f64, b1 from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from . import entities diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 569eca71b7..d7afcf1887 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -5,6 +5,7 @@ from __future__ import absolute_import import srcgen import constant_hash from unique_table import UniqueTable, UniqueSeqTable +import cdsl.types import cretonne @@ -310,11 +311,11 @@ def get_constraint(op, ctrl_typevar, type_sets): - `Free(idx)` where `idx` is an index into `type_sets`. - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. """ + assert op.kind is cretonne.value t = op.typ - assert t.operand_kind() is cretonne.value # A concrete value type. - if isinstance(t, cretonne.ValueType): + if isinstance(t, cdsl.types.ValueType): return 'Concrete({})'.format(t.rust_name()) if t.free_typevar() is not ctrl_typevar: diff --git a/lib/cretonne/meta/gen_types.py b/lib/cretonne/meta/gen_types.py index 2b284d135b..5ffed14cf4 100644 --- a/lib/cretonne/meta/gen_types.py +++ b/lib/cretonne/meta/gen_types.py @@ -9,7 +9,8 @@ This ensures that Python and Rust use the same type numbering. """ from __future__ import absolute_import import srcgen -from cretonne import ValueType +from cdsl.types import ValueType +import base.types # noqa def emit_type(ty, fmt): From 2fe61e83f67c802a0a1ea357656c08470f63db48 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 10:20:07 -0800 Subject: [PATCH 425/968] Split out predicates and settings. - cdsl.predicates defines classes for describing predicates. - cdsl.settings defines classes for describing settings. - base.settings defines shared settings. --- docs/metaref.rst | 9 +- .../meta/{cretonne => base}/settings.py | 2 +- .../meta/{cretonne => cdsl}/predicates.py | 0 lib/cretonne/meta/cdsl/settings.py | 260 ++++++++++++++++++ lib/cretonne/meta/cretonne/__init__.py | 260 +----------------- lib/cretonne/meta/gen_settings.py | 4 +- lib/cretonne/meta/isa/riscv/recipes.py | 2 +- lib/cretonne/meta/isa/riscv/settings.py | 6 +- 8 files changed, 276 insertions(+), 267 deletions(-) rename lib/cretonne/meta/{cretonne => base}/settings.py (93%) rename lib/cretonne/meta/{cretonne => cdsl}/predicates.py (100%) create mode 100644 lib/cretonne/meta/cdsl/settings.py diff --git a/docs/metaref.rst b/docs/metaref.rst index 6d8d8ad2b1..cfb39b00ad 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -28,20 +28,21 @@ process if anything in the :file:`lib/cretonne/meta` directory has changed since the last build. +.. module:: cdsl.settings + Settings ======== Settings are used by the environment embedding Cretonne to control the details of code generation. Each setting is defined in the meta language so a compact and consistent Rust representation can be generated. Shared settings are defined -in the :mod:`cretonne.settings` module. Some settings are specific to a target -ISA, and defined in a `settings` module under the appropriate +in the :mod:`base.settings` module. Some settings are specific to a target ISA, +and defined in a `settings` module under the appropriate :file:`lib/cretonne/meta/isa/*` directory. Settings can take boolean on/off values, small numbers, or explicitly enumerated symbolic values. Each type is represented by a sub-class of :class:`Setting`: -.. currentmodule:: cretonne .. inheritance-diagram:: Setting BoolSetting NumSetting EnumSetting :parts: 1 @@ -70,6 +71,8 @@ a module looks like this:: Instruction descriptions ======================== +.. currentmodule:: cretonne + New instructions are defined as instances of the :class:`Instruction` class. As instruction instances are created, they are added to the currently open :class:`InstructionGroup`. diff --git a/lib/cretonne/meta/cretonne/settings.py b/lib/cretonne/meta/base/settings.py similarity index 93% rename from lib/cretonne/meta/cretonne/settings.py rename to lib/cretonne/meta/base/settings.py index 522a649ef3..c6f87f7fbb 100644 --- a/lib/cretonne/meta/cretonne/settings.py +++ b/lib/cretonne/meta/base/settings.py @@ -4,7 +4,7 @@ Cretonne shared settings. This module defines settings are are relevant for all code generators. """ from __future__ import absolute_import -from . import SettingGroup, BoolSetting, EnumSetting +from cdsl.settings import SettingGroup, BoolSetting, EnumSetting group = SettingGroup('shared') diff --git a/lib/cretonne/meta/cretonne/predicates.py b/lib/cretonne/meta/cdsl/predicates.py similarity index 100% rename from lib/cretonne/meta/cretonne/predicates.py rename to lib/cretonne/meta/cdsl/predicates.py diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py new file mode 100644 index 0000000000..1c1e31643c --- /dev/null +++ b/lib/cretonne/meta/cdsl/settings.py @@ -0,0 +1,260 @@ +"""Classes for describing settings and groups of settings.""" +from __future__ import absolute_import +from collections import OrderedDict +from .predicates import Predicate + + +class Setting(object): + """ + A named setting variable that can be configured externally to Cretonne. + + Settings are normally not named when they are created. They get their name + from the `extract_names` method. + """ + + def __init__(self, doc): + self.name = None # Assigned later by `extract_names()`. + self.__doc__ = doc + # Offset of byte in settings vector containing this setting. + self.byte_offset = None + self.group = SettingGroup.append(self) + + def __str__(self): + return '{}.{}'.format(self.group.name, self.name) + + def predicate_context(self): + """ + Return the context where this setting can be evaluated as a (leaf) + predicate. + """ + return self.group + + def predicate_leafs(self, leafs): + leafs.add(self) + + +class BoolSetting(Setting): + """ + A named setting with a boolean on/off value. + + :param doc: Documentation string. + :param default: The default value of this setting. + """ + + def __init__(self, doc, default=False): + super(BoolSetting, self).__init__(doc) + self.default = default + + def default_byte(self): + """ + Get the default value of this setting, as a byte that can be bitwise + or'ed with the other booleans sharing the same byte. + """ + if self.default: + return 1 << self.bit_offset + else: + return 0 + + def rust_predicate(self, prec): + """ + Return the Rust code to compute the value of this setting. + + The emitted code assumes that the setting group exists as a local + variable. + """ + return '{}.{}()'.format(self.group.name, self.name) + + +class NumSetting(Setting): + """ + A named setting with an integral value in the range 0--255. + + :param doc: Documentation string. + :param default: The default value of this setting. + """ + + def __init__(self, doc, default=0): + super(NumSetting, self).__init__(doc) + assert default == int(default) + assert default >= 0 and default <= 255 + self.default = default + + def default_byte(self): + return self.default + + +class EnumSetting(Setting): + """ + A named setting with an enumerated set of possible values. + + The default value is always the first enumerator. + + :param doc: Documentation string. + :param args: Tuple of unique strings representing the possible values. + """ + + def __init__(self, doc, *args): + super(EnumSetting, self).__init__(doc) + assert len(args) > 0, "EnumSetting must have at least one value" + self.values = tuple(str(x) for x in args) + self.default = self.values[0] + + def default_byte(self): + return 0 + + +class SettingGroup(object): + """ + A group of settings. + + Whenever a :class:`Setting` object is created, it is added to the currently + open group. A setting group must be closed explicitly before another can be + opened. + + :param name: Short mnemonic name for setting group. + :param parent: Parent settings group. + """ + + # The currently open setting group. + _current = None # type: SettingGroup + + def __init__(self, name, parent=None): + self.name = name + self.parent = parent + self.settings = [] + # Named predicates computed from settings in this group or its + # parents. + self.named_predicates = [] + # All boolean predicates that can be accessed by number. This includes: + # - All boolean settings in this group. + # - All named predicates. + # - Added anonymous predicates, see `number_predicate()`. + # - Added parent predicates that are replicated in this group. + # Maps predicate -> number. + self.predicate_number = OrderedDict() + + self.open() + + def open(self): + """ + Open this setting group such that future new settings are added to this + group. + """ + assert SettingGroup._current is None, ( + "Can't open {} since {} is already open" + .format(self, SettingGroup._current)) + SettingGroup._current = self + + def close(self, globs=None): + """ + Close this setting group. This function must be called before opening + another setting group. + + :param globs: Pass in `globals()` to run `extract_names` on all + settings defined in the module. + """ + assert SettingGroup._current is self, ( + "Can't close {}, the open setting group is {}" + .format(self, SettingGroup._current)) + SettingGroup._current = None + if globs: + for name, obj in globs.items(): + if isinstance(obj, Setting): + assert obj.name is None, obj.name + obj.name = name + if isinstance(obj, Predicate): + assert obj.name is None + obj.name = name + self.named_predicates.append(obj) + self.layout() + + @staticmethod + def append(setting): + g = SettingGroup._current + assert g, "Open a setting group before defining settings." + g.settings.append(setting) + return g + + def number_predicate(self, pred): + """ + Make sure that `pred` has an assigned number, and will be included in + this group's bit vector. + + The numbered predicates include: + - `BoolSetting` settings that belong to this group. + - `Predicate` instances in `named_predicates`. + - `Predicate` instances without a name. + - Settings or computed predicates that belong to the parent group, but + need to be accessible by number in this group. + + The numbered predicates are referenced by the encoding tables as ISA + predicates. See the `isap` field on `Encoding`. + + :returns: The assigned predicate number in this group. + """ + if pred in self.predicate_number: + return self.predicate_number[pred] + else: + number = len(self.predicate_number) + self.predicate_number[pred] = number + return number + + def layout(self): + """ + Compute the layout of the byte vector used to represent this settings + group. + + The byte vector contains the following entries in order: + + 1. Byte-sized settings like `NumSetting` and `EnumSetting`. + 2. `BoolSetting` settings. + 3. Precomputed named predicates. + 4. Other numbered predicates, including anonymous predicates and parent + predicates that need to be accessible by number. + + Set `self.settings_size` to the length of the byte vector prefix that + contains the settings. All bytes after that are computed, not + configured. + + Set `self.boolean_offset` to the beginning of the numbered predicates, + 2. in the list above. + + Assign `byte_offset` and `bit_offset` fields in all settings. + + After calling this method, no more settings can be added, but + additional predicates can be made accessible with `number_predicate()`. + """ + assert len(self.predicate_number) == 0, "Too late for layout" + + # Assign the non-boolean settings. + byte_offset = 0 + for s in self.settings: + if not isinstance(s, BoolSetting): + s.byte_offset = byte_offset + byte_offset += 1 + + # Then the boolean settings. + self.boolean_offset = byte_offset + for s in self.settings: + if isinstance(s, BoolSetting): + number = self.number_predicate(s) + s.byte_offset = byte_offset + number // 8 + s.bit_offset = number % 8 + + # This is the end of the settings. Round up to a whole number of bytes. + self.boolean_settings = len(self.predicate_number) + self.settings_size = self.byte_size() + + # Now assign numbers to all our named predicates. + for p in self.named_predicates: + self.number_predicate(p) + + def byte_size(self): + """ + Compute the number of bytes required to hold all settings and + precomputed predicates. + + This is the size of the byte-sized settings plus all the numbered + predcate bits rounded up to a whole number of bytes. + """ + return self.boolean_offset + (len(self.predicate_number) + 7) // 8 diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index b1cc0322bd..d2d4386de5 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -7,14 +7,14 @@ instructions. from __future__ import absolute_import import re import importlib -from collections import OrderedDict -from .predicates import And, Predicate, FieldPredicate # noqa +from cdsl.predicates import And import cdsl.types # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: from typing import Tuple, Union, Any, Iterable, Sequence, TYPE_CHECKING # noqa + from cdsl.predicates import Predicate, FieldPredicate # noqa MaybeBoundInst = Union['Instruction', 'BoundInstruction'] AnyPredicate = Union['Predicate', 'FieldPredicate'] OperandSpec = Union['OperandKind', 'cdsl.types.ValueType', 'TypeVar'] @@ -34,262 +34,6 @@ def camel_case(s): return camel_re.sub(lambda m: m.group(2).upper(), s) -class Setting(object): - """ - A named setting variable that can be configured externally to Cretonne. - - Settings are normally not named when they are created. They get their name - from the `extract_names` method. - """ - - def __init__(self, doc): - self.name = None # Assigned later by `extract_names()`. - self.__doc__ = doc - # Offset of byte in settings vector containing this setting. - self.byte_offset = None - self.group = SettingGroup.append(self) - - def __str__(self): - return '{}.{}'.format(self.group.name, self.name) - - def predicate_context(self): - """ - Return the context where this setting can be evaluated as a (leaf) - predicate. - """ - return self.group - - def predicate_leafs(self, leafs): - leafs.add(self) - - -class BoolSetting(Setting): - """ - A named setting with a boolean on/off value. - - :param doc: Documentation string. - :param default: The default value of this setting. - """ - - def __init__(self, doc, default=False): - super(BoolSetting, self).__init__(doc) - self.default = default - - def default_byte(self): - """ - Get the default value of this setting, as a byte that can be bitwise - or'ed with the other booleans sharing the same byte. - """ - if self.default: - return 1 << self.bit_offset - else: - return 0 - - def rust_predicate(self, prec): - """ - Return the Rust code to compute the value of this setting. - - The emitted code assumes that the setting group exists as a local - variable. - """ - return '{}.{}()'.format(self.group.name, self.name) - - -class NumSetting(Setting): - """ - A named setting with an integral value in the range 0--255. - - :param doc: Documentation string. - :param default: The default value of this setting. - """ - - def __init__(self, doc, default=0): - super(NumSetting, self).__init__(doc) - assert default == int(default) - assert default >= 0 and default <= 255 - self.default = default - - def default_byte(self): - return self.default - - -class EnumSetting(Setting): - """ - A named setting with an enumerated set of possible values. - - The default value is always the first enumerator. - - :param doc: Documentation string. - :param args: Tuple of unique strings representing the possible values. - """ - - def __init__(self, doc, *args): - super(EnumSetting, self).__init__(doc) - assert len(args) > 0, "EnumSetting must have at least one value" - self.values = tuple(str(x) for x in args) - self.default = self.values[0] - - def default_byte(self): - return 0 - - -class SettingGroup(object): - """ - A group of settings. - - Whenever a :class:`Setting` object is created, it is added to the currently - open group. A setting group must be closed explicitly before another can be - opened. - - :param name: Short mnemonic name for setting group. - :param parent: Parent settings group. - """ - - # The currently open setting group. - _current = None # type: SettingGroup - - def __init__(self, name, parent=None): - self.name = name - self.parent = parent - self.settings = [] - # Named predicates computed from settings in this group or its - # parents. - self.named_predicates = [] - # All boolean predicates that can be accessed by number. This includes: - # - All boolean settings in this group. - # - All named predicates. - # - Added anonymous predicates, see `number_predicate()`. - # - Added parent predicates that are replicated in this group. - # Maps predicate -> number. - self.predicate_number = OrderedDict() - - self.open() - - def open(self): - """ - Open this setting group such that future new settings are added to this - group. - """ - assert SettingGroup._current is None, ( - "Can't open {} since {} is already open" - .format(self, SettingGroup._current)) - SettingGroup._current = self - - def close(self, globs=None): - """ - Close this setting group. This function must be called before opening - another setting group. - - :param globs: Pass in `globals()` to run `extract_names` on all - settings defined in the module. - """ - assert SettingGroup._current is self, ( - "Can't close {}, the open setting group is {}" - .format(self, SettingGroup._current)) - SettingGroup._current = None - if globs: - for name, obj in globs.items(): - if isinstance(obj, Setting): - assert obj.name is None, obj.name - obj.name = name - if isinstance(obj, Predicate): - assert obj.name is None - obj.name = name - self.named_predicates.append(obj) - self.layout() - - @staticmethod - def append(setting): - g = SettingGroup._current - assert g, "Open a setting group before defining settings." - g.settings.append(setting) - return g - - def number_predicate(self, pred): - """ - Make sure that `pred` has an assigned number, and will be included in - this group's bit vector. - - The numbered predicates include: - - `BoolSetting` settings that belong to this group. - - `Predicate` instances in `named_predicates`. - - `Predicate` instances without a name. - - Settings or computed predicates that belong to the parent group, but - need to be accessible by number in this group. - - The numbered predicates are referenced by the encoding tables as ISA - predicates. See the `isap` field on `Encoding`. - - :returns: The assigned predicate number in this group. - """ - if pred in self.predicate_number: - return self.predicate_number[pred] - else: - number = len(self.predicate_number) - self.predicate_number[pred] = number - return number - - def layout(self): - """ - Compute the layout of the byte vector used to represent this settings - group. - - The byte vector contains the following entries in order: - - 1. Byte-sized settings like `NumSetting` and `EnumSetting`. - 2. `BoolSetting` settings. - 3. Precomputed named predicates. - 4. Other numbered predicates, including anonymous predicates and parent - predicates that need to be accessible by number. - - Set `self.settings_size` to the length of the byte vector prefix that - contains the settings. All bytes after that are computed, not - configured. - - Set `self.boolean_offset` to the beginning of the numbered predicates, - 2. in the list above. - - Assign `byte_offset` and `bit_offset` fields in all settings. - - After calling this method, no more settings can be added, but - additional predicates can be made accessible with `number_predicate()`. - """ - assert len(self.predicate_number) == 0, "Too late for layout" - - # Assign the non-boolean settings. - byte_offset = 0 - for s in self.settings: - if not isinstance(s, BoolSetting): - s.byte_offset = byte_offset - byte_offset += 1 - - # Then the boolean settings. - self.boolean_offset = byte_offset - for s in self.settings: - if isinstance(s, BoolSetting): - number = self.number_predicate(s) - s.byte_offset = byte_offset + number // 8 - s.bit_offset = number % 8 - - # This is the end of the settings. Round up to a whole number of bytes. - self.boolean_settings = len(self.predicate_number) - self.settings_size = self.byte_size() - - # Now assign numbers to all our named predicates. - for p in self.named_predicates: - self.number_predicate(p) - - def byte_size(self): - """ - Compute the number of bytes required to hold all settings and - precomputed predicates. - - This is the size of the byte-sized settings plus all the numbered - predcate bits rounded up to a whole number of bytes. - """ - return self.boolean_offset + (len(self.predicate_number) + 7) // 8 - - # Kinds of operands. # # Each instruction has an opcode and a number of operands. The opcode diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 8e35d05909..6a05d1fa03 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -5,7 +5,9 @@ from __future__ import absolute_import import srcgen from unique_table import UniqueSeqTable import constant_hash -from cretonne import camel_case, BoolSetting, NumSetting, EnumSetting, settings +from cdsl.settings import BoolSetting, NumSetting, EnumSetting +from cretonne import camel_case +from base import settings def gen_enum_types(sgrp, fmt): diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 375b0e2150..5818bd3fd9 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -11,7 +11,7 @@ instruction formats described in the reference: from __future__ import absolute_import from cretonne import EncRecipe from cretonne.formats import Binary, BinaryImm -from cretonne.predicates import IsSignedInt +from cdsl.predicates import IsSignedInt # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit # instructions have 11 as the two low bits, with bits 6:2 determining the base diff --git a/lib/cretonne/meta/isa/riscv/settings.py b/lib/cretonne/meta/isa/riscv/settings.py index 8aae295342..7571bf1611 100644 --- a/lib/cretonne/meta/isa/riscv/settings.py +++ b/lib/cretonne/meta/isa/riscv/settings.py @@ -2,9 +2,9 @@ RISC-V settings. """ from __future__ import absolute_import -from cretonne import SettingGroup, BoolSetting -from cretonne.predicates import And -import cretonne.settings as shared +from cdsl.settings import SettingGroup, BoolSetting +from cdsl.predicates import And +import base.settings as shared from .defs import isa isa.settings = SettingGroup('riscv', parent=shared.group) From bb28dc6686171352440a80368daa456f400c0371 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 10:58:23 -0800 Subject: [PATCH 426/968] Split out operand descriptions. - cdsl.operands has the Operand and OperandKind classes. --- docs/metaref.rst | 14 +-- lib/cretonne/meta/cdsl/__init__.py | 11 ++ lib/cretonne/meta/cdsl/operands.py | 104 +++++++++++++++++++ lib/cretonne/meta/cretonne/__init__.py | 123 ++--------------------- lib/cretonne/meta/cretonne/base.py | 9 +- lib/cretonne/meta/cretonne/entities.py | 2 +- lib/cretonne/meta/cretonne/formats.py | 43 ++++---- lib/cretonne/meta/cretonne/immediates.py | 2 +- lib/cretonne/meta/cretonne/typevar.py | 6 +- lib/cretonne/meta/gen_instr.py | 9 +- 10 files changed, 167 insertions(+), 156 deletions(-) create mode 100644 lib/cretonne/meta/cdsl/operands.py diff --git a/docs/metaref.rst b/docs/metaref.rst index cfb39b00ad..4699a80a43 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -127,13 +127,14 @@ There are some practical restrictions on the use of type variables, see Immediate operands ------------------ +.. currentmodule:: cdsl.operands + Immediate instruction operands don't correspond to SSA values, but have values that are encoded directly in the instruction. Immediate operands don't have types from the :class:`cdsl.types.ValueType` type system; they often have enumerated values of a specific type. The type of an immediate operand is indicated with an instance of :class:`ImmediateKind`. -.. currentmodule:: cretonne .. autoclass:: ImmediateKind .. automodule:: cretonne.immediates @@ -145,8 +146,7 @@ Entity references Instruction operands can also refer to other entities in the same function. This can be extended basic blocks, or entities declared in the function preamble. -.. currentmodule:: cretonne - +.. currentmodule:: cdsl.operands .. autoclass:: EntityRefKind .. automodule:: cretonne.entities @@ -183,7 +183,7 @@ the :func:`ScalarType.by` function. Instruction representation ========================== -.. currentmodule:: cretonne +.. module:: cdsl.operands The Rust in-memory representation of instructions is derived from the instruction descriptions. Part of the representation is generated, and part is @@ -198,8 +198,10 @@ Since all SSA value operands are represented as a `Value` in Rust code, value types don't affect the representation. Two special operand kinds are used to represent SSA values: -.. autodata:: value -.. autodata:: variable_args +.. autodata:: VALUE +.. autodata:: VARIABLE_ARGS + +.. currentmodule:: cretonne When an instruction description is created, it is automatically assigned a predefined instruction format which is an instance of diff --git a/lib/cretonne/meta/cdsl/__init__.py b/lib/cretonne/meta/cdsl/__init__.py index 417f800950..247b970675 100644 --- a/lib/cretonne/meta/cdsl/__init__.py +++ b/lib/cretonne/meta/cdsl/__init__.py @@ -4,3 +4,14 @@ Cretonne DSL classes. This module defines the classes that are used to define Cretonne instructions and other entitties. """ +from __future__ import absolute_import +import re + + +camel_re = re.compile('(^|_)([a-z])') + + +def camel_case(s): + # type: (str) -> str + """Convert the string s to CamelCase""" + return camel_re.sub(lambda m: m.group(2).upper(), s) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py new file mode 100644 index 0000000000..33157d9e23 --- /dev/null +++ b/lib/cretonne/meta/cdsl/operands.py @@ -0,0 +1,104 @@ +"""Classes for describing instruction operands.""" +from __future__ import absolute_import +from . import camel_case + + +# Kinds of operands. +# +# Each instruction has an opcode and a number of operands. The opcode +# determines the instruction format, and the format determines the number of +# operands and the kind of each operand. +class OperandKind(object): + """ + An instance of the `OperandKind` class corresponds to a kind of operand. + Each operand kind has a corresponding type in the Rust representation of an + instruction. + """ + + def __init__(self, name, doc, default_member=None, rust_type=None): + # type: (str, str, str, str) -> None + self.name = name + self.__doc__ = doc + self.default_member = default_member + # The camel-cased name of an operand kind is also the Rust type used to + # represent it. + self.rust_type = rust_type or camel_case(name) + + def __str__(self): + # type: () -> str + return self.name + + def __repr__(self): + # type: () -> str + return 'OperandKind({})'.format(self.name) + + def operand_kind(self): + # type: () -> OperandKind + """ + An `OperandKind` instance can be used directly as the type of an + `Operand` when defining an instruction. + """ + return self + + def free_typevar(self): + # Return the free typevariable controlling the type of this operand. + return None + +#: An SSA value operand. This is a value defined by another instruction. +VALUE = OperandKind( + 'value', """ + An SSA value defined by another instruction. + + This kind of operand can represent any SSA value type, but the + instruction format may restrict the valid value types for a given + operand. + """) + +#: A variable-sized list of value operands. Use for Ebb and function call +#: arguments. +VARIABLE_ARGS = OperandKind( + 'variable_args', """ + A variable size list of `value` operands. + + Use this to represent arguemtns passed to a function call, arguments + passed to an extended basic block, or a variable number of results + returned from an instruction. + """, + default_member='varargs') + + +# Instances of immediate operand types are provided in the +# `cretonne.immediates` module. +class ImmediateKind(OperandKind): + """ + The kind of an immediate instruction operand. + + :param default_member: The default member name of this kind the + `InstructionData` data structure. + """ + + def __init__(self, name, doc, default_member='imm', rust_type=None): + # type: (str, str, str, str) -> None + super(ImmediateKind, self).__init__( + name, doc, default_member, rust_type) + + def __repr__(self): + # type: () -> str + return 'ImmediateKind({})'.format(self.name) + + +# Instances of entity reference operand types are provided in the +# `cretonne.entities` module. +class EntityRefKind(OperandKind): + """ + The kind of an entity reference instruction operand. + """ + + def __init__(self, name, doc, default_member=None, rust_type=None): + # type: (str, str, str, str) -> None + super(EntityRefKind, self).__init__( + name, doc, default_member or name, rust_type) + + def __repr__(self): + # type: () -> str + return 'EntityRefKind({})'.format(self.name) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index d2d4386de5..d6b9835443 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -5,10 +5,11 @@ This module provides classes and functions used to describe Cretonne instructions. """ from __future__ import absolute_import -import re import importlib +from cdsl import camel_case from cdsl.predicates import And import cdsl.types +from cdsl.operands import VALUE, VARIABLE_ARGS, OperandKind # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -25,116 +26,6 @@ if TYPE_CHECKING: from .typevar import TypeVar # noqa -camel_re = re.compile('(^|_)([a-z])') - - -def camel_case(s): - # type: (str) -> str - """Convert the string s to CamelCase""" - return camel_re.sub(lambda m: m.group(2).upper(), s) - - -# Kinds of operands. -# -# Each instruction has an opcode and a number of operands. The opcode -# determines the instruction format, and the format determines the number of -# operands and the kind of each operand. -class OperandKind(object): - """ - An instance of the `OperandKind` class corresponds to a kind of operand. - Each operand kind has a corresponding type in the Rust representation of an - instruction. - """ - - def __init__(self, name, doc, default_member=None, rust_type=None): - # type: (str, str, str, str) -> None - self.name = name - self.__doc__ = doc - self.default_member = default_member - # The camel-cased name of an operand kind is also the Rust type used to - # represent it. - self.rust_type = rust_type or camel_case(name) - - def __str__(self): - # type: () -> str - return self.name - - def __repr__(self): - # type: () -> str - return 'OperandKind({})'.format(self.name) - - def operand_kind(self): - # type: () -> OperandKind - """ - An `OperandKind` instance can be used directly as the type of an - `Operand` when defining an instruction. - """ - return self - - def free_typevar(self): - # Return the free typevariable controlling the type of this operand. - return None - -#: An SSA value operand. This is a value defined by another instruction. -value = OperandKind( - 'value', """ - An SSA value defined by another instruction. - - This kind of operand can represent any SSA value type, but the - instruction format may restrict the valid value types for a given - operand. - """) - -#: A variable-sized list of value operands. Use for Ebb and function call -#: arguments. -variable_args = OperandKind( - 'variable_args', """ - A variable size list of `value` operands. - - Use this to represent arguemtns passed to a function call, arguments - passed to an extended basic block, or a variable number of results - returned from an instruction. - """, - default_member='varargs') - - -# Instances of immediate operand types are provided in the -# `cretonne.immediates` module. -class ImmediateKind(OperandKind): - """ - The kind of an immediate instruction operand. - - :param default_member: The default member name of this kind the - `InstructionData` data structure. - """ - - def __init__(self, name, doc, default_member='imm', rust_type=None): - # type: (str, str, str, str) -> None - super(ImmediateKind, self).__init__( - name, doc, default_member, rust_type) - - def __repr__(self): - # type: () -> str - return 'ImmediateKind({})'.format(self.name) - - -# Instances of entity reference operand types are provided in the -# `cretonne.entities` module. -class EntityRefKind(OperandKind): - """ - The kind of an entity reference instruction operand. - """ - - def __init__(self, name, doc, default_member=None, rust_type=None): - # type: (str, str, str, str) -> None - super(EntityRefKind, self).__init__( - name, doc, default_member or name, rust_type) - - def __repr__(self): - # type: () -> str - return 'EntityRefKind({})'.format(self.name) - - # Defining instructions. @@ -215,7 +106,7 @@ class Operand(object): self.__doc__ = doc self.typ = typ if isinstance(typ, cdsl.types.ValueType): - self.kind = value + self.kind = VALUE else: self.kind = typ.operand_kind() @@ -235,7 +126,7 @@ class Operand(object): """ Is this an SSA value operand? """ - return self.kind is value + return self.kind is cdsl.operands.VALUE class InstructionFormat(object): @@ -285,12 +176,12 @@ class InstructionFormat(object): # Which of self.kinds are `value`? self.value_operands = tuple( - i for i, k in enumerate(self.kinds) if k is value) + i for i, k in enumerate(self.kinds) if k is VALUE) # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int if self.typevar_operand is not None: - assert self.kinds[self.typevar_operand] is value, \ + assert self.kinds[self.typevar_operand] is VALUE, \ "typevar_operand must indicate a 'value' operand" elif len(self.value_operands) > 0: # Default to the first 'value' operand, if there is one. @@ -361,7 +252,7 @@ class InstructionFormat(object): tuples of :py:`Operand` objects. """ if len(outs) == 1: - multiple_results = outs[0].kind == variable_args + multiple_results = outs[0].kind == VARIABLE_ARGS else: multiple_results = len(outs) > 1 sig = (multiple_results, tuple(op.kind for op in ins)) diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index d991228872..e2ceadaff4 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -5,7 +5,8 @@ This module defines the basic Cretonne instruction set that all targets support. """ from __future__ import absolute_import -from . import Operand, Instruction, InstructionGroup, variable_args +from cdsl.operands import VARIABLE_ARGS +from . import Operand, Instruction, InstructionGroup from .typevar import TypeVar from base.types import i8, f32, f64, b1 from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc @@ -31,7 +32,7 @@ Any = TypeVar( # c = Operand('c', Testable, doc='Controlling value to test') EBB = Operand('EBB', entities.ebb, doc='Destination extended basic block') -args = Operand('args', variable_args, doc='EBB arguments') +args = Operand('args', VARIABLE_ARGS, doc='EBB arguments') jump = Instruction( 'jump', r""" @@ -98,7 +99,7 @@ trapnz = Instruction( """, ins=c) -rvals = Operand('rvals', variable_args, doc='return values') +rvals = Operand('rvals', VARIABLE_ARGS, doc='return values') x_return = Instruction( 'return', r""" @@ -114,7 +115,7 @@ FN = Operand( 'FN', entities.func_ref, doc='function to call, declared by :inst:`function`') -args = Operand('args', variable_args, doc='call arguments') +args = Operand('args', VARIABLE_ARGS, doc='call arguments') call = Instruction( 'call', r""" diff --git a/lib/cretonne/meta/cretonne/entities.py b/lib/cretonne/meta/cretonne/entities.py index afeeea7e98..327d45e8bc 100644 --- a/lib/cretonne/meta/cretonne/entities.py +++ b/lib/cretonne/meta/cretonne/entities.py @@ -4,7 +4,7 @@ operand types. There are corresponding definitions in the `cretonne.entities` Rust module. """ from __future__ import absolute_import -from . import EntityRefKind +from cdsl.operands import EntityRefKind #: A reference to an extended basic block in the same function. diff --git a/lib/cretonne/meta/cretonne/formats.py b/lib/cretonne/meta/cretonne/formats.py index cb8aed3f98..a4d012020d 100644 --- a/lib/cretonne/meta/cretonne/formats.py +++ b/lib/cretonne/meta/cretonne/formats.py @@ -6,51 +6,52 @@ Rust representation of cretonne IL, so all instruction formats must be defined in this module. """ from __future__ import absolute_import -from . import InstructionFormat, value, variable_args +from cdsl.operands import VALUE, VARIABLE_ARGS +from . import InstructionFormat from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from .entities import ebb, sig_ref, func_ref, jump_table Nullary = InstructionFormat() -Unary = InstructionFormat(value) +Unary = InstructionFormat(VALUE) UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) UnaryIeee64 = InstructionFormat(ieee64) UnaryImmVector = InstructionFormat(immvector, boxed_storage=True) -UnarySplit = InstructionFormat(value, multiple_results=True) +UnarySplit = InstructionFormat(VALUE, multiple_results=True) -Binary = InstructionFormat(value, value) -BinaryImm = InstructionFormat(value, imm64) -BinaryImmRev = InstructionFormat(imm64, value) +Binary = InstructionFormat(VALUE, VALUE) +BinaryImm = InstructionFormat(VALUE, imm64) +BinaryImmRev = InstructionFormat(imm64, VALUE) # Generate result + overflow flag. -BinaryOverflow = InstructionFormat(value, value, multiple_results=True) +BinaryOverflow = InstructionFormat(VALUE, VALUE, multiple_results=True) -# The select instructions are controlled by the second value operand. -# The first value operand is the controlling flag which has a derived type. +# The select instructions are controlled by the second VALUE operand. +# The first VALUE operand is the controlling flag which has a derived type. # The fma instruction has the same constraint on all inputs. -Ternary = InstructionFormat(value, value, value, typevar_operand=1) +Ternary = InstructionFormat(VALUE, VALUE, VALUE, typevar_operand=1) # Carry in *and* carry out for `iadd_carry` and friends. TernaryOverflow = InstructionFormat( - value, value, value, multiple_results=True, boxed_storage=True) + VALUE, VALUE, VALUE, multiple_results=True, boxed_storage=True) -InsertLane = InstructionFormat(value, ('lane', uimm8), value) -ExtractLane = InstructionFormat(value, ('lane', uimm8)) +InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE) +ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) -IntCompare = InstructionFormat(intcc, value, value) -FloatCompare = InstructionFormat(floatcc, value, value) +IntCompare = InstructionFormat(intcc, VALUE, VALUE) +FloatCompare = InstructionFormat(floatcc, VALUE, VALUE) -Jump = InstructionFormat(ebb, variable_args, boxed_storage=True) -Branch = InstructionFormat(value, ebb, variable_args, boxed_storage=True) -BranchTable = InstructionFormat(value, jump_table) +Jump = InstructionFormat(ebb, VARIABLE_ARGS, boxed_storage=True) +Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS, boxed_storage=True) +BranchTable = InstructionFormat(VALUE, jump_table) Call = InstructionFormat( - func_ref, variable_args, multiple_results=True, boxed_storage=True) + func_ref, VARIABLE_ARGS, multiple_results=True, boxed_storage=True) IndirectCall = InstructionFormat( - sig_ref, value, variable_args, + sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True, boxed_storage=True) -Return = InstructionFormat(variable_args, boxed_storage=True) +Return = InstructionFormat(VARIABLE_ARGS, boxed_storage=True) # Finally extract the names of global variables in this module. diff --git a/lib/cretonne/meta/cretonne/immediates.py b/lib/cretonne/meta/cretonne/immediates.py index 34b81dc79d..565dd26850 100644 --- a/lib/cretonne/meta/cretonne/immediates.py +++ b/lib/cretonne/meta/cretonne/immediates.py @@ -3,7 +3,7 @@ The `cretonne.immediates` module predefines all the Cretonne immediate operand types. """ from __future__ import absolute_import -from . import ImmediateKind +from cdsl.operands import ImmediateKind #: A 64-bit immediate integer operand. #: diff --git a/lib/cretonne/meta/cretonne/typevar.py b/lib/cretonne/meta/cretonne/typevar.py index a6d8d9637f..7f58ce73c1 100644 --- a/lib/cretonne/meta/cretonne/typevar.py +++ b/lib/cretonne/meta/cretonne/typevar.py @@ -6,7 +6,7 @@ polymorphic by using type variables. """ from __future__ import absolute_import import math -import cretonne +import cdsl.operands try: from typing import Tuple, Union # noqa @@ -315,11 +315,11 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='DoubleWidth') def operand_kind(self): - # type: () -> cretonne.OperandKind + # type: () -> cdsl.operands.OperandKind # When a `TypeVar` object is used to describe the type of an `Operand` # in an instruction definition, the kind of that operand is an SSA # value. - return cretonne.value # type: ignore + return cdsl.operands.VALUE def free_typevar(self): # type: () -> TypeVar diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index d7afcf1887..aae46b5652 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -6,6 +6,7 @@ import srcgen import constant_hash from unique_table import UniqueTable, UniqueSeqTable import cdsl.types +import cdsl.operands import cretonne @@ -56,7 +57,7 @@ def gen_arguments_method(fmt, is_mut): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: n = 'InstructionData::' + f.name - has_varargs = cretonne.variable_args in f.kinds + has_varargs = cdsl.operands.VARIABLE_ARGS in f.kinds # Formats with both fixed and variable arguments delegate to # the data struct. We need to work around borrow checker quirks # when extracting two mutable references. @@ -84,7 +85,7 @@ def gen_arguments_method(fmt, is_mut): capture = 'ref {}args, '.format(mut) arg = 'args' # Varargs. - if cretonne.variable_args in f.kinds: + if cdsl.operands.VARIABLE_ARGS in f.kinds: varg = '&{}data.varargs'.format(mut) capture = 'ref {}data, '.format(mut) else: @@ -311,7 +312,7 @@ def get_constraint(op, ctrl_typevar, type_sets): - `Free(idx)` where `idx` is an index into `type_sets`. - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. """ - assert op.kind is cretonne.value + assert op.kind is cdsl.operands.VALUE t = op.typ # A concrete value type. @@ -504,7 +505,7 @@ def gen_inst_builder(inst, fmt): tmpl_types = list() into_args = list() for op in inst.ins: - if isinstance(op.kind, cretonne.ImmediateKind): + if isinstance(op.kind, cdsl.operands.ImmediateKind): t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name) tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type)) into_args.append(op.name) From 6eaa8eb3824e4dcd70c6b8a8eb2f446a5b44baae Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 11:02:07 -0800 Subject: [PATCH 427/968] Move formats, entities, and immediates to the base package. - base.formats defines instruction formats. - base.entities defines kinds of entity references. - base.immediates defines kinds of imediate operands. --- docs/metaref.rst | 4 ++-- lib/cretonne/meta/{cretonne => base}/entities.py | 0 lib/cretonne/meta/{cretonne => base}/formats.py | 2 +- lib/cretonne/meta/{cretonne => base}/immediates.py | 0 lib/cretonne/meta/cretonne/__init__.py | 2 +- lib/cretonne/meta/cretonne/base.py | 5 +++-- lib/cretonne/meta/isa/riscv/recipes.py | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) rename lib/cretonne/meta/{cretonne => base}/entities.py (100%) rename lib/cretonne/meta/{cretonne => base}/formats.py (98%) rename lib/cretonne/meta/{cretonne => base}/immediates.py (100%) diff --git a/docs/metaref.rst b/docs/metaref.rst index 4699a80a43..a073ed5ad8 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -137,7 +137,7 @@ indicated with an instance of :class:`ImmediateKind`. .. autoclass:: ImmediateKind -.. automodule:: cretonne.immediates +.. automodule:: base.immediates :members: Entity references @@ -149,7 +149,7 @@ can be extended basic blocks, or entities declared in the function preamble. .. currentmodule:: cdsl.operands .. autoclass:: EntityRefKind -.. automodule:: cretonne.entities +.. automodule:: base.entities :members: Value types diff --git a/lib/cretonne/meta/cretonne/entities.py b/lib/cretonne/meta/base/entities.py similarity index 100% rename from lib/cretonne/meta/cretonne/entities.py rename to lib/cretonne/meta/base/entities.py diff --git a/lib/cretonne/meta/cretonne/formats.py b/lib/cretonne/meta/base/formats.py similarity index 98% rename from lib/cretonne/meta/cretonne/formats.py rename to lib/cretonne/meta/base/formats.py index a4d012020d..49300c2ed0 100644 --- a/lib/cretonne/meta/cretonne/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -7,7 +7,7 @@ in this module. """ from __future__ import absolute_import from cdsl.operands import VALUE, VARIABLE_ARGS -from . import InstructionFormat +from cretonne import InstructionFormat from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from .entities import ebb, sig_ref, func_ref, jump_table diff --git a/lib/cretonne/meta/cretonne/immediates.py b/lib/cretonne/meta/base/immediates.py similarity index 100% rename from lib/cretonne/meta/cretonne/immediates.py rename to lib/cretonne/meta/base/immediates.py diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index d6b9835443..2429136143 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -732,4 +732,4 @@ class Encoding(object): # Import the fixed instruction formats now so they can be added to the # registry. -importlib.import_module('cretonne.formats') +importlib.import_module('base.formats') diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index e2ceadaff4..6ecbdcf86d 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -9,8 +9,9 @@ from cdsl.operands import VARIABLE_ARGS from . import Operand, Instruction, InstructionGroup from .typevar import TypeVar from base.types import i8, f32, f64, b1 -from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc -from . import entities +from base.immediates import imm64, uimm8, ieee32, ieee64, immvector +from base.immediates import intcc, floatcc +from base import entities instructions = InstructionGroup("base", "Shared base instruction set") diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 5818bd3fd9..b4f561f8e2 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -10,8 +10,8 @@ instruction formats described in the reference: """ from __future__ import absolute_import from cretonne import EncRecipe -from cretonne.formats import Binary, BinaryImm from cdsl.predicates import IsSignedInt +from base.formats import Binary, BinaryImm # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit # instructions have 11 as the two low bits, with bits 6:2 determining the base From 5498169ca0aafad318ca9b3ee911a5112fe94027 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 11:18:14 -0800 Subject: [PATCH 428/968] Split out the typevar module. - cdsl.typevar defines TypeVar and TypeSet classes. --- docs/metaref.rst | 2 +- lib/cretonne/meta/{cretonne => cdsl}/test_typevar.py | 0 lib/cretonne/meta/{cretonne => cdsl}/typevar.py | 0 lib/cretonne/meta/cretonne/__init__.py | 2 +- lib/cretonne/meta/cretonne/base.py | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename lib/cretonne/meta/{cretonne => cdsl}/test_typevar.py (100%) rename lib/cretonne/meta/{cretonne => cdsl}/typevar.py (100%) diff --git a/docs/metaref.rst b/docs/metaref.rst index a073ed5ad8..31e4ffc9b5 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -102,7 +102,7 @@ instances that refer to a *type variable* instead of a concrete value type. Polymorphism only works for SSA value operands. Other operands have a fixed operand kind. -.. autoclass:: cretonne.typevar.TypeVar +.. autoclass:: cdsl.typevar.TypeVar :members: If multiple operands refer to the same type variable they will be required to diff --git a/lib/cretonne/meta/cretonne/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py similarity index 100% rename from lib/cretonne/meta/cretonne/test_typevar.py rename to lib/cretonne/meta/cdsl/test_typevar.py diff --git a/lib/cretonne/meta/cretonne/typevar.py b/lib/cretonne/meta/cdsl/typevar.py similarity index 100% rename from lib/cretonne/meta/cretonne/typevar.py rename to lib/cretonne/meta/cdsl/typevar.py diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 2429136143..1753200f70 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -23,7 +23,7 @@ except ImportError: TYPE_CHECKING = False if TYPE_CHECKING: - from .typevar import TypeVar # noqa + from cdsl.typevar import TypeVar # noqa # Defining instructions. diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index 6ecbdcf86d..c648633ed9 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -6,8 +6,8 @@ support. """ from __future__ import absolute_import from cdsl.operands import VARIABLE_ARGS +from cdsl.typevar import TypeVar from . import Operand, Instruction, InstructionGroup -from .typevar import TypeVar from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, immvector from base.immediates import intcc, floatcc From 93a1387f2f9360236784b8888423e46adcd35a9d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 11:24:21 -0800 Subject: [PATCH 429/968] Get rid of operand_kind() This method caused lots of import cycles when type checking. Use isinstance() in the Operand constructor instead to decipher the OperandSpec union type. --- lib/cretonne/meta/cdsl/operands.py | 8 -------- lib/cretonne/meta/cdsl/typevar.py | 8 -------- lib/cretonne/meta/cretonne/__init__.py | 27 ++++++++++++++------------ 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index 33157d9e23..279941c9fe 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -32,14 +32,6 @@ class OperandKind(object): # type: () -> str return 'OperandKind({})'.format(self.name) - def operand_kind(self): - # type: () -> OperandKind - """ - An `OperandKind` instance can be used directly as the type of an - `Operand` when defining an instruction. - """ - return self - def free_typevar(self): # Return the free typevariable controlling the type of this operand. return None diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 7f58ce73c1..2c10338bbf 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -6,7 +6,6 @@ polymorphic by using type variables. """ from __future__ import absolute_import import math -import cdsl.operands try: from typing import Tuple, Union # noqa @@ -314,13 +313,6 @@ class TypeVar(object): return TypeVar(None, None, base=self, derived_func='DoubleWidth') - def operand_kind(self): - # type: () -> cdsl.operands.OperandKind - # When a `TypeVar` object is used to describe the type of an `Operand` - # in an instruction definition, the kind of that operand is an SSA - # value. - return cdsl.operands.VALUE - def free_typevar(self): # type: () -> TypeVar if self.is_derived: diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 1753200f70..4fcf1c9460 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -8,7 +8,8 @@ from __future__ import absolute_import import importlib from cdsl import camel_case from cdsl.predicates import And -import cdsl.types +from cdsl.types import ValueType +from cdsl.typevar import TypeVar from cdsl.operands import VALUE, VARIABLE_ARGS, OperandKind # The typing module is only required by mypy, and we don't use these imports @@ -18,7 +19,7 @@ try: from cdsl.predicates import Predicate, FieldPredicate # noqa MaybeBoundInst = Union['Instruction', 'BoundInstruction'] AnyPredicate = Union['Predicate', 'FieldPredicate'] - OperandSpec = Union['OperandKind', 'cdsl.types.ValueType', 'TypeVar'] + OperandSpec = Union['OperandKind', ValueType, TypeVar] except ImportError: TYPE_CHECKING = False @@ -105,10 +106,12 @@ class Operand(object): self.name = name self.__doc__ = doc self.typ = typ - if isinstance(typ, cdsl.types.ValueType): + if isinstance(typ, ValueType): + self.kind = VALUE + elif isinstance(typ, TypeVar): self.kind = VALUE else: - self.kind = typ.operand_kind() + self.kind = typ def get_doc(self): # type: () -> str @@ -126,7 +129,7 @@ class Operand(object): """ Is this an SSA value operand? """ - return self.kind is cdsl.operands.VALUE + return self.kind is VALUE class InstructionFormat(object): @@ -464,7 +467,7 @@ class Instruction(object): return x def bind(self, *args): - # type: (*cdsl.types.ValueType) -> BoundInstruction + # type: (*ValueType) -> BoundInstruction """ Bind a polymorphic instruction to a concrete list of type variable values. @@ -480,10 +483,10 @@ class Instruction(object): >>> iadd.i32 """ - return self.bind(cdsl.types.ValueType.by_name(name)) + return self.bind(ValueType.by_name(name)) def fully_bound(self): - # type: () -> Tuple[Instruction, Tuple[cdsl.types.ValueType, ...]] + # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] """ Verify that all typevars have been bound, and return a `(inst, typevars)` pair. @@ -509,7 +512,7 @@ class BoundInstruction(object): """ def __init__(self, inst, typevars): - # type: (Instruction, Tuple[cdsl.types.ValueType, ...]) -> None + # type: (Instruction, Tuple[ValueType, ...]) -> None self.inst = inst self.typevars = typevars assert len(typevars) <= 1 + len(inst.other_typevars) @@ -518,7 +521,7 @@ class BoundInstruction(object): return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) def bind(self, *args): - # type: (*cdsl.types.ValueType) -> BoundInstruction + # type: (*ValueType) -> BoundInstruction """ Bind additional typevars. """ @@ -531,10 +534,10 @@ class BoundInstruction(object): >>> uext.i32.i8 """ - return self.bind(cdsl.types.ValueType.by_name(name)) + return self.bind(ValueType.by_name(name)) def fully_bound(self): - # type: () -> Tuple[Instruction, Tuple[cdsl.types.ValueType, ...]] + # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] """ Verify that all typevars have been bound, and return a `(inst, typevars)` pair. From fa7dc6825aab5f167155a2f363837658c87223ea Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 11:30:31 -0800 Subject: [PATCH 430/968] Move Operand itself into cdsl.operands. --- lib/cretonne/meta/cdsl/operands.py | 61 ++++++++++++++++++++++++++ lib/cretonne/meta/cretonne/__init__.py | 54 +---------------------- 2 files changed, 62 insertions(+), 53 deletions(-) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index 279941c9fe..f379357611 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -1,6 +1,14 @@ """Classes for describing instruction operands.""" from __future__ import absolute_import from . import camel_case +from .types import ValueType +from .typevar import TypeVar + +try: + from typing import Union + OperandSpec = Union['OperandKind', ValueType, TypeVar] +except ImportError: + pass # Kinds of operands. @@ -94,3 +102,56 @@ class EntityRefKind(OperandKind): def __repr__(self): # type: () -> str return 'EntityRefKind({})'.format(self.name) + + +class Operand(object): + """ + An instruction operand can be an *immediate*, an *SSA value*, or an *entity + reference*. The type of the operand is one of: + + 1. A :py:class:`ValueType` instance indicates an SSA value operand with a + concrete type. + + 2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the + instruction is polymorphic over the possible concrete types that the + type variable can assume. + + 3. An :py:class:`ImmediateKind` instance indicates an immediate operand + whose value is encoded in the instruction itself rather than being + passed as an SSA value. + + 4. An :py:class:`EntityRefKind` instance indicates an operand that + references another entity in the function, typically something declared + in the function preamble. + + """ + def __init__(self, name, typ, doc=''): + # type: (str, OperandSpec, str) -> None + self.name = name + self.__doc__ = doc + self.typ = typ + if isinstance(typ, ValueType): + self.kind = VALUE + elif isinstance(typ, TypeVar): + self.kind = VALUE + else: + assert isinstance(typ, OperandKind) + self.kind = typ + + def get_doc(self): + # type: () -> str + if self.__doc__: + return self.__doc__ + else: + return self.typ.__doc__ + + def __str__(self): + # type: () -> str + return "`{}`".format(self.name) + + def is_value(self): + # type: () -> bool + """ + Is this an SSA value operand? + """ + return self.kind is VALUE diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 4fcf1c9460..ee66dbe00b 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -10,7 +10,7 @@ from cdsl import camel_case from cdsl.predicates import And from cdsl.types import ValueType from cdsl.typevar import TypeVar -from cdsl.operands import VALUE, VARIABLE_ARGS, OperandKind +from cdsl.operands import VALUE, VARIABLE_ARGS, OperandKind, Operand # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -80,58 +80,6 @@ class InstructionGroup(object): InstructionGroup._current.instructions.append(inst) -class Operand(object): - """ - An instruction operand can be an *immediate*, an *SSA value*, or an *entity - reference*. The type of the operand is one of: - - 1. A :py:class:`ValueType` instance indicates an SSA value operand with a - concrete type. - - 2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the - instruction is polymorphic over the possible concrete types that the - type variable can assume. - - 3. An :py:class:`ImmediateKind` instance indicates an immediate operand - whose value is encoded in the instruction itself rather than being - passed as an SSA value. - - 4. An :py:class:`EntityRefKind` instance indicates an operand that - references another entity in the function, typically something declared - in the function preamble. - - """ - def __init__(self, name, typ, doc=''): - # type: (str, OperandSpec, str) -> None - self.name = name - self.__doc__ = doc - self.typ = typ - if isinstance(typ, ValueType): - self.kind = VALUE - elif isinstance(typ, TypeVar): - self.kind = VALUE - else: - self.kind = typ - - def get_doc(self): - # type: () -> str - if self.__doc__: - return self.__doc__ - else: - return self.typ.__doc__ - - def __str__(self): - # type: () -> str - return "`{}`".format(self.name) - - def is_value(self): - # type: () -> bool - """ - Is this an SSA value operand? - """ - return self.kind is VALUE - - class InstructionFormat(object): """ Every instruction opcode has a corresponding instruction format which From 2a1513051886d117463d6c9d375c26d4f39d14c2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 11:47:19 -0800 Subject: [PATCH 431/968] Split out instruction formats. - cdsl.formats provides classes for describing instruction formats. - base.formats provides concrete instruction format definitions. --- lib/cretonne/meta/base/formats.py | 2 +- lib/cretonne/meta/cdsl/formats.py | 189 +++++++++++++++++++++++++ lib/cretonne/meta/cretonne/__init__.py | 180 +---------------------- lib/cretonne/meta/cretonne/base.py | 1 + 4 files changed, 193 insertions(+), 179 deletions(-) create mode 100644 lib/cretonne/meta/cdsl/formats.py diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 49300c2ed0..421913b0e4 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -6,8 +6,8 @@ Rust representation of cretonne IL, so all instruction formats must be defined in this module. """ from __future__ import absolute_import +from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS -from cretonne import InstructionFormat from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc from .entities import ebb, sig_ref, func_ref, jump_table diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py new file mode 100644 index 0000000000..32f4463ae0 --- /dev/null +++ b/lib/cretonne/meta/cdsl/formats.py @@ -0,0 +1,189 @@ +"""Classes for describing instruction formats.""" +from __future__ import absolute_import +from .operands import OperandKind, VALUE, VARIABLE_ARGS +from .operands import Operand # noqa + +# The typing module is only required by mypy, and we don't use these imports +# outside type comments. +try: + from typing import Tuple, Union, Any, Sequence, Iterable # noqa +except ImportError: + pass + + +class InstructionFormat(object): + """ + Every instruction opcode has a corresponding instruction format which + determines the number of operands and their kinds. Instruction formats are + identified structurally, i.e., the format of an instruction is derived from + the kinds of operands used in its declaration. + + Most instruction formats produce a single result, or no result at all. If + an instruction can produce more than one result, the `multiple_results` + flag must be set on its format. All results are of the `value` kind, and + the instruction format does not keep track of how many results are + produced. Some instructions, like `call`, may have a variable number of + results. + + All instruction formats must be predefined in the + :py:mod:`cretonne.formats` module. + + :param kinds: List of `OperandKind` objects describing the operands. + :param name: Instruction format name in CamelCase. This is used as a Rust + variant name in both the `InstructionData` and `InstructionFormat` + enums. + :param multiple_results: Set to `True` if this instruction format allows + more than one result to be produced. + :param boxed_storage: Set to `True` is this instruction format requires a + `data: Box<...>` pointer to additional storage in its `InstructionData` + variant. + :param typevar_operand: Index of the input operand that is used to infer + the controlling type variable. By default, this is the first `value` + operand. + """ + + # Map (multiple_results, kind, kind, ...) -> InstructionFormat + _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...]], InstructionFormat] # noqa + + # All existing formats. + all_formats = list() # type: List[InstructionFormat] + + def __init__(self, *kinds, **kwargs): + # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa + self.name = kwargs.get('name', None) # type: str + self.multiple_results = kwargs.get('multiple_results', False) + self.boxed_storage = kwargs.get('boxed_storage', False) + self.members = list() # type: List[str] + self.kinds = tuple(self._process_member_names(kinds)) + + # Which of self.kinds are `value`? + self.value_operands = tuple( + i for i, k in enumerate(self.kinds) if k is VALUE) + + # The typevar_operand argument must point to a 'value' operand. + self.typevar_operand = kwargs.get('typevar_operand', None) # type: int + if self.typevar_operand is not None: + assert self.kinds[self.typevar_operand] is VALUE, \ + "typevar_operand must indicate a 'value' operand" + elif len(self.value_operands) > 0: + # Default to the first 'value' operand, if there is one. + self.typevar_operand = self.value_operands[0] + + # Compute a signature for the global registry. + sig = (self.multiple_results, self.kinds) + if sig in InstructionFormat._registry: + raise RuntimeError( + "Format '{}' has the same signature as existing format '{}'" + .format(self.name, InstructionFormat._registry[sig])) + InstructionFormat._registry[sig] = self + InstructionFormat.all_formats.append(self) + + def _process_member_names(self, kinds): + # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[OperandKind] # noqa + """ + Extract names of all the immediate operands in the kinds tuple. + + Each entry is either an `OperandKind` instance, or a `(member, kind)` + pair. The member names correspond to members in the Rust + `InstructionData` data structure. + + Yields the operand kinds. + """ + for arg in kinds: + if isinstance(arg, OperandKind): + member = arg.default_member + k = arg + else: + member, k = arg + self.members.append(member) + yield k + + def __str__(self): + # type: () -> str + args = ', '.join('{}: {}'.format(m, k) if m else str(k) + for m, k in zip(self.members, self.kinds)) + return '{}({})'.format(self.name, args) + + def __getattr__(self, attr): + # type: (str) -> FormatField + """ + Make instruction format members available as attributes. + + Each non-value format member becomes a corresponding `FormatField` + attribute. + """ + try: + i = self.members.index(attr) + except ValueError: + raise AttributeError( + '{} is neither a {} member or a ' + .format(attr, self.name) + + 'normal InstructionFormat attribute') + field = FormatField(self, i, attr) + setattr(self, attr, field) + return field + + @staticmethod + def lookup(ins, outs): + # type: (Sequence[Operand], Sequence[Operand]) -> InstructionFormat + """ + Find an existing instruction format that matches the given lists of + instruction inputs and outputs. + + The `ins` and `outs` arguments correspond to the + :py:class:`Instruction` arguments of the same name, except they must be + tuples of :py:`Operand` objects. + """ + if len(outs) == 1: + multiple_results = outs[0].kind == VARIABLE_ARGS + else: + multiple_results = len(outs) > 1 + sig = (multiple_results, tuple(op.kind for op in ins)) + if sig not in InstructionFormat._registry: + raise RuntimeError( + "No instruction format matches ins = ({}){}".format( + ", ".join(map(str, sig[1])), + "[multiple results]" if multiple_results else "")) + return InstructionFormat._registry[sig] + + @staticmethod + def extract_names(globs): + """ + Given a dict mapping name -> object as returned by `globals()`, find + all the InstructionFormat objects and set their name from the dict key. + This is used to name a bunch of global variables in a module. + """ + for name, obj in globs.items(): + if isinstance(obj, InstructionFormat): + assert obj.name is None + obj.name = name + + +class FormatField(object): + """ + A field in an instruction format. + + This corresponds to a single member of a variant of the `InstructionData` + data type. + + :param format: Parent `InstructionFormat`. + :param operand: Operand number in parent. + :param name: Member name in `InstructionData` variant. + """ + + def __init__(self, format, operand, name): + # type: (InstructionFormat, int, str) -> None + self.format = format + self.operand = operand + self.name = name + + def __str__(self): + # type: () -> str + return '{}.{}'.format(self.format.name, self.name) + + def rust_name(self): + # type: () -> str + if self.format.boxed_storage: + return 'data.' + self.name + else: + return self.name diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index ee66dbe00b..0ab55491c9 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -10,7 +10,8 @@ from cdsl import camel_case from cdsl.predicates import And from cdsl.types import ValueType from cdsl.typevar import TypeVar -from cdsl.operands import VALUE, VARIABLE_ARGS, OperandKind, Operand +from cdsl.operands import Operand +from cdsl.formats import InstructionFormat # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -19,7 +20,6 @@ try: from cdsl.predicates import Predicate, FieldPredicate # noqa MaybeBoundInst = Union['Instruction', 'BoundInstruction'] AnyPredicate = Union['Predicate', 'FieldPredicate'] - OperandSpec = Union['OperandKind', ValueType, TypeVar] except ImportError: TYPE_CHECKING = False @@ -80,182 +80,6 @@ class InstructionGroup(object): InstructionGroup._current.instructions.append(inst) -class InstructionFormat(object): - """ - Every instruction opcode has a corresponding instruction format which - determines the number of operands and their kinds. Instruction formats are - identified structurally, i.e., the format of an instruction is derived from - the kinds of operands used in its declaration. - - Most instruction formats produce a single result, or no result at all. If - an instruction can produce more than one result, the `multiple_results` - flag must be set on its format. All results are of the `value` kind, and - the instruction format does not keep track of how many results are - produced. Some instructions, like `call`, may have a variable number of - results. - - All instruction formats must be predefined in the - :py:mod:`cretonne.formats` module. - - :param kinds: List of `OperandKind` objects describing the operands. - :param name: Instruction format name in CamelCase. This is used as a Rust - variant name in both the `InstructionData` and `InstructionFormat` - enums. - :param multiple_results: Set to `True` if this instruction format allows - more than one result to be produced. - :param boxed_storage: Set to `True` is this instruction format requires a - `data: Box<...>` pointer to additional storage in its `InstructionData` - variant. - :param typevar_operand: Index of the input operand that is used to infer - the controlling type variable. By default, this is the first `value` - operand. - """ - - # Map (multiple_results, kind, kind, ...) -> InstructionFormat - _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...]], InstructionFormat] # noqa - - # All existing formats. - all_formats = list() # type: List[InstructionFormat] - - def __init__(self, *kinds, **kwargs): - # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa - self.name = kwargs.get('name', None) # type: str - self.multiple_results = kwargs.get('multiple_results', False) - self.boxed_storage = kwargs.get('boxed_storage', False) - self.members = list() # type: List[str] - self.kinds = tuple(self._process_member_names(kinds)) - - # Which of self.kinds are `value`? - self.value_operands = tuple( - i for i, k in enumerate(self.kinds) if k is VALUE) - - # The typevar_operand argument must point to a 'value' operand. - self.typevar_operand = kwargs.get('typevar_operand', None) # type: int - if self.typevar_operand is not None: - assert self.kinds[self.typevar_operand] is VALUE, \ - "typevar_operand must indicate a 'value' operand" - elif len(self.value_operands) > 0: - # Default to the first 'value' operand, if there is one. - self.typevar_operand = self.value_operands[0] - - # Compute a signature for the global registry. - sig = (self.multiple_results, self.kinds) - if sig in InstructionFormat._registry: - raise RuntimeError( - "Format '{}' has the same signature as existing format '{}'" - .format(self.name, InstructionFormat._registry[sig])) - InstructionFormat._registry[sig] = self - InstructionFormat.all_formats.append(self) - - def _process_member_names(self, kinds): - # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[OperandKind] # noqa - """ - Extract names of all the immediate operands in the kinds tuple. - - Each entry is either an `OperandKind` instance, or a `(member, kind)` - pair. The member names correspond to members in the Rust - `InstructionData` data structure. - - Yields the operand kinds. - """ - for arg in kinds: - if isinstance(arg, OperandKind): - member = arg.default_member - k = arg - else: - member, k = arg - self.members.append(member) - yield k - - def __str__(self): - args = ', '.join('{}: {}'.format(m, k) if m else str(k) - for m, k in zip(self.members, self.kinds)) - return '{}({})'.format(self.name, args) - - def __getattr__(self, attr): - # type: (str) -> FormatField - """ - Make instruction format members available as attributes. - - Each non-value format member becomes a corresponding `FormatField` - attribute. - """ - try: - i = self.members.index(attr) - except ValueError: - raise AttributeError( - '{} is neither a {} member or a ' - .format(attr, self.name) + - 'normal InstructionFormat attribute') - field = FormatField(self, i, attr) - setattr(self, attr, field) - return field - - @staticmethod - def lookup(ins, outs): - # type: (Sequence[Operand], Sequence[Operand]) -> InstructionFormat - """ - Find an existing instruction format that matches the given lists of - instruction inputs and outputs. - - The `ins` and `outs` arguments correspond to the - :py:class:`Instruction` arguments of the same name, except they must be - tuples of :py:`Operand` objects. - """ - if len(outs) == 1: - multiple_results = outs[0].kind == VARIABLE_ARGS - else: - multiple_results = len(outs) > 1 - sig = (multiple_results, tuple(op.kind for op in ins)) - if sig not in InstructionFormat._registry: - raise RuntimeError( - "No instruction format matches ins = ({}){}".format( - ", ".join(map(str, sig[1])), - "[multiple results]" if multiple_results else "")) - return InstructionFormat._registry[sig] - - @staticmethod - def extract_names(globs): - """ - Given a dict mapping name -> object as returned by `globals()`, find - all the InstructionFormat objects and set their name from the dict key. - This is used to name a bunch of global variables in a module. - """ - for name, obj in globs.items(): - if isinstance(obj, InstructionFormat): - assert obj.name is None - obj.name = name - - -class FormatField(object): - """ - A field in an instruction format. - - This corresponds to a single member of a variant of the `InstructionData` - data type. - - :param format: Parent `InstructionFormat`. - :param operand: Operand number in parent. - :param name: Member name in `InstructionData` variant. - """ - - def __init__(self, format, operand, name): - # type: (InstructionFormat, int, str) -> None - self.format = format - self.operand = operand - self.name = name - - def __str__(self): - return '{}.{}'.format(self.format.name, self.name) - - def rust_name(self): - # type: () -> str - if self.format.boxed_storage: - return 'data.' + self.name - else: - return self.name - - class Instruction(object): """ The operands to the instruction are specified as two tuples: ``ins`` and diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/cretonne/base.py index c648633ed9..ce747cc203 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/cretonne/base.py @@ -12,6 +12,7 @@ from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, immvector from base.immediates import intcc, floatcc from base import entities +import base.formats # noqa instructions = InstructionGroup("base", "Shared base instruction set") From 5fa322f7971cf0873bc2d1054d0facac82677fd3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 12:08:14 -0800 Subject: [PATCH 432/968] Split out instruction definitions. - cdsl.instructions defines the Instruction class. - base.instructions defines the base instruction set. --- docs/cton_domain.py | 2 +- docs/langref.rst | 2 +- docs/metaref.rst | 14 +- .../base.py => base/instructions.py} | 8 +- lib/cretonne/meta/cdsl/instructions.py | 316 ++++++++++++++++++ lib/cretonne/meta/cretonne/__init__.py | 310 +---------------- lib/cretonne/meta/cretonne/ast.py | 12 +- lib/cretonne/meta/cretonne/legalize.py | 8 +- lib/cretonne/meta/cretonne/test_ast.py | 2 +- lib/cretonne/meta/cretonne/test_xform.py | 2 +- lib/cretonne/meta/cretonne/xform.py | 2 +- lib/cretonne/meta/gen_instr.py | 22 +- lib/cretonne/meta/gen_settings.py | 2 +- lib/cretonne/meta/isa/riscv/defs.py | 4 +- lib/cretonne/meta/isa/riscv/encodings.py | 2 +- 15 files changed, 360 insertions(+), 348 deletions(-) rename lib/cretonne/meta/{cretonne/base.py => base/instructions.py} (99%) create mode 100644 lib/cretonne/meta/cdsl/instructions.py diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 00242ff565..47c7c2996c 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -269,7 +269,7 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): return False def resolve_name(self, modname, parents, path, base): - return 'cretonne.base', [base] + return 'base.instructions', [base] def format_signature(self): inst = self.object diff --git a/docs/langref.rst b/docs/langref.rst index 5dcf80a893..a9d5ac4c96 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -849,7 +849,7 @@ Base instruction group All of the shared instructions are part of the :instgroup:`base` instruction group. -.. autoinstgroup:: cretonne.base.instructions +.. autoinstgroup:: base.instructions.GROUP Target ISAs may define further instructions in their own instruction groups. diff --git a/docs/metaref.rst b/docs/metaref.rst index 31e4ffc9b5..190945c61d 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -71,7 +71,7 @@ a module looks like this:: Instruction descriptions ======================== -.. currentmodule:: cretonne +.. module:: cdsl.instructions New instructions are defined as instances of the :class:`Instruction` class. As instruction instances are created, they are added to the currently @@ -81,12 +81,14 @@ open :class:`InstructionGroup`. :members: The basic Cretonne instruction set described in :doc:`langref` is defined by the -Python module :mod:`cretonne.base`. This module has a global variable -:data:`cretonne.base.instructions` which is an :class:`InstructionGroup` -instance containing all the base instructions. +Python module :mod:`base.instructions`. This module has a global variable +:data:`base.instructions.GROUP` which is an :class:`InstructionGroup` instance +containing all the base instructions. .. autoclass:: Instruction +.. currentmodule:: cdsl.operands + An instruction is defined with a set of distinct input and output operands which must be instances of the :class:`Operand` class. @@ -201,7 +203,7 @@ represent SSA values: .. autodata:: VALUE .. autodata:: VARIABLE_ARGS -.. currentmodule:: cretonne +.. module:: cdsl.formats When an instruction description is created, it is automatically assigned a predefined instruction format which is an instance of @@ -253,6 +255,8 @@ controlling type variable, or it can vary independently of the other operands. Encodings ========= +.. currentmodule:: cretonne + Encodings describe how Cretonne instructions are mapped to binary machine code for the target architecture. After the legalization pass, all remaining instructions are expected to map 1-1 to native instruction encodings. Cretonne diff --git a/lib/cretonne/meta/cretonne/base.py b/lib/cretonne/meta/base/instructions.py similarity index 99% rename from lib/cretonne/meta/cretonne/base.py rename to lib/cretonne/meta/base/instructions.py index ce747cc203..b5a2c784fa 100644 --- a/lib/cretonne/meta/cretonne/base.py +++ b/lib/cretonne/meta/base/instructions.py @@ -5,16 +5,16 @@ This module defines the basic Cretonne instruction set that all targets support. """ from __future__ import absolute_import -from cdsl.operands import VARIABLE_ARGS +from cdsl.operands import Operand, VARIABLE_ARGS from cdsl.typevar import TypeVar -from . import Operand, Instruction, InstructionGroup +from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, immvector from base.immediates import intcc, floatcc from base import entities import base.formats # noqa -instructions = InstructionGroup("base", "Shared base instruction set") +GROUP = InstructionGroup("base", "Shared base instruction set") Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) iB = TypeVar('iB', 'A scalar integer type', ints=True) @@ -1213,4 +1213,4 @@ iconcat_lohi = Instruction( """, ins=(lo, hi), outs=a) -instructions.close() +GROUP.close() diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py new file mode 100644 index 0000000000..da9f5bb179 --- /dev/null +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -0,0 +1,316 @@ +"""Classes for defining instructions.""" +from __future__ import absolute_import +from . import camel_case +from .types import ValueType +from .operands import Operand +from .formats import InstructionFormat + +try: + from typing import Union, Sequence + # List of operands for ins/outs: + OpList = Union[Sequence[Operand], Operand] + MaybeBoundInst = Union['Instruction', 'BoundInstruction'] + from typing import Tuple, Any # noqa +except ImportError: + pass + + +class InstructionGroup(object): + """ + Every instruction must belong to exactly one instruction group. A given + target architecture can support instructions from multiple groups, and it + does not necessarily support all instructions in a group. + + New instructions are automatically added to the currently open instruction + group. + """ + + # The currently open instruction group. + _current = None # type: InstructionGroup + + def open(self): + # type: () -> None + """ + Open this instruction group such that future new instructions are + added to this group. + """ + assert InstructionGroup._current is None, ( + "Can't open {} since {} is already open" + .format(self, InstructionGroup._current)) + InstructionGroup._current = self + + def close(self): + # type: () -> None + """ + Close this instruction group. This function should be called before + opening another instruction group. + """ + assert InstructionGroup._current is self, ( + "Can't close {}, the open instuction group is {}" + .format(self, InstructionGroup._current)) + InstructionGroup._current = None + + def __init__(self, name, doc): + # type: (str, str) -> None + self.name = name + self.__doc__ = doc + self.instructions = [] # type: List[Instruction] + self.open() + + @staticmethod + def append(inst): + # type: (Instruction) -> None + assert InstructionGroup._current, \ + "Open an instruction group before defining instructions." + InstructionGroup._current.instructions.append(inst) + + +class Instruction(object): + """ + The operands to the instruction are specified as two tuples: ``ins`` and + ``outs``. Since the Python singleton tuple syntax is a bit awkward, it is + allowed to specify a singleton as just the operand itself, i.e., `ins=x` + and `ins=(x,)` are both allowed and mean the same thing. + + :param name: Instruction mnemonic, also becomes opcode name. + :param doc: Documentation string. + :param ins: Tuple of input operands. This can be a mix of SSA value + operands and other operand kinds. + :param outs: Tuple of output operands. The output operands must be SSA + values or `variable_args`. + :param is_terminator: This is a terminator instruction. + :param is_branch: This is a branch instruction. + """ + + def __init__(self, name, doc, ins=(), outs=(), **kwargs): + # type: (str, str, OpList, OpList, **Any) -> None # noqa + self.name = name + self.camel_name = camel_case(name) + self.__doc__ = doc + self.ins = self._to_operand_tuple(ins) + self.outs = self._to_operand_tuple(outs) + self.format = InstructionFormat.lookup(self.ins, self.outs) + # Indexes into outs for value results. Others are `variable_args`. + self.value_results = tuple( + i for i, o in enumerate(self.outs) if o.is_value()) + self._verify_polymorphic() + InstructionGroup.append(self) + + def __str__(self): + prefix = ', '.join(o.name for o in self.outs) + if prefix: + prefix = prefix + ' = ' + suffix = ', '.join(o.name for o in self.ins) + return '{}{} {}'.format(prefix, self.name, suffix) + + def snake_name(self): + # type: () -> str + """ + Get the snake_case name of this instruction. + + Keywords in Rust and Python are altered by appending a '_' + """ + if self.name == 'return': + return 'return_' + else: + return self.name + + def blurb(self): + """Get the first line of the doc comment""" + for line in self.__doc__.split('\n'): + line = line.strip() + if line: + return line + return "" + + def _verify_polymorphic(self): + """ + Check if this instruction is polymorphic, and verify its use of type + variables. + """ + poly_ins = [ + i for i in self.format.value_operands + if self.ins[i].typ.free_typevar()] + poly_outs = [ + i for i, o in enumerate(self.outs) + if o.typ.free_typevar()] + self.is_polymorphic = len(poly_ins) > 0 or len(poly_outs) > 0 + if not self.is_polymorphic: + return + + # Prefer to use the typevar_operand to infer the controlling typevar. + self.use_typevar_operand = False + typevar_error = None + if self.format.typevar_operand is not None: + try: + tv = self.ins[self.format.typevar_operand].typ + if tv is tv.free_typevar(): + self.other_typevars = self._verify_ctrl_typevar(tv) + self.ctrl_typevar = tv + self.use_typevar_operand = True + except RuntimeError as e: + typevar_error = e + + if not self.use_typevar_operand: + # The typevar_operand argument doesn't work. Can we infer from the + # first result instead? + if len(self.outs) == 0: + if typevar_error: + raise typevar_error + else: + raise RuntimeError( + "typevar_operand must be a free type variable") + tv = self.outs[0].typ + if tv is not tv.free_typevar(): + raise RuntimeError("first result must be a free type variable") + self.other_typevars = self._verify_ctrl_typevar(tv) + self.ctrl_typevar = tv + + def _verify_ctrl_typevar(self, ctrl_typevar): + """ + Verify that the use of TypeVars is consistent with `ctrl_typevar` as + the controlling type variable. + + All polymorhic inputs must either be derived from `ctrl_typevar` or be + independent free type variables only used once. + + All polymorphic results must be derived from `ctrl_typevar`. + + Return list of other type variables used, or raise an error. + """ + other_tvs = [] + # Check value inputs. + for opidx in self.format.value_operands: + typ = self.ins[opidx].typ + tv = typ.free_typevar() + # Non-polymorphic or derived form ctrl_typevar is OK. + if tv is None or tv is ctrl_typevar: + continue + # No other derived typevars allowed. + if typ is not tv: + raise RuntimeError( + "{}: type variable {} must be derived from {}" + .format(self.ins[opidx], typ.name, ctrl_typevar)) + # Other free type variables can only be used once each. + if tv in other_tvs: + raise RuntimeError( + "type variable {} can't be used more than once" + .format(tv.name)) + other_tvs.append(tv) + + # Check outputs. + for result in self.outs: + typ = result.typ + tv = typ.free_typevar() + # Non-polymorphic or derived from ctrl_typevar is OK. + if tv is None or tv is ctrl_typevar: + continue + raise RuntimeError( + "type variable in output not derived from ctrl_typevar") + + return other_tvs + + @staticmethod + def _to_operand_tuple(x): + # type: (Union[Sequence[Operand], Operand]) -> Tuple[Operand, ...] + # Allow a single Operand instance instead of the awkward singleton + # tuple syntax. + if isinstance(x, Operand): + x = (x,) + else: + x = tuple(x) + for op in x: + assert isinstance(op, Operand) + return x + + def bind(self, *args): + # type: (*ValueType) -> BoundInstruction + """ + Bind a polymorphic instruction to a concrete list of type variable + values. + """ + assert self.is_polymorphic + return BoundInstruction(self, args) + + def __getattr__(self, name): + # type: (str) -> BoundInstruction + """ + Bind a polymorphic instruction to a single type variable with dot + syntax: + + >>> iadd.i32 + """ + return self.bind(ValueType.by_name(name)) + + def fully_bound(self): + # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] + """ + Verify that all typevars have been bound, and return a + `(inst, typevars)` pair. + + This version in `Instruction` itself allows non-polymorphic + instructions to duck-type as `BoundInstruction`\s. + """ + assert not self.is_polymorphic, self + return (self, ()) + + def __call__(self, *args): + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + from cretonne.ast import Apply + return Apply(self, args) + + +class BoundInstruction(object): + """ + A polymorphic `Instruction` bound to concrete type variables. + """ + + def __init__(self, inst, typevars): + # type: (Instruction, Tuple[ValueType, ...]) -> None + self.inst = inst + self.typevars = typevars + assert len(typevars) <= 1 + len(inst.other_typevars) + + def __str__(self): + return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) + + def bind(self, *args): + # type: (*ValueType) -> BoundInstruction + """ + Bind additional typevars. + """ + return BoundInstruction(self.inst, self.typevars + args) + + def __getattr__(self, name): + # type: (str) -> BoundInstruction + """ + Bind an additional typevar dot syntax: + + >>> uext.i32.i8 + """ + return self.bind(ValueType.by_name(name)) + + def fully_bound(self): + # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] + """ + Verify that all typevars have been bound, and return a + `(inst, typevars)` pair. + """ + if len(self.typevars) < 1 + len(self.inst.other_typevars): + unb = ', '.join( + str(tv) for tv in + self.inst.other_typevars[len(self.typevars) - 1:]) + raise AssertionError("Unbound typevar {} in {}".format(unb, self)) + assert len(self.typevars) == 1 + len(self.inst.other_typevars) + return (self.inst, self.typevars) + + def __call__(self, *args): + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + from cretonne.ast import Apply + return Apply(self, args) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cretonne/__init__.py index 0ab55491c9..fa1da4d302 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cretonne/__init__.py @@ -6,19 +6,15 @@ instructions. """ from __future__ import absolute_import import importlib -from cdsl import camel_case from cdsl.predicates import And -from cdsl.types import ValueType from cdsl.typevar import TypeVar -from cdsl.operands import Operand -from cdsl.formats import InstructionFormat # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: from typing import Tuple, Union, Any, Iterable, Sequence, TYPE_CHECKING # noqa from cdsl.predicates import Predicate, FieldPredicate # noqa - MaybeBoundInst = Union['Instruction', 'BoundInstruction'] + from cdsl.instructions import MaybeBoundInst # noqa AnyPredicate = Union['Predicate', 'FieldPredicate'] except ImportError: TYPE_CHECKING = False @@ -27,310 +23,6 @@ if TYPE_CHECKING: from cdsl.typevar import TypeVar # noqa -# Defining instructions. - - -class InstructionGroup(object): - """ - Every instruction must belong to exactly one instruction group. A given - target architecture can support instructions from multiple groups, and it - does not necessarily support all instructions in a group. - - New instructions are automatically added to the currently open instruction - group. - """ - - # The currently open instruction group. - _current = None # type: InstructionGroup - - def open(self): - # type: () -> None - """ - Open this instruction group such that future new instructions are - added to this group. - """ - assert InstructionGroup._current is None, ( - "Can't open {} since {} is already open" - .format(self, InstructionGroup._current)) - InstructionGroup._current = self - - def close(self): - # type: () -> None - """ - Close this instruction group. This function should be called before - opening another instruction group. - """ - assert InstructionGroup._current is self, ( - "Can't close {}, the open instuction group is {}" - .format(self, InstructionGroup._current)) - InstructionGroup._current = None - - def __init__(self, name, doc): - # type: (str, str) -> None - self.name = name - self.__doc__ = doc - self.instructions = [] # type: List[Instruction] - self.open() - - @staticmethod - def append(inst): - # type: (Instruction) -> None - assert InstructionGroup._current, \ - "Open an instruction group before defining instructions." - InstructionGroup._current.instructions.append(inst) - - -class Instruction(object): - """ - The operands to the instruction are specified as two tuples: ``ins`` and - ``outs``. Since the Python singleton tuple syntax is a bit awkward, it is - allowed to specify a singleton as just the operand itself, i.e., `ins=x` - and `ins=(x,)` are both allowed and mean the same thing. - - :param name: Instruction mnemonic, also becomes opcode name. - :param doc: Documentation string. - :param ins: Tuple of input operands. This can be a mix of SSA value - operands and other operand kinds. - :param outs: Tuple of output operands. The output operands must be SSA - values or `variable_args`. - :param is_terminator: This is a terminator instruction. - :param is_branch: This is a branch instruction. - """ - - def __init__(self, name, doc, ins=(), outs=(), **kwargs): - # type: (str, str, Union[Sequence[Operand], Operand], Union[Sequence[Operand], Operand], **Any) -> None # noqa - self.name = name - self.camel_name = camel_case(name) - self.__doc__ = doc - self.ins = self._to_operand_tuple(ins) - self.outs = self._to_operand_tuple(outs) - self.format = InstructionFormat.lookup(self.ins, self.outs) - # Indexes into outs for value results. Others are `variable_args`. - self.value_results = tuple( - i for i, o in enumerate(self.outs) if o.is_value()) - self._verify_polymorphic() - InstructionGroup.append(self) - - def __str__(self): - prefix = ', '.join(o.name for o in self.outs) - if prefix: - prefix = prefix + ' = ' - suffix = ', '.join(o.name for o in self.ins) - return '{}{} {}'.format(prefix, self.name, suffix) - - def snake_name(self): - # type: () -> str - """ - Get the snake_case name of this instruction. - - Keywords in Rust and Python are altered by appending a '_' - """ - if self.name == 'return': - return 'return_' - else: - return self.name - - def blurb(self): - """Get the first line of the doc comment""" - for line in self.__doc__.split('\n'): - line = line.strip() - if line: - return line - return "" - - def _verify_polymorphic(self): - """ - Check if this instruction is polymorphic, and verify its use of type - variables. - """ - poly_ins = [ - i for i in self.format.value_operands - if self.ins[i].typ.free_typevar()] - poly_outs = [ - i for i, o in enumerate(self.outs) - if o.typ.free_typevar()] - self.is_polymorphic = len(poly_ins) > 0 or len(poly_outs) > 0 - if not self.is_polymorphic: - return - - # Prefer to use the typevar_operand to infer the controlling typevar. - self.use_typevar_operand = False - typevar_error = None - if self.format.typevar_operand is not None: - try: - tv = self.ins[self.format.typevar_operand].typ - if tv is tv.free_typevar(): - self.other_typevars = self._verify_ctrl_typevar(tv) - self.ctrl_typevar = tv - self.use_typevar_operand = True - except RuntimeError as e: - typevar_error = e - - if not self.use_typevar_operand: - # The typevar_operand argument doesn't work. Can we infer from the - # first result instead? - if len(self.outs) == 0: - if typevar_error: - raise typevar_error - else: - raise RuntimeError( - "typevar_operand must be a free type variable") - tv = self.outs[0].typ - if tv is not tv.free_typevar(): - raise RuntimeError("first result must be a free type variable") - self.other_typevars = self._verify_ctrl_typevar(tv) - self.ctrl_typevar = tv - - def _verify_ctrl_typevar(self, ctrl_typevar): - """ - Verify that the use of TypeVars is consistent with `ctrl_typevar` as - the controlling type variable. - - All polymorhic inputs must either be derived from `ctrl_typevar` or be - independent free type variables only used once. - - All polymorphic results must be derived from `ctrl_typevar`. - - Return list of other type variables used, or raise an error. - """ - other_tvs = [] - # Check value inputs. - for opidx in self.format.value_operands: - typ = self.ins[opidx].typ - tv = typ.free_typevar() - # Non-polymorphic or derived form ctrl_typevar is OK. - if tv is None or tv is ctrl_typevar: - continue - # No other derived typevars allowed. - if typ is not tv: - raise RuntimeError( - "{}: type variable {} must be derived from {}" - .format(self.ins[opidx], typ.name, ctrl_typevar)) - # Other free type variables can only be used once each. - if tv in other_tvs: - raise RuntimeError( - "type variable {} can't be used more than once" - .format(tv.name)) - other_tvs.append(tv) - - # Check outputs. - for result in self.outs: - typ = result.typ - tv = typ.free_typevar() - # Non-polymorphic or derived from ctrl_typevar is OK. - if tv is None or tv is ctrl_typevar: - continue - raise RuntimeError( - "type variable in output not derived from ctrl_typevar") - - return other_tvs - - @staticmethod - def _to_operand_tuple(x): - # type: (Union[Sequence[Operand], Operand]) -> Tuple[Operand, ...] - # Allow a single Operand instance instead of the awkward singleton - # tuple syntax. - if isinstance(x, Operand): - x = (x,) - else: - x = tuple(x) - for op in x: - assert isinstance(op, Operand) - return x - - def bind(self, *args): - # type: (*ValueType) -> BoundInstruction - """ - Bind a polymorphic instruction to a concrete list of type variable - values. - """ - assert self.is_polymorphic - return BoundInstruction(self, args) - - def __getattr__(self, name): - # type: (str) -> BoundInstruction - """ - Bind a polymorphic instruction to a single type variable with dot - syntax: - - >>> iadd.i32 - """ - return self.bind(ValueType.by_name(name)) - - def fully_bound(self): - # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] - """ - Verify that all typevars have been bound, and return a - `(inst, typevars)` pair. - - This version in `Instruction` itself allows non-polymorphic - instructions to duck-type as `BoundInstruction`\s. - """ - assert not self.is_polymorphic, self - return (self, ()) - - def __call__(self, *args): - """ - Create an `ast.Apply` AST node representing the application of this - instruction to the arguments. - """ - from .ast import Apply - return Apply(self, args) - - -class BoundInstruction(object): - """ - A polymorphic `Instruction` bound to concrete type variables. - """ - - def __init__(self, inst, typevars): - # type: (Instruction, Tuple[ValueType, ...]) -> None - self.inst = inst - self.typevars = typevars - assert len(typevars) <= 1 + len(inst.other_typevars) - - def __str__(self): - return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) - - def bind(self, *args): - # type: (*ValueType) -> BoundInstruction - """ - Bind additional typevars. - """ - return BoundInstruction(self.inst, self.typevars + args) - - def __getattr__(self, name): - # type: (str) -> BoundInstruction - """ - Bind an additional typevar dot syntax: - - >>> uext.i32.i8 - """ - return self.bind(ValueType.by_name(name)) - - def fully_bound(self): - # type: () -> Tuple[Instruction, Tuple[ValueType, ...]] - """ - Verify that all typevars have been bound, and return a - `(inst, typevars)` pair. - """ - if len(self.typevars) < 1 + len(self.inst.other_typevars): - unb = ', '.join( - str(tv) for tv in - self.inst.other_typevars[len(self.typevars) - 1:]) - raise AssertionError("Unbound typevar {} in {}".format(unb, self)) - assert len(self.typevars) == 1 + len(self.inst.other_typevars) - return (self.inst, self.typevars) - - def __call__(self, *args): - """ - Create an `ast.Apply` AST node representing the application of this - instruction to the arguments. - """ - from .ast import Apply - return Apply(self, args) - - # Defining target ISAs. diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cretonne/ast.py index b437148435..0212de7a0b 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cretonne/ast.py @@ -5,7 +5,7 @@ This module defines classes that can be used to create abstract syntax trees for patern matching an rewriting of cretonne instructions. """ from __future__ import absolute_import -import cretonne +from cdsl import instructions try: from typing import Union, Tuple # noqa @@ -20,7 +20,7 @@ class Def(object): Example: - >>> from .base import iadd_cout, iconst + >>> from base.instructions import iadd_cout, iconst >>> x = Var('x') >>> y = Var('y') >>> x << iconst(4) @@ -162,7 +162,7 @@ class Apply(Expr): instructions. This applies to both bound and unbound polymorphic instructions: - >>> from .base import jump, iadd + >>> from base.instructions import jump, iadd >>> jump('next', ()) Apply(jump, ('next', ())) >>> iadd.i32('x', 'y') @@ -174,12 +174,12 @@ class Apply(Expr): """ def __init__(self, inst, args): - # type: (Union[cretonne.Instruction, cretonne.BoundInstruction], Tuple[Expr, ...]) -> None # noqa - if isinstance(inst, cretonne.BoundInstruction): + # type: (instructions.MaybeBoundInst, Tuple[Expr, ...]) -> None # noqa + if isinstance(inst, instructions.BoundInstruction): self.inst = inst.inst self.typevars = inst.typevars else: - assert isinstance(inst, cretonne.Instruction) + assert isinstance(inst, instructions.Instruction) self.inst = inst self.typevars = () self.args = args diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/cretonne/legalize.py index 189685dce1..dd534cf108 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/cretonne/legalize.py @@ -7,10 +7,10 @@ patterns that describe how base instructions can be transformed to other base instructions that are legal. """ from __future__ import absolute_import -from .base import iadd, iadd_cout, iadd_cin, iadd_carry -from .base import isub, isub_bin, isub_bout, isub_borrow -from .base import band, bor, bxor, isplit_lohi, iconcat_lohi -from .base import icmp +from base.instructions import iadd, iadd_cout, iadd_cin, iadd_carry +from base.instructions import isub, isub_bin, isub_bout, isub_borrow +from base.instructions import band, bor, bxor, isplit_lohi, iconcat_lohi +from base.instructions import icmp from .ast import Var from .xform import Rtl, XFormGroup diff --git a/lib/cretonne/meta/cretonne/test_ast.py b/lib/cretonne/meta/cretonne/test_ast.py index 71791db3f6..750142af0a 100644 --- a/lib/cretonne/meta/cretonne/test_ast.py +++ b/lib/cretonne/meta/cretonne/test_ast.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from unittest import TestCase from doctest import DocTestSuite from . import ast -from .base import jump, iadd +from base.instructions import jump, iadd def load_tests(loader, tests, ignore): diff --git a/lib/cretonne/meta/cretonne/test_xform.py b/lib/cretonne/meta/cretonne/test_xform.py index b472a8f458..1609bb6c5f 100644 --- a/lib/cretonne/meta/cretonne/test_xform.py +++ b/lib/cretonne/meta/cretonne/test_xform.py @@ -1,8 +1,8 @@ from __future__ import absolute_import from unittest import TestCase from doctest import DocTestSuite +from base.instructions import iadd, iadd_imm, iconst from . import xform -from .base import iadd, iadd_imm, iconst from .ast import Var from .xform import Rtl, XForm diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cretonne/xform.py index 6b9b37f513..f8f66de85c 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cretonne/xform.py @@ -54,7 +54,7 @@ class XForm(object): A legalization pattern must have a source pattern containing only a single instruction. - >>> from .base import iconst, iadd, iadd_imm + >>> from base.instructions import iconst, iadd, iadd_imm >>> a = Var('a') >>> c = Var('c') >>> v = Var('v') diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index aae46b5652..56846c90ab 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -7,7 +7,7 @@ import constant_hash from unique_table import UniqueTable, UniqueSeqTable import cdsl.types import cdsl.operands -import cretonne +from cdsl.formats import InstructionFormat def gen_formats(fmt): @@ -21,7 +21,7 @@ def gen_formats(fmt): fmt.doc_comment('and the `InstructionData` enums.') fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') with fmt.indented('pub enum InstructionFormat {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: fmt.doc_comment(str(f)) fmt.line(f.name + ',') fmt.line() @@ -34,7 +34,7 @@ def gen_formats(fmt): "fn from(inst: &'a InstructionData) -> InstructionFormat {", '}'): with fmt.indented('match *inst {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: fmt.line(('InstructionData::{} {{ .. }} => ' + 'InstructionFormat::{},') .format(f.name, f.name)) @@ -55,7 +55,7 @@ def gen_arguments_method(fmt, is_mut): 'pub fn {f}(&{m}self) -> [&{m}[Value]; 2] {{' .format(f=method, m=mut), '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: n = 'InstructionData::' + f.name has_varargs = cdsl.operands.VARIABLE_ARGS in f.kinds # Formats with both fixed and variable arguments delegate to @@ -118,7 +118,7 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Get the opcode of this instruction.') with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: fmt.line( 'InstructionData::{} {{ opcode, .. }} => opcode,' .format(f.name)) @@ -126,7 +126,7 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Type of the first result, or `VOID`.') with fmt.indented('pub fn first_type(&self) -> Type {', '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: fmt.line( 'InstructionData::{} {{ ty, .. }} => ty,' .format(f.name)) @@ -135,7 +135,7 @@ def gen_instruction_data_impl(fmt): with fmt.indented( 'pub fn first_type_mut(&mut self) -> &mut Type {', '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: fmt.line( 'InstructionData::{} {{ ref mut ty, .. }} => ty,' .format(f.name)) @@ -147,7 +147,7 @@ def gen_instruction_data_impl(fmt): with fmt.indented( 'pub fn second_result(&self) -> Option {', '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: if f.multiple_results: fmt.line( 'InstructionData::' + f.name + @@ -164,7 +164,7 @@ def gen_instruction_data_impl(fmt): "pub fn second_result_mut<'a>(&'a mut self)" + " -> Option<&'a mut Value> {", '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: if f.multiple_results: fmt.line( 'InstructionData::' + f.name + @@ -180,7 +180,7 @@ def gen_instruction_data_impl(fmt): with fmt.indented( 'pub fn typevar_operand(&self) -> Option {', '}'): with fmt.indented('match *self {', '}'): - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: n = 'InstructionData::' + f.name if f.typevar_operand is None: fmt.line(n + ' { .. } => None,') @@ -612,7 +612,7 @@ def gen_builder(insts, fmt): "pub trait InstBuilder<'f>: InstBuilderBase<'f> {", '}'): for inst in insts: gen_inst_builder(inst, fmt) - for f in cretonne.InstructionFormat.all_formats: + for f in InstructionFormat.all_formats: gen_format_constructor(f, fmt) diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 6a05d1fa03..74aa0dca48 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -5,8 +5,8 @@ from __future__ import absolute_import import srcgen from unique_table import UniqueSeqTable import constant_hash +from cdsl import camel_case from cdsl.settings import BoolSetting, NumSetting, EnumSetting -from cretonne import camel_case from base import settings diff --git a/lib/cretonne/meta/isa/riscv/defs.py b/lib/cretonne/meta/isa/riscv/defs.py index 4fccbf09a6..2a3aa3cc9d 100644 --- a/lib/cretonne/meta/isa/riscv/defs.py +++ b/lib/cretonne/meta/isa/riscv/defs.py @@ -5,9 +5,9 @@ Commonly used definitions. """ from __future__ import absolute_import from cretonne import TargetISA, CPUMode -import cretonne.base +import base.instructions -isa = TargetISA('riscv', [cretonne.base.instructions]) +isa = TargetISA('riscv', [base.instructions.GROUP]) # CPU modes for 32-bit and 64-bit operation. RV32 = CPUMode('RV32', isa) diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 807fdafc7a..0fc3a3ee8d 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -2,7 +2,7 @@ RISC-V Encodings. """ from __future__ import absolute_import -from cretonne import base +from base import instructions as base from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt, I from .settings import use_m From 09204ca14a09c5cdfe20325bacb59364e334af38 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 12:29:17 -0800 Subject: [PATCH 433/968] Move ast, xform, and legalize modules. - cdsl.ast defines classes representing abstract syntax trees. - cdsl.xform defines classes for instruction transformations. - base.legalize defines legalization patterns. --- lib/cretonne/meta/{cretonne => base}/legalize.py | 12 ++++++------ lib/cretonne/meta/{cretonne => cdsl}/ast.py | 2 +- lib/cretonne/meta/cdsl/instructions.py | 4 ++-- lib/cretonne/meta/{cretonne => cdsl}/test_ast.py | 0 lib/cretonne/meta/{cretonne => cdsl}/test_xform.py | 0 lib/cretonne/meta/{cretonne => cdsl}/xform.py | 3 ++- lib/cretonne/meta/gen_legalizer.py | 6 +++--- 7 files changed, 14 insertions(+), 13 deletions(-) rename lib/cretonne/meta/{cretonne => base}/legalize.py (91%) rename lib/cretonne/meta/{cretonne => cdsl}/ast.py (99%) rename lib/cretonne/meta/{cretonne => cdsl}/test_ast.py (100%) rename lib/cretonne/meta/{cretonne => cdsl}/test_xform.py (100%) rename lib/cretonne/meta/{cretonne => cdsl}/xform.py (99%) diff --git a/lib/cretonne/meta/cretonne/legalize.py b/lib/cretonne/meta/base/legalize.py similarity index 91% rename from lib/cretonne/meta/cretonne/legalize.py rename to lib/cretonne/meta/base/legalize.py index dd534cf108..f054d16d9e 100644 --- a/lib/cretonne/meta/cretonne/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -7,12 +7,12 @@ patterns that describe how base instructions can be transformed to other base instructions that are legal. """ from __future__ import absolute_import -from base.instructions import iadd, iadd_cout, iadd_cin, iadd_carry -from base.instructions import isub, isub_bin, isub_bout, isub_borrow -from base.instructions import band, bor, bxor, isplit_lohi, iconcat_lohi -from base.instructions import icmp -from .ast import Var -from .xform import Rtl, XFormGroup +from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry +from .instructions import isub, isub_bin, isub_bout, isub_borrow +from .instructions import band, bor, bxor, isplit_lohi, iconcat_lohi +from .instructions import icmp +from cdsl.ast import Var +from cdsl.xform import Rtl, XFormGroup narrow = XFormGroup('narrow', """ diff --git a/lib/cretonne/meta/cretonne/ast.py b/lib/cretonne/meta/cdsl/ast.py similarity index 99% rename from lib/cretonne/meta/cretonne/ast.py rename to lib/cretonne/meta/cdsl/ast.py index 0212de7a0b..853437e4e5 100644 --- a/lib/cretonne/meta/cretonne/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -5,7 +5,7 @@ This module defines classes that can be used to create abstract syntax trees for patern matching an rewriting of cretonne instructions. """ from __future__ import absolute_import -from cdsl import instructions +from . import instructions try: from typing import Union, Tuple # noqa diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index da9f5bb179..8132f144f6 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -259,7 +259,7 @@ class Instruction(object): Create an `ast.Apply` AST node representing the application of this instruction to the arguments. """ - from cretonne.ast import Apply + from .ast import Apply return Apply(self, args) @@ -312,5 +312,5 @@ class BoundInstruction(object): Create an `ast.Apply` AST node representing the application of this instruction to the arguments. """ - from cretonne.ast import Apply + from .ast import Apply return Apply(self, args) diff --git a/lib/cretonne/meta/cretonne/test_ast.py b/lib/cretonne/meta/cdsl/test_ast.py similarity index 100% rename from lib/cretonne/meta/cretonne/test_ast.py rename to lib/cretonne/meta/cdsl/test_ast.py diff --git a/lib/cretonne/meta/cretonne/test_xform.py b/lib/cretonne/meta/cdsl/test_xform.py similarity index 100% rename from lib/cretonne/meta/cretonne/test_xform.py rename to lib/cretonne/meta/cdsl/test_xform.py diff --git a/lib/cretonne/meta/cretonne/xform.py b/lib/cretonne/meta/cdsl/xform.py similarity index 99% rename from lib/cretonne/meta/cretonne/xform.py rename to lib/cretonne/meta/cdsl/xform.py index f8f66de85c..164747fac9 100644 --- a/lib/cretonne/meta/cretonne/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -2,10 +2,11 @@ Instruction transformations. """ from __future__ import absolute_import -from .ast import Def, Var, Apply, Expr # noqa +from .ast import Def, Var, Apply try: from typing import Union, Iterator, Sequence, Iterable # noqa + from .ast import Expr # noqa DefApply = Union[Def, Apply] except ImportError: pass diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index bd4ff926eb..409ebb7cf2 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -9,12 +9,12 @@ the input instruction. """ from __future__ import absolute_import from srcgen import Formatter -import cretonne.legalize as legalize -from cretonne.ast import Def # noqa -from cretonne.xform import XForm, XFormGroup # noqa +from base import legalize try: from typing import Sequence # noqa + from cdsl.ast import Def # noqa + from cdsl.xform import XForm, XFormGroup # noqa except ImportError: pass From bd7662326649714ca76d5132328540ae7f2415ca Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 13:21:05 -0800 Subject: [PATCH 434/968] Move ISA definitions into cdsl.isa. The cretonne package is now split into two: cdsl and base. --- .../{cretonne/__init__.py => cdsl/isa.py} | 57 +++++++++---------- lib/cretonne/meta/isa/__init__.py | 2 +- lib/cretonne/meta/isa/riscv/defs.py | 2 +- lib/cretonne/meta/isa/riscv/recipes.py | 2 +- 4 files changed, 29 insertions(+), 34 deletions(-) rename lib/cretonne/meta/{cretonne/__init__.py => cdsl/isa.py} (82%) diff --git a/lib/cretonne/meta/cretonne/__init__.py b/lib/cretonne/meta/cdsl/isa.py similarity index 82% rename from lib/cretonne/meta/cretonne/__init__.py rename to lib/cretonne/meta/cdsl/isa.py index fa1da4d302..7f59a971db 100644 --- a/lib/cretonne/meta/cretonne/__init__.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,29 +1,18 @@ -""" -Cretonne meta language module. - -This module provides classes and functions used to describe Cretonne -instructions. -""" +"""Defining instruction set architectures.""" from __future__ import absolute_import -import importlib -from cdsl.predicates import And -from cdsl.typevar import TypeVar +from .predicates import And # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: from typing import Tuple, Union, Any, Iterable, Sequence, TYPE_CHECKING # noqa - from cdsl.predicates import Predicate, FieldPredicate # noqa - from cdsl.instructions import MaybeBoundInst # noqa - AnyPredicate = Union['Predicate', 'FieldPredicate'] + from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa + from .predicates import Predicate, FieldPredicate # noqa + from .settings import SettingGroup # noqa + from .types import ValueType # noqa + AnyPredicate = Union[Predicate, FieldPredicate] except ImportError: - TYPE_CHECKING = False - -if TYPE_CHECKING: - from cdsl.typevar import TypeVar # noqa - - -# Defining target ISAs. + pass class TargetISA(object): @@ -38,12 +27,14 @@ class TargetISA(object): """ def __init__(self, name, instruction_groups): + # type: (str, Sequence[InstructionGroup]) -> None self.name = name - self.settings = None + self.settings = None # type: SettingGroup self.instruction_groups = instruction_groups - self.cpumodes = list() + self.cpumodes = list() # type: List[CPUMode] def finish(self): + # type: () -> TargetISA """ Finish the definition of a target ISA after adding all CPU modes and settings. @@ -58,11 +49,12 @@ class TargetISA(object): return self def _collect_encoding_recipes(self): + # type: () -> None """ Collect and number all encoding recipes in use. """ - self.all_recipes = list() - rcps = set() + self.all_recipes = list() # type: List[EncRecipe] + rcps = set() # type: Set[EncRecipe] for cpumode in self.cpumodes: for enc in cpumode.encodings: recipe = enc.recipe @@ -72,6 +64,7 @@ class TargetISA(object): self.all_recipes.append(recipe) def _collect_predicates(self): + # type: () -> None """ Collect and number all predicates in use. @@ -81,8 +74,8 @@ class TargetISA(object): Ensures that all ISA predicates have an assigned bit number in `self.settings`. """ - self.all_instps = list() - instps = set() + self.all_instps = list() # type: List[AnyPredicate] + instps = set() # type: Set[AnyPredicate] for cpumode in self.cpumodes: for enc in cpumode.encodings: instp = enc.instp @@ -111,12 +104,14 @@ class CPUMode(object): """ def __init__(self, name, isa): + # type: (str, TargetISA) -> None self.name = name self.isa = isa - self.encodings = [] + self.encodings = [] # type: List[Encoding] isa.cpumodes.append(self) def __str__(self): + # type: () -> str return self.name def enc(self, *args, **kwargs): @@ -142,14 +137,17 @@ class EncRecipe(object): """ def __init__(self, name, format, instp=None, isap=None): + # type: (str, InstructionFormat, AnyPredicate, AnyPredicate) -> None self.name = name self.format = format self.instp = instp self.isap = isap if instp: assert instp.predicate_context() == format + self.number = None # type: int def __str__(self): + # type: () -> str return self.name @@ -185,9 +183,11 @@ class Encoding(object): self.isap = And.combine(recipe.isap, isap) def __str__(self): + # type: () -> str return '[{}#{:02x}]'.format(self.recipe, self.encbits) def ctrl_typevar(self): + # type: () -> ValueType """ Get the controlling type variable for this encoding or `None`. """ @@ -195,8 +195,3 @@ class Encoding(object): return self.typevars[0] else: return None - - -# Import the fixed instruction formats now so they can be added to the -# registry. -importlib.import_module('base.formats') diff --git a/lib/cretonne/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py index 9412f6502c..1bef425c58 100644 --- a/lib/cretonne/meta/isa/__init__.py +++ b/lib/cretonne/meta/isa/__init__.py @@ -6,8 +6,8 @@ The :py:mod:`isa` package contains sub-packages for each target instruction set architecture supported by Cretonne. """ from __future__ import absolute_import +from cdsl.isa import TargetISA # noqa from . import riscv -from cretonne import TargetISA # noqa def all_isas(): diff --git a/lib/cretonne/meta/isa/riscv/defs.py b/lib/cretonne/meta/isa/riscv/defs.py index 2a3aa3cc9d..7a487482d3 100644 --- a/lib/cretonne/meta/isa/riscv/defs.py +++ b/lib/cretonne/meta/isa/riscv/defs.py @@ -4,7 +4,7 @@ RISC-V definitions. Commonly used definitions. """ from __future__ import absolute_import -from cretonne import TargetISA, CPUMode +from cdsl.isa import TargetISA, CPUMode import base.instructions isa = TargetISA('riscv', [base.instructions.GROUP]) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index b4f561f8e2..ae8d5b3add 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -9,7 +9,7 @@ instruction formats described in the reference: Version 2.1 """ from __future__ import absolute_import -from cretonne import EncRecipe +from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm From 9327d567b4474d58ffa37b15536cb0af825835cc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 13:30:17 -0800 Subject: [PATCH 435/968] Clean up meta-language reference after module splitup. --- docs/metaref.rst | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/metaref.rst b/docs/metaref.rst index 190945c61d..ec4555a8d7 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -17,7 +17,7 @@ steps: 1. The Python modules are imported. This has the effect of building static data structures in global variables in the modules. These static data structures in the :mod:`base` and :mod:`isa` packages use the classes in the - :mod:`cdsl` module to describe instruction sets and other properties. + :mod:`cdsl` package to describe instruction sets and other properties. 2. The static data structures are processed to produce Rust source code and constant tables. @@ -37,7 +37,7 @@ Settings are used by the environment embedding Cretonne to control the details of code generation. Each setting is defined in the meta language so a compact and consistent Rust representation can be generated. Shared settings are defined in the :mod:`base.settings` module. Some settings are specific to a target ISA, -and defined in a `settings` module under the appropriate +and defined in a :file:`settings.py` module under the appropriate :file:`lib/cretonne/meta/isa/*` directory. Settings can take boolean on/off values, small numbers, or explicitly enumerated @@ -68,11 +68,11 @@ a module looks like this:: group.close(globals()) +.. module:: cdsl.instructions + Instruction descriptions ======================== -.. module:: cdsl.instructions - New instructions are defined as instances of the :class:`Instruction` class. As instruction instances are created, they are added to the currently open :class:`InstructionGroup`. @@ -96,15 +96,17 @@ must be instances of the :class:`Operand` class. Cretonne uses two separate type systems for operand kinds and SSA values. +.. module:: cdsl.typevar + Type variables -------------- -Instruction descriptions can be made polymorphic by using :class:`Operand` -instances that refer to a *type variable* instead of a concrete value type. -Polymorphism only works for SSA value operands. Other operands have a fixed -operand kind. +Instruction descriptions can be made polymorphic by using +:class:`cdsl.operands.Operand` instances that refer to a *type variable* +instead of a concrete value type. Polymorphism only works for SSA value +operands. Other operands have a fixed operand kind. -.. autoclass:: cdsl.typevar.TypeVar +.. autoclass:: TypeVar :members: If multiple operands refer to the same type variable they will be required to @@ -145,10 +147,11 @@ indicated with an instance of :class:`ImmediateKind`. Entity references ----------------- +.. currentmodule:: cdsl.operands + Instruction operands can also refer to other entities in the same function. This can be extended basic blocks, or entities declared in the function preamble. -.. currentmodule:: cdsl.operands .. autoclass:: EntityRefKind .. automodule:: base.entities @@ -157,10 +160,11 @@ can be extended basic blocks, or entities declared in the function preamble. Value types ----------- -Concrete value types are represented as instances of :class:`cdsl.types.ValueType`. There are -subclasses to represent scalar and vector types. - .. currentmodule:: cdsl.types + +Concrete value types are represented as instances of :class:`ValueType`. There +are subclasses to represent scalar and vector types. + .. autoclass:: ValueType .. inheritance-diagram:: ValueType ScalarType VectorType IntType FloatType BoolType :parts: 1 @@ -182,14 +186,14 @@ There are no predefined vector types, but they can be created as needed with the :func:`ScalarType.by` function. +.. module:: cdsl.operands + Instruction representation ========================== -.. module:: cdsl.operands - The Rust in-memory representation of instructions is derived from the instruction descriptions. Part of the representation is generated, and part is -written as Rust code in the `cretonne.instructions` module. The instruction +written as Rust code in the ``cretonne.instructions`` module. The instruction representation depends on the input operand kinds and whether the instruction can produce multiple results. @@ -255,7 +259,7 @@ controlling type variable, or it can vary independently of the other operands. Encodings ========= -.. currentmodule:: cretonne +.. currentmodule:: cdsl.isa Encodings describe how Cretonne instructions are mapped to binary machine code for the target architecture. After the legalization pass, all remaining @@ -345,12 +349,13 @@ encodings only need the recipe predicates. .. autoclass:: EncRecipe +.. module:: cdsl.isa Targets ======= Cretonne can be compiled with support for multiple target instruction set -architectures. Each ISA is represented by a :py:class:`cretonne.TargetISA` instance. +architectures. Each ISA is represented by a :py:class:`cdsl.isa.TargetISA` instance. .. autoclass:: TargetISA From 45fd134d3e1d6b4cbdfc3a25695bd9843d64b8cc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 14:41:13 -0800 Subject: [PATCH 436/968] Assign a type variable to all VALUE operands. A few operands have a fixed type assigned. Create a singleton type variable for these exceptions. Most instructions are polymorphic, so this is a little overhead. Eliminate the Operand.typ field and replace it with an Operand.typevar field which is always a TypeVar, but which only exists in VALUE operands. --- lib/cretonne/meta/cdsl/instructions.py | 14 ++++---- lib/cretonne/meta/cdsl/operands.py | 15 +++++---- lib/cretonne/meta/cdsl/test_typevar.py | 16 +++++++++ lib/cretonne/meta/cdsl/types.py | 9 ++---- lib/cretonne/meta/cdsl/typevar.py | 33 +++++++++++++++++++ lib/cretonne/meta/gen_instr.py | 45 ++++++++++++++------------ 6 files changed, 93 insertions(+), 39 deletions(-) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 8132f144f6..ee6b9c4a59 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -130,10 +130,10 @@ class Instruction(object): """ poly_ins = [ i for i in self.format.value_operands - if self.ins[i].typ.free_typevar()] + if self.ins[i].typevar.free_typevar()] poly_outs = [ i for i, o in enumerate(self.outs) - if o.typ.free_typevar()] + if o.is_value() and o.typevar.free_typevar()] self.is_polymorphic = len(poly_ins) > 0 or len(poly_outs) > 0 if not self.is_polymorphic: return @@ -143,7 +143,7 @@ class Instruction(object): typevar_error = None if self.format.typevar_operand is not None: try: - tv = self.ins[self.format.typevar_operand].typ + tv = self.ins[self.format.typevar_operand].typevar if tv is tv.free_typevar(): self.other_typevars = self._verify_ctrl_typevar(tv) self.ctrl_typevar = tv @@ -160,7 +160,7 @@ class Instruction(object): else: raise RuntimeError( "typevar_operand must be a free type variable") - tv = self.outs[0].typ + tv = self.outs[0].typevar if tv is not tv.free_typevar(): raise RuntimeError("first result must be a free type variable") self.other_typevars = self._verify_ctrl_typevar(tv) @@ -181,7 +181,7 @@ class Instruction(object): other_tvs = [] # Check value inputs. for opidx in self.format.value_operands: - typ = self.ins[opidx].typ + typ = self.ins[opidx].typevar tv = typ.free_typevar() # Non-polymorphic or derived form ctrl_typevar is OK. if tv is None or tv is ctrl_typevar: @@ -200,7 +200,9 @@ class Instruction(object): # Check outputs. for result in self.outs: - typ = result.typ + if not result.is_value(): + continue + typ = result.typevar tv = typ.free_typevar() # Non-polymorphic or derived from ctrl_typevar is OK. if tv is None or tv is ctrl_typevar: diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index f379357611..2fcc22b0c0 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -40,10 +40,6 @@ class OperandKind(object): # type: () -> str return 'OperandKind({})'.format(self.name) - def free_typevar(self): - # Return the free typevariable controlling the type of this operand. - return None - #: An SSA value operand. This is a value defined by another instruction. VALUE = OperandKind( 'value', """ @@ -129,11 +125,15 @@ class Operand(object): # type: (str, OperandSpec, str) -> None self.name = name self.__doc__ = doc - self.typ = typ + + # Decode the operand spec and set self.kind. + # Only VALUE operands have a typevar member. if isinstance(typ, ValueType): self.kind = VALUE + self.typevar = TypeVar.singleton(typ) elif isinstance(typ, TypeVar): self.kind = VALUE + self.typevar = typ else: assert isinstance(typ, OperandKind) self.kind = typ @@ -142,8 +142,9 @@ class Operand(object): # type: () -> str if self.__doc__: return self.__doc__ - else: - return self.typ.__doc__ + if self.kind is VALUE: + return self.typevar.__doc__ + return self.kind.__doc__ def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index a841acfdb9..7dae18221a 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -3,6 +3,7 @@ from unittest import TestCase from doctest import DocTestSuite from . import typevar from .typevar import TypeSet, TypeVar +from base.types import i32 def load_tests(loader, tests, ignore): @@ -62,3 +63,18 @@ class TestTypeVar(TestCase): self.assertEqual(str(x3.double_width()), '`DoubleWidth(x3)`') with self.assertRaises(AssertionError): x3.half_width() + + def test_singleton(self): + x = TypeVar.singleton(i32) + self.assertEqual(str(x), '`i32`') + self.assertEqual(x.type_set.min_int, 32) + self.assertEqual(x.type_set.max_int, 32) + self.assertEqual(x.type_set.min_lanes, 1) + self.assertEqual(x.type_set.max_lanes, 1) + + x = TypeVar.singleton(i32.by(4)) + self.assertEqual(str(x), '`i32x4`') + self.assertEqual(x.type_set.min_int, 32) + self.assertEqual(x.type_set.max_int, 32) + self.assertEqual(x.type_set.min_lanes, 4) + self.assertEqual(x.type_set.max_lanes, 4) diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index 3d5a5dcd38..62334b7dd7 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -30,8 +30,9 @@ class ValueType(object): # type: () -> str return self.name - def free_typevar(self): - return None + def rust_name(self): + # type: () -> str + return 'types::' + self.name.upper() @staticmethod def by_name(name): @@ -63,10 +64,6 @@ class ScalarType(ValueType): # type: () -> str return 'ScalarType({})'.format(self.name) - def rust_name(self): - # type: () -> str - return 'types::' + self.name.upper() - def by(self, lanes): # type: (int) -> VectorType """ diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 2c10338bbf..9803f1afc6 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -6,6 +6,7 @@ polymorphic by using type variables. """ from __future__ import absolute_import import math +from . import types try: from typing import Tuple, Union # noqa @@ -242,6 +243,7 @@ class TypeVar(object): # type: (str, str, BoolInterval, BoolInterval, BoolInterval, bool, BoolInterval, TypeVar, str) -> None # noqa self.name = name self.__doc__ = doc + self.singleton_type = None # type: types.ValueType self.is_derived = isinstance(base, TypeVar) if base: assert self.is_derived @@ -258,6 +260,34 @@ class TypeVar(object): floats=floats, bools=bools) + @staticmethod + def singleton(typ): + # type: (types.ValueType) -> TypeVar + """Create a type variable that can only assume a single type.""" + if isinstance(typ, types.VectorType): + scalar = typ.base + lanes = (typ.lanes, typ.lanes) + elif isinstance(typ, types.ScalarType): + scalar = typ + lanes = (1, 1) + + ints = None + floats = None + bools = None + + if isinstance(scalar, types.IntType): + ints = (scalar.bits, scalar.bits) + elif isinstance(scalar, types.FloatType): + floats = (scalar.bits, scalar.bits) + elif isinstance(scalar, types.BoolType): + bools = (scalar.bits, scalar.bits) + + tv = TypeVar( + typ.name, 'typeof({})'.format(typ), + ints, floats, bools, simd=lanes) + tv.singleton_type = typ + return tv + def __str__(self): # type: () -> str return "`{}`".format(self.name) @@ -317,5 +347,8 @@ class TypeVar(object): # type: () -> TypeVar if self.is_derived: return self.base + elif self.singleton_type: + # A singleton type variable is not a proper free variable. + return None else: return self diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 56846c90ab..4f240ab6b1 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -5,10 +5,14 @@ from __future__ import absolute_import import srcgen import constant_hash from unique_table import UniqueTable, UniqueSeqTable +from cdsl.operands import ImmediateKind import cdsl.types -import cdsl.operands from cdsl.formats import InstructionFormat +from cdsl.instructions import Instruction # noqa +from cdsl.operands import Operand # noqa +from cdsl.typevar import TypeVar # noqa + def gen_formats(fmt): # type: (srcgen.Formatter) -> None @@ -302,6 +306,7 @@ def gen_opcodes(groups, fmt): def get_constraint(op, ctrl_typevar, type_sets): + # type: (Operand, TypeVar, UniqueTable) -> str """ Get the value type constraint for an SSA value operand, where `ctrl_typevar` is the controlling type variable. @@ -312,22 +317,22 @@ def get_constraint(op, ctrl_typevar, type_sets): - `Free(idx)` where `idx` is an index into `type_sets`. - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. """ - assert op.kind is cdsl.operands.VALUE - t = op.typ + assert op.is_value() + tv = op.typevar # A concrete value type. - if isinstance(t, cdsl.types.ValueType): - return 'Concrete({})'.format(t.rust_name()) + if tv.singleton_type: + return 'Concrete({})'.format(tv.singleton_type.rust_name()) - if t.free_typevar() is not ctrl_typevar: - assert not t.is_derived - return 'Free({})'.format(type_sets.add(t.type_set)) + if tv.free_typevar() is not ctrl_typevar: + assert not tv.is_derived + return 'Free({})'.format(type_sets.add(tv.type_set)) - if t.is_derived: - assert t.base is ctrl_typevar, "Not derived directly from ctrl_typevar" - return t.derived_func + if tv.is_derived: + assert tv.base is ctrl_typevar, "Not derived from ctrl_typevar" + return tv.derived_func - assert t is ctrl_typevar + assert tv is ctrl_typevar return 'Same' @@ -486,6 +491,7 @@ def gen_member_inits(iform, fmt): def gen_inst_builder(inst, fmt): + # type: (Instruction, srcgen.Formatter) -> None """ Emit a method for generating the instruction `inst`. @@ -502,10 +508,10 @@ def gen_inst_builder(inst, fmt): if inst.is_polymorphic and not inst.use_typevar_operand: args.append('{}: Type'.format(inst.ctrl_typevar.name)) - tmpl_types = list() - into_args = list() + tmpl_types = list() # type: List[str] + into_args = list() # type: List[str] for op in inst.ins: - if isinstance(op.kind, cdsl.operands.ImmediateKind): + if isinstance(op.kind, ImmediateKind): t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name) tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type)) into_args.append(op.name) @@ -553,7 +559,7 @@ def gen_inst_builder(inst, fmt): # The format constructor will resolve the result types from the # type var. args.append('ctrl_typevar') - elif inst.outs[inst.value_results[0]].typ == inst.ctrl_typevar: + elif inst.outs[inst.value_results[0]].typevar == inst.ctrl_typevar: # The format constructor expects a simple result type. # No type transformation needed from the controlling type # variable. @@ -567,13 +573,12 @@ def gen_inst_builder(inst, fmt): else: # This non-polymorphic instruction has a fixed result type. args.append( - 'types::' + - inst.outs[inst.value_results[0]].typ.name.upper()) + inst.outs[inst.value_results[0]] + .typevar.singleton_type.rust_name()) args.extend(op.name for op in inst.ins) - args = ', '.join(args) # Call to the format constructor, - fcall = 'self.{}({})'.format(inst.format.name, args) + fcall = 'self.{}({})'.format(inst.format.name, ', '.join(args)) if len(inst.value_results) == 0: fmt.line(fcall + '.0') From dd326350ffd50b976a632e8a26e8848556fa3416 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 8 Nov 2016 15:26:30 -0800 Subject: [PATCH 437/968] Fix doc build. --- docs/cton_domain.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 47c7c2996c..2f56b5625e 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -305,11 +305,19 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): # Add inputs and outputs. for op in self.object.ins: + if op.is_value(): + typ = op.typevar + else: + typ = op.kind self.add_line(u':in {} {}: {}'.format( - op.typ.name, op.name, op.get_doc()), sourcename) + typ, op.name, op.get_doc()), sourcename) for op in self.object.outs: + if op.is_value(): + typ = op.typevar + else: + typ = op.kind self.add_line(u':out {} {}: {}'.format( - op.typ.name, op.name, op.get_doc()), sourcename) + typ, op.name, op.get_doc()), sourcename) # Document type inference for polymorphic instructions. if self.object.is_polymorphic: From 98b3bd9e092ccff34695b8cfae6887cb4b598a7d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 9 Nov 2016 11:17:55 -0800 Subject: [PATCH 438/968] Add TypeVar.derived() function. Add TypeVar constants representing the available type functions, and a TypeVar.derived() static method which creates a derived TypeVar. Keep the existing non-parametric methods for creating derived type variables. Add a method for converting a free type variable to a derived one. --- lib/cretonne/meta/cdsl/typevar.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 9803f1afc6..66786f53cf 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -292,6 +292,27 @@ class TypeVar(object): # type: () -> str return "`{}`".format(self.name) + # Supported functions for derived type variables. + SAMEAS = 'SameAs' + LANEOF = 'LaneOf' + ASBOOL = 'AsBool' + HALFWIDTH = 'HalfWidth' + DOUBLEWIDTH = 'DoubleWidth' + + @staticmethod + def derived(base, derived_func): + # type: (TypeVar, str) -> TypeVar + """Create a type variable that is a function of another.""" + return TypeVar(None, None, base=base, derived_func=derived_func) + + def change_to_derived(self, base, derived_func): + # type: (TypeVar, str) -> None + """Change this type variable into a derived one.""" + self.type_set = None + self.is_derived = True + self.base = base + self.derived_func = derived_func + def lane_of(self): # type: () -> TypeVar """ @@ -301,7 +322,7 @@ class TypeVar(object): When this type variable assumes a scalar type, the derived type will be the same scalar type. """ - return TypeVar(None, None, base=self, derived_func='LaneOf') + return TypeVar.derived(self, self.LANEOF) def as_bool(self): # type: () -> TypeVar @@ -309,7 +330,7 @@ class TypeVar(object): Return a derived type variable that has the same vector geometry as this type variable, but with boolean lanes. Scalar types map to `b1`. """ - return TypeVar(None, None, base=self, derived_func='AsBool') + return TypeVar.derived(self, self.ASBOOL) def half_width(self): # type: () -> TypeVar @@ -325,7 +346,7 @@ class TypeVar(object): if ts.min_bool: assert ts.min_bool > 8, "Can't halve all boolean types" - return TypeVar(None, None, base=self, derived_func='HalfWidth') + return TypeVar.derived(self, self.HALFWIDTH) def double_width(self): # type: () -> TypeVar @@ -341,7 +362,7 @@ class TypeVar(object): if ts.max_bool: assert ts.max_bool < MAX_BITS, "Can't double all boolean types." - return TypeVar(None, None, base=self, derived_func='DoubleWidth') + return TypeVar.derived(self, self.DOUBLEWIDTH) def free_typevar(self): # type: () -> TypeVar From 6fd5c6195d34524415b0e86bdf610fb29d42eba7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 10 Nov 2016 10:40:53 -0800 Subject: [PATCH 439/968] Add TypeVar.strip_sameas(). Strips out any type variable copies from an expression. --- lib/cretonne/meta/cdsl/typevar.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 66786f53cf..c2aeb5de97 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -313,6 +313,19 @@ class TypeVar(object): self.base = base self.derived_func = derived_func + def strip_sameas(self): + # type: () -> TypeVar + """ + Strip any `SAMEAS` functions from this typevar. + + Also rewrite any `SAMEAS` functions nested under this typevar. + """ + if self.is_derived: + self.base = self.base.strip_sameas() + if self.derived_func == self.SAMEAS: + return self.base + return self + def lane_of(self): # type: () -> TypeVar """ From 935da5946f951a8a22f353901022089f1881b11c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 9 Nov 2016 15:19:03 -0800 Subject: [PATCH 440/968] Add a TypeVar.constrain_types() function. This reduces the set of types a type variable can assume. This implementation is not complete yet, so it may yield type sets that are too large. --- lib/cretonne/meta/cdsl/typevar.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index c2aeb5de97..e5bf8f42a3 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -379,6 +379,9 @@ class TypeVar(object): def free_typevar(self): # type: () -> TypeVar + """ + Get the free type variable controlling this one. + """ if self.is_derived: return self.base elif self.singleton_type: @@ -386,3 +389,32 @@ class TypeVar(object): return None else: return self + + def constrain_types(self, other): + # type: (TypeVar) -> None + """ + Constrain the range of types this variable can assume to a subset of + those `other` can assume. + + If this is a SAMEAS-derived type variable, constrain the base instead. + """ + a = self.strip_sameas() + b = other.strip_sameas() + if a is b: + return + + if not a.is_derived and not b.is_derived: + a.type_set &= b.type_set + # TODO: What if a.type_set becomes empty? + if not a.singleton_type: + a.singleton_type = b.singleton_type + return + + # TODO: Implement constraints for derived type variables. + # + # If a and b are both derived with the same derived_func, we could say + # `a.base.constrain_types(b.base)`, but unless the derived_func is + # injective, that may constrain `a.base` more than necessary. + # + # For the fully general case, we would need to compute an image typeset + # for `b` and propagate a `a.derived_func` pre-image to `a.base`. From bf1568035f63e994792db6171c49ca25bd0473bd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 9 Nov 2016 15:05:11 -0800 Subject: [PATCH 441/968] Infer type constraints on patterns. Each instruction used in a pattern has constraints on the types of its operands. These constraints are expressed as symbolic type variables. Compute type variables for each variable used in a transformation pattern. Some are free type variables, and some are derived from the free type variables. The type variables associated with variables can be used for computing the result types of replacement instructions that don't support simple forward type inference from their inputs. The type sets computed by this patch are conservatively too large, so they can't yet be used to type check patterns. --- lib/cretonne/meta/cdsl/ast.py | 174 ++++++++++++++++++++++++++++++ lib/cretonne/meta/cdsl/typevar.py | 19 ++++ lib/cretonne/meta/cdsl/xform.py | 61 +++++++++++ 3 files changed, 254 insertions(+) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 853437e4e5..d8b3b7b875 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -6,6 +6,7 @@ for patern matching an rewriting of cretonne instructions. """ from __future__ import absolute_import from . import instructions +from .typevar import TypeVar try: from typing import Union, Tuple # noqa @@ -90,6 +91,12 @@ class Var(Expr): self.src_def = None # type: Def # The `Def` defining this variable in a destination pattern. self.dst_def = None # type: Def + # TypeVar representing the type of this variable. + self.typevar = None # type: TypeVar + # The original 'typeof(x)' type variable that was created for this Var. + # This one doesn't change. `self.typevar` above may be joined with + # other typevars. + self.original_typevar = None # type: TypeVar def __str__(self): # type: () -> str @@ -153,6 +160,173 @@ class Var(Expr): # type: () -> bool return not self.src_def and self.dst_def + def get_typevar(self): + # type: () -> TypeVar + """Get the type variable representing the type of this variable.""" + if not self.typevar: + # Create a TypeVar allowing all types. + tv = TypeVar( + 'typeof_{}'.format(self), + 'Type of the pattern variable `{}`'.format(self), + ints=True, floats=True, bools=True, + scalars=True, simd=True) + self.original_typevar = tv + self.typevar = tv + return self.typevar + + def link_typevar(self, base, derived_func): + # type: (TypeVar, str) -> None + """ + Link the type variable on this Var to the type variable `base` using + `derived_func`. + """ + self.original_typevar = None + self.typevar.change_to_derived(base, derived_func) + # Possibly eliminate redundant SAMEAS links. + self.typevar = self.typevar.strip_sameas() + + def has_free_typevar(self): + # type: () -> bool + """ + Check if this variable has a free type variable. + + If not, the type of this variable is computed from the type of another + variable. + """ + if not self.typevar or self.typevar.is_derived: + return False + return self.typevar is self.original_typevar + + def constrain_typevar(self, sym_typevar, sym_ctrl, ctrl_var): + # type: (TypeVar, TypeVar, Var) -> None + """ + Constrain the set of allowed types for this variable. + + Merge type variables for the involved variables to minimize the set for + free type variables. + + Suppose we're looking at an instruction defined like this: + + c = Operand('c', TxN.as_bool()) + x = Operand('x', TxN) + y = Operand('y', TxN) + a = Operand('a', TxN) + vselect = Instruction('vselect', ins=(c, x, y), outs=a) + + And suppose the instruction is used in a pattern like this: + + v0 << vselect(v1, v2, v3) + + We want to reconcile the types of the variables v0-v3 with the + constraints from the definition of vselect. This means that v0, v2, and + v3 must all have the same type, and v1 must have the type + `typeof(v2).as_bool()`. + + The types are reconciled by calling this function once for each + input/output operand on the instruction in the pattern with these + arguments. + + :param sym_typevar: Symbolic type variable constraining this variable + in the definition of the instruction. + :param sym_ctrl: Controlling type variable of `sym_typevar` in the + definition of the instruction. + :param ctrl_var: Variable determining the type of `sym_ctrl`. + + When processing `v1` as used in the pattern above, we would get: + + - self: v1 + - sym_typevar: TxN.as_bool() + - sym_ctrl: TxN + - ctrl_var: v2 + + Here, 'v2' represents the controlling variable because of how the + `Ternary` instruction format is defined with `typevar_operand=1`. + """ + # First check if sym_typevar is tied to the controlling type variable + # in the instruction definition. We also allow free type variables on + # instruction inputs that can't be tied to anything else. + # + # This also covers non-polymorphic instructions and other cases where + # we don't have a Var representing the controlling type variable. + sym_free_var = sym_typevar.free_typevar() + if not sym_free_var or sym_free_var is not sym_ctrl or not ctrl_var: + # Just constrain our type to be compatible with the required + # typeset. + self.get_typevar().constrain_types(sym_typevar) + return + + # Now sym_typevar is known to be tied to (or identical to) the + # controlling type variable. + + if not self.typevar: + # If this variable is not yet constrained, just infer its type and + # link it to the controlling type variable. + if not sym_typevar.is_derived: + assert sym_typevar is sym_ctrl + # Identity mapping. + # Note that `self == ctrl_var` is both possible and common. + self.typevar = ctrl_var.get_typevar() + else: + assert self is not ctrl_var, ( + 'Impossible type constraints for {}: {}' + .format(self, sym_typevar)) + # Create a derived type variable identical to sym_typevar, but + # with a different base. + self.typevar = TypeVar.derived( + ctrl_var.get_typevar(), + sym_typevar.derived_func) + # Match the type set constraints of the instruction. + self.typevar.constrain_types(sym_typevar) + return + + # We already have a self.typevar describing our constraints. We need to + # reconcile with the additional constraints. + + # It's likely that ctrl_var and self already share a type + # variable. (Often because `ctrl_var == self`). + if ctrl_var.typevar == self.typevar: + return + + if not sym_typevar.is_derived: + assert sym_typevar is sym_ctrl + # sym_typevar is a direct use of sym_ctrl, so we need to reconcile + # self with ctrl_var. + assert not sym_typevar.is_derived + self.typevar.constrain_types(sym_typevar) + + # It's possible that ctrl_var has not yet been assigned a type + # variable. + if not ctrl_var.typevar: + ctrl_var.typevar = self.typevar + return + + # We can also bind variables with a free type variable to another + # variable. Prefer to do this to temps because they aren't allowed + # to be free, + if self.is_temp() and self.has_free_typevar(): + self.link_typevar(ctrl_var.typevar, TypeVar.SAMEAS) + return + if ctrl_var.is_temp() and ctrl_var.has_free_typevar(): + ctrl_var.link_typevar(self.typevar, TypeVar.SAMEAS) + return + if self.has_free_typevar(): + self.link_typevar(ctrl_var.typevar, TypeVar.SAMEAS) + return + if ctrl_var.has_free_typevar(): + ctrl_var.link_typevar(self.typevar, TypeVar.SAMEAS) + return + + # TODO: Other cases are harder to handle. + # + # - If either variable is an independent free type variable, it + # should be changed to be linked to the other. + # - If both variable are free, we should pick one to link to the + # other. In particular, if one is a temp, it should be linked. + else: + # sym_typevar is derived from sym_ctrl. + # TODO: Other cases are harder to handle. + pass + class Apply(Expr): """ diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index e5bf8f42a3..30986c5db8 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -292,6 +292,25 @@ class TypeVar(object): # type: () -> str return "`{}`".format(self.name) + def __repr__(self): + # type: () -> str + if self.is_derived: + return ( + 'TypeVar({}, base={}, derived_func={})' + .format(self.name, self.base, self.derived_func)) + else: + return ( + 'TypeVar({}, {})' + .format(self.name, self.type_set)) + + def __eq__(self, other): + if self.is_derived and other.is_derived: + return ( + self.derived_func == other.derived_func and + self.base == other.base) + else: + return self is other + # Supported functions for derived type variables. SAMEAS = 'SameAs' LANEOF = 'LaneOf' diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 164747fac9..ee6710a79f 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -100,6 +100,10 @@ class XForm(object): "extra inputs in dst RTL: {}".format( self.inputs[num_src_inputs:])) + self._infer_types(self.src) + self._infer_types(self.dst) + self._collect_typevars() + def __repr__(self): s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs) s += '\n '.join(str(n) for n in self.src) @@ -201,6 +205,63 @@ class XForm(object): raise AssertionError( '{} not defined in dest pattern'.format(d)) + def _infer_types(self, rtl): + # type: (Rtl) -> None + """Assign type variables to all value variables used in `rtl`.""" + for d in rtl.rtl: + inst = d.expr.inst + + # Get the Var corresponding to the controlling type variable. + ctrl_var = None # type: Var + if inst.is_polymorphic: + if inst.use_typevar_operand: + # Should this be an assertion instead? + # Should all value operands be required to be Vars? + arg = d.expr.args[inst.format.typevar_operand] + if isinstance(arg, Var): + ctrl_var = arg + else: + ctrl_var = d.defs[inst.value_results[0]] + + # Reconcile arguments with the requirements of `inst`. + for opnum in inst.format.value_operands: + inst_tv = inst.ins[opnum].typevar + v = d.expr.args[opnum] + if isinstance(v, Var): + v.constrain_typevar(inst_tv, inst.ctrl_typevar, ctrl_var) + + # Reconcile results with the requirements of `inst`. + for resnum in inst.value_results: + inst_tv = inst.outs[resnum].typevar + v = d.defs[resnum] + v.constrain_typevar(inst_tv, inst.ctrl_typevar, ctrl_var) + + def _collect_typevars(self): + # type: () -> None + """ + Collect a list of variables whose type can be used to infer the types + of all expressions. + + This should be called after `_infer_types()` above has computed type + variables for all the used vars. + """ + fvars = list(v for v in self.inputs if v.has_free_typevar()) + fvars += list(v for v in self.defs if v.has_free_typevar()) + self.free_typevars = fvars + + # When substituting a pattern, we know the types of all variables that + # appear on the source side: inut, output, and intermediate values. + # However, temporary values which appear only on the destination side + # must have their type computed somehow. + # + # Some variables have a fixed type which appears as a type variable + # with a singleton_type field set. That's allowed for temps too. + for v in fvars: + if v.is_temp() and not v.typevar.singleton_type: + raise AssertionError( + "Cannot determine type of temp '{}' in xform:\n{}" + .format(v, self)) + class XFormGroup(object): """ From 5c9a12f101e9d6288bcc2576172cebaefd7a0f9f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 10 Nov 2016 14:46:42 -0800 Subject: [PATCH 442/968] Add TypeVar.rust_expr(). Generate a Rust expression that computes the value of a derived type variable. --- lib/cretonne/meta/cdsl/ast.py | 10 +++++ lib/cretonne/meta/cdsl/test_typevar.py | 8 +++- lib/cretonne/meta/cdsl/typevar.py | 56 +++++++++++++++++--------- lib/cretonne/meta/gen_instr.py | 3 +- 4 files changed, 55 insertions(+), 22 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index d8b3b7b875..8db7352333 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -197,6 +197,16 @@ class Var(Expr): return False return self.typevar is self.original_typevar + def rust_type(self): + # type: () -> str + """ + Get a Rust expression that computes the type of this variable. + + It is assumed that local variables exist corresponding to the free type + variables. + """ + return self.typevar.rust_expr() + def constrain_typevar(self, sym_typevar, sym_ctrl, ctrl_var): # type: (TypeVar, TypeVar, Var) -> None """ diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index 7dae18221a..29db26f583 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -57,10 +57,14 @@ class TestTypeVar(TestCase): x2 = TypeVar('x2', 'i16 and up', ints=(16, 64)) with self.assertRaises(AssertionError): x2.double_width() - self.assertEqual(str(x2.half_width()), '`HalfWidth(x2)`') + self.assertEqual(str(x2.half_width()), '`half_width(x2)`') + self.assertEqual(x2.half_width().rust_expr(), 'x2.half_width()') + self.assertEqual( + x2.half_width().double_width().rust_expr(), + 'x2.half_width().double_width()') x3 = TypeVar('x3', 'up to i32', ints=(8, 32)) - self.assertEqual(str(x3.double_width()), '`DoubleWidth(x3)`') + self.assertEqual(str(x3.double_width()), '`double_width(x3)`') with self.assertRaises(AssertionError): x3.half_width() diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 30986c5db8..d14866cf77 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -312,11 +312,14 @@ class TypeVar(object): return self is other # Supported functions for derived type variables. - SAMEAS = 'SameAs' - LANEOF = 'LaneOf' - ASBOOL = 'AsBool' - HALFWIDTH = 'HalfWidth' - DOUBLEWIDTH = 'DoubleWidth' + # The names here must match the method names on `ir::types::Type`. + # The camel_case of the names must match `enum OperandConstraint` in + # `instructions.rs`. + SAMEAS = 'same_as' + LANEOF = 'lane_of' + ASBOOL = 'as_bool' + HALFWIDTH = 'half_width' + DOUBLEWIDTH = 'double_width' @staticmethod def derived(base, derived_func): @@ -370,13 +373,14 @@ class TypeVar(object): Return a derived type variable that has the same number of vector lanes as this one, but the lanes are half the width. """ - ts = self.type_set - if ts.min_int: - assert ts.min_int > 8, "Can't halve all integer types" - if ts.min_float: - assert ts.min_float > 32, "Can't halve all float types" - if ts.min_bool: - assert ts.min_bool > 8, "Can't halve all boolean types" + if not self.is_derived: + ts = self.type_set + if ts.min_int: + assert ts.min_int > 8, "Can't halve all integer types" + if ts.min_float: + assert ts.min_float > 32, "Can't halve all float types" + if ts.min_bool: + assert ts.min_bool > 8, "Can't halve all boolean types" return TypeVar.derived(self, self.HALFWIDTH) @@ -386,13 +390,14 @@ class TypeVar(object): Return a derived type variable that has the same number of vector lanes as this one, but the lanes are double the width. """ - ts = self.type_set - if ts.max_int: - assert ts.max_int < MAX_BITS, "Can't double all integer types." - if ts.max_float: - assert ts.max_float < MAX_BITS, "Can't double all float types." - if ts.max_bool: - assert ts.max_bool < MAX_BITS, "Can't double all boolean types." + if not self.is_derived: + ts = self.type_set + if ts.max_int: + assert ts.max_int < MAX_BITS, "Can't double all integer types." + if ts.max_float: + assert ts.max_float < MAX_BITS, "Can't double all float types." + if ts.max_bool: + assert ts.max_bool < MAX_BITS, "Can't double all bool types." return TypeVar.derived(self, self.DOUBLEWIDTH) @@ -409,6 +414,19 @@ class TypeVar(object): else: return self + def rust_expr(self): + # type: () -> str + """ + Get a Rust expression that computes the type of this type variable. + """ + if self.is_derived: + return '{}.{}()'.format( + self.base.rust_expr(), self.derived_func) + elif self.singleton_type: + return self.singleton_type.rust_name() + else: + return self.name + def constrain_types(self, other): # type: (TypeVar) -> None """ diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 4f240ab6b1..11aaa4fa0d 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -5,6 +5,7 @@ from __future__ import absolute_import import srcgen import constant_hash from unique_table import UniqueTable, UniqueSeqTable +from cdsl import camel_case from cdsl.operands import ImmediateKind import cdsl.types from cdsl.formats import InstructionFormat @@ -330,7 +331,7 @@ def get_constraint(op, ctrl_typevar, type_sets): if tv.is_derived: assert tv.base is ctrl_typevar, "Not derived from ctrl_typevar" - return tv.derived_func + return camel_case(tv.derived_func) assert tv is ctrl_typevar return 'Same' From a7c58b817c503972d7a4b6d72d9d2169b87b7230 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 10 Nov 2016 15:33:16 -0800 Subject: [PATCH 443/968] Emit type arguments to builder methods that need it. Use the inferred type variables to construct a type argument for builder methods. This is for those instructions where the result types cannot be computed from the result types. --- lib/cretonne/meta/cdsl/ast.py | 12 +++++++++--- lib/cretonne/meta/gen_legalizer.py | 14 +++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 8db7352333..ddcc702575 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -9,7 +9,7 @@ from . import instructions from .typevar import TypeVar try: - from typing import Union, Tuple # noqa + from typing import Union, Tuple, Sequence # noqa except ImportError: pass @@ -389,12 +389,18 @@ class Apply(Expr): args = ', '.join(map(str, self.args)) return '{}({})'.format(self.instname(), args) - def rust_builder(self): - # type: () -> str + def rust_builder(self, defs=None): + # type: (Sequence[Var]) -> str """ Return a Rust Builder method call for instantiating this instruction application. + + The `defs` argument should be a list of variables defined by this + instruction. It is used to construct a result type if necessary. """ args = ', '.join(map(str, self.args)) + # Do we need to pass an explicit type argument? + if self.inst.is_polymorphic and not self.inst.use_typevar_operand: + args = defs[0].rust_type() + ', ' + args method = self.inst.snake_name() return '{}({})'.format(method, args) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 409ebb7cf2..983871ac40 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -10,6 +10,7 @@ the input instruction. from __future__ import absolute_import from srcgen import Formatter from base import legalize +from cdsl.ast import Var try: from typing import Sequence # noqa @@ -72,6 +73,12 @@ def unwrap_inst(iref, node, fmt): fmt.outdented_line('} else {') fmt.line('unreachable!("bad instruction format")') + # Get the types of any variables where it is needed. + for i in iform.value_operands: + v = expr.args[i] + if isinstance(v, Var) and v.has_free_typevar(): + fmt.line('let typeof_{0} = dfg.value_type({0});'.format(v)) + # If the node has multiple results, detach the values. # Place the secondary values in 'src_{}' locals. if len(node.defs) > 1: @@ -91,6 +98,11 @@ def unwrap_inst(iref, node, fmt): for d in node.defs[1:]: fmt.line('src_{} = vals.next().unwrap();'.format(d)) fmt.line('assert_eq!(vals.next(), None);') + for d in node.defs[1:]: + if d.has_free_typevar(): + fmt.line( + 'let typeof_{0} = dfg.value_type(src_{0});' + .format(d)) def wrap_tup(seq): @@ -128,7 +140,7 @@ def emit_dst_inst(node, fmt): builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) fixup_first_result = node.defs[0].is_output() - fmt.line('{}.{};'.format(builder, node.expr.rust_builder())) + fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs))) # If we just replaced an instruction, we need to bump the cursor so # following instructions are inserted *after* the replaced insruction. From d76280afba31afc5571a9e7a9fb4eda885ddeb93 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 7 Nov 2016 13:05:37 -0800 Subject: [PATCH 444/968] Add expansion patterns for large immediates. Expand foo_imm into iconst + foo. --- filetests/isa/riscv/expand-i32.cton | 11 +++++++++++ lib/cretonne/meta/base/legalize.py | 12 ++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index c0ca7be56f..166b058eb2 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -19,3 +19,14 @@ ebb0(v1: i32, v2: i32): ; It's possible the legalizer will rewrite these value aliases in the future. ; check: $v4 -> $cout ; check: return $v3, $v4 + +; Expanding illegal immediate constants. +; Note that at some point we'll probably expand the iconst as well. +function large_imm(i32) -> i32 { +ebb0(v0: i32): + v1 = iadd_imm v0, 1000000000 + return v1 +} +; check: $(cst=$V) = iconst.i32 0x3b9a_ca00 +; check: $v1 = iadd $v0, $cst +; check: return $v1 diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index f054d16d9e..b2f8fc4f6d 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -7,10 +7,10 @@ patterns that describe how base instructions can be transformed to other base instructions that are legal. """ from __future__ import absolute_import -from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry +from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm from .instructions import isub, isub_bin, isub_bout, isub_borrow from .instructions import band, bor, bxor, isplit_lohi, iconcat_lohi -from .instructions import icmp +from .instructions import icmp, iconst from cdsl.ast import Var from cdsl.xform import Rtl, XFormGroup @@ -127,3 +127,11 @@ expand.legalize( (a, b2) << isub_bout(a1, b_in), b << bor(b1, b2) )) + +# Expansions for immediates that are too large. +expand.legalize( + a << iadd_imm(x, y), + Rtl( + a1 << iconst(y), + a << iadd(x, a1) + )) From 856b8c99aaa3196fcaa544a7bd0a4e45924305c4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Nov 2016 11:17:40 -0800 Subject: [PATCH 445/968] Use uppercase for the global riscv.ISA constant. --- lib/cretonne/meta/isa/__init__.py | 2 +- lib/cretonne/meta/isa/riscv/__init__.py | 2 +- lib/cretonne/meta/isa/riscv/defs.py | 6 +++--- lib/cretonne/meta/isa/riscv/settings.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py index 1bef425c58..33b787f494 100644 --- a/lib/cretonne/meta/isa/__init__.py +++ b/lib/cretonne/meta/isa/__init__.py @@ -16,4 +16,4 @@ def all_isas(): Get a list of all the supported target ISAs. Each target ISA is represented as a :py:class:`cretonne.TargetISA` instance. """ - return [riscv.isa] + return [riscv.ISA] diff --git a/lib/cretonne/meta/isa/riscv/__init__.py b/lib/cretonne/meta/isa/riscv/__init__.py index e187a95c0e..3d5d19ed99 100644 --- a/lib/cretonne/meta/isa/riscv/__init__.py +++ b/lib/cretonne/meta/isa/riscv/__init__.py @@ -29,4 +29,4 @@ from . import defs from . import encodings, settings # noqa # Re-export the primary target ISA definition. -isa = defs.isa.finish() +ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/riscv/defs.py b/lib/cretonne/meta/isa/riscv/defs.py index 7a487482d3..485dbd7ef0 100644 --- a/lib/cretonne/meta/isa/riscv/defs.py +++ b/lib/cretonne/meta/isa/riscv/defs.py @@ -7,8 +7,8 @@ from __future__ import absolute_import from cdsl.isa import TargetISA, CPUMode import base.instructions -isa = TargetISA('riscv', [base.instructions.GROUP]) +ISA = TargetISA('riscv', [base.instructions.GROUP]) # CPU modes for 32-bit and 64-bit operation. -RV32 = CPUMode('RV32', isa) -RV64 = CPUMode('RV64', isa) +RV32 = CPUMode('RV32', ISA) +RV64 = CPUMode('RV64', ISA) diff --git a/lib/cretonne/meta/isa/riscv/settings.py b/lib/cretonne/meta/isa/riscv/settings.py index 7571bf1611..e6fe230f89 100644 --- a/lib/cretonne/meta/isa/riscv/settings.py +++ b/lib/cretonne/meta/isa/riscv/settings.py @@ -5,9 +5,9 @@ from __future__ import absolute_import from cdsl.settings import SettingGroup, BoolSetting from cdsl.predicates import And import base.settings as shared -from .defs import isa +from .defs import ISA -isa.settings = SettingGroup('riscv', parent=shared.group) +ISA.settings = SettingGroup('riscv', parent=shared.group) supports_m = BoolSetting("CPU supports the 'M' extension (mul/div)") supports_a = BoolSetting("CPU supports the 'A' extension (atomics)") @@ -25,4 +25,4 @@ use_d = And(supports_d, shared.enable_float) full_float = And(shared.enable_simd, supports_f, supports_d) -isa.settings.close(globals()) +ISA.settings.close(globals()) From 77c672a2793afd91c8407a8e1d40c57b7657596d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Nov 2016 11:27:29 -0800 Subject: [PATCH 446/968] Add stubs for Intel and ARM architectures. The Intel ISA handles both 32-bit and 64-bit code. ARM is split into separate arm32 and arm64 ISAs since the architectures have little in common in instruction encodings and register files. --- lib/cretonne/meta/isa/__init__.py | 4 ++-- lib/cretonne/meta/isa/arm32/__init__.py | 13 +++++++++++++ lib/cretonne/meta/isa/arm32/defs.py | 14 ++++++++++++++ lib/cretonne/meta/isa/arm64/__init__.py | 12 ++++++++++++ lib/cretonne/meta/isa/arm64/defs.py | 11 +++++++++++ lib/cretonne/meta/isa/intel/__init__.py | 22 ++++++++++++++++++++++ lib/cretonne/meta/isa/intel/defs.py | 14 ++++++++++++++ 7 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 lib/cretonne/meta/isa/arm32/__init__.py create mode 100644 lib/cretonne/meta/isa/arm32/defs.py create mode 100644 lib/cretonne/meta/isa/arm64/__init__.py create mode 100644 lib/cretonne/meta/isa/arm64/defs.py create mode 100644 lib/cretonne/meta/isa/intel/__init__.py create mode 100644 lib/cretonne/meta/isa/intel/defs.py diff --git a/lib/cretonne/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py index 33b787f494..8f623d18bb 100644 --- a/lib/cretonne/meta/isa/__init__.py +++ b/lib/cretonne/meta/isa/__init__.py @@ -7,7 +7,7 @@ architecture supported by Cretonne. """ from __future__ import absolute_import from cdsl.isa import TargetISA # noqa -from . import riscv +from . import riscv, intel, arm32, arm64 def all_isas(): @@ -16,4 +16,4 @@ def all_isas(): Get a list of all the supported target ISAs. Each target ISA is represented as a :py:class:`cretonne.TargetISA` instance. """ - return [riscv.ISA] + return [riscv.ISA, intel.ISA, arm32.ISA, arm64.ISA] diff --git a/lib/cretonne/meta/isa/arm32/__init__.py b/lib/cretonne/meta/isa/arm32/__init__.py new file mode 100644 index 0000000000..9c992d0c17 --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/__init__.py @@ -0,0 +1,13 @@ +""" +ARM 32-bit Architecture +---------------------- + +This target ISA generates code for ARMv7 and ARMv8 CPUs in 32-bit mode +(AArch32). We support both ARM and Thumb2 instruction encodings. +""" + +from __future__ import absolute_import +from . import defs + +# Re-export the primary target ISA definition. +ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/arm32/defs.py b/lib/cretonne/meta/isa/arm32/defs.py new file mode 100644 index 0000000000..f90abc2001 --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/defs.py @@ -0,0 +1,14 @@ +""" +ARM 32-bit definitions. + +Commonly used definitions. +""" +from __future__ import absolute_import +from cdsl.isa import TargetISA, CPUMode +import base.instructions + +ISA = TargetISA('arm32', [base.instructions.GROUP]) + +# CPU modes for 32-bit ARM and Thumb2. +A32 = CPUMode('A32', ISA) +T32 = CPUMode('T32', ISA) diff --git a/lib/cretonne/meta/isa/arm64/__init__.py b/lib/cretonne/meta/isa/arm64/__init__.py new file mode 100644 index 0000000000..198fc338b1 --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/__init__.py @@ -0,0 +1,12 @@ +""" +ARM 64-bit Architecture +----------------------- + +ARMv8 CPUs running the Aarch64 architecture. +""" + +from __future__ import absolute_import +from . import defs + +# Re-export the primary target ISA definition. +ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/arm64/defs.py b/lib/cretonne/meta/isa/arm64/defs.py new file mode 100644 index 0000000000..e493b4424a --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/defs.py @@ -0,0 +1,11 @@ +""" +ARM64 definitions. + +Commonly used definitions. +""" +from __future__ import absolute_import +from cdsl.isa import TargetISA, CPUMode +import base.instructions + +ISA = TargetISA('arm64', [base.instructions.GROUP]) +A64 = CPUMode('A64', ISA) diff --git a/lib/cretonne/meta/isa/intel/__init__.py b/lib/cretonne/meta/isa/intel/__init__.py new file mode 100644 index 0000000000..a97b424099 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/__init__.py @@ -0,0 +1,22 @@ +""" +Intel Target Architecture +------------------------- + +This target ISA generates code for Intel CPUs with two separate CPU modes: + +`I32` + IA-32 architecture, also known as 'x86'. Generates code for the Intel 386 + and later processors in 32-bit mode. +`I64` + Intel 64 architecture, also known as 'x86-64, 'x64', and 'amd64'. Intel and + AMD CPUs running in 64-bit mode. + +Floating point is supported only on CPUs with support for SSE2 or later. There +is no x87 floating point support. +""" + +from __future__ import absolute_import +from . import defs + +# Re-export the primary target ISA definition. +ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/intel/defs.py b/lib/cretonne/meta/isa/intel/defs.py new file mode 100644 index 0000000000..50251b44af --- /dev/null +++ b/lib/cretonne/meta/isa/intel/defs.py @@ -0,0 +1,14 @@ +""" +Intel definitions. + +Commonly used definitions. +""" +from __future__ import absolute_import +from cdsl.isa import TargetISA, CPUMode +import base.instructions + +ISA = TargetISA('intel', [base.instructions.GROUP]) + +# CPU modes for 32-bit and 64-bit operation. +I32 = CPUMode('I32', ISA) +I64 = CPUMode('I64', ISA) From c20d7d8f138ac184e3c82f910d6809a0e6b42bd2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Nov 2016 13:04:30 -0800 Subject: [PATCH 447/968] Move some utility functions into the cdsl package. - is_power_of_two - next_power_of_two Make sure we run doctests on these functions. --- lib/cretonne/meta/cdsl/__init__.py | 44 +++++++++++++++++++++++++- lib/cretonne/meta/cdsl/test_package.py | 8 +++++ lib/cretonne/meta/cdsl/typevar.py | 7 +--- lib/cretonne/meta/constant_hash.py | 22 +------------ 4 files changed, 53 insertions(+), 28 deletions(-) create mode 100644 lib/cretonne/meta/cdsl/test_package.py diff --git a/lib/cretonne/meta/cdsl/__init__.py b/lib/cretonne/meta/cdsl/__init__.py index 247b970675..44445268ea 100644 --- a/lib/cretonne/meta/cdsl/__init__.py +++ b/lib/cretonne/meta/cdsl/__init__.py @@ -13,5 +13,47 @@ camel_re = re.compile('(^|_)([a-z])') def camel_case(s): # type: (str) -> str - """Convert the string s to CamelCase""" + """Convert the string s to CamelCase: + >>> camel_case('x') + 'X' + >>> camel_case('camel_case') + 'CamelCase' + """ return camel_re.sub(lambda m: m.group(2).upper(), s) + + +def is_power_of_two(x): + # type: (int) -> bool + """Check if `x` is a power of two: + >>> is_power_of_two(0) + False + >>> is_power_of_two(1) + True + >>> is_power_of_two(2) + True + >>> is_power_of_two(3) + False + """ + return x > 0 and x & (x-1) == 0 + + +def next_power_of_two(x): + # type: (int) -> int + """ + Compute the next power of two that is greater than `x`: + >>> next_power_of_two(0) + 1 + >>> next_power_of_two(1) + 2 + >>> next_power_of_two(2) + 4 + >>> next_power_of_two(3) + 4 + >>> next_power_of_two(4) + 8 + """ + s = 1 + while x & (x + 1) != 0: + x |= x >> s + s *= 2 + return x + 1 diff --git a/lib/cretonne/meta/cdsl/test_package.py b/lib/cretonne/meta/cdsl/test_package.py new file mode 100644 index 0000000000..b66d60d694 --- /dev/null +++ b/lib/cretonne/meta/cdsl/test_package.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import +import doctest +import cdsl + + +def load_tests(loader, tests, ignore): + tests.addTests(doctest.DocTestSuite(cdsl)) + return tests diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index d14866cf77..580d367f51 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -6,7 +6,7 @@ polymorphic by using type variables. """ from __future__ import absolute_import import math -from . import types +from . import types, is_power_of_two try: from typing import Tuple, Union # noqa @@ -20,11 +20,6 @@ MAX_LANES = 256 MAX_BITS = 64 -def is_power_of_two(x): - # type: (int) -> bool - return x > 0 and x & (x-1) == 0 - - def int_log2(x): # type: (int) -> int return int(math.log(x, 2)) diff --git a/lib/cretonne/meta/constant_hash.py b/lib/cretonne/meta/constant_hash.py index 38911278ed..8c3c355aa1 100644 --- a/lib/cretonne/meta/constant_hash.py +++ b/lib/cretonne/meta/constant_hash.py @@ -6,6 +6,7 @@ don't attempt parfect hashing, but simply generate an open addressed quadratically probed hash table. """ from __future__ import absolute_import +from cdsl import next_power_of_two def simple_hash(s): @@ -24,27 +25,6 @@ def simple_hash(s): return h -def next_power_of_two(x): - """ - Compute the next power of two that is greater than `x`: - >>> next_power_of_two(0) - 1 - >>> next_power_of_two(1) - 2 - >>> next_power_of_two(2) - 4 - >>> next_power_of_two(3) - 4 - >>> next_power_of_two(4) - 8 - """ - s = 1 - while x & (x + 1) != 0: - x |= x >> s - s *= 2 - return x + 1 - - def compute_quadratic(items, hash_function): """ Compute an open addressed, quadratically probed hash table containing From b0b6a8f6935900a34be62f830815709de1c33e66 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Nov 2016 14:17:10 -0800 Subject: [PATCH 448/968] Define register banks. Add a RegBank class for describing CPU register banks. Define register banks for all the ISA stubs. The ARM32 floating point bank in particular requires attention. --- lib/cretonne/meta/cdsl/isa.py | 2 + lib/cretonne/meta/cdsl/registers.py | 80 ++++++++++++++++++++++++ lib/cretonne/meta/isa/arm32/registers.py | 29 +++++++++ lib/cretonne/meta/isa/arm64/registers.py | 19 ++++++ lib/cretonne/meta/isa/intel/registers.py | 39 ++++++++++++ lib/cretonne/meta/isa/riscv/registers.py | 18 ++++++ 6 files changed, 187 insertions(+) create mode 100644 lib/cretonne/meta/cdsl/registers.py create mode 100644 lib/cretonne/meta/isa/arm32/registers.py create mode 100644 lib/cretonne/meta/isa/arm64/registers.py create mode 100644 lib/cretonne/meta/isa/intel/registers.py create mode 100644 lib/cretonne/meta/isa/riscv/registers.py diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 7f59a971db..6c324603a5 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -10,6 +10,7 @@ try: from .predicates import Predicate, FieldPredicate # noqa from .settings import SettingGroup # noqa from .types import ValueType # noqa + from .registers import RegBank # noqa AnyPredicate = Union[Predicate, FieldPredicate] except ImportError: pass @@ -32,6 +33,7 @@ class TargetISA(object): self.settings = None # type: SettingGroup self.instruction_groups = instruction_groups self.cpumodes = list() # type: List[CPUMode] + self.regbanks = list() # type: List[RegBank] def finish(self): # type: () -> TargetISA diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py new file mode 100644 index 0000000000..6c1d379dac --- /dev/null +++ b/lib/cretonne/meta/cdsl/registers.py @@ -0,0 +1,80 @@ +""" +Register set definitions +------------------------ + +Each ISA defines a separate register set that is used by the register allocator +and the final binary encoding of machine code. + +The CPU registers are first divided into disjoint register banks, represented +by a `RegBank` instance. Registers in different register banks never interfere +with each other. A typical CPU will have a general purpose and a floating point +register bank. + +A register bank consists of a number of *register units* which are the smallest +indivisible units of allocation and interference. A register unit doesn't +necesarily correspond to a particular number of bits in a register, it is more +like a placeholder that can be used to determine of a register is taken or not. + +The register allocator works with *register classes* which can allocate one or +more register units at a time. A register class allocates more than one +register unit at a time when its registers are composed of smaller alocatable +units. For example, the ARM double precision floating point registers are +composed of two single precision registers. """ +from __future__ import absolute_import +from . import is_power_of_two, next_power_of_two + + +try: + from typing import Sequence # noqa + from .isa import TargetISA # noqa +except ImportError: + pass + + +class RegBank(object): + """ + A register bank belonging to an ISA. + + A register bank controls a set of *register units* disjoint from all the + other register banks in the ISA. The register units are numbered uniquely + within the target ISA, and the units in a register bank form a contiguous + sequence starting from a sufficiently aligned point that their low bits can + be used directly when encoding machine code instructions. + + Register units can be given generated names like `r0`, `r1`, ..., or a + tuple of special register unit names can be provided. + + :param name: Name of this register bank. + :param doc: Documentation string. + :param units: Number of register units. + :param prefix: Prefix for generated unit names. + :param names: Special names for the first units. May be shorter than + `units`, the remaining units are named using `prefix`. + """ + + def __init__(self, name, isa, doc, units, prefix='p', names=()): + # type: (str, TargetISA, str, int, str, Sequence[str]) -> None + self.name = name + self.isa = isa + self.first_unit = 0 + self.units = units + self.prefix = prefix + self.names = names + + assert len(names) <= units + + if isa.regbanks: + # Get the next free unit number. + last = isa.regbanks[-1] + u = last.first_unit + last.units + align = units + if not is_power_of_two(align): + align = next_power_of_two(align) + self.first_unit = (u + align - 1) & -align + + isa.regbanks.append(self) + + def __repr__(self): + # type: () -> str + return ('RegBank({}, units={}, first_unit={})' + .format(self.name, self.units, self.first_unit)) diff --git a/lib/cretonne/meta/isa/arm32/registers.py b/lib/cretonne/meta/isa/arm32/registers.py new file mode 100644 index 0000000000..8d2b0480ae --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/registers.py @@ -0,0 +1,29 @@ +""" +ARM32 register banks. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank +from .defs import ISA + + +# Special register units: +# - r15 is the program counter. +# - r14 is the link register. +# - r13 is usually the stack pointer. +IntRegs = RegBank( + 'IntRegs', ISA, + 'General purpose registers', + units=16, prefix='r') + +FloatRegs = RegBank( + 'FloatRegs', ISA, r""" + Floating point registers. + + The floating point register units correspond to the S-registers, but + extended as if there were 64 registers. + + - S registers are one unit each. + - D registers are two units each, even D16 and above. + - Q registers are 4 units each. + """, + units=64, prefix='s') diff --git a/lib/cretonne/meta/isa/arm64/registers.py b/lib/cretonne/meta/isa/arm64/registers.py new file mode 100644 index 0000000000..fa1c87120b --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/registers.py @@ -0,0 +1,19 @@ +""" +Aarch64 register banks. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank +from .defs import ISA + + +# The `x31` regunit serves as the stack pointer / zero register depending on +# context. We reserve it and don't model the difference. +IntRegs = RegBank( + 'IntRegs', ISA, + 'General purpose registers', + units=32, prefix='x') + +FloatRegs = RegBank( + 'FloatRegs', ISA, + 'Floating point registers', + units=32, prefix='v') diff --git a/lib/cretonne/meta/isa/intel/registers.py b/lib/cretonne/meta/isa/intel/registers.py new file mode 100644 index 0000000000..abd6353eb1 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/registers.py @@ -0,0 +1,39 @@ +""" +Intel register banks. + +While the floating-point registers are straight-forward, the general purpose +register bank has a few quirks on Intel architectures. We have these encodings +of the 8-bit registers: + + I32 I64 | 16b 32b 64b + 000 AL AL | AX EAX RAX + 001 CL CL | CX ECX RCX + 010 DL DL | DX EDX RDX + 011 BL BL | BX EBX RBX + 100 AH SPL | SP ESP RSP + 101 CH BPL | BP EBP RBP + 110 DH SIL | SI ESI RSI + 111 BH DIL | DI EDI RDI + +Here, the I64 column refers to the registers you get with a REX prefix. Without +the REX prefix, you get the I32 registers. + +The 8-bit registers are not that useful since WebAssembly only has i32 and i64 +data types, and the H-registers even less so. Rather than trying to model the +H-registers accurately, we'll avoid using them in both I32 and I64 modes. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank +from .defs import ISA + + +IntRegs = RegBank( + 'IntRegs', ISA, + 'General purpose registers', + units=16, prefix='r', + names='rax rcx rdx rbx rsp rbp rsi rdi'.split()) + +FloatRegs = RegBank( + 'FloatRegs', ISA, + 'SSE floating point registers', + units=16, prefix='xmm') diff --git a/lib/cretonne/meta/isa/riscv/registers.py b/lib/cretonne/meta/isa/riscv/registers.py new file mode 100644 index 0000000000..9d1d0eaadb --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/registers.py @@ -0,0 +1,18 @@ +""" +RISC-V register banks. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank +from .defs import ISA + + +# We include `x0`, a.k.a `zero` in the register bank. It will be reserved. +IntRegs = RegBank( + 'IntRegs', ISA, + 'General purpose registers', + units=32, prefix='x') + +FloatRegs = RegBank( + 'FloatRegs', ISA, + 'Floating point registers', + units=32, prefix='f') From 4192ba0532cfdd0aeea7fe8602493e57f148f606 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 11 Nov 2016 15:08:12 -0800 Subject: [PATCH 449/968] Define register classes for 4 ISAs. --- lib/cretonne/meta/cdsl/registers.py | 53 +++++++++++++++++++++++- lib/cretonne/meta/isa/arm32/registers.py | 7 +++- lib/cretonne/meta/isa/arm64/registers.py | 5 ++- lib/cretonne/meta/isa/intel/registers.py | 5 ++- lib/cretonne/meta/isa/riscv/registers.py | 5 ++- 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 6c1d379dac..243a72e669 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -19,7 +19,8 @@ The register allocator works with *register classes* which can allocate one or more register units at a time. A register class allocates more than one register unit at a time when its registers are composed of smaller alocatable units. For example, the ARM double precision floating point registers are -composed of two single precision registers. """ +composed of two single precision registers. +""" from __future__ import absolute_import from . import is_power_of_two, next_power_of_two @@ -78,3 +79,53 @@ class RegBank(object): # type: () -> str return ('RegBank({}, units={}, first_unit={})' .format(self.name, self.units, self.first_unit)) + + +class RegClass(object): + """ + A register class is a subset of register units in a RegBank along with a + strategy for allocating registers. + + The *width* parameter determines how many register units are allocated at a + time. Usually it that is one, but for example the ARM D registers are + allocated two units at a time. When multiple units are allocated, it is + always a contiguous set of unit numbers. + + :param name: Name of this register class. + :param bank: The register bank we're allocating from. + :param count: The maximum number of allocations in this register class. By + default, the whole register bank can be allocated. + :param width: How many units to allocate at a time. + :param start: The first unit to allocate, relative to `bank.first.unit`. + :param stride: How many units to skip to get to the next allocation. + Default is `width`. + """ + + def __init__(self, name, bank, count=None, width=1, start=0, stride=None): + # type: (str, RegBank, int, int, int, int) -> None + self.name = name + self.bank = bank + self.start = start + self.width = width + + assert width > 0 + assert start >= 0 and start < bank.units + + if stride is None: + stride = width + assert stride > 0 + self.stride = stride + + if count is None: + count = bank.units / stride + self.count = count + + # When the stride is 1, we can wrap around to the beginning of the + # register bank, but with a larger stride, we wouldn't cover all the + # possible allocations with a simple modulo stride. For example, + # attempting to allocate the even registers before the odd ones + # wouldn't work. Only if stride is coprime to bank.units would it work, + # but that is unlikely since the bank size is almost always a power of + # two. + if start + count*stride > bank.units: + assert stride == 1, 'Wrapping with stride not supported' diff --git a/lib/cretonne/meta/isa/arm32/registers.py b/lib/cretonne/meta/isa/arm32/registers.py index 8d2b0480ae..3084c2bf98 100644 --- a/lib/cretonne/meta/isa/arm32/registers.py +++ b/lib/cretonne/meta/isa/arm32/registers.py @@ -2,7 +2,7 @@ ARM32 register banks. """ from __future__ import absolute_import -from cdsl.registers import RegBank +from cdsl.registers import RegBank, RegClass from .defs import ISA @@ -27,3 +27,8 @@ FloatRegs = RegBank( - Q registers are 4 units each. """, units=64, prefix='s') + +GPR = RegClass('GPR', IntRegs) +S = RegClass('S', FloatRegs, count=32) +D = RegClass('D', FloatRegs, width=2) +Q = RegClass('Q', FloatRegs, width=4) diff --git a/lib/cretonne/meta/isa/arm64/registers.py b/lib/cretonne/meta/isa/arm64/registers.py index fa1c87120b..fec3601367 100644 --- a/lib/cretonne/meta/isa/arm64/registers.py +++ b/lib/cretonne/meta/isa/arm64/registers.py @@ -2,7 +2,7 @@ Aarch64 register banks. """ from __future__ import absolute_import -from cdsl.registers import RegBank +from cdsl.registers import RegBank, RegClass from .defs import ISA @@ -17,3 +17,6 @@ FloatRegs = RegBank( 'FloatRegs', ISA, 'Floating point registers', units=32, prefix='v') + +GPR = RegClass('GPR', IntRegs) +FPR = RegClass('FPR', FloatRegs) diff --git a/lib/cretonne/meta/isa/intel/registers.py b/lib/cretonne/meta/isa/intel/registers.py index abd6353eb1..6a4c59403e 100644 --- a/lib/cretonne/meta/isa/intel/registers.py +++ b/lib/cretonne/meta/isa/intel/registers.py @@ -23,7 +23,7 @@ data types, and the H-registers even less so. Rather than trying to model the H-registers accurately, we'll avoid using them in both I32 and I64 modes. """ from __future__ import absolute_import -from cdsl.registers import RegBank +from cdsl.registers import RegBank, RegClass from .defs import ISA @@ -37,3 +37,6 @@ FloatRegs = RegBank( 'FloatRegs', ISA, 'SSE floating point registers', units=16, prefix='xmm') + +GPR = RegClass('GPR', IntRegs) +FPR = RegClass('FPR', FloatRegs) diff --git a/lib/cretonne/meta/isa/riscv/registers.py b/lib/cretonne/meta/isa/riscv/registers.py index 9d1d0eaadb..f00c9a0f0d 100644 --- a/lib/cretonne/meta/isa/riscv/registers.py +++ b/lib/cretonne/meta/isa/riscv/registers.py @@ -2,7 +2,7 @@ RISC-V register banks. """ from __future__ import absolute_import -from cdsl.registers import RegBank +from cdsl.registers import RegBank, RegClass from .defs import ISA @@ -16,3 +16,6 @@ FloatRegs = RegBank( 'FloatRegs', ISA, 'Floating point registers', units=32, prefix='f') + +GPR = RegClass('GPR', IntRegs) +FPR = RegClass('FPR', FloatRegs) From a8cd8bc18bf0da35d9bf2d25552b25cf5998a6ee Mon Sep 17 00:00:00 2001 From: Jacob Shaffer Date: Mon, 14 Nov 2016 08:45:10 -0500 Subject: [PATCH 450/968] Added link to README about nasal demons. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b98a4893fa..5656547e49 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ Cretonne is designed to be a code generator for WebAssembly with these design goals: No undefined behavior - Cretonne does not have a nasal demons clause, and it won't generate code + Cretonne does not have a `nasal demons clause `_, and it won't generate code with unexpected behavior if invariants are broken. Portable semantics As far as possible, Cretonne's input language has well-defined semantics From 977adddc0ac04f4cc0b56f9e6010bff46a3fbf14 Mon Sep 17 00:00:00 2001 From: ranma42 Date: Tue, 15 Nov 2016 15:35:00 +0100 Subject: [PATCH 451/968] Fix some typos in the Language Reference --- docs/langref.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index a9d5ac4c96..40cc65dbe3 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -913,8 +913,8 @@ Glossary through to the following instructions in the block, but only the first instruction in the EBB can be a branch target. - The last instrution in an EBB must be a :term:`terminator instruction`, - so execion cannot flow through to the next EBB in the function. (But + The last instruction in an EBB must be a :term:`terminator instruction`, + so execution cannot flow through to the next EBB in the function. (But there may be a branch to the next EBB.) Note that some textbooks define an EBB as a maximal *subtree* in the From 9cdccf66912ebf7db650f774bdaf017c923cce0f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 21 Nov 2016 13:49:15 -0800 Subject: [PATCH 452/968] Start a design document for the Cretonne register allocator. --- docs/index.rst | 1 + docs/regalloc.rst | 232 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 docs/regalloc.rst diff --git a/docs/index.rst b/docs/index.rst index 8df4042db3..301346a9e3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Contents: langref metaref testing + regalloc compare-llvm Indices and tables diff --git a/docs/regalloc.rst b/docs/regalloc.rst new file mode 100644 index 0000000000..18057c2511 --- /dev/null +++ b/docs/regalloc.rst @@ -0,0 +1,232 @@ +******************************* +Register Allocation in Cretonne +******************************* + +.. default-domain:: cton +.. highlight:: rust + +Cretonne uses a *decoupled, SSA-based* register allocator. Decoupled means that +register allocation is split into two primary phases: *spilling* and +*coloring*. SSA-based means that the code stays in SSA form throughout the +register allocator, and in fact is still in SSA form after register allocation. + +Before the register allocator is run, all instructions in the function must be +*legalized*, which means that every instruction has an entry in the +``encodings`` table. The encoding entries also provide register class +constraints on the instruction's operands that the register allocator must +satisfy. + +After the register allocator has run, the ``locations`` table provides a +register or stack slot location for all SSA values used by the function. The +register allocator may have inserted :inst:`spill`, :inst:`fill`, and +:inst:`copy` instructions to make that possible. + +SSA-based register allocation +============================= + +The phases of the SSA-based register allocator are: + +Liveness analysis + For each SSA value, determine exactly where it is live. + +Spilling + The process of deciding which SSA values go in a stack slot and which + values go in a register. The spilling phase can also split live ranges by + inserting :inst:`copy` instructions, or transform the code in other ways to + reduce the number of values kept in registers. + + After spilling, the number of live register values never exceeds the number + of available registers. + +Coloring + The process of assigning specific registers to the live values. It's a + property of SSA form that this can be done in a linear scan of the + dominator tree without causing any additional spills. + +EBB argument fixup + The coloring phase does not guarantee that EBB arguments are placed in the + correct registers and/or stack slots before jumping to the EBB. It will + try its best, but not making this guarantee is essential to the speed of + the coloring phase. (EBB arguments correspond to PHI nodes in traditional + SSA form). + + The argument fixup phase inserts 'shuffle code' before jumps and branches + to place the argument values in their expected locations. + +The contract between the spilling and coloring phases is that the number of +values in registers never exceeds the number of available registers. This +sounds simple enough in theory, but in pratice there are some complications. + +Real-world complications to SSA coloring +---------------------------------------- + +In practice, instruction set architectures don't have "K interchangable +registers", and register pressure can't be measured with a single number. There +are complications: + +Different register banks + Most ISAs separate integer registers from floating point registers, and + instructions require their operands to come from a specific bank. This is a + fairly simple problem to deal with since the register banks are completely + disjoint. We simply count the number of integer and floating-point values + that are live independently, and make sure that each number does not exceed + the size of their respective register banks. + +Instructions with fixed operands + Some instructions use a fixed register for an operand. This happens on the + Intel ISAs: + + - Dynamic shift and rotate instructions take the shift amount in CL. + - Division instructions use RAX and RDX for both input and output operands. + - Wide multiply instructions use fixed RAX and RDX registers for input and + output operands. + - A few SSE variable blend instructions use a hardwired XMM0 input operand. + +Operands constrained to register subclasses + Some instructions can only use a subset of the registers for some operands. + For example, the ARM NEON vmla (scalar) instruction requires the scalar + operand to be located in D0-15 or even D0-7, depending on the data type. + The other operands can be from the full D0-31 register set. + +ABI boundaries + Before making a function call, arguments must be placed in specific + registers and stack locations determined by the ABI, and return values + appear in fixed registers. + + Some registers can be clobbered by the call and some are saved by the + callee. In some cases, only the low bits of a register are saved by the + callee. For example, ARM64 callees save only the low 64 bits of v8-15, and + Win64 callees only save the low 128 bits of AVX registers. + + ABI boundaries also affect the location of arguments to the entry block and + return values passed to the :inst:`return` instruction. + +Aliasing registers + Different registers sometimes share the same bits in the register bank. + This can make it difficult to measure register pressure. For example, the + Intel registers RAX, EAX, AX, AL, and AH overlap. + + If only one of the aliasing registers can be used at a time, the aliasing + doesn't cause problems since the registers can simply be counted as one + unit. + +Early clobbers + Sometimes an instruction requires that the register used for an output + operand does not alias any of the input operands. This happens for inline + assembly and in some other special cases. + + +Liveness Analysis +================= + +Both spilling and coloring need to know exactly where SSA values are live. The +liveness analysis computes this information. + +The data structure representing the live range of a value uses the linear +layout of the function. All instructions and EBB headers are assigned a +*program position*. A starting point for a live range can be one of the +following: + +- The instruction where the value is defined. +- The EBB header where the value is an EBB argument. +- An EBB header where the value is live-in because it was defined in a + dominating block. + +The ending point of a live range can be: + +- The last instruction to use the value. +- A branch or jump to an EBB where the value is live-in. + +When all the EBBs in a function are laid out linearly, the live range of a +value doesn't have to be a contiguous interval, although it will be in a +majority of cases. There can be holes in the linear live range. + +The live range of an SSA value is represented as: + +- The earliest program point where the value is live. +- The latest program point where the value is live. +- A (often empty) list of holes, sorted in program order. + +Any value that is only used inside a single EBB will have a live range without +holes. Some values are live across large parts of the function, and this can +often be represented with very few holes. It is important that the live range +data structure doesn't have to grow linearly with the number of EBBs covered by +a live range. + +This representation is very similar to LLVM's ``LiveInterval`` data structure +with a few important differences: + +- The Cretonne ``LiveRange`` only covers a single SSA value, while LLVM's + ``LiveInterval`` represents the union of multiple related SSA values in a + virtual register. This makes Cretonne's representation smaller because + individual segments don't have to annotated with a value number. +- Cretonne stores the min and max program points separately from a list of + holes, while LLVM stores an array of segments. The two representations are + equivalent, but Cretonne optimizes for the common case of a single contiguous + interval. +- LLVM represents a program point as ``SlotIndex`` which holds a pointer to a + 32-byte ``IndexListEntry`` struct. The entries are organized in a double + linked list that mirrors the ordering of instructions in a basic block. This + allows 'tombstone' program points corresponding to instructions that have + been deleted. + + Cretonne uses a 32-bit program point representation that encodes an + instruction or EBB number directly. There are no 'tombstones' for deleted + instructions, and no mirrored linked list of instructions. Live ranges must + be updated when instructions are deleted. + +A consequence of Cretonne's more compact representation is that two program +points can't be compared without the context of a function layout. + + +Spilling algorithm +================== + +There is no one way of implementing spilling, and different tradeoffs between +compilation time and code quality are possible. Any spilling algorithm will +need a way of tracking the register pressure so the colorability condition can +be satisfied. + +Coloring algorithm +================== + +The SSA coloring algorithm is based on a single observation: If two SSA values +interfere, one of the values must be live where the other value is defined. + +We visit the EBBs in a topological order such that all dominating EBBs are +visited before the current EBB. The instructions in an EBB are visited in a +top-down order, and each value define by the instruction is assigned an +available register. With this iteration order, every value that is live at an +instruction has already been assigned to a register. + +This coloring algorith works if the following condition holds: + + At every instruction, consider the values live through the instruction. No + matter how the live values have been assigned to registers, there must be + available registers of the right register classes available for the values + defined by the instruction. + +We'll need to modify this condition in order to deal with the real-world +complications. + +The coloring algorithm needs to keep track of the set of live values at each +instruction. At the top of an EBB, this set can be computed as the union of: + +- The set of live values before the immediately dominating branch or jump + instruction. The topological iteration order guarantees that this set is + available. Values whose live range indicate that they are not live-in to the + current EBB should be filtered out. +- The set of arguments to the EBB. These values should all be live-in, although + it is possible that some are dead and never used anywhere. + +For each live value, we also track its kill point in the current EBB. This is +the last instruction to use the value in the EBB. Values that are live-out +through the EBB terminator don't have a kill point. Note that the kill point +can be a branch to another EBB that uses the value, so the kill instruction +doesn't have to be a use of the value. + +When advancing past an instruction, the live set is updated: + +- Any values whose kill point is the current instruction are removed. +- Any values defined by the instruction are added, unless their kill point is + the current instruction. This corresponds to a dead def which has no uses. From 1f6dd0dab7111d0fc0799829abf5bfaa5e3c1d38 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 22 Nov 2016 10:51:42 -0800 Subject: [PATCH 453/968] Generate register bank descriptions. Use the information in the ISA's registers.py files to generate a RegInfo Rust data structure. --- lib/cretonne/meta/build.py | 2 + lib/cretonne/meta/gen_registers.py | 51 ++++++++++ lib/cretonne/meta/isa/arm32/__init__.py | 1 + lib/cretonne/meta/isa/arm64/__init__.py | 1 + lib/cretonne/meta/isa/intel/__init__.py | 1 + lib/cretonne/meta/isa/riscv/__init__.py | 2 +- lib/cretonne/src/isa/mod.rs | 5 + lib/cretonne/src/isa/registers.rs | 126 ++++++++++++++++++++++++ lib/cretonne/src/isa/riscv/mod.rs | 7 +- lib/cretonne/src/isa/riscv/registers.py | 1 + lib/cretonne/src/isa/riscv/registers.rs | 37 +++++++ 11 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 lib/cretonne/meta/gen_registers.py create mode 100644 lib/cretonne/src/isa/registers.rs create mode 100644 lib/cretonne/src/isa/riscv/registers.py create mode 100644 lib/cretonne/src/isa/riscv/registers.rs diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index 51f8e7c056..dd7aeac5f9 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -11,6 +11,7 @@ import gen_settings import gen_build_deps import gen_encoding import gen_legalizer +import gen_registers parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') @@ -25,4 +26,5 @@ gen_instr.generate(isas, out_dir) gen_settings.generate(isas, out_dir) gen_encoding.generate(isas, out_dir) gen_legalizer.generate(isas, out_dir) +gen_registers.generate(isas, out_dir) gen_build_deps.generate() diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py new file mode 100644 index 0000000000..323aec6173 --- /dev/null +++ b/lib/cretonne/meta/gen_registers.py @@ -0,0 +1,51 @@ +""" +Generate register bank descriptions for each ISA. +""" + +from __future__ import absolute_import +import srcgen + +try: + from typing import Sequence # noqa + from cdsl.isa import TargetISA # noqa + from cdsl.registers import RegBank # noqa +except ImportError: + pass + + +def gen_regbank(regbank, fmt): + # type: (RegBank, srcgen.Formatter) -> None + """ + Emit a static data definition for regbank. + """ + with fmt.indented( + 'RegBank {{'.format(regbank.name), '},'): + fmt.line('name: "{}",'.format(regbank.name)) + fmt.line('first_unit: {},'.format(regbank.first_unit)) + fmt.line('units: {},'.format(regbank.units)) + fmt.line( + 'names: &[{}],' + .format(', '.join('"{}"'.format(n) for n in regbank.names))) + fmt.line('prefix: "{}",'.format(regbank.prefix)) + + +def gen_isa(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Generate register tables for isa. + """ + if not isa.regbanks: + print('cargo:warning={} has no register banks'.format(isa.name)) + with fmt.indented('pub static INFO: RegInfo = RegInfo {', '};'): + # Bank descriptors. + with fmt.indented('banks: &[', '],'): + for regbank in isa.regbanks: + gen_regbank(regbank, fmt) + + +def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None + for isa in isas: + fmt = srcgen.Formatter() + gen_isa(isa, fmt) + fmt.update_file('registers-{}.rs'.format(isa.name), out_dir) diff --git a/lib/cretonne/meta/isa/arm32/__init__.py b/lib/cretonne/meta/isa/arm32/__init__.py index 9c992d0c17..52f150b8e1 100644 --- a/lib/cretonne/meta/isa/arm32/__init__.py +++ b/lib/cretonne/meta/isa/arm32/__init__.py @@ -8,6 +8,7 @@ This target ISA generates code for ARMv7 and ARMv8 CPUs in 32-bit mode from __future__ import absolute_import from . import defs +from . import registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/arm64/__init__.py b/lib/cretonne/meta/isa/arm64/__init__.py index 198fc338b1..75f5e85f38 100644 --- a/lib/cretonne/meta/isa/arm64/__init__.py +++ b/lib/cretonne/meta/isa/arm64/__init__.py @@ -7,6 +7,7 @@ ARMv8 CPUs running the Aarch64 architecture. from __future__ import absolute_import from . import defs +from . import registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/intel/__init__.py b/lib/cretonne/meta/isa/intel/__init__.py index a97b424099..81948699d2 100644 --- a/lib/cretonne/meta/isa/intel/__init__.py +++ b/lib/cretonne/meta/isa/intel/__init__.py @@ -17,6 +17,7 @@ is no x87 floating point support. from __future__ import absolute_import from . import defs +from . import registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/riscv/__init__.py b/lib/cretonne/meta/isa/riscv/__init__.py index 3d5d19ed99..cf61cbdcf5 100644 --- a/lib/cretonne/meta/isa/riscv/__init__.py +++ b/lib/cretonne/meta/isa/riscv/__init__.py @@ -26,7 +26,7 @@ RV32G / RV64G """ from __future__ import absolute_import from . import defs -from . import encodings, settings # noqa +from . import encodings, settings, registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index aa7b9a76d8..b1b3a8a8df 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -41,12 +41,14 @@ //! concurrent function compilations. pub use isa::encoding::Encoding; +pub use isa::registers::{RegUnit, RegBank, RegInfo}; use settings; use ir::{InstructionData, DataFlowGraph}; pub mod riscv; mod encoding; mod enc_tables; +mod registers; /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. @@ -108,6 +110,9 @@ pub trait TargetIsa { /// Get the ISA-independent flags that were used to make this trait object. fn flags(&self) -> &settings::Flags; + /// Get a data structure describing the registers in this ISA. + fn register_info(&self) -> &RegInfo; + /// Encode an instruction after determining it is legal. /// /// If `inst` can legally be encoded in this ISA, produce the corresponding `Encoding` object. diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs new file mode 100644 index 0000000000..8736f06e84 --- /dev/null +++ b/lib/cretonne/src/isa/registers.rs @@ -0,0 +1,126 @@ +//! Data structures describing the registers in an ISA. + +use std::fmt; + +/// Register units are the smallest units of register allocation. +/// +/// Normally there is a 1-1 correspondence between registers and register units, but when an ISA +/// has aliasing registers, the aliasing can be modeled with registers that cover multiple +/// register units. +/// +/// The register allocator will enforce that each register unit only gets used for one thing. +pub type RegUnit = u16; + +/// The register units in a target ISA are divided into disjoint register banks. Each bank covers a +/// contiguous range of register units. +/// +/// The `RegBank` struct provides a static description of a register bank. +pub struct RegBank { + /// The name of this register bank as defined in the ISA's `registers.py` file. + pub name: &'static str, + + /// The first register unit in this bank. + pub first_unit: RegUnit, + + /// The total number of register units in this bank. + pub units: u16, + + /// Array of specially named register units. This array can be shorter than the number of units + /// in the bank. + pub names: &'static [&'static str], + + /// Name prefix to use for those register units in the bank not covered by the `names` array. + /// The remaining register units will be named this prefix followed by their decimal offset in + /// the bank. So with a prefix `r`, registers will be named `r8`, `r9`, ... + pub prefix: &'static str, +} + +impl RegBank { + /// Does this bank contain `regunit`? + fn contains(&self, regunit: RegUnit) -> bool { + regunit >= self.first_unit && regunit - self.first_unit < self.units + } + + /// Try to parse a regunit name. The name is not expected to begin with `%`. + fn parse_regunit(&self, name: &str) -> Option { + match self.names.iter().position(|&x| x == name) { + Some(offset) => { + // This is one of the special-cased names. + Some(offset as RegUnit) + } + None => { + // Try a regular prefixed name. + if name.starts_with(self.prefix) { + name[self.prefix.len()..].parse().ok() + } else { + None + } + } + } + .and_then(|offset| { + if offset < self.units { + Some(offset + self.first_unit) + } else { + None + } + }) + } + + /// Write `regunit` to `w`, assuming that it belongs to this bank. + /// All regunits are written with a `%` prefix. + fn write_regunit(&self, f: &mut fmt::Formatter, regunit: RegUnit) -> fmt::Result { + let offset = regunit - self.first_unit; + assert!(offset < self.units); + if (offset as usize) < self.names.len() { + write!(f, "%{}", self.names[offset as usize]) + } else { + write!(f, "%{}{}", self.prefix, offset) + } + } +} + +/// Information about the registers in an ISA. +/// +/// The `RegUnit` data structure collects all relevant static information about the registers in an +/// ISA. +pub struct RegInfo { + /// All register banks, ordered by their `first_unit`. The register banks are disjoint, but + /// there may be holes of unused register unit numbers between banks due to alignment. + pub banks: &'static [RegBank], +} + +impl RegInfo { + /// Get the register bank holding `regunit`. + pub fn bank_containing_regunit(&self, regunit: RegUnit) -> Option<&RegBank> { + // We could do a binary search, but most ISAs have only two register banks... + self.banks.iter().find(|b| b.contains(regunit)) + } + + /// Try to parse a regunit name. The name is not expected to begin with `%`. + pub fn parse_regunit(&self, name: &str) -> Option { + self.banks.iter().filter_map(|b| b.parse_regunit(name)).next() + } + + /// Make a temporary object that can display a register unit. + pub fn display_regunit(&self, regunit: RegUnit) -> DisplayRegUnit { + DisplayRegUnit { + regunit: regunit, + reginfo: self, + } + } +} + +/// Temporary object that holds enough information to print a register unit. +pub struct DisplayRegUnit<'a> { + pub regunit: RegUnit, + pub reginfo: &'a RegInfo, +} + +impl<'a> fmt::Display for DisplayRegUnit<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.reginfo.bank_containing_regunit(self.regunit) { + Some(b) => b.write_regunit(f, self.regunit), + None => write!(f, "%INVALID{}", self.regunit), + } + } +} diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 63ab5ccf89..762df2249c 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -2,11 +2,12 @@ pub mod settings; mod enc_tables; +mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -48,6 +49,10 @@ impl TargetIsa for Isa { &self.shared_flags } + fn register_info(&self) -> &RegInfo { + ®isters::INFO + } + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { lookup_enclist(inst.first_type(), inst.opcode(), diff --git a/lib/cretonne/src/isa/riscv/registers.py b/lib/cretonne/src/isa/riscv/registers.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/registers.py @@ -0,0 +1 @@ + diff --git a/lib/cretonne/src/isa/riscv/registers.rs b/lib/cretonne/src/isa/riscv/registers.rs new file mode 100644 index 0000000000..a3fe4739e8 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/registers.rs @@ -0,0 +1,37 @@ +//! RISC-V register descriptions. + +use isa::registers::{RegBank, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs")); + +#[cfg(test)] +mod tests { + use super::INFO; + use isa::RegUnit; + + #[test] + fn unit_encodings() { + assert_eq!(INFO.parse_regunit("x0"), Some(0)); + assert_eq!(INFO.parse_regunit("x31"), Some(31)); + assert_eq!(INFO.parse_regunit("f0"), Some(32)); + assert_eq!(INFO.parse_regunit("f31"), Some(63)); + + assert_eq!(INFO.parse_regunit("x32"), None); + assert_eq!(INFO.parse_regunit("f32"), None); + } + + #[test] + fn unit_names() { + fn uname(ru: RegUnit) -> String { + INFO.display_regunit(ru).to_string() + } + + assert_eq!(uname(0), "%x0"); + assert_eq!(uname(1), "%x1"); + assert_eq!(uname(31), "%x31"); + assert_eq!(uname(32), "%f0"); + assert_eq!(uname(33), "%f1"); + assert_eq!(uname(63), "%f31"); + assert_eq!(uname(64), "%INVALID64"); + } +} From 19ac05577c07cc2485b1ad0abe35c15100007557 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 23 Nov 2016 10:42:07 -0800 Subject: [PATCH 454/968] Fill in boilerplate for Intel and ARM targets. The intel, arm32, and arm32 targets were only defined in the meta language previously. Add Rust implementations too. This is mostly boilerplate, except for the unit tests in the registers.rs files. --- lib/cretonne/meta/base/settings.py | 2 + lib/cretonne/meta/gen_encoding.py | 2 + lib/cretonne/meta/gen_settings.py | 12 ++-- lib/cretonne/meta/isa/arm32/__init__.py | 2 +- lib/cretonne/meta/isa/arm32/registers.py | 19 +++--- lib/cretonne/meta/isa/arm32/settings.py | 11 ++++ lib/cretonne/meta/isa/arm64/__init__.py | 2 +- lib/cretonne/meta/isa/arm64/settings.py | 11 ++++ lib/cretonne/meta/isa/intel/__init__.py | 2 +- lib/cretonne/meta/isa/intel/settings.py | 11 ++++ lib/cretonne/src/isa/arm32/enc_tables.rs | 8 +++ lib/cretonne/src/isa/arm32/mod.rs | 73 ++++++++++++++++++++++++ lib/cretonne/src/isa/arm32/registers.rs | 32 +++++++++++ lib/cretonne/src/isa/arm32/settings.rs | 9 +++ lib/cretonne/src/isa/arm64/enc_tables.rs | 8 +++ lib/cretonne/src/isa/arm64/mod.rs | 66 +++++++++++++++++++++ lib/cretonne/src/isa/arm64/registers.rs | 37 ++++++++++++ lib/cretonne/src/isa/arm64/settings.rs | 9 +++ lib/cretonne/src/isa/intel/enc_tables.rs | 8 +++ lib/cretonne/src/isa/intel/mod.rs | 73 ++++++++++++++++++++++++ lib/cretonne/src/isa/intel/registers.rs | 49 ++++++++++++++++ lib/cretonne/src/isa/intel/settings.rs | 9 +++ lib/cretonne/src/isa/mod.rs | 18 ++++++ lib/cretonne/src/settings.rs | 1 + 24 files changed, 456 insertions(+), 18 deletions(-) create mode 100644 lib/cretonne/meta/isa/arm32/settings.py create mode 100644 lib/cretonne/meta/isa/arm64/settings.py create mode 100644 lib/cretonne/meta/isa/intel/settings.py create mode 100644 lib/cretonne/src/isa/arm32/enc_tables.rs create mode 100644 lib/cretonne/src/isa/arm32/mod.rs create mode 100644 lib/cretonne/src/isa/arm32/registers.rs create mode 100644 lib/cretonne/src/isa/arm32/settings.rs create mode 100644 lib/cretonne/src/isa/arm64/enc_tables.rs create mode 100644 lib/cretonne/src/isa/arm64/mod.rs create mode 100644 lib/cretonne/src/isa/arm64/registers.rs create mode 100644 lib/cretonne/src/isa/arm64/settings.rs create mode 100644 lib/cretonne/src/isa/intel/enc_tables.rs create mode 100644 lib/cretonne/src/isa/intel/mod.rs create mode 100644 lib/cretonne/src/isa/intel/registers.rs create mode 100644 lib/cretonne/src/isa/intel/settings.rs diff --git a/lib/cretonne/meta/base/settings.py b/lib/cretonne/meta/base/settings.py index c6f87f7fbb..1f7e21668b 100644 --- a/lib/cretonne/meta/base/settings.py +++ b/lib/cretonne/meta/base/settings.py @@ -20,6 +20,8 @@ opt_level = EnumSetting( is_64bit = BoolSetting("Enable 64-bit code generation") +is_compressed = BoolSetting("Enable compressed instructions") + enable_float = BoolSetting( """Enable the use of floating-point instructions""", default=True) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index e404fb79ec..8477f31de2 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -91,6 +91,8 @@ def emit_instps(instps, fmt): Emit a function for matching instruction predicates. """ + if not instps: + fmt.line('#[allow(unused_variables)]') with fmt.indented( 'pub fn check_instp(inst: &InstructionData, instp_idx: u16) ' + '-> bool {', '}'): diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 74aa0dca48..3f435c8fc3 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -192,6 +192,7 @@ def gen_constructor(sgrp, parent, fmt): p = sgrp.parent args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args) fmt.doc_comment('Create flags {} settings group.'.format(sgrp.name)) + fmt.line('#[allow(unused_variables)]') with fmt.indented( 'pub fn new({}) -> Flags {{'.format(args), '}'): fmt.line('let bvec = builder.state_for("{}");'.format(sgrp.name)) @@ -259,9 +260,8 @@ def generate(isas, out_dir): # Generate ISA-specific settings. for isa in isas: - if isa.settings: - isa.settings.qual_mod = 'isa::{}::settings'.format( - isa.settings.name) - fmt = srcgen.Formatter() - gen_group(isa.settings, fmt) - fmt.update_file('settings-{}.rs'.format(isa.name), out_dir) + isa.settings.qual_mod = 'isa::{}::settings'.format( + isa.settings.name) + fmt = srcgen.Formatter() + gen_group(isa.settings, fmt) + fmt.update_file('settings-{}.rs'.format(isa.name), out_dir) diff --git a/lib/cretonne/meta/isa/arm32/__init__.py b/lib/cretonne/meta/isa/arm32/__init__.py index 52f150b8e1..d2de00667a 100644 --- a/lib/cretonne/meta/isa/arm32/__init__.py +++ b/lib/cretonne/meta/isa/arm32/__init__.py @@ -8,7 +8,7 @@ This target ISA generates code for ARMv7 and ARMv8 CPUs in 32-bit mode from __future__ import absolute_import from . import defs -from . import registers # noqa +from . import settings, registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/arm32/registers.py b/lib/cretonne/meta/isa/arm32/registers.py index 3084c2bf98..af031e145b 100644 --- a/lib/cretonne/meta/isa/arm32/registers.py +++ b/lib/cretonne/meta/isa/arm32/registers.py @@ -6,15 +6,7 @@ from cdsl.registers import RegBank, RegClass from .defs import ISA -# Special register units: -# - r15 is the program counter. -# - r14 is the link register. -# - r13 is usually the stack pointer. -IntRegs = RegBank( - 'IntRegs', ISA, - 'General purpose registers', - units=16, prefix='r') - +# Define the larger float bank first to avoid the alignment gap. FloatRegs = RegBank( 'FloatRegs', ISA, r""" Floating point registers. @@ -28,6 +20,15 @@ FloatRegs = RegBank( """, units=64, prefix='s') +# Special register units: +# - r15 is the program counter. +# - r14 is the link register. +# - r13 is usually the stack pointer. +IntRegs = RegBank( + 'IntRegs', ISA, + 'General purpose registers', + units=16, prefix='r') + GPR = RegClass('GPR', IntRegs) S = RegClass('S', FloatRegs, count=32) D = RegClass('D', FloatRegs, width=2) diff --git a/lib/cretonne/meta/isa/arm32/settings.py b/lib/cretonne/meta/isa/arm32/settings.py new file mode 100644 index 0000000000..5cc948cf2d --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/settings.py @@ -0,0 +1,11 @@ +""" +ARM32 settings. +""" +from __future__ import absolute_import +from cdsl.settings import SettingGroup +import base.settings as shared +from .defs import ISA + +ISA.settings = SettingGroup('arm32', parent=shared.group) + +ISA.settings.close(globals()) diff --git a/lib/cretonne/meta/isa/arm64/__init__.py b/lib/cretonne/meta/isa/arm64/__init__.py index 75f5e85f38..3dd69feb4b 100644 --- a/lib/cretonne/meta/isa/arm64/__init__.py +++ b/lib/cretonne/meta/isa/arm64/__init__.py @@ -7,7 +7,7 @@ ARMv8 CPUs running the Aarch64 architecture. from __future__ import absolute_import from . import defs -from . import registers # noqa +from . import settings, registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/arm64/settings.py b/lib/cretonne/meta/isa/arm64/settings.py new file mode 100644 index 0000000000..9a2fc13dc7 --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/settings.py @@ -0,0 +1,11 @@ +""" +ARM64 settings. +""" +from __future__ import absolute_import +from cdsl.settings import SettingGroup +import base.settings as shared +from .defs import ISA + +ISA.settings = SettingGroup('arm64', parent=shared.group) + +ISA.settings.close(globals()) diff --git a/lib/cretonne/meta/isa/intel/__init__.py b/lib/cretonne/meta/isa/intel/__init__.py index 81948699d2..6aea0fd288 100644 --- a/lib/cretonne/meta/isa/intel/__init__.py +++ b/lib/cretonne/meta/isa/intel/__init__.py @@ -17,7 +17,7 @@ is no x87 floating point support. from __future__ import absolute_import from . import defs -from . import registers # noqa +from . import settings, registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/intel/settings.py b/lib/cretonne/meta/isa/intel/settings.py new file mode 100644 index 0000000000..42e1a0ab99 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/settings.py @@ -0,0 +1,11 @@ +""" +Intel settings. +""" +from __future__ import absolute_import +from cdsl.settings import SettingGroup +import base.settings as shared +from .defs import ISA + +ISA.settings = SettingGroup('intel', parent=shared.group) + +ISA.settings.close(globals()) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs new file mode 100644 index 0000000000..e40362a32f --- /dev/null +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -0,0 +1,8 @@ +//! Encoding tables for ARM32 ISA. + +use ir::InstructionData; +use ir::instructions::InstructionFormat; +use ir::types; +use isa::enc_tables::{Level1Entry, Level2Entry}; + +include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs new file mode 100644 index 0000000000..617f52f43e --- /dev/null +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -0,0 +1,73 @@ +//! ARM 32-bit Instruction Set Architecture. + +pub mod settings; +mod enc_tables; +mod registers; + +use super::super::settings as shared_settings; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; +use isa::Builder as IsaBuilder; +use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use ir::{InstructionData, DataFlowGraph}; + +#[allow(dead_code)] +struct Isa { + shared_flags: shared_settings::Flags, + isa_flags: settings::Flags, + cpumode: &'static [shared_enc_tables::Level1Entry], +} + +/// Get an ISA builder for creating ARM32 targets. +pub fn isa_builder() -> IsaBuilder { + IsaBuilder { + setup: settings::builder(), + constructor: isa_constructor, + } +} + +fn isa_constructor(shared_flags: shared_settings::Flags, + builder: &shared_settings::Builder) + -> Box { + let level1 = if shared_flags.is_compressed() { + &enc_tables::LEVEL1_T32[..] + } else { + &enc_tables::LEVEL1_A32[..] + }; + Box::new(Isa { + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + cpumode: level1, + }) +} + +impl TargetIsa for Isa { + fn name(&self) -> &'static str { + "arm32" + } + + fn flags(&self) -> &shared_settings::Flags { + &self.shared_flags + } + + fn register_info(&self) -> &RegInfo { + ®isters::INFO + } + + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.first_type(), + inst.opcode(), + self.cpumode, + &enc_tables::LEVEL2[..]) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) + } + + fn recipe_names(&self) -> &'static [&'static str] { + &enc_tables::RECIPE_NAMES[..] + } +} diff --git a/lib/cretonne/src/isa/arm32/registers.rs b/lib/cretonne/src/isa/arm32/registers.rs new file mode 100644 index 0000000000..30e7fd58cf --- /dev/null +++ b/lib/cretonne/src/isa/arm32/registers.rs @@ -0,0 +1,32 @@ +//! ARM32 register descriptions. + +use isa::registers::{RegBank, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); + +#[cfg(test)] +mod tests { + use super::INFO; + use isa::RegUnit; + + #[test] + fn unit_encodings() { + assert_eq!(INFO.parse_regunit("s0"), Some(0)); + assert_eq!(INFO.parse_regunit("s31"), Some(31)); + assert_eq!(INFO.parse_regunit("s32"), Some(32)); + assert_eq!(INFO.parse_regunit("r0"), Some(64)); + assert_eq!(INFO.parse_regunit("r15"), Some(79)); + } + + #[test] + fn unit_names() { + fn uname(ru: RegUnit) -> String { + INFO.display_regunit(ru).to_string() + } + + assert_eq!(uname(0), "%s0"); + assert_eq!(uname(1), "%s1"); + assert_eq!(uname(31), "%s31"); + assert_eq!(uname(64), "%r0"); + } +} diff --git a/lib/cretonne/src/isa/arm32/settings.rs b/lib/cretonne/src/isa/arm32/settings.rs new file mode 100644 index 0000000000..e857716a64 --- /dev/null +++ b/lib/cretonne/src/isa/arm32/settings.rs @@ -0,0 +1,9 @@ +//! ARM32 Settings. + +use settings::{self, detail, Builder}; +use std::fmt; + +// Include code generated by `lib/cretonne/meta/gen_settings.py`. This file contains a public +// `Flags` struct with an impl for all of the settings defined in +// `lib/cretonne/meta/cretonne/settings.py`. +include!(concat!(env!("OUT_DIR"), "/settings-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs new file mode 100644 index 0000000000..dbe65c4e8f --- /dev/null +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -0,0 +1,8 @@ +//! Encoding tables for ARM64 ISA. + +use ir::InstructionData; +use ir::instructions::InstructionFormat; +use ir::types; +use isa::enc_tables::{Level1Entry, Level2Entry}; + +include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs new file mode 100644 index 0000000000..a4367a878b --- /dev/null +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -0,0 +1,66 @@ +//! ARM 64-bit Instruction Set Architecture. + +pub mod settings; +mod enc_tables; +mod registers; + +use super::super::settings as shared_settings; +use isa::enc_tables::{lookup_enclist, general_encoding}; +use isa::Builder as IsaBuilder; +use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use ir::{InstructionData, DataFlowGraph}; + +#[allow(dead_code)] +struct Isa { + shared_flags: shared_settings::Flags, + isa_flags: settings::Flags, +} + +/// Get an ISA builder for creating ARM64 targets. +pub fn isa_builder() -> IsaBuilder { + IsaBuilder { + setup: settings::builder(), + constructor: isa_constructor, + } +} + +fn isa_constructor(shared_flags: shared_settings::Flags, + builder: &shared_settings::Builder) + -> Box { + Box::new(Isa { + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + }) +} + +impl TargetIsa for Isa { + fn name(&self) -> &'static str { + "arm64" + } + + fn flags(&self) -> &shared_settings::Flags { + &self.shared_flags + } + + fn register_info(&self) -> &RegInfo { + ®isters::INFO + } + + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.first_type(), + inst.opcode(), + &enc_tables::LEVEL1_A64[..], + &enc_tables::LEVEL2[..]) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) + } + + fn recipe_names(&self) -> &'static [&'static str] { + &enc_tables::RECIPE_NAMES[..] + } +} diff --git a/lib/cretonne/src/isa/arm64/registers.rs b/lib/cretonne/src/isa/arm64/registers.rs new file mode 100644 index 0000000000..2420db6df1 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/registers.rs @@ -0,0 +1,37 @@ +//! ARM64 register descriptions. + +use isa::registers::{RegBank, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-arm64.rs")); + +#[cfg(test)] +mod tests { + use super::INFO; + use isa::RegUnit; + + #[test] + fn unit_encodings() { + assert_eq!(INFO.parse_regunit("x0"), Some(0)); + assert_eq!(INFO.parse_regunit("x31"), Some(31)); + assert_eq!(INFO.parse_regunit("v0"), Some(32)); + assert_eq!(INFO.parse_regunit("v31"), Some(63)); + + assert_eq!(INFO.parse_regunit("x32"), None); + assert_eq!(INFO.parse_regunit("v32"), None); + } + + #[test] + fn unit_names() { + fn uname(ru: RegUnit) -> String { + INFO.display_regunit(ru).to_string() + } + + assert_eq!(uname(0), "%x0"); + assert_eq!(uname(1), "%x1"); + assert_eq!(uname(31), "%x31"); + assert_eq!(uname(32), "%v0"); + assert_eq!(uname(33), "%v1"); + assert_eq!(uname(63), "%v31"); + assert_eq!(uname(64), "%INVALID64"); + } +} diff --git a/lib/cretonne/src/isa/arm64/settings.rs b/lib/cretonne/src/isa/arm64/settings.rs new file mode 100644 index 0000000000..6427d7be99 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/settings.rs @@ -0,0 +1,9 @@ +//! ARM64 Settings. + +use settings::{self, detail, Builder}; +use std::fmt; + +// Include code generated by `lib/cretonne/meta/gen_settings.py`. This file contains a public +// `Flags` struct with an impl for all of the settings defined in +// `lib/cretonne/meta/cretonne/settings.py`. +include!(concat!(env!("OUT_DIR"), "/settings-arm64.rs")); diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs new file mode 100644 index 0000000000..842629b058 --- /dev/null +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -0,0 +1,8 @@ +//! Encoding tables for Intel ISAs. + +use ir::InstructionData; +use ir::instructions::InstructionFormat; +use ir::types; +use isa::enc_tables::{Level1Entry, Level2Entry}; + +include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs new file mode 100644 index 0000000000..acad76743b --- /dev/null +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -0,0 +1,73 @@ +//! Intel Instruction Set Architectures. + +pub mod settings; +mod enc_tables; +mod registers; + +use super::super::settings as shared_settings; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; +use isa::Builder as IsaBuilder; +use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use ir::{InstructionData, DataFlowGraph}; + +#[allow(dead_code)] +struct Isa { + shared_flags: shared_settings::Flags, + isa_flags: settings::Flags, + cpumode: &'static [shared_enc_tables::Level1Entry], +} + +/// Get an ISA builder for creating Intel targets. +pub fn isa_builder() -> IsaBuilder { + IsaBuilder { + setup: settings::builder(), + constructor: isa_constructor, + } +} + +fn isa_constructor(shared_flags: shared_settings::Flags, + builder: &shared_settings::Builder) + -> Box { + let level1 = if shared_flags.is_64bit() { + &enc_tables::LEVEL1_I64[..] + } else { + &enc_tables::LEVEL1_I32[..] + }; + Box::new(Isa { + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + cpumode: level1, + }) +} + +impl TargetIsa for Isa { + fn name(&self) -> &'static str { + "intel" + } + + fn flags(&self) -> &shared_settings::Flags { + &self.shared_flags + } + + fn register_info(&self) -> &RegInfo { + ®isters::INFO + } + + fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.first_type(), + inst.opcode(), + self.cpumode, + &enc_tables::LEVEL2[..]) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) + } + + fn recipe_names(&self) -> &'static [&'static str] { + &enc_tables::RECIPE_NAMES[..] + } +} diff --git a/lib/cretonne/src/isa/intel/registers.rs b/lib/cretonne/src/isa/intel/registers.rs new file mode 100644 index 0000000000..1946d0fc57 --- /dev/null +++ b/lib/cretonne/src/isa/intel/registers.rs @@ -0,0 +1,49 @@ +//! Intel register descriptions. + +use isa::registers::{RegBank, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-intel.rs")); + +#[cfg(test)] +mod tests { + use super::INFO; + use isa::RegUnit; + + #[test] + fn unit_encodings() { + // The encoding of integer registers is not alphabetical. + assert_eq!(INFO.parse_regunit("rax"), Some(0)); + assert_eq!(INFO.parse_regunit("rbx"), Some(3)); + assert_eq!(INFO.parse_regunit("rcx"), Some(1)); + assert_eq!(INFO.parse_regunit("rdx"), Some(2)); + assert_eq!(INFO.parse_regunit("rsi"), Some(6)); + assert_eq!(INFO.parse_regunit("rdi"), Some(7)); + assert_eq!(INFO.parse_regunit("rbp"), Some(5)); + assert_eq!(INFO.parse_regunit("rsp"), Some(4)); + assert_eq!(INFO.parse_regunit("r8"), Some(8)); + assert_eq!(INFO.parse_regunit("r15"), Some(15)); + + assert_eq!(INFO.parse_regunit("xmm0"), Some(16)); + assert_eq!(INFO.parse_regunit("xmm15"), Some(31)); + } + + #[test] + fn unit_names() { + fn uname(ru: RegUnit) -> String { + INFO.display_regunit(ru).to_string() + } + + assert_eq!(uname(0), "%rax"); + assert_eq!(uname(3), "%rbx"); + assert_eq!(uname(1), "%rcx"); + assert_eq!(uname(2), "%rdx"); + assert_eq!(uname(6), "%rsi"); + assert_eq!(uname(7), "%rdi"); + assert_eq!(uname(5), "%rbp"); + assert_eq!(uname(4), "%rsp"); + assert_eq!(uname(8), "%r8"); + assert_eq!(uname(15), "%r15"); + assert_eq!(uname(16), "%xmm0"); + assert_eq!(uname(31), "%xmm15"); + } +} diff --git a/lib/cretonne/src/isa/intel/settings.rs b/lib/cretonne/src/isa/intel/settings.rs new file mode 100644 index 0000000000..341eb2dcc9 --- /dev/null +++ b/lib/cretonne/src/isa/intel/settings.rs @@ -0,0 +1,9 @@ +//! Intel Settings. + +use settings::{self, detail, Builder}; +use std::fmt; + +// Include code generated by `lib/cretonne/meta/gen_settings.py`. This file contains a public +// `Flags` struct with an impl for all of the settings defined in +// `lib/cretonne/meta/cretonne/settings.py`. +include!(concat!(env!("OUT_DIR"), "/settings-intel.rs")); diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index b1b3a8a8df..dc66355904 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -46,6 +46,9 @@ use settings; use ir::{InstructionData, DataFlowGraph}; pub mod riscv; +pub mod intel; +pub mod arm32; +pub mod arm64; mod encoding; mod enc_tables; mod registers; @@ -55,6 +58,9 @@ mod registers; pub fn lookup(name: &str) -> Option { match name { "riscv" => riscv_builder(), + "intel" => intel_builder(), + "arm32" => arm32_builder(), + "arm64" => arm64_builder(), _ => None, } } @@ -64,6 +70,18 @@ fn riscv_builder() -> Option { Some(riscv::isa_builder()) } +fn intel_builder() -> Option { + Some(intel::isa_builder()) +} + +fn arm32_builder() -> Option { + Some(arm32::isa_builder()) +} + +fn arm64_builder() -> Option { + Some(arm64::isa_builder()) +} + /// Builder for a `TargetIsa`. /// Modify the ISA-specific settings before creating the `TargetIsa` trait object with `finish`. pub struct Builder { diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 291211b1b3..30ecff68e4 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -270,6 +270,7 @@ mod tests { "[shared]\n\ opt_level = \"default\"\n\ is_64bit = false\n\ + is_compressed = false\n\ enable_float = true\n\ enable_simd = true\n\ enable_atomics = true\n"); From 93aa2b456e32433db793f14b03fd1b389bd433ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Inf=C3=BChr?= Date: Thu, 1 Dec 2016 00:11:06 +0100 Subject: [PATCH 455/968] added Opcode flags methods generate `is_branch`, `is_terminator`, `can_trap` methods for `enum Opcode`. --- lib/cretonne/meta/base/instructions.py | 20 ++++++++++---------- lib/cretonne/meta/cdsl/instructions.py | 4 ++++ lib/cretonne/meta/gen_instr.py | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index b5a2c784fa..eb98d175ef 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -83,7 +83,7 @@ trap = Instruction( 'trap', r""" Terminate execution unconditionally. """, - is_terminator=True) + is_terminator=True, can_trap=True) trapz = Instruction( 'trapz', r""" @@ -91,7 +91,7 @@ trapz = Instruction( if ``c`` is non-zero, execution continues at the following instruction. """, - ins=c) + ins=c, can_trap=True) trapnz = Instruction( 'trapnz', r""" @@ -99,7 +99,7 @@ trapnz = Instruction( if ``c`` is zero, execution continues at the following instruction. """, - ins=c) + ins=c, can_trap=True) rvals = Operand('rvals', VARIABLE_ARGS, doc='return values') @@ -111,7 +111,7 @@ x_return = Instruction( provided return values. The list of return values must match the function signature's return types. """, - ins=rvals) + ins=rvals, is_terminator=True) FN = Operand( 'FN', @@ -366,7 +366,7 @@ udiv = Instruction( This operation traps if the divisor is zero. """, - ins=(x, y), outs=a) + ins=(x, y), outs=a, can_trap=True) sdiv = Instruction( 'sdiv', r""" @@ -377,7 +377,7 @@ sdiv = Instruction( representable in :math:`B` bits two's complement. This only happens when :math:`x = -2^{B-1}, y = -1`. """, - ins=(x, y), outs=a) + ins=(x, y), outs=a, can_trap=True) urem = Instruction( 'urem', """ @@ -385,7 +385,7 @@ urem = Instruction( This operation traps if the divisor is zero. """, - ins=(x, y), outs=a) + ins=(x, y), outs=a, can_trap=True) srem = Instruction( 'srem', """ @@ -399,7 +399,7 @@ srem = Instruction( dividend. Should we add a ``smod`` instruction for the case where the result has the same sign as the divisor? """, - ins=(x, y), outs=a) + ins=(x, y), outs=a, can_trap=True) a = Operand('a', iB) x = Operand('x', iB) @@ -1136,7 +1136,7 @@ fcvt_to_uint = Instruction( The result type must have the same number of vector lanes as the input. """, - ins=x, outs=a) + ins=x, outs=a, can_trap=True) fcvt_to_sint = Instruction( 'fcvt_to_sint', r""" @@ -1148,7 +1148,7 @@ fcvt_to_sint = Instruction( The result type must have the same number of vector lanes as the input. """, - ins=x, outs=a) + ins=x, outs=a, can_trap=True) x = Operand('x', Int) a = Operand('a', FloatTo) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index ee6b9c4a59..84968c1b6b 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -80,6 +80,7 @@ class Instruction(object): values or `variable_args`. :param is_terminator: This is a terminator instruction. :param is_branch: This is a branch instruction. + :param can_trap: This instruction can trap. """ def __init__(self, name, doc, ins=(), outs=(), **kwargs): @@ -94,6 +95,9 @@ class Instruction(object): self.value_results = tuple( i for i, o in enumerate(self.outs) if o.is_value()) self._verify_polymorphic() + self.is_branch = 'is_branch' in kwargs + self.is_terminator = 'is_terminator' in kwargs + self.can_trap = 'can_trap' in kwargs InstructionGroup.append(self) def __str__(self): diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 11aaa4fa0d..799c080473 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -271,6 +271,22 @@ def gen_opcodes(groups, fmt): fmt.line(i.camel_name + ',') fmt.line() + with fmt.indented('impl Opcode {', '}'): + attrs = ['is_branch', 'is_terminator', 'can_trap'] + + for attr in attrs: + if attr != attrs[0]: + fmt.line() + + with fmt.indented('fn {}(self) -> bool {{' + .format(attr), '}'): + with fmt.indented('match self {', '}'): + for i in instrs: + if getattr(i, attr): + fmt.format('Opcode::{} => true,', i.camel_name, i.name) + + fmt.line('_ => false') + # Generate a private opcode_format table. with fmt.indented( 'const OPCODE_FORMAT: [InstructionFormat; {}] = [' From c1ecb29851dc0caa6569d6562dee6f7605accee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Inf=C3=BChr?= Date: Wed, 7 Dec 2016 19:32:58 +0100 Subject: [PATCH 456/968] remove old `is_terminating` function for InstructionData. Use generated `is_terminator()` for `Opcode` instead. `is_terminator`, `can_trap` and `is_branch` functions are now public. fix syntax error --- lib/cretonne/meta/gen_instr.py | 13 +++++++++---- lib/cretonne/src/ir/instructions.rs | 10 ---------- lib/cretonne/src/verifier.rs | 2 +- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 799c080473..e0055bcf98 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -272,17 +272,22 @@ def gen_opcodes(groups, fmt): fmt.line() with fmt.indented('impl Opcode {', '}'): - attrs = ['is_branch', 'is_terminator', 'can_trap'] + attrs = [ + { 'name': 'is_branch', 'comment': 'True for all branch instructions.' }, + { 'name': 'is_terminator', 'comment': 'True for instructions that terminate EBB.' }, + { 'name': 'can_trap', 'comment': 'True if instruction could trap.' } + ] for attr in attrs: if attr != attrs[0]: fmt.line() - with fmt.indented('fn {}(self) -> bool {{' - .format(attr), '}'): + fmt.doc_comment(attr['comment']) + with fmt.indented('pub fn {}(self) -> bool {{' + .format(attr['name']), '}'): with fmt.indented('match self {', '}'): for i in instrs: - if getattr(i, attr): + if getattr(i, attr['name']): fmt.format('Opcode::{} => true,', i.camel_name, i.name) fmt.line('_ => false') diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index b8c5bdc4fa..21f9b92c3c 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -465,16 +465,6 @@ impl InstructionData { _ => CallInfo::NotACall, } } - - /// Return true if an instruction is terminating, or false otherwise. - pub fn is_terminating<'a>(&'a self) -> bool { - match self { - &InstructionData::Jump { .. } => true, - &InstructionData::Return { .. } => true, - &InstructionData::Nullary { .. } => true, - _ => false, - } - } } /// Information about branch and jump instructions. diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 9e044a738a..939d722e60 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -109,7 +109,7 @@ impl<'a> Verifier<'a> { fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<()> { - let is_terminator = self.func.dfg[inst].is_terminating(); + let is_terminator = self.func.dfg[inst].opcode().is_terminator(); let is_last_inst = self.func.layout.last_inst(ebb) == inst; if is_terminator && !is_last_inst { From 3392a2b79ab874a157aaa966c337dd07fefefda3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 8 Dec 2016 13:30:40 -1000 Subject: [PATCH 457/968] Fix Python formatting to keep flake8 happy. Python coding style is verified by the lib/cretonne/meta/check.sh script which is not run as part of test-all.sh. cc @dinfuehr --- lib/cretonne/meta/gen_instr.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index e0055bcf98..ac4650bc96 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -273,9 +273,18 @@ def gen_opcodes(groups, fmt): with fmt.indented('impl Opcode {', '}'): attrs = [ - { 'name': 'is_branch', 'comment': 'True for all branch instructions.' }, - { 'name': 'is_terminator', 'comment': 'True for instructions that terminate EBB.' }, - { 'name': 'can_trap', 'comment': 'True if instruction could trap.' } + { + 'name': 'is_branch', + 'comment': 'True for all branch instructions.' + }, + { + 'name': 'is_terminator', + 'comment': 'True for instructions that terminate EBB.' + }, + { + 'name': 'can_trap', + 'comment': 'True if instruction could trap.' + } ] for attr in attrs: @@ -288,7 +297,9 @@ def gen_opcodes(groups, fmt): with fmt.indented('match self {', '}'): for i in instrs: if getattr(i, attr['name']): - fmt.format('Opcode::{} => true,', i.camel_name, i.name) + fmt.format( + 'Opcode::{} => true,', + i.camel_name, i.name) fmt.line('_ => false') From f9af88c49e8c4da64d9bc1e30434aaf050b983df Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 8 Dec 2016 13:57:28 -1000 Subject: [PATCH 458/968] Add a ValueLoc type and the locations table. This table holds the result of register allocation. --- lib/cretonne/src/ir/entities.rs | 11 +++++++++++ lib/cretonne/src/ir/function.rs | 8 ++++++-- lib/cretonne/src/ir/mod.rs | 2 ++ lib/cretonne/src/ir/valueloc.rs | 24 ++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 lib/cretonne/src/ir/valueloc.rs diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index df7d81988f..4570a21009 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -98,6 +98,17 @@ impl Default for Inst { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Value(u32); +impl EntityRef for Value { + fn new(index: usize) -> Value { + assert!(index < (u32::MAX as usize)); + Value(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } +} + /// Value references can either reference an instruction directly, or they can refer to the /// extended value table. pub enum ExpandedValue { diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 844101bf95..db6c811b5a 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -4,8 +4,8 @@ //! instructions. use std::fmt::{self, Display, Debug, Formatter}; -use ir::{FunctionName, Signature, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData, - DataFlowGraph, Layout}; +use ir::{FunctionName, Signature, Value, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData, + ValueLoc, DataFlowGraph, Layout}; use isa::Encoding; use entity_map::{EntityMap, PrimaryEntityData}; use write::write_function; @@ -37,6 +37,9 @@ pub struct Function { /// Encoding recipe and bits for the legal instructions. /// Illegal instructions have the `Encoding::default()` value. pub encodings: EntityMap, + + /// Location assigned to every value. + pub locations: EntityMap, } impl PrimaryEntityData for StackSlotData {} @@ -53,6 +56,7 @@ impl Function { dfg: DataFlowGraph::new(), layout: Layout::new(), encodings: EntityMap::new(), + locations: EntityMap::new(), } } diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index b8cda7711c..738c6336d5 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -13,6 +13,7 @@ pub mod function; mod funcname; mod extfunc; mod builder; +mod valueloc; pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; @@ -21,6 +22,7 @@ pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; +pub use ir::valueloc::ValueLoc; pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs new file mode 100644 index 0000000000..3526c58190 --- /dev/null +++ b/lib/cretonne/src/ir/valueloc.rs @@ -0,0 +1,24 @@ +//! Value locations. +//! +//! The register allocator assigns every SSA value to either a register or a stack slot. This +//! assignment is represented by a `ValueLoc` object. + +use isa::RegUnit; +use ir::StackSlot; + +/// Value location. +#[derive(Copy, Clone, Debug)] +pub enum ValueLoc { + /// This value has not been assigned to a location yet. + Unassigned, + /// Value is assigned to a register. + Reg(RegUnit), + /// Value is assigned to a stack slot. + Stack(StackSlot), +} + +impl Default for ValueLoc { + fn default() -> Self { + ValueLoc::Unassigned + } +} From 3568b93b84bb779d3814026398e2e296bfb5739b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Dec 2016 15:50:29 -0800 Subject: [PATCH 459/968] Add program points. Program points are used to represent a linear position in a function. Thus will be used for the live ranges of values. --- lib/cretonne/src/ir/mod.rs | 2 + lib/cretonne/src/ir/progpoint.rs | 97 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 lib/cretonne/src/ir/progpoint.rs diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 738c6336d5..7e05622645 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -14,6 +14,7 @@ mod funcname; mod extfunc; mod builder; mod valueloc; +mod progpoint; pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; @@ -27,3 +28,4 @@ pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; pub use ir::builder::InstBuilder; +pub use ir::progpoint::{ProgramPoint, ProgramOrder}; diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs new file mode 100644 index 0000000000..c609a03d8f --- /dev/null +++ b/lib/cretonne/src/ir/progpoint.rs @@ -0,0 +1,97 @@ +//! Program points. + +use entity_map::EntityRef; +use ir::{Ebb, Inst}; +use std::fmt; +use std::u32; +use std::cmp; + +/// A `ProgramPoint` represents a position in a function where the live range of an SSA value can +/// begin or end. It can be either: +/// +/// 1. An instruction or +/// 2. An EBB header. +/// +/// This corresponds more or less to the lines in the textual representation of Cretonne IL. +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct ProgramPoint(u32); + +impl From for ProgramPoint { + fn from(inst: Inst) -> ProgramPoint { + let idx = inst.index(); + assert!(idx < (u32::MAX / 2) as usize); + ProgramPoint((idx * 2) as u32) + } +} + +impl From for ProgramPoint { + fn from(ebb: Ebb) -> ProgramPoint { + let idx = ebb.index(); + assert!(idx < (u32::MAX / 2) as usize); + ProgramPoint((idx * 2 + 1) as u32) + } +} + +/// An expanded program point directly exposes the variants, but takes twice the space to +/// represent. +pub enum ExpandedProgramPoint { + // An instruction in the function. + Inst(Inst), + // An EBB header. + Ebb(Ebb), +} + +impl ProgramPoint { + /// Expand compact program point representation. + pub fn expand(self) -> ExpandedProgramPoint { + if self.0 & 1 == 0 { + ExpandedProgramPoint::Inst(Inst::new((self.0 / 2) as usize)) + } else { + ExpandedProgramPoint::Ebb(Ebb::new((self.0 / 2) as usize)) + } + } +} + +impl fmt::Display for ProgramPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.expand() { + ExpandedProgramPoint::Inst(x) => write!(f, "{}", x), + ExpandedProgramPoint::Ebb(x) => write!(f, "{}", x), + } + } +} + +impl fmt::Debug for ProgramPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ProgramPoint({})", self) + } +} + +/// Context for ordering program points. +/// +/// `ProgramPoint` objects don't carry enough information to be ordered independently, they need a +/// context providing the program order. +pub trait ProgramOrder { + /// Compare the program points `a` and `b` relative to this program order. Return `Less` if `a` + /// appears in the program before `b`. + fn cmp(&self, a: ProgramPoint, b: ProgramPoint) -> cmp::Ordering; +} + +#[cfg(test)] +mod tests { + use super::*; + use entity_map::EntityRef; + use ir::{Inst, Ebb}; + + #[test] + fn convert() { + let i5 = Inst::new(5); + let b3 = Ebb::new(3); + + let pp1: ProgramPoint = i5.into(); + let pp2: ProgramPoint = b3.into(); + + assert_eq!(pp1.to_string(), "inst5"); + assert_eq!(pp2.to_string(), "ebb3"); + } +} From 893a630ca0fe59750819bdf755e583b971dcd776 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Dec 2016 10:05:14 -0800 Subject: [PATCH 460/968] Upgrade to rustfmt 0.6.3 --- lib/filecheck/src/pattern.rs | 36 ++++++++++++++++++------------------ test-all.sh | 2 +- tests/cfg_traversal.rs | 20 +++++++++++++------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs index 88c47011f9..030758d41e 100644 --- a/lib/filecheck/src/pattern.rs +++ b/lib/filecheck/src/pattern.rs @@ -165,9 +165,9 @@ impl Pattern { let refname = s[refname_begin..refname_end].to_string(); return if let Some(defidx) = def { Ok((Part::DefVar { - def: defidx, - var: refname, - }, + def: defidx, + var: refname, + }, refname_end + 1)) } else { Err(Error::Syntax(format!("expected variable name in $(=${})", refname))) @@ -462,37 +462,37 @@ mod tests { assert_eq!(pat.parse_part("$(foo=$bar)").unwrap(), (Part::DefVar { - def: 0, - var: "bar".to_string(), - }, + def: 0, + var: "bar".to_string(), + }, 11)); assert_eq!(pat.parse_part("$(foo=$bar)").unwrap_err().to_string(), "duplicate definition of $foo in same pattern"); assert_eq!(pat.parse_part("$(fxo=$bar)x").unwrap(), (Part::DefVar { - def: 1, - var: "bar".to_string(), - }, + def: 1, + var: "bar".to_string(), + }, 11)); assert_eq!(pat.parse_part("$(fo2=[a-z])").unwrap(), (Part::DefLit { - def: 2, - regex: "(?P[a-z])".to_string(), - }, + def: 2, + regex: "(?P[a-z])".to_string(), + }, 12)); assert_eq!(pat.parse_part("$(fo3=[a-)])").unwrap(), (Part::DefLit { - def: 3, - regex: "(?P[a-)])".to_string(), - }, + def: 3, + regex: "(?P[a-)])".to_string(), + }, 12)); assert_eq!(pat.parse_part("$(fo4=)").unwrap(), (Part::DefLit { - def: 4, - regex: "(?P)".to_string(), - }, + def: 4, + regex: "(?P)".to_string(), + }, 7)); assert_eq!(pat.parse_part("$(=.*)").unwrap(), diff --git a/test-all.sh b/test-all.sh index 0ad58aa114..f55464ce3b 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.6.2" +RUSTFMT_VERSION="0.6.3" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index b766f9d96c..2bdeab2347 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -9,8 +9,9 @@ use self::cretonne::entity_map::EntityMap; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &parse_functions(function_source).unwrap()[0]; let cfg = ControlFlowGraph::new(&func); - let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()) - .collect::>(); + let ebbs = ebb_order.iter() + .map(|n| Ebb::with_number(*n).unwrap()) + .collect::>(); let mut postorder_ebbs = cfg.postorder_ebbs(); let mut postorder_map = EntityMap::with_capacity(postorder_ebbs.len()); @@ -50,7 +51,8 @@ fn simple_traversal() { ebb5: trap } - ", vec![0, 2, 1, 3, 4, 5]); + ", + vec![0, 2, 1, 3, 4, 5]); } #[test] @@ -67,7 +69,8 @@ fn loops_one() { ebb3: return } - ", vec![0, 1, 2, 3]); + ", + vec![0, 1, 2, 3]); } #[test] @@ -91,7 +94,8 @@ fn loops_two() { brz v0, ebb4 return } - ", vec![0, 1, 2, 5, 4, 3]); + ", + vec![0, 1, 2, 5, 4, 3]); } #[test] @@ -120,7 +124,8 @@ fn loops_three() { ebb7: return } - ", vec![0, 1, 2, 5, 4, 3, 6, 7]); + ", + vec![0, 1, 2, 5, 4, 3, 6, 7]); } #[test] @@ -142,5 +147,6 @@ fn back_edge_one() { ebb4: trap } - ", vec![0, 1, 3, 2, 4]); + ", + vec![0, 1, 3, 2, 4]); } From f1234003a9de7d08006316bd46c2593bf75431f7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 28 Dec 2016 17:05:00 -0800 Subject: [PATCH 461/968] Implement ProgramOrder for Layout. Assign sequence numbers to both instructions and EBB headers such that their relative position can be determined in constant time simply by comparing sequence numbers. Implement the sequence numbers with a scheme similar to the line numbers used in BASIC programs. Start out with 10, 20, 30, ... and pick numbers in the gaps as long as possible. Renumber locally when needed. --- lib/cretonne/src/ir/layout.rs | 249 ++++++++++++++++++++++++++++++++-- 1 file changed, 237 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index bdcf4cdd52..16f5aa5483 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -3,9 +3,11 @@ //! The order of extended basic blocks in a function and the order of instructions in an EBB is //! determined by the `Layout` data structure defined in this module. +use std::cmp; use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; +use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -49,6 +51,206 @@ impl Layout { } } +// Sequence numbers. +// +// All instructions and EBBs are given a sequence number that can be used to quickly determine +// their relative position in the layout. The sequence numbers are not contiguous, but are assigned +// like line numbers in BASIC: 10, 20, 30, ... +// +// The EBB sequence numbers are strictly increasing, and so are the instruction sequence numbers +// within an EBB. The instruction sequence numbers are all between the sequence number of their +// containing EBB and the following EBB. +// +// The result is that sequence numbers work like BASIC line numbers for the textual representation +// of the IL. +type SequenceNumber = u32; + +// Initial stride assigned to new sequence numbers. +const MAJOR_STRIDE: SequenceNumber = 10; + +// Secondary stride used when renumbering locally. +const MINOR_STRIDE: SequenceNumber = 2; + +// Compute the midpoint between `a` and `b`. +// Return `None` if the midpoint would be equal to either. +fn midpoint(a: SequenceNumber, b: SequenceNumber) -> Option { + assert!(a < b); + // Avoid integer overflow. + let m = a + (b - a) / 2; + if m > a { Some(m) } else { None } +} + +#[test] +fn test_midpoint() { + assert_eq!(midpoint(0, 1), None); + assert_eq!(midpoint(0, 2), Some(1)); + assert_eq!(midpoint(0, 3), Some(1)); + assert_eq!(midpoint(0, 4), Some(2)); + assert_eq!(midpoint(1, 4), Some(2)); + assert_eq!(midpoint(2, 4), Some(3)); + assert_eq!(midpoint(3, 4), None); + assert_eq!(midpoint(3, 4), None); +} + +impl ProgramOrder for Layout { + fn cmp(&self, a: ProgramPoint, b: ProgramPoint) -> cmp::Ordering { + let a_seq = self.pp_seq(a); + let b_seq = self.pp_seq(b); + a_seq.cmp(&b_seq) + } +} + +// Private methods for dealing with sequence numbers. +impl Layout { + /// Get the sequence number of a program point that must correspond to an entity in the layout. + fn pp_seq(&self, pp: ProgramPoint) -> SequenceNumber { + match pp.expand() { + ExpandedProgramPoint::Ebb(ebb) => self.ebbs[ebb].seq, + ExpandedProgramPoint::Inst(inst) => self.insts[inst].seq, + } + } + + /// Get the last sequence number in `ebb`. + fn last_ebb_seq(&self, ebb: Ebb) -> SequenceNumber { + // Get the seq of the last instruction if it exists, otherwise use the EBB header seq. + self.ebbs[ebb] + .last_inst + .wrap() + .map(|inst| self.insts[inst].seq) + .unwrap_or(self.ebbs[ebb].seq) + } + + /// Assign a valid sequence number to `ebb` such that the numbers are still monotonic. This may + /// require renumbering. + fn assign_ebb_seq(&mut self, ebb: Ebb) { + assert!(self.is_ebb_inserted(ebb)); + + // Get the sequence number immediately before `ebb`, or 0. + let prev_seq = + self.ebbs[ebb].prev.wrap().map(|prev_ebb| self.last_ebb_seq(prev_ebb)).unwrap_or(0); + + // Get the sequence number immediately following `ebb`. + let next_seq = if let Some(inst) = self.ebbs[ebb].first_inst.wrap() { + self.insts[inst].seq + } else if let Some(next_ebb) = self.ebbs[ebb].next.wrap() { + self.ebbs[next_ebb].seq + } else { + // There is nothing after `ebb`. We can just use a major stride. + self.ebbs[ebb].seq = prev_seq + MAJOR_STRIDE; + return; + }; + + // Check if there is room between these sequence numbers. + if let Some(seq) = midpoint(prev_seq, next_seq) { + self.ebbs[ebb].seq = seq; + } else { + // No available integers between `prev_seq` and `next_seq`. We have to renumber. + self.renumber_from_ebb(ebb, prev_seq + MINOR_STRIDE); + } + } + + /// Assign a valid sequence number to `inst` such that the numbers are still monotonic. This may + /// require renumbering. + fn assign_inst_seq(&mut self, inst: Inst) { + let ebb = self.inst_ebb(inst).expect("inst must be inserted before assigning an seq"); + + // Get the sequence number immediately before `inst`. + let prev_seq = match self.insts[inst].prev.wrap() { + Some(prev_inst) => self.insts[prev_inst].seq, + None => self.ebbs[ebb].seq, + }; + + // Get the sequence number immediately following `inst`. + let next_seq = if let Some(next_inst) = self.insts[inst].next.wrap() { + self.insts[next_inst].seq + } else if let Some(next_ebb) = self.ebbs[ebb].next.wrap() { + self.ebbs[next_ebb].seq + } else { + // There is nothing after `inst`. We can just use a major stride. + self.insts[inst].seq = prev_seq + MAJOR_STRIDE; + return; + }; + + // Check if there is room between these sequence numbers. + if let Some(seq) = midpoint(prev_seq, next_seq) { + self.insts[inst].seq = seq; + } else { + // No available integers between `prev_seq` and `next_seq`. We have to renumber. + self.renumber_from_inst(inst, prev_seq + MINOR_STRIDE); + } + } + + /// Renumber instructions starting from `inst` until the end of the EBB or until numbers catch + /// up. + /// + /// Return `None` if renumbering has caught up and the sequence is monotonic again. Otherwise + /// return the last used sequence number. + fn renumber_insts(&mut self, inst: Inst, seq: SequenceNumber) -> Option { + let mut inst = inst; + let mut seq = seq; + + loop { + self.insts[inst].seq = seq; + + // Next instruction. + inst = match self.insts[inst].next.wrap() { + None => return Some(seq), + Some(next) => next, + }; + + if seq < self.insts[inst].seq { + // Sequence caught up. + return None; + } + + seq += MINOR_STRIDE; + } + } + + /// Renumber starting from `ebb` to `seq` and continuing until the sequence numbers are + /// monotonic again. + fn renumber_from_ebb(&mut self, ebb: Ebb, first_seq: SequenceNumber) { + let mut ebb = ebb; + let mut seq = first_seq; + + loop { + self.ebbs[ebb].seq = seq; + + // Renumber instructions in `ebb`. Stop when the numbers catch up. + if let Some(inst) = self.ebbs[ebb].first_inst.wrap() { + seq = match self.renumber_insts(inst, seq + MINOR_STRIDE) { + Some(s) => s, + None => return, + } + } + + // Advance to the next EBB. + ebb = match self.ebbs[ebb].next.wrap() { + Some(next) => next, + None => return, + }; + + // Stop renumbering once the numbers catch up. + if seq < self.ebbs[ebb].seq { + return; + } + + seq += MINOR_STRIDE; + } + } + + /// Renumber starting from `inst` to `seq` and continuing until the sequence numbers are + /// monotonic again. + fn renumber_from_inst(&mut self, inst: Inst, first_seq: SequenceNumber) { + if let Some(seq) = self.renumber_insts(inst, first_seq) { + // Renumbering spills over into next EBB. + if let Some(next_ebb) = self.ebbs[self.inst_ebb(inst).unwrap()].next.wrap() { + self.renumber_from_ebb(next_ebb, seq + MINOR_STRIDE); + } + } + } +} + /// Methods for laying out EBBs. /// /// An unknown EBB starts out as *not inserted* in the EBB layout. The layout is a linear order of @@ -80,6 +282,7 @@ impl Layout { self.first_ebb = Some(ebb); } self.last_ebb = Some(ebb); + self.assign_ebb_seq(ebb); } /// Insert `ebb` in the layout before the existing EBB `before`. @@ -100,6 +303,7 @@ impl Layout { } else { self.ebbs[after].next = ebb; } + self.assign_ebb_seq(ebb); } /// Insert `ebb` in the layout *after* the existing EBB `after`. @@ -120,6 +324,7 @@ impl Layout { } else { self.ebbs[before].prev = ebb; } + self.assign_ebb_seq(ebb); } /// Return an iterator over all EBBs in layout order. @@ -143,6 +348,7 @@ struct EbbNode { next: Ebb, first_inst: Inst, last_inst: Inst, + seq: SequenceNumber, } /// Iterate over EBBs in layout order. See `Layout::ebbs()`. @@ -194,19 +400,22 @@ impl Layout { assert_eq!(self.inst_ebb(inst), None); assert!(self.is_ebb_inserted(ebb), "Cannot append instructions to EBB not in layout"); - let ebb_node = &mut self.ebbs[ebb]; { - let inst_node = self.insts.ensure(inst); - inst_node.ebb = ebb; - inst_node.prev = ebb_node.last_inst; - assert_eq!(inst_node.next, NO_INST); + let ebb_node = &mut self.ebbs[ebb]; + { + let inst_node = self.insts.ensure(inst); + inst_node.ebb = ebb; + inst_node.prev = ebb_node.last_inst; + assert_eq!(inst_node.next, NO_INST); + } + if ebb_node.first_inst == NO_INST { + ebb_node.first_inst = inst; + } else { + self.insts[ebb_node.last_inst].next = inst; + } + ebb_node.last_inst = inst; } - if ebb_node.first_inst == NO_INST { - ebb_node.first_inst = inst; - } else { - self.insts[ebb_node.last_inst].next = inst; - } - ebb_node.last_inst = inst; + self.assign_inst_seq(inst); } /// Fetch an ebb's last instruction. @@ -232,6 +441,7 @@ impl Layout { } else { self.insts[after].next = inst; } + self.assign_inst_seq(inst); } /// Iterate over the instructions in `ebb` in layout order. @@ -305,6 +515,8 @@ impl Layout { self.insts[i].ebb = new_ebb; i = self.insts[i].next; } + + self.assign_ebb_seq(new_ebb); } } @@ -313,6 +525,7 @@ struct InstNode { ebb: Ebb, prev: Inst, next: Inst, + seq: SequenceNumber, } /// Iterate over instructions in an EBB in layout order. See `Layout::ebb_insts()`. @@ -665,21 +878,28 @@ impl<'f> Cursor<'f> { mod tests { use super::{Layout, Cursor, CursorPosition}; use entity_map::EntityRef; - use ir::{Ebb, Inst}; + use ir::{Ebb, Inst, ProgramOrder}; + use std::cmp::Ordering; fn verify(layout: &mut Layout, ebbs: &[(Ebb, &[Inst])]) { // Check that EBBs are inserted and instructions belong the right places. // Check forward linkage with iterators. + // Check that layout sequence numbers are strictly monotonic. { + let mut seq = 0; let mut ebb_iter = layout.ebbs(); for &(ebb, insts) in ebbs { assert!(layout.is_ebb_inserted(ebb)); assert_eq!(ebb_iter.next(), Some(ebb)); + assert!(layout.ebbs[ebb].seq > seq); + seq = layout.ebbs[ebb].seq; let mut inst_iter = layout.ebb_insts(ebb); for &inst in insts { assert_eq!(layout.inst_ebb(inst), Some(ebb)); assert_eq!(inst_iter.next(), Some(inst)); + assert!(layout.insts[inst].seq > seq); + seq = layout.insts[inst].seq; } assert_eq!(inst_iter.next(), None); } @@ -1018,5 +1238,10 @@ mod tests { assert_eq!(cur.prev_inst(), None); assert_eq!(cur.prev_ebb(), None); } + + // Check ProgramOrder. + assert_eq!(layout.cmp(e2.into(), e2.into()), Ordering::Equal); + assert_eq!(layout.cmp(e2.into(), i2.into()), Ordering::Less); + assert_eq!(layout.cmp(i3.into(), i2.into()), Ordering::Greater); } } From 27483d93969fd2ab6a0212facefb50e803b2a703 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 5 Jan 2017 14:03:09 -0800 Subject: [PATCH 462/968] Encourage better optimization of ProgramOrder::cmp. The ProgramOrder::cmp() comparison is often used where one or both arguments are statically known to be an Inst or Ebb. Give the compiler a better chance to discover this via inlining and other optimizations. - Make cmp() generic with Into bounds. - Implement the natural From traits for ExpandedProgramPoint. - Make Layout::pp_seq() generic with the same bound. Now, with inlining and constant folding, passing an Inst argument to PO::cmp() will result in a call to a monomorphized Layout::seq::() which can avoid the dynamic match to select a table for looking up the sequence number. The result is that comparing two program points of statically known type results in two direct table lookups and a sequence number comparison. This all uses ExpandedProgramPoint because it is more likely to be transparent to the constant folder than the bit-packed ProgramPoint type. --- lib/cretonne/src/ir/layout.rs | 22 ++++++++++-------- lib/cretonne/src/ir/progpoint.rs | 39 ++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 16f5aa5483..cecc8645ef 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -7,7 +7,7 @@ use std::cmp; use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; -use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; +use ir::progpoint::{ProgramOrder, ExpandedProgramPoint}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not /// contain definitions of instructions or EBBs, but depends on `Inst` and `Ebb` entity references @@ -93,9 +93,12 @@ fn test_midpoint() { } impl ProgramOrder for Layout { - fn cmp(&self, a: ProgramPoint, b: ProgramPoint) -> cmp::Ordering { - let a_seq = self.pp_seq(a); - let b_seq = self.pp_seq(b); + fn cmp(&self, a: A, b: B) -> cmp::Ordering + where A: Into, + B: Into + { + let a_seq = self.seq(a); + let b_seq = self.seq(b); a_seq.cmp(&b_seq) } } @@ -103,8 +106,9 @@ impl ProgramOrder for Layout { // Private methods for dealing with sequence numbers. impl Layout { /// Get the sequence number of a program point that must correspond to an entity in the layout. - fn pp_seq(&self, pp: ProgramPoint) -> SequenceNumber { - match pp.expand() { + fn seq>(&self, pp: PP) -> SequenceNumber { + // When `PP = Inst` or `PP = Ebb`, we expect this dynamic type check to be optimized out. + match pp.into() { ExpandedProgramPoint::Ebb(ebb) => self.ebbs[ebb].seq, ExpandedProgramPoint::Inst(inst) => self.insts[inst].seq, } @@ -1240,8 +1244,8 @@ mod tests { } // Check ProgramOrder. - assert_eq!(layout.cmp(e2.into(), e2.into()), Ordering::Equal); - assert_eq!(layout.cmp(e2.into(), i2.into()), Ordering::Less); - assert_eq!(layout.cmp(i3.into(), i2.into()), Ordering::Greater); + assert_eq!(layout.cmp(e2, e2), Ordering::Equal); + assert_eq!(layout.cmp(e2, i2), Ordering::Less); + assert_eq!(layout.cmp(i3, i2), Ordering::Greater); } } diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index c609a03d8f..ec2aa562cf 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -34,6 +34,7 @@ impl From for ProgramPoint { /// An expanded program point directly exposes the variants, but takes twice the space to /// represent. +#[derive(PartialEq, Eq, Clone, Copy)] pub enum ExpandedProgramPoint { // An instruction in the function. Inst(Inst), @@ -41,20 +42,31 @@ pub enum ExpandedProgramPoint { Ebb(Ebb), } -impl ProgramPoint { - /// Expand compact program point representation. - pub fn expand(self) -> ExpandedProgramPoint { - if self.0 & 1 == 0 { - ExpandedProgramPoint::Inst(Inst::new((self.0 / 2) as usize)) +impl From for ExpandedProgramPoint { + fn from(inst: Inst) -> ExpandedProgramPoint { + ExpandedProgramPoint::Inst(inst) + } +} + +impl From for ExpandedProgramPoint { + fn from(ebb: Ebb) -> ExpandedProgramPoint { + ExpandedProgramPoint::Ebb(ebb) + } +} + +impl From for ExpandedProgramPoint { + fn from(pp: ProgramPoint) -> ExpandedProgramPoint { + if pp.0 & 1 == 0 { + ExpandedProgramPoint::Inst(Inst::new((pp.0 / 2) as usize)) } else { - ExpandedProgramPoint::Ebb(Ebb::new((self.0 / 2) as usize)) + ExpandedProgramPoint::Ebb(Ebb::new((pp.0 / 2) as usize)) } } } impl fmt::Display for ProgramPoint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.expand() { + match (*self).into() { ExpandedProgramPoint::Inst(x) => write!(f, "{}", x), ExpandedProgramPoint::Ebb(x) => write!(f, "{}", x), } @@ -72,9 +84,16 @@ impl fmt::Debug for ProgramPoint { /// `ProgramPoint` objects don't carry enough information to be ordered independently, they need a /// context providing the program order. pub trait ProgramOrder { - /// Compare the program points `a` and `b` relative to this program order. Return `Less` if `a` - /// appears in the program before `b`. - fn cmp(&self, a: ProgramPoint, b: ProgramPoint) -> cmp::Ordering; + /// Compare the program points `a` and `b` relative to this program order. + /// + /// Return `Less` if `a` appears in the program before `b`. + /// + /// This is declared as a generic such that it can be called with `Inst` and `Ebb` arguments + /// directly. Depending on the implementation, there is a good chance performance will be + /// improved for those cases where the type of either argument is known statically. + fn cmp(&self, a: A, b: B) -> cmp::Ordering + where A: Into, + B: Into; } #[cfg(test)] From 94a54eaf3011fcfb4935849e524f4f9e62c1a992 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 5 Jan 2017 10:34:19 -0800 Subject: [PATCH 463/968] Add a LiveRange data structure. We will track live ranges separately for each SSA value, rather than per virtual register like LLVM does. This is the basis for a register allocator, so place it in a new regalloc module. --- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/ir/progpoint.rs | 4 +- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/regalloc/liverange.rs | 510 +++++++++++++++++++++++++ lib/cretonne/src/regalloc/mod.rs | 5 + 5 files changed, 519 insertions(+), 3 deletions(-) create mode 100644 lib/cretonne/src/regalloc/liverange.rs create mode 100644 lib/cretonne/src/regalloc/mod.rs diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 7e05622645..f4b2a337b4 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -28,4 +28,4 @@ pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; pub use ir::builder::InstBuilder; -pub use ir::progpoint::{ProgramPoint, ProgramOrder}; +pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index ec2aa562cf..83c565fca7 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -36,9 +36,9 @@ impl From for ProgramPoint { /// represent. #[derive(PartialEq, Eq, Clone, Copy)] pub enum ExpandedProgramPoint { - // An instruction in the function. + /// An instruction in the function. Inst(Inst), - // An EBB header. + /// An EBB header. Ebb(Ebb), } diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 51a8d9b367..6445d7866b 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -16,6 +16,7 @@ pub mod dominator_tree; pub mod entity_map; pub mod settings; pub mod verifier; +pub mod regalloc; mod write; mod constant_hash; diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs new file mode 100644 index 0000000000..b26dc8e8b1 --- /dev/null +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -0,0 +1,510 @@ +//! Data structure representing the live range of an SSA value. +//! +//! Live ranges are tracked per SSA value, not per variable or virtual register. The live range of +//! an SSA value begins where it is defined and extends to all program points where the value is +//! still needed. +//! +//! # Local Live Ranges +//! +//! Inside a single extended basic block, the live range of a value is always an interval between +//! two program points (if the value is live in the EBB at all). The starting point is either: +//! +//! 1. The instruction that defines the value, or +//! 2. The EBB header, because the value is an argument to the EBB, or +//! 3. The EBB header, because the value is defined in another EBB and live-in to this one. +//! +//! The ending point of the local live range is the last of the following program points in the +//! EBB: +//! +//! 1. The last use in the EBB, where a *use* is an instruction that has the value as an argument. +//! 2. The last branch or jump instruction in the EBB that can reach a use. +//! 3. If the value has no uses anywhere (a *dead value*), the program point that defines it. +//! +//! Note that 2. includes loop back-edges to the same EBB. In general, if a value is defined +//! outside a loop and used inside the loop, it will be live in the entire loop. +//! +//! # Global Live Ranges +//! +//! Values that appear in more than one EBB have a *global live range* which can be seen as the +//! disjoint union of the per-EBB local intervals for all of the EBBs where the value is live. +//! Together with a `ProgramOrder` which provides a linear ordering of the EBBs, the global live +//! range becomes a linear sequence of disjoint intervals, at most one per EBB. +//! +//! In the special case of a dead value, the global live range is a single interval where the start +//! and end points are the same. The global live range of a value is never completely empty. +//! +//! # Register interference +//! +//! The register allocator uses live ranges to determine if values *interfere*, which means that +//! they can't be stored in the same register. Two live ranges interfere if and only if any of +//! their intervals overlap. +//! +//! If one live range ends at an instruction that defines another live range, those two live ranges +//! are not considered to interfere. This is because most ISAs allow instructions to reuse an input +//! register for an output value. If Cretonne gets support for inline assembly, we will need to +//! handle *early clobbers* which are output registers that are not allowed to alias any input +//! registers. +//! +//! If i1 < i2 < i3 are program points, we have: +//! +//! - i1-i2 and i1-i3 interfere because the intervals overlap. +//! - i1-i2 and i2-i3 don't interfere. +//! - i1-i3 and i2-i2 do interfere because the dead def would clobber the register. +//! - i1-i2 and i2-i2 don't interfere. +//! - i2-i3 and i2-i2 do interfere. +//! +//! Because of this behavior around interval end points, live range interference is not completely +//! equivalent to mathematical intersection of open or half-open intervals. +//! +//! # Implementation notes +//! +//! A few notes about the implementation of this data structure. This should not concern someone +//! only looking to use the public interface. +//! +//! ## EBB ordering +//! +//! The relative order of EBBs is used to maintain a sorted list of live-in intervals and to +//! coalesce adjacent live-in intervals when the prior interval covers the whole EBB. This doesn't +//! depend on any property of the program order, so alternative orderings are possible: +//! +//! 1. The EBB layout order. This is what we currently use. +//! 2. A topological order of the dominator tree. All the live-in intervals would come after the +//! def interval. +//! 3. A numerical order by EBB number. Performant because it doesn't need to indirect through the +//! `ProgramOrder` for comparisons. +//! +//! These orderings will cause small differences in coalescing opportinities, but all of them would +//! do a decent job of compressing a long live range. The numerical order might be preferable +//! beacuse: +//! +//! - It has better performance because EBB numbers can be compared directly without any table +//! lookups. +//! - If EBB numbers are not reused, it is safe to allocate new EBBs without getting spurious +//! live-in intervals from any coalesced representations that happen to cross a new EBB. +//! +//! For comparing instructions, the layout order is always what we want. +//! +//! ## Alternative representation +//! +//! Since a local live-in interval always begins at its EBB header, it is uniquely described by its +//! end point instruction alone. We can use the layout to look up the EBB containing the end point. +//! This means that a sorted `Vec` would be enough to represent the set of live-in intervals. +//! +//! Coalescing is an important compression technique because some live ranges can span thousands of +//! EBBs. We can represent that by switching to a sorted `Vec` representation where +//! an `[Ebb, Inst]` pair represents a coalesced range, while an `Inst` entry without a preceeding +//! `Ebb` entry represents a single live-in interval. +//! +//! This representation is more compact for a live range with many uncoalesced live-in intervals. +//! It is more complicated to work with, though, so it is probably not worth it. The performance +//! benefits of switching to a numerical EBB order only appears if the binary search is doing +//! EBB-EBB comparisons. +//! +//! ## B-tree representation +//! +//! A `BTreeMap` could also be used for the live-in intervals. It looks like the +//! standard library B-tree doesn't provide the necessary interface for an efficient implementation +//! of coalescing, so we would need to roll our own. +//! + +use std::cmp::Ordering; +use ir::{Inst, Ebb, ProgramPoint, ProgramOrder}; + +/// Global live range of a single SSA value. +/// +/// As [explained in the module documentation](index.html#local-live-ranges), the live range of an +/// SSA value is the disjoint union of a set of intervals, each local to a single EBB, and with at +/// most one interval per EBB. We further distinguish between: +/// +/// 1. The *def interval* is the local interval in the EBB where the value is defined, and +/// 2. The *live-in intervals* are the local intervals in the remaining EBBs. +/// +/// A live-in interval always begins at the EBB header, while the def interval can begin at the +/// defining instruction, or at the EBB header for an EBB argument value. +/// +/// All values have a def interval, but a large proportion of values don't have any live-in +/// intervals. These are called *local live ranges*. +/// +/// # Program order requirements +/// +/// The internal representation of a `LiveRange` depends on a consistent `ProgramOrder` both for +/// ordering instructions inside an EBB *and* for ordering EBBs. The methods that depend on the +/// ordering take an explicit `ProgramOrder` object, and it is the caller's responsibility to +/// ensure that the provided ordering is consistent between calls. +/// +/// In particular, changing the order of EBBs or inserting new EBBs will invalidate live ranges. +/// +/// Inserting new instructions in the layout is safe, but removing instructions is not. Besides the +/// instructions using or defining their value, `LiveRange` structs can contain references to +/// branch and jump instructions. +pub struct LiveRange { + /// The instruction or EBB header where this value is defined. + def_begin: ProgramPoint, + + /// The end point of the def interval. This must always belong to the same EBB as `def_begin`. + /// + /// We always have `def_begin <= def_end` with equality implying a dead def live range with no + /// uses. + def_end: ProgramPoint, + + /// Additional live-in intervals sorted in program order. + /// + /// This vector is empty for most values which are only used in one EBB. + /// + /// Invariants: + /// + /// - Sorted, disjoint: For all `i < j`: `liveins[i].end < liveins[j].begin`. + /// - Not overlapping defining EBB: For all `i`: + /// `liveins[i].end < def_begin` or `liveins[i].begin > def_end`. + liveins: Vec, +} + +/// An additional contiguous interval of a global live range. +/// +/// This represents a live-in interval for a single EBB, or a coalesced set of live-in intervals +/// for contiguous EBBs where all but the last live-in inteval covers the whole EBB. +/// +struct Interval { + /// Interval starting point. + /// + /// Since this interval does not represent the def of the value, it must begin at an EBB header + /// where the value is live-in. + begin: Ebb, + + /// Interval end point. + /// + /// The only interval end point that can be an EBB header is `def_end` above in a dead def + /// live range for an unused EBB argument. All other intervals must end at an instruction -- + /// either the last use in the EBB or the last branch/jump that can reach a use. + /// + /// When this represents multiple contiguous live-in intervals, this is the end point of the + /// last interval. The other intervals end at the terminator instructions of their respective + /// EBB. + end: Inst, +} + +impl Interval { + /// Extend the interval end point to reach `to`, but only if it would make the interval longer. + fn extend_to(&mut self, to: Inst, order: &PO) { + if order.cmp(to, self.end) == Ordering::Greater { + self.end = to; + } + } +} + +impl LiveRange { + /// Create a new live range defined at `def`. + /// + /// The live range will be created as dead, but it can be extended with `extend_in_ebb()`. + pub fn new>(def: PP) -> LiveRange { + let def = def.into(); + LiveRange { + def_begin: def, + def_end: def, + liveins: Vec::new(), + } + } + + /// Find the live-in interval containing `ebb`, if any. + /// + /// Return `Ok(n)` if `liveins[n]` already contains `ebb`. + /// Otherwise, return `Err(n)` with the index where such an interval should be inserted. + fn find_ebb_interval(&self, ebb: Ebb, order: &PO) -> Result { + self.liveins + .binary_search_by(|intv| order.cmp(intv.begin, ebb)) + .or_else(|n| { + // The interval at `n-1` may cover `ebb`. + if n > 0 && order.cmp(self.liveins[n - 1].end, ebb) == Ordering::Greater { + Ok(n - 1) + } else { + Err(n) + } + }) + } + + /// Extend the local interval for `ebb` so it reaches `to` which must belong to `ebb`. + /// Create a live-in interval if necessary. + /// + /// If the live range already has a local interval in `ebb`, extend its end point so it + /// includes `to`, and return false. + /// + /// If the live range did not previously have a local interval in `ebb`, add one so the value + /// is live-in to `ebb`, extending to `to`. Return true. + /// + /// The return value can be used to detect if we just learned that the value is live-in to + /// `ebb`. This can trigger recursive extensions in `ebb`'s CFG precedessor blocks. + pub fn extend_in_ebb(&mut self, ebb: Ebb, to: Inst, order: &PO) -> bool { + // First check if we're extending the def interval. + // + // We're assuming here that `to` never precedes `def_begin` in the same EBB, but we can't + // check it without a method for getting `to`'s EBB. + if order.cmp(ebb, self.def_end) != Ordering::Greater && + order.cmp(to, self.def_begin) != Ordering::Less { + let to_pp = to.into(); + assert_ne!(to_pp, + self.def_begin, + "Can't use value in the defining instruction."); + if order.cmp(to, self.def_end) == Ordering::Greater { + self.def_end = to_pp; + } + return false; + } + + // Now check if we're extending any of the existing live-in intervals. + match self.find_ebb_interval(ebb, order) { + Ok(n) => { + // We have an interval that contains `ebb`, so we can simply extend it. + self.liveins[n].extend_to(to, order); + // TODO: Check if this interval can be coalesced with the `n+1` one. + false + } + Err(n) => { + // Insert a new live-in interval at `n`. + // TODO: Check if this interval can be coalesced with the `n-1` or `n+1` one (or + // both). + self.liveins.insert(n, + Interval { + begin: ebb, + end: to, + }); + true + } + } + } + + /// Is this the live range of a dead value? + /// + /// A dead value has no uses, and its live range ends at the same program point where it is + /// defined. + pub fn is_dead(&self) -> bool { + self.def_begin == self.def_end + } + + /// Is this a local live range? + /// + /// A local live range is only used in the same EBB where it was defined. It is allowed to span + /// multiple basic blocks within that EBB. + pub fn is_local(&self) -> bool { + self.liveins.is_empty() + } + + /// Get the program point where this live range is defined. + /// + /// This will be an EBB header when the value is an EBB argument, otherwise it is the defining + /// instruction. + pub fn def(&self) -> ProgramPoint { + self.def_begin + } + + /// Get the local end-point of this live range in the EBB where it is defined. + /// + /// This can be the EBB header itself in the case of a dead EBB argument. + /// Otherwise, it will be the last local use or branch/jump that can reach a use. + pub fn def_local_end(&self) -> ProgramPoint { + self.def_end + } + + /// Get the local end-point of this live range in an EBB where it is live-in. + /// + /// If this live range is not live-in to `ebb`, return `None`. Otherwise, return the end-point + /// of this live range's local interval in `ebb`. + /// + /// If the live range is live through all of `ebb`, the terminator of `ebb` is a correct + /// answer, but it is also possible that an even later program point is returned. So don't + /// depend on the returned `Inst` to belong to `ebb`. + pub fn livein_local_end(&self, ebb: Ebb, order: &PO) -> Option { + self.find_ebb_interval(ebb, order).ok().map(|n| self.liveins[n].end) + } +} + +#[cfg(test)] +mod tests { + use super::LiveRange; + use ir::{Inst, Ebb}; + use entity_map::EntityRef; + use ir::{ProgramOrder, ExpandedProgramPoint}; + use std::cmp::Ordering; + + // Dummy program order which simply compares indexes. + // It is assumed that EBBs have indexes that are multiples of 10, and instructions have indexes + // in between. + struct ProgOrder {} + + impl ProgramOrder for ProgOrder { + fn cmp(&self, a: A, b: B) -> Ordering + where A: Into, + B: Into + { + fn idx(pp: ExpandedProgramPoint) -> usize { + match pp { + ExpandedProgramPoint::Inst(i) => i.index(), + ExpandedProgramPoint::Ebb(e) => e.index(), + } + } + + let ia = idx(a.into()); + let ib = idx(b.into()); + ia.cmp(&ib) + } + } + + impl ProgOrder { + // Get the EBB corresponding to `inst`. + fn inst_ebb(&self, inst: Inst) -> Ebb { + let i = inst.index(); + Ebb::new(i - i % 10) + } + + // Get the EBB of a program point. + fn pp_ebb>(&self, pp: PP) -> Ebb { + match pp.into() { + ExpandedProgramPoint::Inst(i) => self.inst_ebb(i), + ExpandedProgramPoint::Ebb(e) => e, + } + } + + // Validate the live range invariants. + fn validate(&self, lr: &LiveRange) { + // The def interval must cover a single EBB. + let def_ebb = self.pp_ebb(lr.def_begin); + assert_eq!(def_ebb, self.pp_ebb(lr.def_end)); + + // Check that the def interval isn't backwards. + match self.cmp(lr.def_begin, lr.def_end) { + Ordering::Equal => assert!(lr.liveins.is_empty()), + Ordering::Greater => { + panic!("Backwards def interval: {}-{}", lr.def_begin, lr.def_end) + } + Ordering::Less => {} + } + + // Check the live-in intervals. + let mut prev_end = None; + for li in &lr.liveins { + assert_eq!(self.cmp(li.begin, li.end), Ordering::Less); + if let Some(e) = prev_end { + assert_eq!(self.cmp(e, li.begin), Ordering::Less); + } + + assert!(self.cmp(lr.def_end, li.begin) == Ordering::Less || + self.cmp(lr.def_begin, li.end) == Ordering::Greater, + "Interval can't overlap the def EBB"); + + // Save for next round. + prev_end = Some(li.end); + } + + } + } + + // Singleton ProgramOrder for tests below. + const PO: &'static ProgOrder = &ProgOrder {}; + + #[test] + fn dead_def_range() { + let i1 = Inst::new(1); + let e2 = Ebb::new(2); + let lr = LiveRange::new(i1); + assert!(lr.is_dead()); + assert!(lr.is_local()); + assert_eq!(lr.def(), i1.into()); + assert_eq!(lr.def_local_end(), i1.into()); + assert_eq!(lr.livein_local_end(e2, PO), None); + PO.validate(&lr); + } + + #[test] + fn dead_arg_range() { + let e2 = Ebb::new(2); + let lr = LiveRange::new(e2); + assert!(lr.is_dead()); + assert!(lr.is_local()); + assert_eq!(lr.def(), e2.into()); + assert_eq!(lr.def_local_end(), e2.into()); + // The def interval of an EBB arg does not count as live-in. + assert_eq!(lr.livein_local_end(e2, PO), None); + PO.validate(&lr); + } + + #[test] + fn local_def() { + let e10 = Ebb::new(10); + let i11 = Inst::new(11); + let i12 = Inst::new(12); + let i13 = Inst::new(13); + let mut lr = LiveRange::new(i11); + + assert_eq!(lr.extend_in_ebb(e10, i13, PO), false); + PO.validate(&lr); + assert!(!lr.is_dead()); + assert!(lr.is_local()); + assert_eq!(lr.def(), i11.into()); + assert_eq!(lr.def_local_end(), i13.into()); + + // Extending to an already covered inst should not change anything. + assert_eq!(lr.extend_in_ebb(e10, i12, PO), false); + PO.validate(&lr); + assert_eq!(lr.def(), i11.into()); + assert_eq!(lr.def_local_end(), i13.into()); + } + + #[test] + fn local_arg() { + let e10 = Ebb::new(10); + let i11 = Inst::new(11); + let i12 = Inst::new(12); + let i13 = Inst::new(13); + let mut lr = LiveRange::new(e10); + + // Extending a dead EBB arg in its own block should not indicate that a live-in interval + // was created. + assert_eq!(lr.extend_in_ebb(e10, i12, PO), false); + PO.validate(&lr); + assert!(!lr.is_dead()); + assert!(lr.is_local()); + assert_eq!(lr.def(), e10.into()); + assert_eq!(lr.def_local_end(), i12.into()); + + // Extending to an already covered inst should not change anything. + assert_eq!(lr.extend_in_ebb(e10, i11, PO), false); + PO.validate(&lr); + assert_eq!(lr.def(), e10.into()); + assert_eq!(lr.def_local_end(), i12.into()); + + // Extending further. + assert_eq!(lr.extend_in_ebb(e10, i13, PO), false); + PO.validate(&lr); + assert_eq!(lr.def(), e10.into()); + assert_eq!(lr.def_local_end(), i13.into()); + } + + #[test] + fn global_def() { + let e10 = Ebb::new(10); + let i11 = Inst::new(11); + let i12 = Inst::new(12); + let e20 = Ebb::new(20); + let i21 = Inst::new(21); + let i22 = Inst::new(22); + let i23 = Inst::new(23); + let mut lr = LiveRange::new(i11); + + assert_eq!(lr.extend_in_ebb(e10, i12, PO), false); + + // Adding a live-in interval. + assert_eq!(lr.extend_in_ebb(e20, i22, PO), true); + PO.validate(&lr); + assert_eq!(lr.livein_local_end(e20, PO), Some(i22)); + + // Non-extending the live-in. + assert_eq!(lr.extend_in_ebb(e20, i21, PO), false); + assert_eq!(lr.livein_local_end(e20, PO), Some(i22)); + + // Extending the existing live-in. + assert_eq!(lr.extend_in_ebb(e20, i23, PO), false); + PO.validate(&lr); + assert_eq!(lr.livein_local_end(e20, PO), Some(i23)); + } + + // TODO: Add more tests that exercise the binary search and the coalescing algorithm. +} diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs new file mode 100644 index 0000000000..0d6c9c9574 --- /dev/null +++ b/lib/cretonne/src/regalloc/mod.rs @@ -0,0 +1,5 @@ +//! Register allocation. +//! +//! This module contains data structures and algorithms used for register allocation. + +pub mod liverange; From 56177ce351562632e3c428ebeafa6803631dcb1b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 10 Jan 2017 10:01:05 -0800 Subject: [PATCH 464/968] Implement a SparseMap data structure. This implements the classic Briggs/Torczon sparse set construct. Adapt it to our existing EntityRef infrastructure so we can use types keys instead of just integers like the original paper does. Also provide a SparseSet type alias which implements a sparse set of entity refeences. This is actually closer to what the original paper describes. --- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/sparse_map.rs | 297 +++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 lib/cretonne/src/sparse_map.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 6445d7866b..67694e139f 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -14,6 +14,7 @@ pub mod isa; pub mod cfg; pub mod dominator_tree; pub mod entity_map; +pub mod sparse_map; pub mod settings; pub mod verifier; pub mod regalloc; diff --git a/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs new file mode 100644 index 0000000000..5194d8a760 --- /dev/null +++ b/lib/cretonne/src/sparse_map.rs @@ -0,0 +1,297 @@ +//! Sparse mapping of entity references to larger value types. +//! +//! This module provides a `SparseMap` data structure which implements a sparse mapping from an +//! `EntityRef` key to a value type that may be on the larger side. This implementation is based on +//! the paper: +//! +//! > Briggs, Torczon, *An efficient representation for sparse sets*, +//! ACM Letters on Programming Languages and Systems, Volume 2, Issue 1-4, March-Dec. 1993. +//! +//! A `SparseMap` map provides: +//! +//! - Memory usage equivalent to `EntityMap` + `Vec`, so much smaller than +//! `EntityMap` for sparse mappings of larger `V` types. +//! - Constant time lookup, slightly slower than `EntityMap`. +//! - A very fast, constant time `clear()` operation. +//! - Fast insert and erase operations. +//! - Stable iteration that is as fast as a `Vec`. +//! +//! # Compared to `EntityMap` +//! +//! When should we use a `SparseMap` instead of a secondary `EntityMap`? First of all, `SparseMap` +//! does not provide the functionality of a primary `EntityMap` which can allocate and assign +//! entity references to objects as they are pushed onto the map. It is only the secondary +//! entity maps that can be replaced with a `SparseMap`. +//! +//! - A secondary entity map requires its values to implement `Default`, and it is a bit loose +//! about creating new mappings to the default value. It doesn't distinguish clearly between an +//! unmapped key and one that maps to the default value. `SparseMap` does not require `Default` +//! values, and it tracks accurately if a key has been mapped or not. +//! - Iterating over the contants of an `EntityMap` is linear in the size of the *key space*, while +//! iterating over a `SparseMap` is linear in the number of elements in the mapping. This is an +//! advantage precisely when the mapping is sparse. +//! - `SparseMap::clear()` is constant time and super-fast. `EntityMap::clear()` is linear in the +//! size of the key space. (Or, rather the required `resize()` call following the `clear()` is). +//! - `SparseMap` requires the values to implement `SparseMapValue` which means that they must +//! contain their own key. + +use entity_map::{EntityRef, EntityMap}; +use std::mem; +use std::u32; + +/// Trait for extracting keys from values stored in a `SparseMap`. +/// +/// All values stored in a `SparseMap` must keep track of their own key in the map and implement +/// this trait to provide access to the key. +pub trait SparseMapValue { + /// Get the key of this sparse map value. This key is not alowed to change while the value + /// is a member of the map. + fn key(&self) -> K; +} + +/// A sparse mapping of entity references. +pub struct SparseMap + where K: EntityRef, + V: SparseMapValue +{ + sparse: EntityMap, + dense: Vec, +} + +impl SparseMap + where K: EntityRef, + V: SparseMapValue +{ + /// Create a new empty mapping. + pub fn new() -> Self { + SparseMap { + sparse: EntityMap::new(), + dense: Vec::new(), + } + } + + /// Returns the number of elements in the map. + pub fn len(&self) -> usize { + self.dense.len() + } + + /// Returns true is the map contains no elements. + pub fn is_empty(&self) -> bool { + self.dense.is_empty() + } + + /// Returns a reference to the value corresponding to the key. + pub fn get(&self, key: K) -> Option<&V> { + if let Some(idx) = self.sparse.get(key).cloned() { + if let Some(entry) = self.dense.get(idx as usize) { + if entry.key() == key { + return Some(entry); + } + } + } + None + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// Note that the returned value must not be mutated in a way that would change its key. This + /// would invalidate the sparse set data structure. + pub fn get_mut(&mut self, key: K) -> Option<&mut V> { + if let Some(idx) = self.sparse.get(key).cloned() { + if let Some(entry) = self.dense.get_mut(idx as usize) { + if entry.key() == key { + return Some(entry); + } + } + } + None + } + + /// Return the index into `dense` of the value corresponding to `key`. + fn index(&self, key: K) -> Option { + if let Some(idx) = self.sparse.get(key).cloned() { + let idx = idx as usize; + if let Some(entry) = self.dense.get(idx) { + if entry.key() == key { + return Some(idx); + } + } + } + None + } + + /// Insert a value into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// + /// If the map did have this key present, the value is updated, and the old value is returned. + /// + /// It is not necessary to provide a key since the value knows its own key already. + pub fn insert(&mut self, value: V) -> Option { + let key = value.key(); + + // Replace the existing entry for `key` if there is one. + if let Some(entry) = self.get_mut(key) { + return Some(mem::replace(entry, value)); + } + + // There was no previous entry for `key`. Add it to the end of `dense`. + let idx = self.dense.len(); + assert!(idx <= u32::MAX as usize, "SparseMap overflow"); + self.dense.push(value); + *self.sparse.ensure(key) = idx as u32; + None + } + + /// Remove a value from the map and return it. + pub fn remove(&mut self, key: K) -> Option { + if let Some(idx) = self.index(key) { + let back = self.dense.pop().unwrap(); + + // Are we popping the back of `dense`? + if idx == self.dense.len() { + return Some(back); + } + + // We're removing an element from the middle of `dense`. + // Replace the element at `idx` with the back of `dense`. + // Repair `sparse` first. + self.sparse[back.key()] = idx as u32; + return Some(mem::replace(&mut self.dense[idx], back)); + } + + // Nothing to remove. + None + } +} + +/// Any `EntityRef` can be used as a sparse map value representing itself. +impl SparseMapValue for T + where T: EntityRef +{ + fn key(&self) -> T { + *self + } +} + +/// A sparse set of entity references. +/// +/// Any type that implements `EntityRef` can be used as a sparse set value too. +pub type SparseSet = SparseMap; + +#[cfg(test)] +mod tests { + use super::*; + use entity_map::EntityRef; + use ir::Inst; + + // Mock key-value object for testing. + #[derive(PartialEq, Eq, Debug)] + struct Obj(Inst, &'static str); + + impl SparseMapValue for Obj { + fn key(&self) -> Inst { + self.0 + } + } + + #[test] + fn empty_immutable_map() { + let i1 = Inst::new(1); + let map: SparseMap = SparseMap::new(); + + assert!(map.is_empty()); + assert_eq!(map.len(), 0); + assert_eq!(map.get(i1), None); + } + + #[test] + fn single_entry() { + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let mut map = SparseMap::new(); + + assert!(map.is_empty()); + assert_eq!(map.len(), 0); + assert_eq!(map.get(i1), None); + assert_eq!(map.get_mut(i1), None); + assert_eq!(map.remove(i1), None); + + assert_eq!(map.insert(Obj(i1, "hi")), None); + assert!(!map.is_empty()); + assert_eq!(map.len(), 1); + assert_eq!(map.get(i0), None); + assert_eq!(map.get(i1), Some(&Obj(i1, "hi"))); + assert_eq!(map.get(i2), None); + assert_eq!(map.get_mut(i0), None); + assert_eq!(map.get_mut(i1), Some(&mut Obj(i1, "hi"))); + assert_eq!(map.get_mut(i2), None); + + assert_eq!(map.remove(i0), None); + assert_eq!(map.remove(i2), None); + assert_eq!(map.remove(i1), Some(Obj(i1, "hi"))); + assert_eq!(map.len(), 0); + assert_eq!(map.get(i1), None); + assert_eq!(map.get_mut(i1), None); + assert_eq!(map.remove(i0), None); + assert_eq!(map.remove(i1), None); + assert_eq!(map.remove(i2), None); + } + + #[test] + fn multiple_entries() { + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let i3 = Inst::new(3); + let mut map = SparseMap::new(); + + assert_eq!(map.insert(Obj(i2, "foo")), None); + assert_eq!(map.insert(Obj(i1, "bar")), None); + assert_eq!(map.insert(Obj(i0, "baz")), None); + + assert_eq!(map.len(), 3); + assert_eq!(map.get(i0), Some(&Obj(i0, "baz"))); + assert_eq!(map.get(i1), Some(&Obj(i1, "bar"))); + assert_eq!(map.get(i2), Some(&Obj(i2, "foo"))); + assert_eq!(map.get(i3), None); + + // Remove front object, causing back to be swapped down. + assert_eq!(map.remove(i1), Some(Obj(i1, "bar"))); + assert_eq!(map.len(), 2); + assert_eq!(map.get(i0), Some(&Obj(i0, "baz"))); + assert_eq!(map.get(i1), None); + assert_eq!(map.get(i2), Some(&Obj(i2, "foo"))); + assert_eq!(map.get(i3), None); + + // Reinsert something at a previously used key. + assert_eq!(map.insert(Obj(i1, "barbar")), None); + assert_eq!(map.len(), 3); + assert_eq!(map.get(i0), Some(&Obj(i0, "baz"))); + assert_eq!(map.get(i1), Some(&Obj(i1, "barbar"))); + assert_eq!(map.get(i2), Some(&Obj(i2, "foo"))); + assert_eq!(map.get(i3), None); + + // Replace an entry. + assert_eq!(map.insert(Obj(i0, "bazbaz")), Some(Obj(i0, "baz"))); + assert_eq!(map.len(), 3); + assert_eq!(map.get(i0), Some(&Obj(i0, "bazbaz"))); + assert_eq!(map.get(i1), Some(&Obj(i1, "barbar"))); + assert_eq!(map.get(i2), Some(&Obj(i2, "foo"))); + assert_eq!(map.get(i3), None); + } + + #[test] + fn entity_set() { + let i0 = Inst::new(0); + let i1 = Inst::new(1); + let mut set = SparseSet::new(); + + assert_eq!(set.insert(i0), None); + assert_eq!(set.insert(i0), Some(i0)); + assert_eq!(set.insert(i1), None); + assert_eq!(set.get(i0), Some(&i0)); + assert_eq!(set.get(i1), Some(&i1)); + } +} From a26a8b5f0d0af9526370db4d268806c732fcaffc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 10 Jan 2017 13:51:20 -0800 Subject: [PATCH 465/968] Add iteration support to SparseMap. This is simply the slice iterator for the dense vector. - map.values() returns an iterator with references to the values. - for i in &map iterates over references to the values. --- lib/cretonne/src/sparse_map.rs | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs index 5194d8a760..9f0d93a55a 100644 --- a/lib/cretonne/src/sparse_map.rs +++ b/lib/cretonne/src/sparse_map.rs @@ -37,6 +37,7 @@ use entity_map::{EntityRef, EntityMap}; use std::mem; +use std::slice; use std::u32; /// Trait for extracting keys from values stored in a `SparseMap`. @@ -163,6 +164,28 @@ impl SparseMap // Nothing to remove. None } + + /// Get an iterator over the values in the map. + /// + /// The iteration order is entirely determined by the preceding sequence of `insert` and + /// `remove` operations. In particular, if no elements were removed, this is the insertion + /// order. + pub fn values(&self) -> slice::Iter { + self.dense.iter() + } +} + +/// Iterating over the elements of a set. +impl<'a, K, V> IntoIterator for &'a SparseMap + where K: EntityRef, + V: SparseMapValue +{ + type Item = &'a V; + type IntoIter = slice::Iter<'a, V>; + + fn into_iter(self) -> Self::IntoIter { + self.values() + } } /// Any `EntityRef` can be used as a sparse map value representing itself. @@ -203,6 +226,7 @@ mod tests { assert!(map.is_empty()); assert_eq!(map.len(), 0); assert_eq!(map.get(i1), None); + assert_eq!(map.values().count(), 0); } #[test] @@ -251,6 +275,10 @@ mod tests { assert_eq!(map.insert(Obj(i1, "bar")), None); assert_eq!(map.insert(Obj(i0, "baz")), None); + // Iteration order = insertion order when nothing has been removed yet. + assert_eq!(map.values().map(|obj| obj.1).collect::>(), + ["foo", "bar", "baz"]); + assert_eq!(map.len(), 3); assert_eq!(map.get(i0), Some(&Obj(i0, "baz"))); assert_eq!(map.get(i1), Some(&Obj(i1, "bar"))); @@ -280,6 +308,13 @@ mod tests { assert_eq!(map.get(i1), Some(&Obj(i1, "barbar"))); assert_eq!(map.get(i2), Some(&Obj(i2, "foo"))); assert_eq!(map.get(i3), None); + + // Check the reference `IntoIter` impl. + let mut v = Vec::new(); + for i in &map { + v.push(i.1); + } + assert_eq!(v.len(), map.len()); } #[test] From 29777e26eedf8313ab97bf1b74b65d9a94aa0ad3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 10 Jan 2017 14:21:56 -0800 Subject: [PATCH 466/968] Allow live ranges to be values in a SparseMap. This requires the value number to be stored in the live range itself. --- lib/cretonne/src/regalloc/liverange.rs | 36 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index b26dc8e8b1..e192efdc4d 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -108,7 +108,8 @@ //! use std::cmp::Ordering; -use ir::{Inst, Ebb, ProgramPoint, ProgramOrder}; +use ir::{Inst, Ebb, Value, ProgramPoint, ProgramOrder}; +use sparse_map::SparseMapValue; /// Global live range of a single SSA value. /// @@ -138,6 +139,10 @@ use ir::{Inst, Ebb, ProgramPoint, ProgramOrder}; /// instructions using or defining their value, `LiveRange` structs can contain references to /// branch and jump instructions. pub struct LiveRange { + /// The value described by this live range. + /// This member can't be modified in case the live range is stored in a `SparseMap`. + value: Value, + /// The instruction or EBB header where this value is defined. def_begin: ProgramPoint, @@ -193,12 +198,13 @@ impl Interval { } impl LiveRange { - /// Create a new live range defined at `def`. + /// Create a new live range for `value` defined at `def`. /// /// The live range will be created as dead, but it can be extended with `extend_in_ebb()`. - pub fn new>(def: PP) -> LiveRange { + pub fn new>(value: Value, def: PP) -> LiveRange { let def = def.into(); LiveRange { + value: value, def_begin: def, def_end: def, liveins: Vec::new(), @@ -317,10 +323,17 @@ impl LiveRange { } } +/// Allow a `LiveRange` to be stored in a `SparseMap` indexed by values. +impl SparseMapValue for LiveRange { + fn key(&self) -> Value { + self.value + } +} + #[cfg(test)] mod tests { use super::LiveRange; - use ir::{Inst, Ebb}; + use ir::{Inst, Ebb, Value}; use entity_map::EntityRef; use ir::{ProgramOrder, ExpandedProgramPoint}; use std::cmp::Ordering; @@ -402,9 +415,10 @@ mod tests { #[test] fn dead_def_range() { + let v0 = Value::new(0); let i1 = Inst::new(1); let e2 = Ebb::new(2); - let lr = LiveRange::new(i1); + let lr = LiveRange::new(v0, i1); assert!(lr.is_dead()); assert!(lr.is_local()); assert_eq!(lr.def(), i1.into()); @@ -415,8 +429,9 @@ mod tests { #[test] fn dead_arg_range() { + let v0 = Value::new(0); let e2 = Ebb::new(2); - let lr = LiveRange::new(e2); + let lr = LiveRange::new(v0, e2); assert!(lr.is_dead()); assert!(lr.is_local()); assert_eq!(lr.def(), e2.into()); @@ -428,11 +443,12 @@ mod tests { #[test] fn local_def() { + let v0 = Value::new(0); let e10 = Ebb::new(10); let i11 = Inst::new(11); let i12 = Inst::new(12); let i13 = Inst::new(13); - let mut lr = LiveRange::new(i11); + let mut lr = LiveRange::new(v0, i11); assert_eq!(lr.extend_in_ebb(e10, i13, PO), false); PO.validate(&lr); @@ -450,11 +466,12 @@ mod tests { #[test] fn local_arg() { + let v0 = Value::new(0); let e10 = Ebb::new(10); let i11 = Inst::new(11); let i12 = Inst::new(12); let i13 = Inst::new(13); - let mut lr = LiveRange::new(e10); + let mut lr = LiveRange::new(v0, e10); // Extending a dead EBB arg in its own block should not indicate that a live-in interval // was created. @@ -480,6 +497,7 @@ mod tests { #[test] fn global_def() { + let v0 = Value::new(0); let e10 = Ebb::new(10); let i11 = Inst::new(11); let i12 = Inst::new(12); @@ -487,7 +505,7 @@ mod tests { let i21 = Inst::new(21); let i22 = Inst::new(22); let i23 = Inst::new(23); - let mut lr = LiveRange::new(i11); + let mut lr = LiveRange::new(v0, i11); assert_eq!(lr.extend_in_ebb(e10, i12, PO), false); From 8390c829d353bf1deca9fe02443b951e75e40191 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 10 Jan 2017 15:33:03 -0800 Subject: [PATCH 467/968] Implement DoubleEndedIterator for the ebb_insts() iterator. Make it possible to iterate backwards over the instructions in an EBB. --- lib/cretonne/src/ir/layout.rs | 38 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index cecc8645ef..7f2497df1b 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -452,7 +452,8 @@ impl Layout { pub fn ebb_insts<'f>(&'f self, ebb: Ebb) -> Insts<'f> { Insts { layout: self, - next: self.ebbs[ebb].first_inst.wrap(), + head: self.ebbs[ebb].first_inst.wrap(), + tail: self.ebbs[ebb].last_inst.wrap(), } } @@ -535,20 +536,39 @@ struct InstNode { /// Iterate over instructions in an EBB in layout order. See `Layout::ebb_insts()`. pub struct Insts<'f> { layout: &'f Layout, - next: Option, + head: Option, + tail: Option, } impl<'f> Iterator for Insts<'f> { type Item = Inst; fn next(&mut self) -> Option { - match self.next { - Some(inst) => { - self.next = self.layout.insts[inst].next.wrap(); - Some(inst) + let rval = self.head; + if let Some(inst) = rval { + if self.head == self.tail { + self.head = None; + self.tail = None; + } else { + self.head = Some(self.layout.insts[inst].next); } - None => None, } + rval + } +} + +impl<'f> DoubleEndedIterator for Insts<'f> { + fn next_back(&mut self) -> Option { + let rval = self.tail; + if let Some(inst) = rval { + if self.head == self.tail { + self.head = None; + self.tail = None; + } else { + self.tail = Some(self.layout.insts[inst].prev); + } + } + rval } } @@ -1077,6 +1097,10 @@ mod tests { let v: Vec = layout.ebb_insts(e1).collect(); assert_eq!(v, [i1, i2]); + // Test double-ended instruction iterator. + let v: Vec = layout.ebb_insts(e1).rev().collect(); + assert_eq!(v, [i2, i1]); + layout.append_inst(i0, e1); verify(&mut layout, &[(e1, &[i1, i2, i0])]); From 8033deda3ac152d0b95bb0ad80b419625c3f0d58 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 13 Jan 2017 11:42:26 -0800 Subject: [PATCH 468/968] Add a liveness analysis. This code is best tested with larger functions with more EBBs. Perhaps a new file-test category is in order? --- lib/cretonne/src/ir/instructions.rs | 8 +- lib/cretonne/src/regalloc/liveness.rs | 294 ++++++++++++++++++++++++++ lib/cretonne/src/regalloc/mod.rs | 1 + lib/cretonne/src/sparse_map.rs | 5 + 4 files changed, 304 insertions(+), 4 deletions(-) create mode 100644 lib/cretonne/src/regalloc/liveness.rs diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 21f9b92c3c..eb0b1e5bd4 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -412,8 +412,8 @@ pub struct ReturnData { impl InstructionData { /// Execute a closure once for each argument to this instruction. /// See also the `arguments()` method. - pub fn each_arg(&self, func: F) - where F: Fn(Value) + pub fn each_arg(&self, mut func: F) + where F: FnMut(Value) { for part in &self.arguments() { for &arg in part.iter() { @@ -424,8 +424,8 @@ impl InstructionData { /// Execute a closure with a mutable reference to each argument to this instruction. /// See also the `arguments_mut()` method. - pub fn each_arg_mut(&mut self, func: F) - where F: Fn(&mut Value) + pub fn each_arg_mut(&mut self, mut func: F) + where F: FnMut(&mut Value) { for part in &mut self.arguments_mut() { for arg in part.iter_mut() { diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs new file mode 100644 index 0000000000..c7646ed701 --- /dev/null +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -0,0 +1,294 @@ +//! Liveness analysis for SSA values. +//! +//! This module computes the live range of all the SSA values in a function and produces a +//! `LiveRange` instance for each. +//! +//! +//! # Liveness consumers +//! +//! The primary consumer of the liveness analysis is the SSA coloring pass which goes through each +//! EBB and assigns a register to the defined values. This algorithm needs to maintain a set of the +//! curently live values as it is iterating down the instructions in the EBB. It asks the following +//! questions: +//! +//! - What is the set of live values at the entry to the EBB? +//! - When moving past a use of a value, is that value still alive in the EBB, or was that the last +//! use? +//! - When moving past a branch, which of the live values are still live below the branch? +//! +//! The set of `LiveRange` instances can answer these questions through their `def_local_end` and +//! `livein_local_end` queries. The coloring algorithm visits EBBs in a topological order of the +//! dominator tree, so it can compute the set of live values at the begining of an EBB by starting +//! from the set of live values at the dominating branch instruction and filtering it with +//! `livein_local_end`. These sets do not need to be stored in the liveness analysis. +//! +//! The secondary consumer of the liveness analysis is the spilling pass which needs to count the +//! number of live values at every program point and insert spill code until the number of +//! registers needed is small enough. +//! +//! +//! # Alternative algorithms +//! +//! A number of different liveness analysis algorithms exist, so it is worthwhile to look at a few +//! alternatives. +//! +//! ## Dataflow equations +//! +//! The classic *live variables analysis* that you will find in all compiler books from the +//! previous century does not depend on SSA form. It is typically implemented by iteratively +//! solving dataflow equations on bitvectors of variables. The result is a live-out bitvector of +//! variables for every basic block in the program. +//! +//! This algorithm has some disadvantages that makes us look elsewhere: +//! +//! - Quadratic memory use. We need a bit per variable per basic block in the function. +//! - Sparse representation. In practice, the majority of SSA values never leave their basic block, +//! and those that do span basic blocks rarely span a large number of basic blocks. This makes +//! the bitvectors quite sparse. +//! - Traditionally, the dataflow equations were solved for real program *variables* which does not +//! include temporaries used in evaluating expressions. We have an SSA form program which blurs +//! the distinction between temporaries and variables. This makes the quadratic memory problem +//! worse because there are many more SSA values than there was variables in the original +//! program, and we don't know a priori which SSA values leave their basic block. +//! - Missing last-use information. For values that are not live-out of a basic block, we would +//! need to store information about the last use in the block somewhere. LLVM stores this +//! information as a 'kill bit' on the last use in the IR. Maintaining these kill bits has been a +//! source of problems for LLVM's register allocator. +//! +//! Dataflow equations can detect when a variable is used uninitialized, and they can handle +//! multiple definitions of the same variable. We don't need this generality since we already have +//! a program in SSA form. +//! +//! ## LLVM's liveness analysis +//! +//! LLVM's register allocator computes liveness per *virtual register*, where a virtual register is +//! a disjoint union of related SSA values that should be assigned to the same physical register. +//! It uses a compact data structure very similar to our `LiveRange`. The important difference is +//! that Cretonne's `LiveRange` only describes a single SSA value, while LLVM's `LiveInterval` +//! describes the live range of a virtual register *and* which one of the related SSA values is +//! live at any given program point. +//! +//! LLVM computes the live range of each virtual register independently by using the use-def chains +//! that are baked into its IR. The algorithm for a single virtual register is: +//! +//! 1. Initialize the live range with a single-instruction snippet of liveness at each def, using +//! the def-chain. This does not include any phi-values. +//! 2. Go through the virtual register's use chain and perform the following steps at each use: +//! 3. Perform an exhaustive depth-first traversal up the CFG from the use. Look for basic blocks +//! that already contain some liveness and extend the last live SSA value in the block to be +//! live-out. Also build a list of new basic blocks where the register needs to be live-in. +//! 4. Iteratively propagate live-out SSA values to the new live-in blocks. This may require new +//! PHI values to be created when different SSA values can reach the same block. +//! +//! The iterative SSA form reconstruction can be skipped if the depth-first search only encountered +//! one SSA value. +//! +//! This algorithm has some advantages compared to the dataflow equations: +//! +//! - The live ranges of local virtual registers are computed very quickly without ever traversing +//! the CFG. The memory needed to store these live ranges is independent of the number of basic +//! blocks in the program. +//! - The time to compute the live range of a global virtual register is proportional to the number +//! of basic blocks covered. Many virtual registers only cover a few blocks, even in very large +//! functions. +//! - A single live range can be recomputed after making modifications to the IR. No global +//! algorithm is necessary. This feature depends on having use-def chains for virtual registers +//! which Cretonne doesn't. +//! +//! Cretonne uses a very similar data structures and algorithms to LLVM, with the important +//! difference that live ranges are computed per SSA value instead of per virtual register, and the +//! uses in Cretonne IR refers to SSA values instead of virtual registers. This means that Cretonne +//! can skip the last step of reconstructing SSA form for the virtual register uses. +//! +//! ## Fast Liveness Checking for SSA-Form Programs +//! +//! A liveness analysis that is often brought up in the context of SSA-based register allocation +//! was presented at CGO 2008: +//! +//! > Boissinot, B., Hack, S., Grund, D., de Dinechin, B. D., & Rastello, F. (2008). *Fast Liveness +//! Checking for SSA-Form Programs.* CGO. +//! +//! This analysis uses a global precomputation that only depends on the CFG of the function. It +//! then allows liveness queries for any (value, program point) pair. Each query traverses the use +//! chain of the value and performs lookups in the precomputed bitvectors. +//! +//! I did not seriously consider this analysis for Cretonne because: +//! +//! - It depends critically on use chains which Cretonne doesn't have. +//! - Popular variables like the `this` pointer in a C++ method can have very large use chains. +//! Traversing such a long use chain on every liveness lookup has the potential for some nasty +//! quadratic behavior in unfortunate cases. +//! - It says "fast" in the title, but the paper only claims to be 16% faster than a dataflow based +//! approach, which isn't that impressive. +//! +//! Nevertheless, the property of only depending in the CFG structure is very useful. If Cretonne +//! gains use chains, this approach would be worth a proper evaluation. +//! +//! +//! # Cretonne's liveness analysis +//! +//! The algorithm implemented in this module is similar to LLVM's with these differences: +//! +//! - The `LiveRange` data structure describes the liveness of a single SSA value, not a virtual +//! register. +//! - Instructions in Cretonne IR contains references to SSA values, not virtual registers. +//! - All live ranges are computed in one traversal of the program. Cretonne doesn't have use +//! chains, so it is not possible to compute the live range for a single SSA value independently. +//! +//! The liveness computation visits all instructions in the program. The order is not important for +//! the algorithm to be correct. At each instruction, the used values are examined. +//! +//! - The first time a value is encountered, its live range is constructed as a dead live range +//! containing only the defining program point. +//! - The local interval of the value's live range is extended so it reaches the use. This may +//! require creating a new live-in local interval for the EBB. +//! - If the live range became live-in to the EBB, add the EBB to a work-list. +//! - While the work-list is non-empty pop a live-in EBB and repeat the two steps above, using each +//! of the live-in EBB's CFG predecessor instructions as a 'use'. +//! +//! The effect of this algorithm is to extend the live range of each to reach uses as they are +//! visited. No data about each value beyond the live range is needed between visiting uses, so +//! nothing is lost by computing the live range of all values simultaneously. +//! +//! ## Cache efficiency of Cretonne vs LLVM +//! +//! Since LLVM computes the complete live range of a virtual register in one go, it can keep the +//! whole `LiveInterval` for the register in L1 cache. Since it is visiting the instructions in use +//! chain order, some cache thrashing can occur as a result of pulling instructions into cache +//! somewhat chaotically. +//! +//! Cretonne uses a transposed algorithm, visiting instructions in order. This means that each +//! instruction is brought into cache only once, and it is likely that the other instructions on +//! the same cache line will be visited before the line is evicted. +//! +//! Cretonne's problem is that the `LiveRange` structs are visited many times and not always +//! regularly. We should strive to make the `LiveRange` struct as small as possible such that +//! multiple related values can live on the same cache line. +//! +//! - Local values should fit in a 16-byte `LiveRange` struct or smaller. The current +//! implementation contains a 24-byte `Vec` object and a redundant `value` member pushing the +//! size to 32 bytes. +//! - Related values should be stored on the same cache line. The current sparse set implementation +//! does a decent job of that. +//! - For global values, the list of live-in intervals is very likely to fit on a single cache +//! line. These lists are very likely ot be found in L2 cache at least. +//! +//! There is some room for improvement. + +use regalloc::liverange::LiveRange; +use ir::{Function, Value, Inst, Ebb, ProgramPoint}; +use ir::dfg::{DataFlowGraph, ValueDef}; +use cfg::ControlFlowGraph; +use sparse_map::SparseMap; + +/// A set of live ranges, indexed by value number. +struct LiveRangeSet(SparseMap); + +impl LiveRangeSet { + pub fn new() -> LiveRangeSet { + LiveRangeSet(SparseMap::new()) + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Get a mutable reference to the live range for `value`. + /// Create it if necessary. + pub fn get_or_create(&mut self, value: Value, dfg: &DataFlowGraph) -> &mut LiveRange { + // It would be better to use `get_mut()` here, but that leads to borrow checker fighting + // which can probably only be resolved by non-lexical lifetimes. + // https://github.com/rust-lang/rfcs/issues/811 + if self.0.get(value).is_none() { + // Create a live range for value. We need the program point that defines it. + let def: ProgramPoint = match dfg.value_def(value) { + ValueDef::Res(inst, _) => inst.into(), + ValueDef::Arg(ebb, _) => ebb.into(), + }; + self.0.insert(LiveRange::new(value, def)); + } + self.0.get_mut(value).unwrap() + } +} + +/// Liveness analysis for a function. +/// +/// Compute a live range for every SSA value used in the function. +pub struct Liveness { + /// The live ranges that have been computed so far. + ranges: LiveRangeSet, + + /// Working space for the `extend_to_use` algorithm. + /// This vector is always empty, except for inside that function. + /// It lives here to avoid repeated allocation of scratch memory. + worklist: Vec, +} + +impl Liveness { + /// Create a new empty liveness analysis. + /// + /// The memory allocated for this analysis can be reused for multiple functions. Use the + /// `compute` method to actually runs the analysis for a function. + pub fn new() -> Liveness { + Liveness { + ranges: LiveRangeSet::new(), + worklist: Vec::new(), + } + } + + /// Compute the live ranges of all SSA values used in `func`. + /// This clears out any existing analysis stored in this data structure. + pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) { + self.ranges.clear(); + + // The liveness computation needs to visit all uses, but the order doesn't matter. + // TODO: Perhaps this traversal of the function could be combined with a dead code + // elimination pass if we visit a post-order of the dominator tree? + // TODO: Resolve value aliases while we're visiting instructions? + for ebb in func.layout.ebbs() { + for inst in func.layout.ebb_insts(ebb) { + func.dfg[inst].each_arg(|arg| self.extend_to_use(arg, ebb, inst, func, cfg)); + } + } + } + + /// Extend the live range for `value` so it reaches `to` which must live in `ebb`. + fn extend_to_use(&mut self, + value: Value, + ebb: Ebb, + to: Inst, + func: &Function, + cfg: &ControlFlowGraph) { + // Get the live range, create it as a dead range if necessary. + let lr = self.ranges.get_or_create(value, &func.dfg); + + // This is our scratch working space, and we'll leave it empty when we return. + assert!(self.worklist.is_empty()); + + // Extend the range locally in `ebb`. + // If there already was a live interval in that block, we're done. + if lr.extend_in_ebb(ebb, to, &func.layout) { + self.worklist.push(ebb); + } + + // The worklist contains those EBBs where we have learned that the value needs to be + // live-in. + // + // This algorithm bcomes a depth-first traversal up the CFG, enumerating all paths through + // the CFG from the existing live range to `ebb`. + // + // Extend the live range as we go. The live range itself also serves as a visited set since + // `extend_in_ebb` will never return true twice for the same EBB. + // + while let Some(livein) = self.worklist.pop() { + // We've learned that the value needs to be live-in to the `livein` EBB. + // Make sure it is also live at all predecessor branches to `livein`. + for &(pred, branch) in cfg.get_predecessors(livein) { + if lr.extend_in_ebb(pred, branch, &func.layout) { + // This predecessor EBB also became live-in. We need to process it later. + self.worklist.push(pred); + } + } + } + } +} diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 0d6c9c9574..dff29bc5bc 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -3,3 +3,4 @@ //! This module contains data structures and algorithms used for register allocation. pub mod liverange; +pub mod liveness; diff --git a/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs index 9f0d93a55a..48844c026a 100644 --- a/lib/cretonne/src/sparse_map.rs +++ b/lib/cretonne/src/sparse_map.rs @@ -81,6 +81,11 @@ impl SparseMap self.dense.is_empty() } + /// Remove all elements from the mapping. + pub fn clear(&mut self) { + self.dense.clear(); + } + /// Returns a reference to the value corresponding to the key. pub fn get(&self, key: K) -> Option<&V> { if let Some(idx) = self.sparse.get(key).cloned() { From 7b80bd03e30d74bb408fac150580901a659b5e17 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 11:04:11 -0800 Subject: [PATCH 469/968] Update regalloc document to reflect implementation. --- docs/regalloc.rst | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/regalloc.rst b/docs/regalloc.rst index 18057c2511..1d662e9a63 100644 --- a/docs/regalloc.rst +++ b/docs/regalloc.rst @@ -141,17 +141,18 @@ When all the EBBs in a function are laid out linearly, the live range of a value doesn't have to be a contiguous interval, although it will be in a majority of cases. There can be holes in the linear live range. -The live range of an SSA value is represented as: +The part of a value's live range that falls inside a single EBB will always be +an interval without any holes. This follows from the dominance requirements of +SSA. A live range is represented as: -- The earliest program point where the value is live. -- The latest program point where the value is live. -- A (often empty) list of holes, sorted in program order. +- The interval inside the EBB where the value is defined. +- A set of intervals for EBBs where the value is live-in. -Any value that is only used inside a single EBB will have a live range without -holes. Some values are live across large parts of the function, and this can -often be represented with very few holes. It is important that the live range -data structure doesn't have to grow linearly with the number of EBBs covered by -a live range. +Any value that is only used inside a single EBB will have an empty set of +live-in intervals. Some values are live across large parts of the function, and +this can often be represented with coalesced live-in intervals covering many +EBBs. It is important that the live range data structure doesn't have to grow +linearly with the number of EBBs covered by a live range. This representation is very similar to LLVM's ``LiveInterval`` data structure with a few important differences: @@ -160,10 +161,16 @@ with a few important differences: ``LiveInterval`` represents the union of multiple related SSA values in a virtual register. This makes Cretonne's representation smaller because individual segments don't have to annotated with a value number. -- Cretonne stores the min and max program points separately from a list of - holes, while LLVM stores an array of segments. The two representations are - equivalent, but Cretonne optimizes for the common case of a single contiguous - interval. +- Cretonne stores the def-interval separately from a list of coalesced live-in + intervals, while LLVM stores an array of segments. The two representations + are equivalent, but Cretonne optimizes for the common case of a value that is + only used locally. +- It is simpler to check if two live ranges are overlapping. The dominance + properties of SSA form means that it is only necessary to check the + def-interval of each live range against the intervals of the other range. It + is not necessary to check for overlap between the two sets of live-in + intervals. This makes the overlap check logarithmic in the number of live-in + intervals instead of linear. - LLVM represents a program point as ``SlotIndex`` which holds a pointer to a 32-byte ``IndexListEntry`` struct. The entries are organized in a double linked list that mirrors the ordering of instructions in a basic block. This From e8993e79e42eab3a5ff64c4a3205aa165ddee09a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 12:13:00 -0800 Subject: [PATCH 470/968] Implement PackedOption to address #19. The PackedOption struct uses the same amount of memory as T, but can represent None via a reserved value. --- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/packed_option.rs | 121 ++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 lib/cretonne/src/packed_option.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 67694e139f..25feb3c0db 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -24,3 +24,4 @@ mod constant_hash; mod predicates; mod legalizer; mod ref_slice; +mod packed_option; diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs new file mode 100644 index 0000000000..2c93b38ead --- /dev/null +++ b/lib/cretonne/src/packed_option.rs @@ -0,0 +1,121 @@ +//! Compact representation of `Option` for types with a reserved value. +//! +//! Small Cretonne types like the 32-bit entity references are often used in tables and linked +//! lists where an `Option` is needed. Unfortunately, that would double the size of the tables +//! because `Option` is twice as big as `T`. +//! +//! This module provides a `PackedOption` for types that have a reserved value that can be used +//! to represent `None`. + +use std::fmt; + +/// Types that have a reserved value which can't be created any other way. +pub trait ReservedValue: Eq { + /// Create an instance of the reserved value. + fn reserved_value() -> Self; +} + +/// Packed representation of `Option`. +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub struct PackedOption(T); + +impl PackedOption { + /// Returns `true` if the packed option is a `None` value. + pub fn is_none(&self) -> bool { + self.0 == T::reserved_value() + } + + /// Expand the packed option into a normal `Option`. + pub fn expand(self) -> Option { + if self.is_none() { None } else { Some(self.0) } + } +} + +impl Default for PackedOption { + /// Create a default packed option representing `None`. + fn default() -> PackedOption { + PackedOption(T::reserved_value()) + } +} + +impl From for PackedOption { + /// Convert `t` into a packed `Some(x)`. + fn from(t: T) -> PackedOption { + debug_assert!(t != T::reserved_value(), + "Can't make a PackedOption from the reserved value."); + PackedOption(t) + } +} + +impl From> for PackedOption { + /// Convert an option into its packed equivalent. + fn from(opt: Option) -> PackedOption { + match opt { + None => Self::default(), + Some(t) => t.into(), + } + } +} + +impl Into> for PackedOption { + fn into(self) -> Option { + self.expand() + } +} + +impl fmt::Debug for PackedOption + where T: ReservedValue + fmt::Debug +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_none() { + write!(f, "None") + } else { + write!(f, "Some({:?})", self.0) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Dummy entity class, with no Copy or Clone. + #[derive(Debug, PartialEq, Eq)] + struct NoC(u32); + + impl ReservedValue for NoC { + fn reserved_value() -> Self { + NoC(13) + } + } + + #[test] + fn moves() { + let x = NoC(3); + let somex: PackedOption = x.into(); + assert!(!somex.is_none()); + assert_eq!(somex.expand(), Some(NoC(3))); + + let none: PackedOption = None.into(); + assert!(none.is_none()); + assert_eq!(none.expand(), None); + } + + // Dummy entity class, with Copy. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct Ent(u32); + + impl ReservedValue for Ent { + fn reserved_value() -> Self { + Ent(13) + } + } + + #[test] + fn copies() { + let x = Ent(2); + let some: PackedOption = x.into(); + assert_eq!(some.expand(), x.into()); + assert_eq!(some, x.into()); + } +} From c041a51e2ea90b6a07db048eb8eba864af244629 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 12:34:18 -0800 Subject: [PATCH 471/968] Move duplicated entity code into a macro. Implement ReservedValue for all the entities. --- lib/cretonne/src/ir/entities.rs | 162 +++++++++----------------------- 1 file changed, 43 insertions(+), 119 deletions(-) diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 4570a21009..ed42b33633 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -20,24 +20,50 @@ //! format. use entity_map::EntityRef; +use packed_option::ReservedValue; use std::default::Default; use std::fmt::{self, Display, Formatter}; use std::u32; +// Implement the common traits for a 32-bit entity reference. +macro_rules! entity_impl { + // Basic traits. + ($entity:ident) => { + impl EntityRef for $entity { + fn new(index: usize) -> Self { + assert!(index < (u32::MAX as usize)); + $entity(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } + } + + impl ReservedValue for $entity { + fn reserved_value() -> $entity { + $entity(u32::MAX) + } + } + }; + + // Include basic `Display` impl using the given display prefix. + // Display an `Ebb` reference as "ebb12". + ($entity:ident, $display_prefix:expr) => { + entity_impl!($entity); + + impl Display for $entity { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write!(fmt, "{}{}", $display_prefix, self.0) + } + } + } +} + /// An opaque reference to an extended basic block in a function. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Ebb(u32); - -impl EntityRef for Ebb { - fn new(index: usize) -> Self { - assert!(index < (u32::MAX as usize)); - Ebb(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} +entity_impl!(Ebb, "ebb"); impl Ebb { /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. @@ -46,13 +72,6 @@ impl Ebb { } } -/// Display an `Ebb` reference as "ebb12". -impl Display for Ebb { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "ebb{}", self.0) - } -} - /// A guaranteed invalid EBB reference. pub const NO_EBB: Ebb = Ebb(u32::MAX); @@ -65,24 +84,7 @@ impl Default for Ebb { /// An opaque reference to an instruction in a function. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Inst(u32); - -impl EntityRef for Inst { - fn new(index: usize) -> Self { - assert!(index < (u32::MAX as usize)); - Inst(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} - -/// Display an `Inst` reference as "inst7". -impl Display for Inst { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "inst{}", self.0) - } -} +entity_impl!(Inst, "inst"); /// A guaranteed invalid instruction reference. pub const NO_INST: Inst = Inst(u32::MAX); @@ -97,17 +99,7 @@ impl Default for Inst { /// An opaque reference to an SSA value. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Value(u32); - -impl EntityRef for Value { - fn new(index: usize) -> Value { - assert!(index < (u32::MAX as usize)); - Value(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} +entity_impl!(Value); /// Value references can either reference an instruction directly, or they can refer to the /// extended value table. @@ -214,24 +206,7 @@ impl Default for Value { /// An opaque reference to a stack slot. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct StackSlot(u32); - -impl EntityRef for StackSlot { - fn new(index: usize) -> StackSlot { - assert!(index < (u32::MAX as usize)); - StackSlot(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} - -/// Display a `StackSlot` reference as "ss12". -impl Display for StackSlot { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "ss{}", self.0) - } -} +entity_impl!(StackSlot, "ss"); /// A guaranteed invalid stack slot reference. pub const NO_STACK_SLOT: StackSlot = StackSlot(u32::MAX); @@ -245,24 +220,7 @@ impl Default for StackSlot { /// An opaque reference to a jump table. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct JumpTable(u32); - -impl EntityRef for JumpTable { - fn new(index: usize) -> JumpTable { - assert!(index < (u32::MAX as usize)); - JumpTable(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} - -/// Display a `JumpTable` reference as "jt12". -impl Display for JumpTable { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "jt{}", self.0) - } -} +entity_impl!(JumpTable, "jt"); /// A guaranteed invalid jump table reference. pub const NO_JUMP_TABLE: JumpTable = JumpTable(u32::MAX); @@ -276,24 +234,7 @@ impl Default for JumpTable { /// A reference to an external function. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct FuncRef(u32); - -impl EntityRef for FuncRef { - fn new(index: usize) -> FuncRef { - assert!(index < (u32::MAX as usize)); - FuncRef(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} - -/// Display a `FuncRef` reference as "fn12". -impl Display for FuncRef { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "fn{}", self.0) - } -} +entity_impl!(FuncRef, "fn"); /// A guaranteed invalid function reference. pub const NO_FUNC_REF: FuncRef = FuncRef(u32::MAX); @@ -307,24 +248,7 @@ impl Default for FuncRef { /// A reference to a function signature. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct SigRef(u32); - -impl EntityRef for SigRef { - fn new(index: usize) -> SigRef { - assert!(index < (u32::MAX as usize)); - SigRef(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} - -/// Display a `SigRef` reference as "sig12". -impl Display for SigRef { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "sig{}", self.0) - } -} +entity_impl!(SigRef, "sig"); /// A guaranteed invalid function reference. pub const NO_SIG_REF: SigRef = SigRef(u32::MAX); From f004f370c5657dcf01821de909cc1fa07ef10326 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 13:15:50 -0800 Subject: [PATCH 472/968] Remove Default implementations from many entity references. These types can be wrapped in a PackedOption now, so we don't need the NO_* constants and default values. --- lib/cretonne/src/ir/entities.rs | 36 --------------------------------- 1 file changed, 36 deletions(-) diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index ed42b33633..0b01fa9a79 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -208,57 +208,21 @@ impl Default for Value { pub struct StackSlot(u32); entity_impl!(StackSlot, "ss"); -/// A guaranteed invalid stack slot reference. -pub const NO_STACK_SLOT: StackSlot = StackSlot(u32::MAX); - -impl Default for StackSlot { - fn default() -> StackSlot { - NO_STACK_SLOT - } -} - /// An opaque reference to a jump table. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct JumpTable(u32); entity_impl!(JumpTable, "jt"); -/// A guaranteed invalid jump table reference. -pub const NO_JUMP_TABLE: JumpTable = JumpTable(u32::MAX); - -impl Default for JumpTable { - fn default() -> JumpTable { - NO_JUMP_TABLE - } -} - /// A reference to an external function. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct FuncRef(u32); entity_impl!(FuncRef, "fn"); -/// A guaranteed invalid function reference. -pub const NO_FUNC_REF: FuncRef = FuncRef(u32::MAX); - -impl Default for FuncRef { - fn default() -> FuncRef { - NO_FUNC_REF - } -} - /// A reference to a function signature. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct SigRef(u32); entity_impl!(SigRef, "sig"); -/// A guaranteed invalid function reference. -pub const NO_SIG_REF: SigRef = SigRef(u32::MAX); - -impl Default for SigRef { - fn default() -> SigRef { - NO_SIG_REF - } -} - /// A reference to any of the entities defined in this module. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum AnyEntity { From 5fc222348d3a25db8ccab1402938bebd6dd9965b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 13:41:56 -0800 Subject: [PATCH 473/968] Use PackedOption to represent jump tables. Avoid NO_EBB. --- lib/cretonne/src/ir/jumptable.rs | 51 +++++++++++++------------------ lib/cretonne/src/packed_option.rs | 5 +++ lib/reader/src/parser.rs | 10 +++--- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index cbd8630e46..824e2dcf23 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -3,7 +3,8 @@ //! Jump tables are declared in the preamble and assigned an `ir::entities::JumpTable` reference. //! The actual table of destinations is stored in a `JumpTableData` struct defined in this module. -use ir::entities::{Ebb, NO_EBB}; +use packed_option::PackedOption; +use ir::entities::Ebb; use std::iter; use std::slice; use std::fmt::{self, Display, Formatter}; @@ -14,10 +15,10 @@ use std::fmt::{self, Display, Formatter}; /// to be completely populated, though. Individual entries can be missing. #[derive(Clone)] pub struct JumpTableData { - // Table entries, using NO_EBB as a placeholder for missing entries. - table: Vec, + // Table entries, using `None` as a placeholder for missing entries. + table: Vec>, - // How many `NO_EBB` holes in table? + // How many `None` holes in table? holes: usize, } @@ -34,16 +35,15 @@ impl JumpTableData { /// /// The table will grow as needed to fit 'idx'. pub fn set_entry(&mut self, idx: usize, dest: Ebb) { - assert!(dest != NO_EBB); // Resize table to fit `idx`. if idx >= self.table.len() { self.holes += idx - self.table.len(); - self.table.resize(idx + 1, NO_EBB); - } else if self.table[idx] == NO_EBB { + self.table.resize(idx + 1, None.into()); + } else if self.table[idx].is_none() { // We're filling in an existing hole. self.holes -= 1; } - self.table[idx] = dest; + self.table[idx] = dest.into(); } /// Clear a table entry. @@ -51,19 +51,15 @@ impl JumpTableData { /// The `br_table` instruction will fall through if given an index corresponding to a cleared /// table entry. pub fn clear_entry(&mut self, idx: usize) { - if idx < self.table.len() && self.table[idx] != NO_EBB { + if idx < self.table.len() && self.table[idx].is_some() { self.holes += 1; - self.table[idx] = NO_EBB; + self.table[idx] = None.into(); } } /// Get the entry for `idx`, or `None`. pub fn get_entry(&self, idx: usize) -> Option { - if idx < self.table.len() && self.table[idx] != NO_EBB { - Some(self.table[idx]) - } else { - None - } + self.table.get(idx).and_then(|e| e.expand()) } /// Enumerate over all `(idx, dest)` pairs in the table in order. @@ -74,13 +70,13 @@ impl JumpTableData { } /// Access the whole table as a mutable slice. - pub fn as_mut_slice(&mut self) -> &mut [Ebb] { + pub fn as_mut_slice(&mut self) -> &mut [PackedOption] { self.table.as_mut_slice() } } /// Enumerate `(idx, dest)` pairs in order. -pub struct Entries<'a>(iter::Enumerate>>); +pub struct Entries<'a>(iter::Enumerate>>>); impl<'a> Iterator for Entries<'a> { type Item = (usize, Ebb); @@ -88,8 +84,8 @@ impl<'a> Iterator for Entries<'a> { fn next(&mut self) -> Option { loop { if let Some((idx, dest)) = self.0.next() { - if dest != NO_EBB { - return Some((idx, dest)); + if let Some(ebb) = dest.expand() { + return Some((idx, ebb)); } } else { return None; @@ -100,18 +96,15 @@ impl<'a> Iterator for Entries<'a> { impl Display for JumpTableData { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - let first = self.table.first().cloned().unwrap_or_default(); - if first == NO_EBB { - try!(write!(fmt, "jump_table 0")); - } else { - try!(write!(fmt, "jump_table {}", first)); + match self.table.first().and_then(|e| e.expand()) { + None => try!(write!(fmt, "jump_table 0")), + Some(first) => try!(write!(fmt, "jump_table {}", first)), } - for dest in self.table.iter().cloned().skip(1) { - if dest == NO_EBB { - try!(write!(fmt, ", 0")); - } else { - try!(write!(fmt, ", {}", dest)); + for dest in self.table.iter().skip(1).map(|e| e.expand()) { + match dest { + None => try!(write!(fmt, ", 0")), + Some(ebb) => try!(write!(fmt, ", {}", ebb)), } } Ok(()) diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs index 2c93b38ead..3bc7a3ed5f 100644 --- a/lib/cretonne/src/packed_option.rs +++ b/lib/cretonne/src/packed_option.rs @@ -25,6 +25,11 @@ impl PackedOption { self.0 == T::reserved_value() } + /// Returns `true` if the packed option is a `Some` value. + pub fn is_some(&self) -> bool { + !self.is_none() + } + /// Expand the packed option into a normal `Option`. pub fn expand(self) -> Option { if self.is_none() { None } else { Some(self.0) } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index c513e1a86a..f73bfc7a3c 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -13,7 +13,7 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa FuncRef}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::ir::entities::{AnyEntity, NO_EBB, NO_INST, NO_VALUE}; +use cretonne::ir::entities::{AnyEntity, NO_INST, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, TernaryOverflowData, JumpData, BranchData, CallData, IndirectCallData, ReturnData}; @@ -204,9 +204,11 @@ impl Context { // Rewrite EBB references in jump tables. for jt in self.function.jump_tables.keys() { let loc = jt.into(); - for ebb in self.function.jump_tables[jt].as_mut_slice() { - if *ebb != NO_EBB { - try!(self.map.rewrite_ebb(ebb, loc)); + for ebb_ref in self.function.jump_tables[jt].as_mut_slice() { + if let Some(mut ebb) = ebb_ref.expand() { + try!(self.map.rewrite_ebb(&mut ebb, loc)); + // Convert back to a packed option. + *ebb_ref = ebb.into(); } } } From 52db486500c902bd8788994334ca9ab957f1665f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 13:59:57 -0800 Subject: [PATCH 474/968] Use PackedOption in the Layout implementation. The doubly linked lists are terminated with None. Remove NO_EBB and the Default impl for Ebb. --- lib/cretonne/src/ir/entities.rs | 9 ---- lib/cretonne/src/ir/layout.rs | 75 +++++++++++++++---------------- lib/cretonne/src/packed_option.rs | 12 +++++ 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 0b01fa9a79..119873777b 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -72,15 +72,6 @@ impl Ebb { } } -/// A guaranteed invalid EBB reference. -pub const NO_EBB: Ebb = Ebb(u32::MAX); - -impl Default for Ebb { - fn default() -> Ebb { - NO_EBB - } -} - /// An opaque reference to an instruction in a function. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Inst(u32); diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 7f2497df1b..2092329fba 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -6,7 +6,8 @@ use std::cmp; use std::iter::{Iterator, IntoIterator}; use entity_map::{EntityMap, EntityRef}; -use ir::entities::{Ebb, NO_EBB, Inst, NO_INST}; +use packed_option::PackedOption; +use ir::entities::{Ebb, Inst, NO_INST}; use ir::progpoint::{ProgramOrder, ExpandedProgramPoint}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not @@ -25,7 +26,7 @@ use ir::progpoint::{ProgramOrder, ExpandedProgramPoint}; #[derive(Clone)] pub struct Layout { // Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in - // both ends by NO_EBB. + // both ends by `None`. ebbs: EntityMap, // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, @@ -130,13 +131,12 @@ impl Layout { assert!(self.is_ebb_inserted(ebb)); // Get the sequence number immediately before `ebb`, or 0. - let prev_seq = - self.ebbs[ebb].prev.wrap().map(|prev_ebb| self.last_ebb_seq(prev_ebb)).unwrap_or(0); + let prev_seq = self.ebbs[ebb].prev.map(|prev_ebb| self.last_ebb_seq(prev_ebb)).unwrap_or(0); // Get the sequence number immediately following `ebb`. let next_seq = if let Some(inst) = self.ebbs[ebb].first_inst.wrap() { self.insts[inst].seq - } else if let Some(next_ebb) = self.ebbs[ebb].next.wrap() { + } else if let Some(next_ebb) = self.ebbs[ebb].next.expand() { self.ebbs[next_ebb].seq } else { // There is nothing after `ebb`. We can just use a major stride. @@ -167,7 +167,7 @@ impl Layout { // Get the sequence number immediately following `inst`. let next_seq = if let Some(next_inst) = self.insts[inst].next.wrap() { self.insts[next_inst].seq - } else if let Some(next_ebb) = self.ebbs[ebb].next.wrap() { + } else if let Some(next_ebb) = self.ebbs[ebb].next.expand() { self.ebbs[next_ebb].seq } else { // There is nothing after `inst`. We can just use a major stride. @@ -229,7 +229,7 @@ impl Layout { } // Advance to the next EBB. - ebb = match self.ebbs[ebb].next.wrap() { + ebb = match self.ebbs[ebb].next.expand() { Some(next) => next, None => return, }; @@ -248,7 +248,7 @@ impl Layout { fn renumber_from_inst(&mut self, inst: Inst, first_seq: SequenceNumber) { if let Some(seq) = self.renumber_insts(inst, first_seq) { // Renumbering spills over into next EBB. - if let Some(next_ebb) = self.ebbs[self.inst_ebb(inst).unwrap()].next.wrap() { + if let Some(next_ebb) = self.ebbs[self.inst_ebb(inst).unwrap()].next.expand() { self.renumber_from_ebb(next_ebb, seq + MINOR_STRIDE); } } @@ -267,7 +267,7 @@ impl Layout { impl Layout { /// Is `ebb` currently part of the layout? pub fn is_ebb_inserted(&self, ebb: Ebb) -> bool { - Some(ebb) == self.first_ebb || (self.ebbs.is_valid(ebb) && self.ebbs[ebb].prev != NO_EBB) + Some(ebb) == self.first_ebb || (self.ebbs.is_valid(ebb) && self.ebbs[ebb].prev.is_some()) } /// Insert `ebb` as the last EBB in the layout. @@ -277,11 +277,11 @@ impl Layout { { let node = self.ebbs.ensure(ebb); assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); - node.prev = self.last_ebb.unwrap_or_default(); - node.next = NO_EBB; + node.prev = self.last_ebb.into(); + node.next = None.into(); } if let Some(last) = self.last_ebb { - self.ebbs[last].next = ebb; + self.ebbs[last].next = ebb.into(); } else { self.first_ebb = Some(ebb); } @@ -298,14 +298,13 @@ impl Layout { let after = self.ebbs[before].prev; { let node = self.ebbs.ensure(ebb); - node.next = before; + node.next = before.into(); node.prev = after; } - self.ebbs[before].prev = ebb; - if after == NO_EBB { - self.first_ebb = Some(ebb); - } else { - self.ebbs[after].next = ebb; + self.ebbs[before].prev = ebb.into(); + match after.expand() { + None => self.first_ebb = Some(ebb), + Some(a) => self.ebbs[a].next = ebb.into(), } self.assign_ebb_seq(ebb); } @@ -320,13 +319,12 @@ impl Layout { { let node = self.ebbs.ensure(ebb); node.next = before; - node.prev = after; + node.prev = after.into(); } - self.ebbs[after].next = ebb; - if before == NO_EBB { - self.last_ebb = Some(ebb); - } else { - self.ebbs[before].prev = ebb; + self.ebbs[after].next = ebb.into(); + match before.expand() { + None => self.last_ebb = Some(ebb), + Some(b) => self.ebbs[b].prev = ebb.into(), } self.assign_ebb_seq(ebb); } @@ -348,8 +346,8 @@ impl Layout { #[derive(Clone, Debug, Default)] struct EbbNode { - prev: Ebb, - next: Ebb, + prev: PackedOption, + next: PackedOption, first_inst: Inst, last_inst: Inst, seq: SequenceNumber, @@ -367,7 +365,7 @@ impl<'f> Iterator for Ebbs<'f> { fn next(&mut self) -> Option { match self.next { Some(ebb) => { - self.next = self.layout.ebbs[ebb].next.wrap(); + self.next = self.layout.ebbs[ebb].next.expand(); Some(ebb) } None => None, @@ -393,7 +391,7 @@ impl Layout { /// Get the EBB containing `inst`, or `None` if `inst` is not inserted in the layout. pub fn inst_ebb(&self, inst: Inst) -> Option { if self.insts.is_valid(inst) { - self.insts[inst].ebb.wrap() + self.insts[inst].ebb.into() } else { None } @@ -408,7 +406,7 @@ impl Layout { let ebb_node = &mut self.ebbs[ebb]; { let inst_node = self.insts.ensure(inst); - inst_node.ebb = ebb; + inst_node.ebb = ebb.into(); inst_node.prev = ebb_node.last_inst; assert_eq!(inst_node.next, NO_INST); } @@ -435,7 +433,7 @@ impl Layout { let after = self.insts[before].prev; { let inst_node = self.insts.ensure(inst); - inst_node.ebb = ebb; + inst_node.ebb = ebb.into(); inst_node.next = before; inst_node.prev = after; } @@ -489,18 +487,18 @@ impl Layout { let last_inst = self.ebbs[old_ebb].last_inst; { let node = self.ebbs.ensure(new_ebb); - node.prev = old_ebb; + node.prev = old_ebb.into(); node.next = next_ebb; node.first_inst = before; node.last_inst = last_inst; } - self.ebbs[old_ebb].next = new_ebb; + self.ebbs[old_ebb].next = new_ebb.into(); // Fix backwards link. if Some(old_ebb) == self.last_ebb { self.last_ebb = Some(new_ebb); } else { - self.ebbs[next_ebb].prev = new_ebb; + self.ebbs[next_ebb.unwrap()].prev = new_ebb.into(); } // Disconnect the instruction links. @@ -516,8 +514,8 @@ impl Layout { // Fix the instruction -> ebb pointers. let mut i = before; while i != NO_INST { - debug_assert_eq!(self.insts[i].ebb, old_ebb); - self.insts[i].ebb = new_ebb; + debug_assert_eq!(self.insts[i].ebb.expand(), Some(old_ebb)); + self.insts[i].ebb = new_ebb.into(); i = self.insts[i].next; } @@ -527,7 +525,8 @@ impl Layout { #[derive(Clone, Debug, Default)] struct InstNode { - ebb: Ebb, + // The Ebb containing this instruction, or `None` if the instruction is not yet inserted. + ebb: PackedOption, prev: Inst, next: Inst, seq: SequenceNumber, @@ -686,7 +685,7 @@ impl<'f> Cursor<'f> { /// ``` pub fn next_ebb(&mut self) -> Option { let next = if let Some(ebb) = self.current_ebb() { - self.layout.ebbs[ebb].next.wrap() + self.layout.ebbs[ebb].next.expand() } else { self.layout.first_ebb }; @@ -719,7 +718,7 @@ impl<'f> Cursor<'f> { /// ``` pub fn prev_ebb(&mut self) -> Option { let prev = if let Some(ebb) = self.current_ebb() { - self.layout.ebbs[ebb].prev.wrap() + self.layout.ebbs[ebb].prev.expand() } else { self.layout.last_ebb }; diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs index 3bc7a3ed5f..a36676a4b4 100644 --- a/lib/cretonne/src/packed_option.rs +++ b/lib/cretonne/src/packed_option.rs @@ -34,6 +34,18 @@ impl PackedOption { pub fn expand(self) -> Option { if self.is_none() { None } else { Some(self.0) } } + + /// Maps a `PackedOption` to `Option` by applying a function to a contained value. + pub fn map(self, f: F) -> Option + where F: FnOnce(T) -> U + { + self.expand().map(f) + } + + /// Unwrap a packed `Some` value or panic. + pub fn unwrap(self) -> T { + self.expand().unwrap() + } } impl Default for PackedOption { From 3fc0f802235d623fb0373472418afc9737603639 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 14:26:47 -0800 Subject: [PATCH 475/968] Use PackedOption in the Layout implementation. This also revealed that the last_inst() method should return an Option. --- lib/cretonne/src/ir/layout.rs | 85 +++++++++++++++++------------------ lib/cretonne/src/verifier.rs | 2 +- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 2092329fba..2a8d1ba4ac 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -5,9 +5,9 @@ use std::cmp; use std::iter::{Iterator, IntoIterator}; -use entity_map::{EntityMap, EntityRef}; +use entity_map::EntityMap; use packed_option::PackedOption; -use ir::entities::{Ebb, Inst, NO_INST}; +use ir::entities::{Ebb, Inst}; use ir::progpoint::{ProgramOrder, ExpandedProgramPoint}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not @@ -30,7 +30,7 @@ pub struct Layout { ebbs: EntityMap, // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, - // terminated in both ends by NO_INST. + // terminated in both ends by `None`. insts: EntityMap, // First EBB in the layout order, or `None` when no EBBs have been laid out. @@ -120,7 +120,6 @@ impl Layout { // Get the seq of the last instruction if it exists, otherwise use the EBB header seq. self.ebbs[ebb] .last_inst - .wrap() .map(|inst| self.insts[inst].seq) .unwrap_or(self.ebbs[ebb].seq) } @@ -134,7 +133,7 @@ impl Layout { let prev_seq = self.ebbs[ebb].prev.map(|prev_ebb| self.last_ebb_seq(prev_ebb)).unwrap_or(0); // Get the sequence number immediately following `ebb`. - let next_seq = if let Some(inst) = self.ebbs[ebb].first_inst.wrap() { + let next_seq = if let Some(inst) = self.ebbs[ebb].first_inst.expand() { self.insts[inst].seq } else if let Some(next_ebb) = self.ebbs[ebb].next.expand() { self.ebbs[next_ebb].seq @@ -159,13 +158,13 @@ impl Layout { let ebb = self.inst_ebb(inst).expect("inst must be inserted before assigning an seq"); // Get the sequence number immediately before `inst`. - let prev_seq = match self.insts[inst].prev.wrap() { + let prev_seq = match self.insts[inst].prev.expand() { Some(prev_inst) => self.insts[prev_inst].seq, None => self.ebbs[ebb].seq, }; // Get the sequence number immediately following `inst`. - let next_seq = if let Some(next_inst) = self.insts[inst].next.wrap() { + let next_seq = if let Some(next_inst) = self.insts[inst].next.expand() { self.insts[next_inst].seq } else if let Some(next_ebb) = self.ebbs[ebb].next.expand() { self.ebbs[next_ebb].seq @@ -197,7 +196,7 @@ impl Layout { self.insts[inst].seq = seq; // Next instruction. - inst = match self.insts[inst].next.wrap() { + inst = match self.insts[inst].next.expand() { None => return Some(seq), Some(next) => next, }; @@ -221,7 +220,7 @@ impl Layout { self.ebbs[ebb].seq = seq; // Renumber instructions in `ebb`. Stop when the numbers catch up. - if let Some(inst) = self.ebbs[ebb].first_inst.wrap() { + if let Some(inst) = self.ebbs[ebb].first_inst.expand() { seq = match self.renumber_insts(inst, seq + MINOR_STRIDE) { Some(s) => s, None => return, @@ -276,7 +275,7 @@ impl Layout { "Cannot append EBB that is already in the layout"); { let node = self.ebbs.ensure(ebb); - assert!(node.first_inst == NO_INST && node.last_inst == NO_INST); + assert!(node.first_inst.is_none() && node.last_inst.is_none()); node.prev = self.last_ebb.into(); node.next = None.into(); } @@ -348,8 +347,8 @@ impl Layout { struct EbbNode { prev: PackedOption, next: PackedOption, - first_inst: Inst, - last_inst: Inst, + first_inst: PackedOption, + last_inst: PackedOption, seq: SequenceNumber, } @@ -408,21 +407,21 @@ impl Layout { let inst_node = self.insts.ensure(inst); inst_node.ebb = ebb.into(); inst_node.prev = ebb_node.last_inst; - assert_eq!(inst_node.next, NO_INST); + assert!(inst_node.next.is_none()); } - if ebb_node.first_inst == NO_INST { - ebb_node.first_inst = inst; + if ebb_node.first_inst.is_none() { + ebb_node.first_inst = inst.into(); } else { - self.insts[ebb_node.last_inst].next = inst; + self.insts[ebb_node.last_inst.unwrap()].next = inst.into(); } - ebb_node.last_inst = inst; + ebb_node.last_inst = inst.into(); } self.assign_inst_seq(inst); } /// Fetch an ebb's last instruction. - pub fn last_inst(&self, ebb: Ebb) -> Inst { - self.ebbs[ebb].last_inst + pub fn last_inst(&self, ebb: Ebb) -> Option { + self.ebbs[ebb].last_inst.into() } /// Insert `inst` before the instruction `before` in the same EBB. @@ -434,14 +433,13 @@ impl Layout { { let inst_node = self.insts.ensure(inst); inst_node.ebb = ebb.into(); - inst_node.next = before; + inst_node.next = before.into(); inst_node.prev = after; } - self.insts[before].prev = inst; - if after == NO_INST { - self.ebbs[ebb].first_inst = inst; - } else { - self.insts[after].next = inst; + self.insts[before].prev = inst.into(); + match after.expand() { + None => self.ebbs[ebb].first_inst = inst.into(), + Some(a) => self.insts[a].next = inst.into(), } self.assign_inst_seq(inst); } @@ -450,8 +448,8 @@ impl Layout { pub fn ebb_insts<'f>(&'f self, ebb: Ebb) -> Insts<'f> { Insts { layout: self, - head: self.ebbs[ebb].first_inst.wrap(), - tail: self.ebbs[ebb].last_inst.wrap(), + head: self.ebbs[ebb].first_inst.into(), + tail: self.ebbs[ebb].last_inst.into(), } } @@ -489,7 +487,7 @@ impl Layout { let node = self.ebbs.ensure(new_ebb); node.prev = old_ebb.into(); node.next = next_ebb; - node.first_inst = before; + node.first_inst = before.into(); node.last_inst = last_inst; } self.ebbs[old_ebb].next = new_ebb.into(); @@ -503,20 +501,19 @@ impl Layout { // Disconnect the instruction links. let prev_inst = self.insts[before].prev; - self.insts[before].prev = NO_INST; + self.insts[before].prev = None.into(); self.ebbs[old_ebb].last_inst = prev_inst; - if prev_inst == NO_INST { - self.ebbs[old_ebb].first_inst = NO_INST; - } else { - self.insts[prev_inst].next = NO_INST; + match prev_inst.expand() { + None => self.ebbs[old_ebb].first_inst = None.into(), + Some(pi) => self.insts[pi].next = None.into(), } // Fix the instruction -> ebb pointers. - let mut i = before; - while i != NO_INST { + let mut opt_i = Some(before); + while let Some(i) = opt_i { debug_assert_eq!(self.insts[i].ebb.expand(), Some(old_ebb)); self.insts[i].ebb = new_ebb.into(); - i = self.insts[i].next; + opt_i = self.insts[i].next.into(); } self.assign_ebb_seq(new_ebb); @@ -527,8 +524,8 @@ impl Layout { struct InstNode { // The Ebb containing this instruction, or `None` if the instruction is not yet inserted. ebb: PackedOption, - prev: Inst, - next: Inst, + prev: PackedOption, + next: PackedOption, seq: SequenceNumber, } @@ -549,7 +546,7 @@ impl<'f> Iterator for Insts<'f> { self.head = None; self.tail = None; } else { - self.head = Some(self.layout.insts[inst].next); + self.head = self.layout.insts[inst].next.into(); } } rval @@ -564,7 +561,7 @@ impl<'f> DoubleEndedIterator for Insts<'f> { self.head = None; self.tail = None; } else { - self.tail = Some(self.layout.insts[inst].prev); + self.tail = self.layout.insts[inst].prev.into(); } } rval @@ -775,7 +772,7 @@ impl<'f> Cursor<'f> { match self.pos { Nowhere | After(..) => None, At(inst) => { - if let Some(next) = self.layout.insts[inst].next.wrap() { + if let Some(next) = self.layout.insts[inst].next.expand() { self.pos = At(next); Some(next) } else { @@ -785,7 +782,7 @@ impl<'f> Cursor<'f> { } } Before(ebb) => { - if let Some(next) = self.layout.ebbs[ebb].first_inst.wrap() { + if let Some(next) = self.layout.ebbs[ebb].first_inst.expand() { self.pos = At(next); Some(next) } else { @@ -826,7 +823,7 @@ impl<'f> Cursor<'f> { match self.pos { Nowhere | Before(..) => None, At(inst) => { - if let Some(prev) = self.layout.insts[inst].prev.wrap() { + if let Some(prev) = self.layout.insts[inst].prev.expand() { self.pos = At(prev); Some(prev) } else { @@ -836,7 +833,7 @@ impl<'f> Cursor<'f> { } } After(ebb) => { - if let Some(prev) = self.layout.ebbs[ebb].last_inst.wrap() { + if let Some(prev) = self.layout.ebbs[ebb].last_inst.expand() { self.pos = At(prev); Some(prev) } else { diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 939d722e60..f0a4b66b32 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -110,7 +110,7 @@ impl<'a> Verifier<'a> { fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<()> { let is_terminator = self.func.dfg[inst].opcode().is_terminator(); - let is_last_inst = self.func.layout.last_inst(ebb) == inst; + let is_last_inst = self.func.layout.last_inst(ebb) == Some(inst); if is_terminator && !is_last_inst { // Terminating instructions only occur at the end of blocks. From f2b9f62f248995380e894d1e5b347ef2ad4b0198 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 14:49:55 -0800 Subject: [PATCH 476/968] Avoid using NO_INST in the parser. This was only used for the comment rewrite mechanism, and we can just predict the next allocated instruction number instead. See the other uses of next_key() for gather_comments(). --- lib/cretonne/src/ir/dfg.rs | 8 ++++++++ lib/reader/src/parser.rs | 22 +++------------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 7e843d0c87..7ab9ee24d8 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -301,6 +301,14 @@ impl DataFlowGraph { self.insts.push(data) } + /// Get the instruction reference that will be assigned to the next instruction created by + /// `make_inst`. + /// + /// This is only really useful to the parser. + pub fn next_inst(&self) -> Inst { + self.insts.next_key() + } + /// Create result values for an instruction that produces multiple results. /// /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f73bfc7a3c..2d1d85f93d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -13,7 +13,7 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa FuncRef}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::ir::entities::{AnyEntity, NO_INST, NO_VALUE}; +use cretonne::ir::entities::{AnyEntity, NO_VALUE}; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, TernaryOverflowData, JumpData, BranchData, CallData, IndirectCallData, ReturnData}; @@ -283,17 +283,6 @@ impl<'a> Parser<'a> { mem::replace(&mut self.comments, Vec::new()) } - // Rewrite the entity of the last added comments from `old` to `new`. - // Also switch to collecting future comments for `new`. - fn rewrite_last_comment_entities>(&mut self, old: E, new: E) { - let old = old.into(); - let new = new.into(); - for comment in (&mut self.comments).into_iter().rev().take_while(|c| c.entity == old) { - comment.entity = new; - } - self.comment_entity = Some(new); - } - // Match and consume a token without payload. fn match_token(&mut self, want: Token<'a>, err_msg: &str) -> Result> { if self.token() == Some(want) { @@ -906,9 +895,8 @@ impl<'a> Parser<'a> { // inst-results ::= Value(v) { "," Value(vx) } // fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { - // Collect comments for `NO_INST` while parsing the instruction, then rewrite after we - // allocate an instruction number. - self.gather_comments(NO_INST); + // Collect comments for the next instruction to be allocated. + self.gather_comments(ctx.function.dfg.next_inst()); // Result value numbers. let mut results = Vec::new(); @@ -969,10 +957,6 @@ impl<'a> Parser<'a> { results.len()); } - // If we saw any comments while parsing the instruction, they will have been recorded as - // belonging to `NO_INST`. - self.rewrite_last_comment_entities(NO_INST, inst); - // Now map the source result values to the just created instruction results. // Pass a reference to `ctx.values` instead of `ctx` itself since the `Values` iterator // holds a reference to `ctx.function`. From 2e6cf219e9a9d7fc8e68632f700750a0ea439c10 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 15:52:29 -0800 Subject: [PATCH 477/968] Use PackedOption instead of NO_VALUE. - Remove NO_VALUE and ExpandedValue::None. - Remove the Default implelmentation for Value. - InstructionData::second_result() returns an Option. - InstructionData::second_result() returns a reference to the packed option. --- lib/cretonne/meta/gen_instr.py | 9 +- lib/cretonne/src/ir/builder.rs | 10 +- lib/cretonne/src/ir/dfg.rs | 136 ++++++++++++++-------------- lib/cretonne/src/ir/entities.rs | 17 +--- lib/cretonne/src/ir/instructions.rs | 11 ++- lib/cretonne/src/packed_option.rs | 6 ++ lib/reader/src/parser.rs | 12 +-- 7 files changed, 97 insertions(+), 104 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index ac4650bc96..d05bef96df 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -112,7 +112,8 @@ def gen_instruction_data_impl(fmt): - `pub fn opcode(&self) -> Opcode` - `pub fn first_type(&self) -> Type` - `pub fn second_result(&self) -> Option` - - `pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value>` + - `pub fn second_result_mut<'a>(&'a mut self) + -> Option<&'a mut PackedOption>` - `pub fn arguments(&self) -> (&[Value], &[Value])` """ @@ -157,7 +158,7 @@ def gen_instruction_data_impl(fmt): fmt.line( 'InstructionData::' + f.name + ' { second_result, .. }' + - ' => Some(second_result),') + ' => second_result.into(),') else: # Single or no results. fmt.line( @@ -167,7 +168,7 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Mutable reference to second result value, if any.') with fmt.indented( "pub fn second_result_mut<'a>(&'a mut self)" + - " -> Option<&'a mut Value> {", '}'): + " -> Option<&'a mut PackedOption> {", '}'): with fmt.indented('match *self {', '}'): for f in InstructionFormat.all_formats: if f.multiple_results: @@ -489,7 +490,7 @@ def gen_format_constructor(iform, fmt): fmt.line('opcode: opcode,') fmt.line('ty: {},'.format(result_type)) if iform.multiple_results: - fmt.line('second_result: Value::default(),') + fmt.line('second_result: None.into(),') if iform.boxed_storage: with fmt.indented( 'data: Box::new(instructions::{}Data {{' diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 695bc8e5a3..ad3f8e22fe 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -132,9 +132,9 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { fn simple_instruction(self, data: InstructionData) -> (Inst, &'f mut DataFlowGraph) { // The replacement instruction cannot generate multiple results, so verify that the old // instruction's secondary results have been detached. - let old_second_value = self.dfg[self.inst].second_result().unwrap_or_default(); + let old_second_value = self.dfg[self.inst].second_result(); assert_eq!(old_second_value, - Value::default(), + None, "Secondary result values {:?} would be left dangling by replacing {} with {}", self.dfg.inst_results(self.inst).collect::>(), self.dfg[self.inst].opcode(), @@ -150,12 +150,12 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) { // If the old instruction still has secondary results attached, we'll keep them. - let old_second_value = self.dfg[self.inst].second_result().unwrap_or_default(); + let old_second_value = self.dfg[self.inst].second_result(); // Splat the new instruction on top of the old one. self.dfg[self.inst] = data; - if old_second_value == Value::default() { + if old_second_value.is_none() { // The old secondary values were either detached or non-existent. // Construct new ones and set the first result type too. self.dfg.make_inst_results(self.inst, ctrl_typevar); @@ -163,7 +163,7 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { // Reattach the old secondary values. if let Some(val_ref) = self.dfg[self.inst].second_result_mut() { // Don't check types here. Leave that to the verifier. - *val_ref = old_second_value; + *val_ref = old_second_value.into(); } else { // Actually, this instruction format should have called `simple_instruction()`, but // we don't have a rule against calling `complex_instruction()` even when it is diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 7ab9ee24d8..a01a4dd654 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,14 +1,14 @@ //! Data flow graph tracking Instructions, Values, and EBBs. use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef}; -use ir::entities::{NO_VALUE, ExpandedValue}; +use ir::entities::ExpandedValue; use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; use ir::builder::{InsertBuilder, ReplaceBuilder}; use ir::layout::Cursor; +use packed_option::PackedOption; -use std::mem; use std::ops::{Index, IndexMut}; use std::u16; @@ -103,7 +103,6 @@ impl DataFlowGraph { ValueData::Alias { ty, .. } => ty, } } - None => panic!("NO_VALUE has no type"), } } @@ -126,7 +125,6 @@ impl DataFlowGraph { } } } - None => panic!("NO_VALUE has no def"), } } @@ -237,7 +235,7 @@ enum ValueData { ty: Type, num: u16, // Result number starting from 0. inst: Inst, - next: Value, // Next result defined by `def`. + next: PackedOption, // Next result defined by `def`. }, // Value is an EBB argument. @@ -245,7 +243,7 @@ enum ValueData { ty: Type, num: u16, // Argument number, starting from 0. ebb: Ebb, - next: Value, // Next argument to `ebb`. + next: PackedOption, // Next argument to `ebb`. }, // Value is an alias of another value. @@ -262,31 +260,30 @@ enum ValueData { /// A value iterator borrows a `DataFlowGraph` reference. pub struct Values<'a> { dfg: &'a DataFlowGraph, - cur: Value, + cur: Option, } impl<'a> Iterator for Values<'a> { type Item = Value; fn next(&mut self) -> Option { - let prev = self.cur; - - // Advance self.cur to the next value, or NO_VALUE. - self.cur = match prev.expand() { - ExpandedValue::Direct(inst) => self.dfg.insts[inst].second_result().unwrap_or_default(), - ExpandedValue::Table(index) => { - match self.dfg.extended_values[index] { - ValueData::Inst { next, .. } => next, - ValueData::Arg { next, .. } => next, - ValueData::Alias { .. } => { - panic!("Alias value {} appeared in value list", prev) + let rval = self.cur; + if let Some(prev) = rval { + // Advance self.cur to the next value, or `None`. + self.cur = match prev.expand() { + ExpandedValue::Direct(inst) => self.dfg.insts[inst].second_result(), + ExpandedValue::Table(index) => { + match self.dfg.extended_values[index] { + ValueData::Inst { next, .. } => next.into(), + ValueData::Arg { next, .. } => next.into(), + ValueData::Alias { .. } => { + panic!("Alias value {} appeared in value list", prev) + } } } - } - ExpandedValue::None => return None, - }; - - Some(prev) + }; + } + rval } } @@ -334,7 +331,7 @@ impl DataFlowGraph { // causes additional result values to be numbered backwards which is not the aestetic // choice, but since it is only visible in extremely rare instructions with 3+ results, // we don't care). - let mut head = NO_VALUE; + let mut head = None; let mut first_type = None; let mut rev_num = 1; @@ -346,37 +343,37 @@ impl DataFlowGraph { for res_idx in (0..var_results).rev() { if let Some(ty) = first_type { - head = self.make_value(ValueData::Inst { + head = Some(self.make_value(ValueData::Inst { ty: ty, num: (total_results - rev_num) as u16, inst: inst, - next: head, - }); + next: head.into(), + })); rev_num += 1; } first_type = Some(self.signatures[sig].return_types[res_idx].value_type); } } - // Then the fixed results whic will appear at the front of the list. + // Then the fixed results which will appear at the front of the list. for res_idx in (0..fixed_results).rev() { if let Some(ty) = first_type { - head = self.make_value(ValueData::Inst { + head = Some(self.make_value(ValueData::Inst { ty: ty, num: (total_results - rev_num) as u16, inst: inst, - next: head, - }); + next: head.into(), + })); rev_num += 1; } first_type = Some(constraints.result_type(res_idx, ctrl_typevar)); } // Update the second_result pointer in `inst`. - if head != NO_VALUE { + if head.is_some() { *self.insts[inst] .second_result_mut() - .expect("instruction format doesn't allow multiple results") = head; + .expect("instruction format doesn't allow multiple results") = head.into(); } *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); @@ -403,8 +400,7 @@ impl DataFlowGraph { /// Use this method to detach secondary values before using `replace(inst)` to provide an /// alternate instruction for computing the primary result value. pub fn detach_secondary_results(&mut self, inst: Inst) -> Values { - let second_result = - self[inst].second_result_mut().map(|r| mem::replace(r, NO_VALUE)).unwrap_or_default(); + let second_result = self[inst].second_result_mut().and_then(|r| r.take()); Values { dfg: self, cur: second_result, @@ -423,9 +419,9 @@ impl DataFlowGraph { Values { dfg: self, cur: if self.insts[inst].first_type().is_void() { - NO_VALUE + None } else { - Value::new_direct(inst) + Some(Value::new_direct(inst)) }, } } @@ -494,17 +490,16 @@ impl DataFlowGraph { /// Get the number of arguments on `ebb`. pub fn num_ebb_args(&self, ebb: Ebb) -> usize { - let last_arg = self.ebbs[ebb].last_arg; - match last_arg.expand() { - ExpandedValue::None => 0, - ExpandedValue::Table(idx) => { - if let ValueData::Arg { num, .. } = self.extended_values[idx] { - num as usize + 1 - } else { - panic!("inconsistent value table entry for EBB arg"); + match self.ebbs[ebb].last_arg.expand() { + None => 0, + Some(last_arg) => { + if let ExpandedValue::Table(idx) = last_arg.expand() { + if let ValueData::Arg { num, .. } = self.extended_values[idx] { + return num as usize + 1; + } } + panic!("inconsistent value table entry for EBB arg"); } - ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"), } } @@ -516,25 +511,30 @@ impl DataFlowGraph { ty: ty, ebb: ebb, num: num_args as u16, - next: NO_VALUE, + next: None.into(), }); - let last_arg = self.ebbs[ebb].last_arg; - match last_arg.expand() { - // If last_arg is NO_VALUE, we're adding the first EBB argument. - ExpandedValue::None => { - self.ebbs[ebb].first_arg = val; + match self.ebbs[ebb].last_arg.expand() { + // If last_arg is `None`, we're adding the first EBB argument. + None => { + self.ebbs[ebb].first_arg = val.into(); } - // Append to linked list of arguments. - ExpandedValue::Table(idx) => { - if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] { - *next = val; - } else { - panic!("inconsistent value table entry for EBB arg"); + Some(last_arg) => { + match last_arg.expand() { + // Append to linked list of arguments. + ExpandedValue::Table(idx) => { + if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] { + *next = val.into(); + } else { + panic!("inconsistent value table entry for EBB arg"); + } + } + ExpandedValue::Direct(_) => { + panic!("inconsistent value table entry for EBB arg") + } } } - ExpandedValue::Direct(_) => panic!("inconsistent value table entry for EBB arg"), - }; - self.ebbs[ebb].last_arg = val; + } + self.ebbs[ebb].last_arg = val.into(); val } @@ -542,7 +542,7 @@ impl DataFlowGraph { pub fn ebb_args(&self, ebb: Ebb) -> Values { Values { dfg: self, - cur: self.ebbs[ebb].first_arg, + cur: self.ebbs[ebb].first_arg.into(), } } } @@ -554,21 +554,21 @@ impl DataFlowGraph { // match the function arguments. #[derive(Clone)] struct EbbData { - // First argument to this EBB, or `NO_VALUE` if the block has no arguments. + // First argument to this EBB, or `None` if the block has no arguments. // // The arguments are all ValueData::Argument entries that form a linked list from `first_arg` // to `last_arg`. - first_arg: Value, + first_arg: PackedOption, - // Last argument to this EBB, or `NO_VALUE` if the block has no arguments. - last_arg: Value, + // Last argument to this EBB, or `None` if the block has no arguments. + last_arg: PackedOption, } impl EbbData { fn new() -> EbbData { EbbData { - first_arg: NO_VALUE, - last_arg: NO_VALUE, + first_arg: None.into(), + last_arg: None.into(), } } } diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 119873777b..e0b1477af0 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -100,9 +100,6 @@ pub enum ExpandedValue { /// This value is described in the extended value table. Table(usize), - - /// This is NO_VALUE. - None, } impl Value { @@ -129,6 +126,7 @@ impl Value { None } } + /// Create a `Direct` value corresponding to the first value produced by `i`. pub fn new_direct(i: Inst) -> Value { let encoding = i.index() * 2; @@ -148,9 +146,6 @@ impl Value { /// Expand the internal representation into something useful. pub fn expand(&self) -> ExpandedValue { use self::ExpandedValue::*; - if *self == NO_VALUE { - return None; - } let index = (self.0 / 2) as usize; if self.0 % 2 == 0 { Direct(Inst::new(index)) @@ -180,20 +175,10 @@ impl Display for Value { match self.expand() { Direct(i) => write!(fmt, "v{}", i.0), Table(i) => write!(fmt, "vx{}", i), - None => write!(fmt, "NO_VALUE"), } } } -/// A guaranteed invalid value reference. -pub const NO_VALUE: Value = Value(u32::MAX); - -impl Default for Value { - fn default() -> Value { - NO_VALUE - } -} - /// An opaque reference to a stack slot. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct StackSlot(u32); diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index eb0b1e5bd4..f25c58d197 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -16,6 +16,7 @@ use ir::condcodes::*; use ir::types; use ref_slice::*; +use packed_option::PackedOption; // Include code generated by `lib/cretonne/meta/gen_instr.py`. This file contains: // @@ -126,7 +127,7 @@ pub enum InstructionData { UnarySplit { opcode: Opcode, ty: Type, - second_result: Value, + second_result: PackedOption, arg: Value, }, Binary { @@ -150,7 +151,7 @@ pub enum InstructionData { BinaryOverflow { opcode: Opcode, ty: Type, - second_result: Value, + second_result: PackedOption, args: [Value; 2], }, Ternary { @@ -161,7 +162,7 @@ pub enum InstructionData { TernaryOverflow { opcode: Opcode, ty: Type, - second_result: Value, + second_result: PackedOption, data: Box, }, InsertLane { @@ -207,13 +208,13 @@ pub enum InstructionData { Call { opcode: Opcode, ty: Type, - second_result: Value, + second_result: PackedOption, data: Box, }, IndirectCall { opcode: Opcode, ty: Type, - second_result: Value, + second_result: PackedOption, data: Box, }, Return { diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs index a36676a4b4..c647c3793c 100644 --- a/lib/cretonne/src/packed_option.rs +++ b/lib/cretonne/src/packed_option.rs @@ -8,6 +8,7 @@ //! to represent `None`. use std::fmt; +use std::mem; /// Types that have a reserved value which can't be created any other way. pub trait ReservedValue: Eq { @@ -46,6 +47,11 @@ impl PackedOption { pub fn unwrap(self) -> T { self.expand().unwrap() } + + /// Takes the value out of the packed option, leaving a `None` in its place. + pub fn take(&mut self) -> Option { + mem::replace(self, None.into()).expand() + } } impl Default for PackedOption { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 2d1d85f93d..9decd562b0 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -13,7 +13,7 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa FuncRef}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; -use cretonne::ir::entities::{AnyEntity, NO_VALUE}; +use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, TernaryOverflowData, JumpData, BranchData, CallData, IndirectCallData, ReturnData}; @@ -1125,7 +1125,7 @@ impl<'a> Parser<'a> { InstructionData::UnarySplit { opcode: opcode, ty: VOID, - second_result: NO_VALUE, + second_result: None.into(), arg: try!(self.match_value("expected SSA value operand")), } } @@ -1168,7 +1168,7 @@ impl<'a> Parser<'a> { InstructionData::BinaryOverflow { opcode: opcode, ty: VOID, - second_result: NO_VALUE, + second_result: None.into(), args: [lhs, rhs], } } @@ -1196,7 +1196,7 @@ impl<'a> Parser<'a> { InstructionData::TernaryOverflow { opcode: opcode, ty: VOID, - second_result: NO_VALUE, + second_result: None.into(), data: Box::new(TernaryOverflowData { args: [lhs, rhs, cin] }), } } @@ -1287,7 +1287,7 @@ impl<'a> Parser<'a> { InstructionData::Call { opcode: opcode, ty: VOID, - second_result: NO_VALUE, + second_result: None.into(), data: Box::new(CallData { func_ref: func_ref, varargs: args, @@ -1305,7 +1305,7 @@ impl<'a> Parser<'a> { InstructionData::IndirectCall { opcode: opcode, ty: VOID, - second_result: NO_VALUE, + second_result: None.into(), data: Box::new(IndirectCallData { sig_ref: sig_ref, arg: callee, From 4aa5c313ea96021e1942e694d7f1dabdd7500691 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 18:44:33 -0800 Subject: [PATCH 478/968] Use PackedOption in the dominator tree. Also rework the algorithm to be more robust against unreachable blocks. - Add an is_reachable(ebb) method. - Change idom(ebb) to just return an instruction. - Make idom() return None for the entry block as well as unreachable blocks. --- lib/cretonne/src/dominator_tree.rs | 270 +++++++++++++++++++---------- lib/cretonne/src/packed_option.rs | 5 + src/filetest/domtree.rs | 6 +- 3 files changed, 184 insertions(+), 97 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index e331f5657b..f940d3c910 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -1,116 +1,195 @@ //! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. -use cfg::*; -use ir::Ebb; -use ir::entities::NO_INST; +use cfg::{ControlFlowGraph, BasicBlock}; +use ir::{Ebb, Inst, Function, Layout, ProgramOrder}; use entity_map::EntityMap; +use packed_option::PackedOption; + +use std::cmp::Ordering; + +// Dominator tree node. We keep one of these per EBB. +#[derive(Clone, Default)] +struct DomNode { + // Number of this node in a reverse post-order traversal of the CFG, starting from 1. + // Unreachable nodes get number 0, all others are positive. + rpo_number: u32, + + // The immediate dominator of this EBB, represented as the branch or jump instruction at the + // end of the dominating basic block. + // + // This is `None` for unreachable blocks and the entry block which doesn't have an immediate + // dominator. + idom: PackedOption, +} /// The dominator tree for a single function. pub struct DominatorTree { - data: EntityMap>, + nodes: EntityMap, +} + +/// Methods for querying the dominator tree. +impl DominatorTree { + /// Is `ebb` reachable from the entry block? + pub fn is_reachable(&self, ebb: Ebb) -> bool { + self.nodes[ebb].rpo_number != 0 + } + + /// Returns the immediate dominator of `ebb`. + /// + /// The immediate dominator of an extended basic block is a basic block which we represent by + /// the branch or jump instruction at the end of the basic block. This does not have to be the + /// terminator of its EBB. + /// + /// A branch or jump is said to *dominate* `ebb` if all control flow paths from the function + /// entry to `ebb` must go through the branch. + /// + /// The *immediate dominator* is the dominator that is closest to `ebb`. All other dominators + /// also dominate the immediate dominator. + /// + /// This returns `None` if `ebb` is not reachable from the entry EBB, or if it is the entry EBB + /// which has no dominators. + pub fn idom(&self, ebb: Ebb) -> Option { + self.nodes[ebb].idom.into() + } + + /// Compare two EBBs relative to a reverse pst-order traversal of the control-flow graph. + /// + /// Return `Ordering::Less` if `a` comes before `b` in the RPO. + pub fn rpo_cmp(&self, a: Ebb, b: Ebb) -> Ordering { + self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number) + } + + /// Returns `true` if `a` dominates `b`. + /// + /// This means that every control-flow path from the function entry to `b` must go through `a`. + /// + /// Dominance is ill defined for unreachable blocks. This function can always determine + /// dominance for instructions in the same EBB, but otherwise returns `false` if either block + /// is unreachable. + /// + /// An instruction is considered to dominate itself. + pub fn dominates(&self, a: Inst, mut b: Inst, layout: &Layout) -> bool { + let ebb_a = layout.inst_ebb(a).expect("Instruction not in layout."); + let mut ebb_b = layout.inst_ebb(b).expect("Instruction not in layout."); + let rpo_a = self.nodes[ebb_a].rpo_number; + + // Run a finger up the dominator tree from b until we see a. + // Do nothing if b is unreachable. + while rpo_a < self.nodes[ebb_b].rpo_number { + b = self.idom(ebb_b).expect("Shouldn't meet unreachable here."); + ebb_b = layout.inst_ebb(b).expect("Dominator got removed."); + } + + ebb_a == ebb_b && layout.cmp(a, b) != Ordering::Greater + } + + /// Compute the common dominator of two basic blocks. + /// + /// Both basic blocks are assumed to be reachable. + pub fn common_dominator(&self, + mut a: BasicBlock, + mut b: BasicBlock, + layout: &Layout) + -> BasicBlock { + loop { + match self.rpo_cmp(a.0, b.0) { + Ordering::Less => { + // `a` comes before `b` in the RPO. Move `b` up. + let idom = self.nodes[b.0].idom.expect("Unreachable basic block?"); + b = (layout.inst_ebb(idom).expect("Dangling idom instruction"), idom); + } + Ordering::Greater => { + // `b` comes before `a` in the RPO. Move `a` up. + let idom = self.nodes[a.0].idom.expect("Unreachable basic block?"); + a = (layout.inst_ebb(idom).expect("Dangling idom instruction"), idom); + } + Ordering::Equal => break, + } + } + + assert_eq!(a.0, b.0, "Unreachable block passed to common_dominator?"); + + // We're in the same EBB. The common dominator is the earlier instruction. + if layout.cmp(a.1, b.1) == Ordering::Less { + a + } else { + b + } + } } impl DominatorTree { /// Build a dominator tree from a control flow graph using Keith D. Cooper's /// "Simple, Fast Dominator Algorithm." - pub fn new(cfg: &ControlFlowGraph) -> DominatorTree { - let mut ebbs = cfg.postorder_ebbs(); - ebbs.reverse(); + pub fn new(func: &Function, cfg: &ControlFlowGraph) -> DominatorTree { + let mut domtree = DominatorTree { nodes: EntityMap::with_capacity(func.dfg.num_ebbs()) }; - let len = ebbs.len(); + // We'll be iterating over a reverse postorder of the CFG. + // This vector only contains reachable EBBs. + let mut postorder = cfg.postorder_ebbs(); - // The mappings which designate the dominator tree. - let mut data = EntityMap::with_capacity(len); + // Remove the entry block, and abort if the function is empty. + // The last block visited in a post-order traversal must be the entry block. + let entry_block = match postorder.pop() { + Some(ebb) => ebb, + None => return domtree, + }; + assert_eq!(Some(entry_block), func.layout.entry_block()); - let mut postorder_map = EntityMap::with_capacity(len); - for (i, ebb) in ebbs.iter().enumerate() { - postorder_map[ebb.clone()] = len - i; - } - - let mut changed = false; - - if len > 0 { - data[ebbs[0]] = Some((ebbs[0], NO_INST)); - changed = true; + // Do a first pass where we assign RPO numbers to all reachable nodes. + domtree.nodes[entry_block].rpo_number = 1; + for (rpo_idx, &ebb) in postorder.iter().rev().enumerate() { + // Update the current node and give it an RPO number. + // The entry block got 1, the rest start at 2. + // + // Nodes do not appear as reachable until the have an assigned RPO number, and + // `compute_idom` will only look at reachable nodes. This means that the function will + // never see an uninitialized predecessor. + // + // Due to the nature of the post-order traversal, every node we visit will have at + // least one predecessor that has previously been visited during this RPO. + domtree.nodes[ebb] = DomNode { + idom: domtree.compute_idom(ebb, cfg, &func.layout).into(), + rpo_number: rpo_idx as u32 + 2, + } } + // Now that we have RPO numbers for everything and initial idom estimates, iterate until + // convergence. + // + // If the function is free of irreducible control flow, this will exit after one iteration. + let mut changed = true; while changed { changed = false; - for i in 1..len { - let ebb = ebbs[i]; - let preds = cfg.get_predecessors(ebb); - let mut new_idom = None; - - for pred in preds { - if new_idom == None { - new_idom = Some(pred.clone()); - continue; - } - // If this predecessor has an idom available find its common - // ancestor with the current value of new_idom. - if let Some(_) = data[pred.0] { - new_idom = match new_idom { - Some(cur_idom) => { - Some((DominatorTree::intersect(&mut data, - &postorder_map, - *pred, - cur_idom))) - } - None => panic!("A 'current idom' should have been set!"), - } - } - } - match data[ebb] { - None => { - data[ebb] = new_idom; - changed = true; - } - Some(idom) => { - // Old idom != New idom - if idom.0 != new_idom.unwrap().0 { - data[ebb] = new_idom; - changed = true; - } - } + for &ebb in postorder.iter().rev() { + let idom = domtree.compute_idom(ebb, cfg, &func.layout).into(); + if domtree.nodes[ebb].idom != idom { + domtree.nodes[ebb].idom = idom; + changed = true; } } } - DominatorTree { data: data } + domtree } - /// Find the common dominator of two ebbs. - fn intersect(data: &EntityMap>, - ordering: &EntityMap, - first: BasicBlock, - second: BasicBlock) - -> BasicBlock { - let mut a = first; - let mut b = second; + // Compute the idom for `ebb` using the current `idom` states for the reachable nodes. + fn compute_idom(&self, ebb: Ebb, cfg: &ControlFlowGraph, layout: &Layout) -> Inst { + // Get an iterator with just the reachable predecessors to `ebb`. + // Note that during the first pass, `is_reachable` returns false for blocks that haven't + // been visited yet. + let mut reachable_preds = + cfg.get_predecessors(ebb).iter().cloned().filter(|&(ebb, _)| self.is_reachable(ebb)); - // Here we use 'ordering', a mapping of ebbs to their postorder - // visitation number, to ensure that we move upward through the tree. - // Walking upward means that we may always expect self.data[a] and - // self.data[b] to contain non-None entries. - while a.0 != b.0 { - while ordering[a.0] < ordering[b.0] { - a = data[a.0].unwrap(); - } - while ordering[b.0] < ordering[a.0] { - b = data[b.0].unwrap(); - } + // The RPO must visit at least one predecessor before this node. + let mut idom = reachable_preds.next() + .expect("EBB node must have one reachable predecessor"); + + for pred in reachable_preds { + idom = self.common_dominator(idom, pred, layout); } - // TODO: we can't rely on instruction numbers to always be ordered - // from lowest to highest. Given that, it will be necessary to create - // an abolute mapping to determine the instruction order in the future. - if a.1 == NO_INST || a.1 < b.1 { a } else { b } - } - - /// Returns the immediate dominator of some ebb or None if the - /// node is unreachable. - pub fn idom(&self, ebb: Ebb) -> Option { - self.data[ebb].clone() + idom.1 } } @@ -118,15 +197,14 @@ impl DominatorTree { mod test { use super::*; use ir::{Function, InstBuilder, Cursor, VariableArgs, types}; - use ir::entities::NO_INST; use cfg::ControlFlowGraph; #[test] fn empty() { let func = Function::new(); let cfg = ControlFlowGraph::new(&func); - let dtree = DominatorTree::new(&cfg); - assert_eq!(0, dtree.data.keys().count()); + let dtree = DominatorTree::new(&func, &cfg); + assert_eq!(0, dtree.nodes.keys().count()); } #[test] @@ -160,12 +238,16 @@ mod test { } let cfg = ControlFlowGraph::new(&func); - let dt = DominatorTree::new(&cfg); + let dt = DominatorTree::new(&func, &cfg); assert_eq!(func.layout.entry_block().unwrap(), ebb3); - assert_eq!(dt.idom(ebb3).unwrap(), (ebb3, NO_INST)); - assert_eq!(dt.idom(ebb1).unwrap(), (ebb3, jmp_ebb3_ebb1)); - assert_eq!(dt.idom(ebb2).unwrap(), (ebb1, jmp_ebb1_ebb2)); - assert_eq!(dt.idom(ebb0).unwrap(), (ebb1, br_ebb1_ebb0)); + assert_eq!(dt.idom(ebb3), None); + assert_eq!(dt.idom(ebb1).unwrap(), jmp_ebb3_ebb1); + assert_eq!(dt.idom(ebb2).unwrap(), jmp_ebb1_ebb2); + assert_eq!(dt.idom(ebb0).unwrap(), br_ebb1_ebb0); + + assert!(dt.dominates(br_ebb1_ebb0, br_ebb1_ebb0, &func.layout)); + assert!(!dt.dominates(br_ebb1_ebb0, jmp_ebb3_ebb1, &func.layout)); + assert!(dt.dominates(jmp_ebb3_ebb1, br_ebb1_ebb0, &func.layout)); } } diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs index c647c3793c..9082a4846f 100644 --- a/lib/cretonne/src/packed_option.rs +++ b/lib/cretonne/src/packed_option.rs @@ -48,6 +48,11 @@ impl PackedOption { self.expand().unwrap() } + /// Unwrap a packed `Some` value or panic. + pub fn expect(self, msg: &str) -> T { + self.expand().expect(msg) + } + /// Takes the value out of the packed option, leaving a `None` in its place. pub fn take(&mut self) -> Option { mem::replace(self, None.into()).expand() diff --git a/src/filetest/domtree.rs b/src/filetest/domtree.rs index 306fc433e9..0a570e3253 100644 --- a/src/filetest/domtree.rs +++ b/src/filetest/domtree.rs @@ -41,7 +41,7 @@ impl SubTest for TestDomtree { fn run(&self, func: Cow, context: &Context) -> Result<()> { let func = func.borrow(); let cfg = ControlFlowGraph::new(func); - let domtree = DominatorTree::new(&cfg); + let domtree = DominatorTree::new(func, &cfg); // Build an expected domtree from the source annotations. let mut expected = HashMap::new(); @@ -68,7 +68,7 @@ impl SubTest for TestDomtree { // Compare to computed domtree. match domtree.idom(ebb) { - Some((_, got_inst)) if got_inst != inst => { + Some(got_inst) if got_inst != inst => { return Err(format!("mismatching idoms for {}:\n\ want: {}, got: {}", src_ebb, @@ -90,7 +90,7 @@ impl SubTest for TestDomtree { // Now we know that everything in `expected` is consistent with `domtree`. // All other EBB's should be either unreachable or the entry block. for ebb in func.layout.ebbs().skip(1).filter(|ebb| !expected.contains_key(&ebb)) { - if let Some((_, got_inst)) = domtree.idom(ebb) { + if let Some(got_inst) = domtree.idom(ebb) { return Err(format!("mismatching idoms for renumbered {}:\n\ want: unrechable, got: {}", ebb, From 1221d942727196490ca60b69a9f33eddbf2901b7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 16:07:16 -0800 Subject: [PATCH 479/968] Remove NO_INST and the Default+Ord impls for Inst. Clean up comments. Add an assertion to check that PackedOption is working as designed. --- lib/cretonne/src/ir/entities.rs | 40 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index e0b1477af0..69a9902151 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -13,15 +13,14 @@ //! The `entities` module defines public types for the entity references along with constants //! representing an invalid reference. We prefer to use `Option` whenever possible, but //! unfortunately that type is twice as large as the 32-bit index type on its own. Thus, compact -//! data structures use the sentinen constant, while function arguments and return values prefer -//! the more Rust-like `Option` variant. +//! data structures use the `PackedOption` representation, while function arguments and +//! return values prefer the more Rust-like `Option` variant. //! //! The entity references all implement the `Display` trait in a way that matches the textual IL //! format. use entity_map::EntityRef; use packed_option::ReservedValue; -use std::default::Default; use std::fmt::{self, Display, Formatter}; use std::u32; @@ -67,26 +66,13 @@ entity_impl!(Ebb, "ebb"); impl Ebb { /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. + /// + /// This method is for use by the parser. pub fn with_number(n: u32) -> Option { if n < u32::MAX { Some(Ebb(n)) } else { None } } } -/// An opaque reference to an instruction in a function. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] -pub struct Inst(u32); -entity_impl!(Inst, "inst"); - -/// A guaranteed invalid instruction reference. -pub const NO_INST: Inst = Inst(u32::MAX); - -impl Default for Inst { - fn default() -> Inst { - NO_INST - } -} - - /// An opaque reference to an SSA value. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Value(u32); @@ -105,6 +91,8 @@ pub enum ExpandedValue { impl Value { /// Create a `Direct` value from its number representation. /// This is the number in the vNN notation. + /// + /// This method is for use by the parser. pub fn direct_with_number(n: u32) -> Option { if n < u32::MAX / 2 { let encoding = n * 2; @@ -117,6 +105,8 @@ impl Value { /// Create a `Table` value from its number representation. /// This is the number in the vxNN notation. + /// + /// This method is for use by the parser. pub fn table_with_number(n: u32) -> Option { if n < u32::MAX / 2 { let encoding = n * 2 + 1; @@ -179,6 +169,11 @@ impl Display for Value { } } +/// An opaque reference to an instruction in a function. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct Inst(u32); +entity_impl!(Inst, "inst"); + /// An opaque reference to a stack slot. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct StackSlot(u32); @@ -304,4 +299,13 @@ mod tests { }, u32::MAX / 2 - 1); } + + #[test] + fn memory() { + use std::mem; + use packed_option::PackedOption; + // This is the whole point of `PackedOption`. + assert_eq!(mem::size_of::(), + mem::size_of::>()); + } } From 8f2ca7c4718a3090dc6d41a82d581fc26aa56c4d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 19 Jan 2017 21:01:54 -0800 Subject: [PATCH 480/968] Remove EntityRef::wrap(). This has been superceded by PackedOption. --- lib/cretonne/src/entity_map.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/cretonne/src/entity_map.rs b/lib/cretonne/src/entity_map.rs index ba2075370f..33d355e9f1 100644 --- a/lib/cretonne/src/entity_map.rs +++ b/lib/cretonne/src/entity_map.rs @@ -24,24 +24,6 @@ pub trait EntityRef: Copy + Eq { /// Get the index that was used to create this entity reference. fn index(self) -> usize; - - /// Convert an `EntityRef` to an `Optional` by using the default value as the null - /// reference. - /// - /// Entity references are often used in compact data structures like linked lists where a - /// sentinel 'null' value is needed. Normally we would use an `Optional` for that, but - /// currently that uses twice the memory of a plain `EntityRef`. - /// - /// This method is called `wrap()` because it is the inverse of `unwrap()`. - fn wrap(self) -> Option - where Self: Default - { - if self == Self::default() { - None - } else { - Some(self) - } - } } /// A mapping `K -> V` for densely indexed entity references. From ac798c1aed5227828b578f62261eabe2f660697a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 20 Jan 2017 10:33:45 -0800 Subject: [PATCH 481/968] Fix flake8 style issue. --- lib/cretonne/meta/cdsl/operands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index 2fcc22b0c0..f7ec85eafa 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -40,6 +40,7 @@ class OperandKind(object): # type: () -> str return 'OperandKind({})'.format(self.name) + #: An SSA value operand. This is a value defined by another instruction. VALUE = OperandKind( 'value', """ From ae926157c29c900621b2e98d07a742a7ab32b67a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 20 Jan 2017 11:27:56 -0800 Subject: [PATCH 482/968] Generate register class descriptors. Add a mechanism for defining sub-classes of register classes. --- lib/cretonne/meta/cdsl/registers.py | 81 ++++++++++++++++++------ lib/cretonne/meta/gen_registers.py | 36 ++++++++++- lib/cretonne/meta/isa/arm32/registers.py | 10 +-- lib/cretonne/meta/isa/arm64/registers.py | 6 +- lib/cretonne/meta/isa/intel/registers.py | 7 +- lib/cretonne/meta/isa/riscv/registers.py | 6 +- lib/cretonne/src/isa/arm32/registers.rs | 2 +- lib/cretonne/src/isa/arm64/registers.rs | 2 +- lib/cretonne/src/isa/intel/registers.rs | 2 +- lib/cretonne/src/isa/registers.rs | 37 ++++++++++- lib/cretonne/src/isa/riscv/registers.rs | 2 +- 11 files changed, 152 insertions(+), 39 deletions(-) diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 243a72e669..b27c6128ec 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -32,6 +32,15 @@ except ImportError: pass +# The number of 32-bit elements in a register unit mask +MASK_LEN = 3 + +# The maximum total number of register units allowed. +# This limit can be raised by also adjusting the RegUnitMask type in +# src/isa/registers.rs. +MAX_UNITS = MASK_LEN * 32 + + class RegBank(object): """ A register bank belonging to an ISA. @@ -61,6 +70,7 @@ class RegBank(object): self.units = units self.prefix = prefix self.names = names + self.classes = list() # type: List[RegClass] assert len(names) <= units @@ -91,19 +101,16 @@ class RegClass(object): allocated two units at a time. When multiple units are allocated, it is always a contiguous set of unit numbers. - :param name: Name of this register class. :param bank: The register bank we're allocating from. :param count: The maximum number of allocations in this register class. By default, the whole register bank can be allocated. :param width: How many units to allocate at a time. :param start: The first unit to allocate, relative to `bank.first.unit`. - :param stride: How many units to skip to get to the next allocation. - Default is `width`. """ - def __init__(self, name, bank, count=None, width=1, start=0, stride=None): - # type: (str, RegBank, int, int, int, int) -> None - self.name = name + def __init__(self, bank, count=None, width=1, start=0): + # type: (RegBank, int, int, int) -> None + self.name = None # type: str self.bank = bank self.start = start self.width = width @@ -111,21 +118,53 @@ class RegClass(object): assert width > 0 assert start >= 0 and start < bank.units - if stride is None: - stride = width - assert stride > 0 - self.stride = stride - if count is None: - count = bank.units / stride + count = bank.units // width self.count = count - # When the stride is 1, we can wrap around to the beginning of the - # register bank, but with a larger stride, we wouldn't cover all the - # possible allocations with a simple modulo stride. For example, - # attempting to allocate the even registers before the odd ones - # wouldn't work. Only if stride is coprime to bank.units would it work, - # but that is unlikely since the bank size is almost always a power of - # two. - if start + count*stride > bank.units: - assert stride == 1, 'Wrapping with stride not supported' + bank.classes.append(self) + + def __getitem__(self, sliced): + """ + Create a sub-class of a register class using slice notation. The slice + indexes refer to allocations in the parent register class, not register + units. + """ + assert isinstance(sliced, slice), "RegClass slicing can't be 1 reg" + # We could add strided sub-classes if needed. + assert sliced.step is None, 'Subclass striding not supported' + + w = self.width + s = self.start + sliced.start * w + c = sliced.stop - sliced.start + assert c > 1, "Can't have single-register classes" + + return RegClass(self.bank, count=c, width=w, start=s) + + def mask(self): + """ + Compute a bit-mask of the register units allocated by this register + class. + + Return as a list of 32-bit integers. + """ + mask = [0] * MASK_LEN + + start = self.bank.first_unit + self.start + for a in range(self.count): + u = start + a * self.width + mask[u // 32] |= 1 << (u % 32) + + return mask + + @staticmethod + def extract_names(globs): + """ + Given a dict mapping name -> object as returned by `globals()`, find + all the RegClass objects and set their name from the dict key. + This is used to name a bunch of global variables in a module. + """ + for name, obj in globs.items(): + if isinstance(obj, RegClass): + assert obj.name is None + obj.name = name diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index 323aec6173..d3afe3ca4c 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -8,7 +8,7 @@ import srcgen try: from typing import Sequence # noqa from cdsl.isa import TargetISA # noqa - from cdsl.registers import RegBank # noqa + from cdsl.registers import RegBank, RegClass # noqa except ImportError: pass @@ -18,8 +18,7 @@ def gen_regbank(regbank, fmt): """ Emit a static data definition for regbank. """ - with fmt.indented( - 'RegBank {{'.format(regbank.name), '},'): + with fmt.indented('RegBank {', '},'): fmt.line('name: "{}",'.format(regbank.name)) fmt.line('first_unit: {},'.format(regbank.first_unit)) fmt.line('units: {},'.format(regbank.units)) @@ -29,6 +28,19 @@ def gen_regbank(regbank, fmt): fmt.line('prefix: "{}",'.format(regbank.prefix)) +def gen_regclass(idx, rc, fmt): + # type: (int, RegClass, srcgen.Formatter) -> None + """ + Emit a static data definition for a register class. + """ + fmt.comment(rc.name) + with fmt.indented('RegClassData {', '},'): + fmt.line('index: {},'.format(idx)) + fmt.line('width: {},'.format(rc.width)) + mask = ', '.join('0x{:08x}'.format(x) for x in rc.mask()) + fmt.line('mask: [{}],'.format(mask)) + + def gen_isa(isa, fmt): # type: (TargetISA, srcgen.Formatter) -> None """ @@ -36,11 +48,29 @@ def gen_isa(isa, fmt): """ if not isa.regbanks: print('cargo:warning={} has no register banks'.format(isa.name)) + + rcs = list() # type: List[RegClass] with fmt.indented('pub static INFO: RegInfo = RegInfo {', '};'): # Bank descriptors. with fmt.indented('banks: &[', '],'): for regbank in isa.regbanks: gen_regbank(regbank, fmt) + rcs += regbank.classes + fmt.line('classes: &CLASSES,') + + # Register class descriptors. + with fmt.indented( + 'const CLASSES: [RegClassData; {}] = ['.format(len(rcs)), '];'): + for idx, rc in enumerate(rcs): + gen_regclass(idx, rc, fmt) + + # Emit constants referencing the register classes. + for idx, rc in enumerate(rcs): + if rc.name: + fmt.line('#[allow(dead_code)]') + fmt.line( + 'pub const {}: RegClass = &CLASSES[{}];' + .format(rc.name, idx)) def generate(isas, out_dir): diff --git a/lib/cretonne/meta/isa/arm32/registers.py b/lib/cretonne/meta/isa/arm32/registers.py index af031e145b..9522057e34 100644 --- a/lib/cretonne/meta/isa/arm32/registers.py +++ b/lib/cretonne/meta/isa/arm32/registers.py @@ -29,7 +29,9 @@ IntRegs = RegBank( 'General purpose registers', units=16, prefix='r') -GPR = RegClass('GPR', IntRegs) -S = RegClass('S', FloatRegs, count=32) -D = RegClass('D', FloatRegs, width=2) -Q = RegClass('Q', FloatRegs, width=4) +GPR = RegClass(IntRegs) +S = RegClass(FloatRegs, count=32) +D = RegClass(FloatRegs, width=2) +Q = RegClass(FloatRegs, width=4) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/meta/isa/arm64/registers.py b/lib/cretonne/meta/isa/arm64/registers.py index fec3601367..b39bc917a1 100644 --- a/lib/cretonne/meta/isa/arm64/registers.py +++ b/lib/cretonne/meta/isa/arm64/registers.py @@ -18,5 +18,7 @@ FloatRegs = RegBank( 'Floating point registers', units=32, prefix='v') -GPR = RegClass('GPR', IntRegs) -FPR = RegClass('FPR', FloatRegs) +GPR = RegClass(IntRegs) +FPR = RegClass(FloatRegs) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/meta/isa/intel/registers.py b/lib/cretonne/meta/isa/intel/registers.py index 6a4c59403e..92e76191fa 100644 --- a/lib/cretonne/meta/isa/intel/registers.py +++ b/lib/cretonne/meta/isa/intel/registers.py @@ -38,5 +38,8 @@ FloatRegs = RegBank( 'SSE floating point registers', units=16, prefix='xmm') -GPR = RegClass('GPR', IntRegs) -FPR = RegClass('FPR', FloatRegs) +GPR = RegClass(IntRegs) +ABCD = GPR[0:4] +FPR = RegClass(FloatRegs) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/meta/isa/riscv/registers.py b/lib/cretonne/meta/isa/riscv/registers.py index f00c9a0f0d..d9d43f0432 100644 --- a/lib/cretonne/meta/isa/riscv/registers.py +++ b/lib/cretonne/meta/isa/riscv/registers.py @@ -17,5 +17,7 @@ FloatRegs = RegBank( 'Floating point registers', units=32, prefix='f') -GPR = RegClass('GPR', IntRegs) -FPR = RegClass('FPR', FloatRegs) +GPR = RegClass(IntRegs) +FPR = RegClass(FloatRegs) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/src/isa/arm32/registers.rs b/lib/cretonne/src/isa/arm32/registers.rs index 30e7fd58cf..69571976cb 100644 --- a/lib/cretonne/src/isa/arm32/registers.rs +++ b/lib/cretonne/src/isa/arm32/registers.rs @@ -1,6 +1,6 @@ //! ARM32 register descriptions. -use isa::registers::{RegBank, RegInfo}; +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm64/registers.rs b/lib/cretonne/src/isa/arm64/registers.rs index 2420db6df1..0a0e043a20 100644 --- a/lib/cretonne/src/isa/arm64/registers.rs +++ b/lib/cretonne/src/isa/arm64/registers.rs @@ -1,6 +1,6 @@ //! ARM64 register descriptions. -use isa::registers::{RegBank, RegInfo}; +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; include!(concat!(env!("OUT_DIR"), "/registers-arm64.rs")); diff --git a/lib/cretonne/src/isa/intel/registers.rs b/lib/cretonne/src/isa/intel/registers.rs index 1946d0fc57..82c5b9886e 100644 --- a/lib/cretonne/src/isa/intel/registers.rs +++ b/lib/cretonne/src/isa/intel/registers.rs @@ -1,6 +1,6 @@ //! Intel register descriptions. -use isa::registers::{RegBank, RegInfo}; +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; include!(concat!(env!("OUT_DIR"), "/registers-intel.rs")); diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 8736f06e84..26dc65b7d1 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -11,6 +11,14 @@ use std::fmt; /// The register allocator will enforce that each register unit only gets used for one thing. pub type RegUnit = u16; +/// A bit mask indexed by register units. +/// +/// The size of this type is determined by the target ISA that has the most register units defined. +/// Currently that is arm32 which has 64+16 units. +/// +/// This type should be coordinated with meta/cdsl/registers.py. +pub type RegUnitMask = [u32; 3]; + /// The register units in a target ISA are divided into disjoint register banks. Each bank covers a /// contiguous range of register units. /// @@ -23,7 +31,7 @@ pub struct RegBank { pub first_unit: RegUnit, /// The total number of register units in this bank. - pub units: u16, + pub units: RegUnit, /// Array of specially named register units. This array can be shorter than the number of units /// in the bank. @@ -79,6 +87,30 @@ impl RegBank { } } +/// A register class reference. +/// +/// All register classes are statically defined in tables generated from the meta descriptions. +pub type RegClass = &'static RegClassData; + +/// Data about a register class. +/// +/// A register class represents a subset of the registers in a bank. It describes the set of +/// permitted registers for a register operand in a given encoding of an instruction. +/// +/// A register class can be a subset of another register class. The top-level register classes are +/// disjoint. +pub struct RegClassData { + /// The index of this class in the ISA's RegInfo description. + pub index: u8, + + /// How many register units to allocate per register. + pub width: u8, + + /// Mask of register units in the class. If `width > 1`, the mask only has a bit set for the + /// first register unit in each allocatable register. + pub mask: RegUnitMask, +} + /// Information about the registers in an ISA. /// /// The `RegUnit` data structure collects all relevant static information about the registers in an @@ -87,6 +119,9 @@ pub struct RegInfo { /// All register banks, ordered by their `first_unit`. The register banks are disjoint, but /// there may be holes of unused register unit numbers between banks due to alignment. pub banks: &'static [RegBank], + + /// All register classes ordered topologically so a sub-class always follows its parent. + pub classes: &'static [RegClassData], } impl RegInfo { diff --git a/lib/cretonne/src/isa/riscv/registers.rs b/lib/cretonne/src/isa/riscv/registers.rs index a3fe4739e8..7deef9251a 100644 --- a/lib/cretonne/src/isa/riscv/registers.rs +++ b/lib/cretonne/src/isa/riscv/registers.rs @@ -1,6 +1,6 @@ //! RISC-V register descriptions. -use isa::registers::{RegBank, RegInfo}; +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs")); From 58dedb673a09739abc048b21d8a73abb542fef04 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 20 Jan 2017 14:41:06 -0800 Subject: [PATCH 483/968] Add an AllocatableSet for registers. This set of available register units also manages register aliasing in an efficient way. Detect if the units in a register straddles mask words. The algorithm for allocating multi-unit registers expect the whole register to be inside a single mask word. We could handle this if necessary, but so far no ISAs need it. --- lib/cretonne/meta/cdsl/registers.py | 7 +- lib/cretonne/src/isa/mod.rs | 4 +- lib/cretonne/src/isa/registers.rs | 4 +- lib/cretonne/src/regalloc/allocatable_set.rs | 176 +++++++++++++++++++ lib/cretonne/src/regalloc/mod.rs | 1 + 5 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 lib/cretonne/src/regalloc/allocatable_set.rs diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index b27c6128ec..dc6885c296 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -153,7 +153,12 @@ class RegClass(object): start = self.bank.first_unit + self.start for a in range(self.count): u = start + a * self.width - mask[u // 32] |= 1 << (u % 32) + b = u % 32 + # We need fancier masking code if a register can straddle mask + # words. This will only happen with widths that are not powers of + # two. + assert b + self.width <= 32, 'Register straddles words' + mask[u // 32] |= 1 << b return mask diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index dc66355904..6d22d5dd71 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -41,7 +41,7 @@ //! concurrent function compilations. pub use isa::encoding::Encoding; -pub use isa::registers::{RegUnit, RegBank, RegInfo}; +pub use isa::registers::{RegInfo, RegUnit, RegClass}; use settings; use ir::{InstructionData, DataFlowGraph}; @@ -49,9 +49,9 @@ pub mod riscv; pub mod intel; pub mod arm32; pub mod arm64; +pub mod registers; mod encoding; mod enc_tables; -mod registers; /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 26dc65b7d1..48173c50bd 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -147,8 +147,8 @@ impl RegInfo { /// Temporary object that holds enough information to print a register unit. pub struct DisplayRegUnit<'a> { - pub regunit: RegUnit, - pub reginfo: &'a RegInfo, + regunit: RegUnit, + reginfo: &'a RegInfo, } impl<'a> fmt::Display for DisplayRegUnit<'a> { diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs new file mode 100644 index 0000000000..34145cd01a --- /dev/null +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -0,0 +1,176 @@ +//! Set of allocatable registers as a bit vector of register units. +//! +//! While allocating registers, we need to keep track of which registers are available and which +//! registers are in use. Since registers can alias in different ways, we track this via the +//! "register unit" abstraction. Every register contains one or more register units. Registers that +//! share a register unit can't be in use at the same time. + +use std::mem::size_of_val; +use isa::registers::{RegUnit, RegUnitMask, RegClass}; + +/// Set of registers available for allocation. +#[derive(Clone)] +pub struct AllocatableSet { + avail: RegUnitMask, +} + +// Given a register class and a register unit in the class, compute a word index and a bit mask of +// register units representing that register. +// +// Note that a register is not allowed to straddle words. +fn bitmask(rc: RegClass, reg: RegUnit) -> (usize, u32) { + // Bit mask representing the register. It is `rc.width` consecutive units. + let width_bits = (1 << rc.width) - 1; + // Index into avail[] of the word containing `reg`. + let word_index = (reg / 32) as usize; + // The actual bits in the word that cover `reg`. + let reg_bits = width_bits << (reg % 32); + + (word_index, reg_bits) +} + +impl AllocatableSet { + /// Create a new register set with all registers available. + /// + /// Note that this includes *all* registers. Query the `TargetIsa` object to get a set of + /// allocatable registers where reserved registers have been filtered out. + pub fn new() -> AllocatableSet { + AllocatableSet { avail: [!0; 3] } + } + + /// Returns `true` if the spoecified register is available. + pub fn is_avail(&self, rc: RegClass, reg: RegUnit) -> bool { + let (idx, bits) = bitmask(rc, reg); + (self.avail[idx] & bits) == bits + } + + /// Allocate `reg` from `rc` so it is no longer available. + /// + /// It is an error to take a register that doesn't have all of its register units available. + pub fn take(&mut self, rc: RegClass, reg: RegUnit) { + let (idx, bits) = bitmask(rc, reg); + debug_assert!((self.avail[idx] & bits) == bits, "Not available"); + self.avail[idx] &= !bits; + } + + /// Make `reg` available for allocation again. + pub fn free(&mut self, rc: RegClass, reg: RegUnit) { + let (idx, bits) = bitmask(rc, reg); + debug_assert!((self.avail[idx] & bits) == 0, "Not allocated"); + self.avail[idx] |= bits; + } + + /// Return an iterator over all available registers belonging to the register class `rc`. + /// + /// This doesn't allocate anything from the set; use `take()` for that. + pub fn iter(&self, rc: RegClass) -> RegSetIter { + // Start by copying the RC mask. It is a single set bit for each register in the class. + let mut rsi = RegSetIter { regs: rc.mask }; + + // Mask out the unavailable units. + for idx in 0..self.avail.len() { + // If a single unit in a register is unavailable, the whole register can't be used. + // If a register straddles a word boundary, it will be marked as unavailable. + // There's an assertion in cdsl/registers.py to check for that. + for i in 0..rc.width { + rsi.regs[idx] &= self.avail[idx] >> i; + } + } + rsi + } +} + +/// Iterator over available registers in a register class. +pub struct RegSetIter { + regs: RegUnitMask, +} + +impl Iterator for RegSetIter { + type Item = RegUnit; + + fn next(&mut self) -> Option { + let mut unit_offset = 0; + + // Find the first set bit in `self.regs`. + for word in &mut self.regs { + if *word != 0 { + // Compute the register unit number from the lowest set bit in the word. + let unit = unit_offset + word.trailing_zeros() as RegUnit; + + // Clear that lowest bit so we won't find it again. + *word = *word & (*word - 1); + + return Some(unit); + } + // How many register units was there in the word? This is a constant 32 for u32 etc. + unit_offset += 8 * size_of_val(word) as RegUnit; + } + + // All of `self.regs` is 0. + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use isa::registers::{RegClass, RegClassData}; + + // Register classes for testing. + const GPR: RegClass = &RegClassData { + index: 0, + width: 1, + mask: [0xf0000000, 0x0000000f, 0], + }; + const DPR: RegClass = &RegClassData { + index: 0, + width: 2, + mask: [0x50000000, 0x0000000a, 0], + }; + + #[test] + fn put_and_take() { + let mut regs = AllocatableSet::new(); + + // GPR has units 28-36. + assert_eq!(regs.iter(GPR).count(), 8); + assert_eq!(regs.iter(DPR).collect::>(), [28, 30, 33, 35]); + + assert!(regs.is_avail(GPR, 29)); + regs.take(&GPR, 29); + assert!(!regs.is_avail(GPR, 29)); + + assert_eq!(regs.iter(GPR).count(), 7); + assert_eq!(regs.iter(DPR).collect::>(), [30, 33, 35]); + + assert!(regs.is_avail(GPR, 30)); + regs.take(&GPR, 30); + assert!(!regs.is_avail(GPR, 30)); + + assert_eq!(regs.iter(GPR).count(), 6); + assert_eq!(regs.iter(DPR).collect::>(), [33, 35]); + + assert!(regs.is_avail(GPR, 32)); + regs.take(&GPR, 32); + assert!(!regs.is_avail(GPR, 32)); + + assert_eq!(regs.iter(GPR).count(), 5); + assert_eq!(regs.iter(DPR).collect::>(), [33, 35]); + + regs.free(&GPR, 30); + assert!(regs.is_avail(GPR, 30)); + assert!(!regs.is_avail(GPR, 29)); + assert!(!regs.is_avail(GPR, 32)); + + assert_eq!(regs.iter(GPR).count(), 6); + assert_eq!(regs.iter(DPR).collect::>(), [30, 33, 35]); + + regs.free(&GPR, 32); + assert!(regs.is_avail(GPR, 31)); + assert!(!regs.is_avail(GPR, 29)); + assert!(regs.is_avail(GPR, 32)); + + assert_eq!(regs.iter(GPR).count(), 7); + assert_eq!(regs.iter(DPR).collect::>(), [30, 33, 35]); + } +} diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index dff29bc5bc..9e0f7b820d 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -4,3 +4,4 @@ pub mod liverange; pub mod liveness; +pub mod allocatable_set; From 0394f3503480497b346dcf3bbb8781c0527a698a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 24 Jan 2017 11:19:31 -0800 Subject: [PATCH 484/968] Add operand register constraints. Every encoding recipe must specify register constraints on input and output values. Generate recipe constraint tables along with the other encoding tables. --- docs/metaref.rst | 82 +++++++++++++++++++++++- lib/cretonne/meta/cdsl/isa.py | 37 ++++++++++- lib/cretonne/meta/cdsl/registers.py | 26 +++++++- lib/cretonne/meta/gen_encoding.py | 53 ++++++++++++++- lib/cretonne/meta/isa/riscv/recipes.py | 9 ++- lib/cretonne/src/isa/arm32/enc_tables.rs | 1 + lib/cretonne/src/isa/arm32/mod.rs | 6 +- lib/cretonne/src/isa/arm64/enc_tables.rs | 1 + lib/cretonne/src/isa/arm64/mod.rs | 6 +- lib/cretonne/src/isa/constraints.rs | 66 +++++++++++++++++++ lib/cretonne/src/isa/intel/enc_tables.rs | 1 + lib/cretonne/src/isa/intel/mod.rs | 6 +- lib/cretonne/src/isa/mod.rs | 11 +++- lib/cretonne/src/isa/riscv/enc_tables.rs | 2 + lib/cretonne/src/isa/riscv/mod.rs | 6 +- 15 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 lib/cretonne/src/isa/constraints.rs diff --git a/docs/metaref.rst b/docs/metaref.rst index ec4555a8d7..05050f8f40 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -331,8 +331,6 @@ encoded: - The CPU mode that must be active. - A :term:`sub-target predicate` that must be satisfied by the currently active sub-target. -- :term:`Register constraint`\s that must be satisfied by the instruction's value - operands and results. An encoding specifies an *encoding recipe* along with some *encoding bits* that the recipe can use for native opcode fields etc. The encoding recipe has @@ -349,6 +347,83 @@ encodings only need the recipe predicates. .. autoclass:: EncRecipe +Register constraints +==================== + +After an encoding recipe has been chosen for an instruction, it is the register +allocator's job to make sure that the recipe's :term:`Register constraint`\s +are satisfied. Most ISAs have separate integer and floating point registers, +and instructions can usually only use registers from one of the banks. Some +instruction encodings are even more constrained and can only use a subset of +the registers in a bank. These constraints are expressed in terms of register +classes. + +Sometimes the result of an instruction is placed in a register that must be the +same as one of the input registers. Some instructions even use a fixed register +for inputs or results. + +Each encoding recipe specifies separate constraints for its value operands and +result. These constraints are separate from the instruction predicate which can +only evaluate the instruction's immediate operands. + +.. module:: cdsl.registers +.. autoclass:: RegBank + +Register class constraints +-------------------------- + +The most common type of register constraint is the register class. It specifies +that an operand or result must be allocated one of the registers from the given +register class:: + + IntRegs = RegBank('IntRegs', ISA, 'General purpose registers', units=16, prefix='r') + GPR = RegClass(IntRegs) + R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) + +This defines an encoding recipe for the ``Binary`` instruction format where +both input operands must be allocated from the ``GPR`` register class. + +.. autoclass:: RegClass + +Tied register operands +---------------------- + +In more compact machine code encodings, it is common to require that the result +register is the same as one of the inputs. This is represented with tied +operands:: + + CR = EncRecipe('CR', Binary, ins=(GPR, GPR), outs=0) + +This indicates that the result value must be allocated to the same register as +the first input value. Tied operand constraints can only be used for result +values, so the number always refers to one of the input values. + +Fixed register operands +----------------------- + +Some instructions use hard-coded input and output registers for some value +operands. An example is the ``pblendvb`` Intel SSE instruction which takes one +of its three value operands in the hard-coded ``%xmm0`` register:: + + XMM0 = FPR[0] + SSE66_XMM0 = EncRecipe('SSE66_XMM0', Ternary, ins=(FPR, FPR, XMM0), outs=0) + +The syntax ``FPR[0]`` selects the first register from the ``FPR`` register +class which consists of all the XMM registers. + +Stack operands +-------------- + +Cretonne's register allocator can assign an SSA value to a stack slot if there +isn't enough registers. It will insert :cton:inst:`spill` and :cton:inst:`fill` +instructions as needed to satisfy instruction operand constraints, but it is +also possible to have instructions that can access stack slots directly:: + + CSS = EncRecipe('CSS', Unary, ins=GPR, outs=Stack(GPR)) + +An output stack value implies a store to the stack, an input value implies a +load. + .. module:: cdsl.isa Targets @@ -366,6 +441,9 @@ The definitions for each supported target live in a package under :members: .. automodule:: isa.riscv +.. automodule:: isa.intel +.. automodule:: isa.arm32 +.. automodule:: isa.arm64 Glossary diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 6c324603a5..8e1423315c 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,6 +1,7 @@ """Defining instruction set architectures.""" from __future__ import absolute_import from .predicates import And +from .registers import RegClass, Register # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -12,6 +13,8 @@ try: from .types import ValueType # noqa from .registers import RegBank # noqa AnyPredicate = Union[Predicate, FieldPredicate] + OperandConstraint = Union[RegClass, Register, int] + ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] except ImportError: pass @@ -133,13 +136,24 @@ class EncRecipe(object): Many different instructions can be encoded by the same recipe, but they must all have the same instruction format. + The `ins` and `outs` arguments are tuples specifying the register + allocation constraints for the value operands and results respectively. The + possible constraints for an operand are: + + - A `RegClass` specifying the set of allowed registers. + - A `Register` specifying a fixed-register operand. + - An integer indicating that this result is tied to a value operand, so + they must use the same register. + :param name: Short mnemonic name for this recipe. :param format: All encoded instructions must have this :py:class:`InstructionFormat`. + :param: ins Tuple of register constraints for value operands. + :param: outs Tuple of register constraints for results. """ - def __init__(self, name, format, instp=None, isap=None): - # type: (str, InstructionFormat, AnyPredicate, AnyPredicate) -> None + def __init__(self, name, format, ins, outs, instp=None, isap=None): + # type: (str, InstructionFormat, ConstraintSeq, ConstraintSeq, AnyPredicate, AnyPredicate) -> None # noqa self.name = name self.format = format self.instp = instp @@ -148,10 +162,29 @@ class EncRecipe(object): assert instp.predicate_context() == format self.number = None # type: int + self.ins = self._verify_constraints(ins) + assert len(self.ins) == len(format.value_operands) + self.outs = self._verify_constraints(outs) + if len(self.outs) > 1: + assert format.multiple_results + def __str__(self): # type: () -> str return self.name + def _verify_constraints(self, seq): + # (ConstraintSeq) -> Sequence[OperandConstraint] + if not isinstance(seq, tuple): + seq = (seq,) + for c in seq: + if isinstance(c, int): + # An integer constraint is bound to a value operand. + # Check that it is in range. + assert c >= 0 and c < len(self.format.value_operands) + else: + assert isinstance(c, RegClass) or isinstance(c, Register) + return seq + class Encoding(object): """ diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index dc6885c296..828e6a2704 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -62,7 +62,7 @@ class RegBank(object): `units`, the remaining units are named using `prefix`. """ - def __init__(self, name, isa, doc, units, prefix='p', names=()): + def __init__(self, name, isa, doc, units, prefix='r', names=()): # type: (str, TargetISA, str, int, str, Sequence[str]) -> None self.name = name self.isa = isa @@ -124,12 +124,16 @@ class RegClass(object): bank.classes.append(self) + def __str__(self): + return self.name + def __getitem__(self, sliced): """ Create a sub-class of a register class using slice notation. The slice indexes refer to allocations in the parent register class, not register units. """ + print(repr(sliced)) assert isinstance(sliced, slice), "RegClass slicing can't be 1 reg" # We could add strided sub-classes if needed. assert sliced.step is None, 'Subclass striding not supported' @@ -142,6 +146,7 @@ class RegClass(object): return RegClass(self.bank, count=c, width=w, start=s) def mask(self): + # type: () -> List[int] """ Compute a bit-mask of the register units allocated by this register class. @@ -173,3 +178,22 @@ class RegClass(object): if isinstance(obj, RegClass): assert obj.name is None obj.name = name + + +class Register(object): + """ + A specific register in a register class. + + A register is identified by the top-level register class it belongs to and + its first register unit. + + Specific registers are used to describe constraints on instructions where + some operands must use a fixed register. + + Register objects should be created using the indexing syntax on the + register class. + """ + def __init__(self, rc, unit): + # type: (RegClass, int) -> None + self.regclass = rc + self.unit = unit diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 8477f31de2..30d1ed3370 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -1,7 +1,7 @@ """ Generate sources for instruction encoding. -The tables and functions generated here support the `TargetIsa::encode()` +The tables and functions generated here support the `TargetISA::encode()` function which determines if a given instruction is legal, and if so, it's `Encoding` data which consists of a *recipe* and some *encoding* bits. @@ -56,6 +56,13 @@ from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict import math import itertools +from cdsl.registers import RegClass, Register + +try: + from typing import Sequence # noqa + from cdsl.isa import TargetISA, OperandConstraint # noqa +except ImportError: + pass def emit_instp(instp, fmt): @@ -399,6 +406,7 @@ def offset_type(length): def emit_recipe_names(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None """ Emit a table of encoding recipe names keyed by recipe number. @@ -411,6 +419,48 @@ def emit_recipe_names(isa, fmt): fmt.line('"{}",'.format(r.name)) +def emit_recipe_constraints(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Emit a table of encoding recipe operand constraints keyed by recipe number. + + These are used by the register allocator to pick registers that can be + properly encoded. + """ + with fmt.indented( + 'pub static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = [' + .format(len(isa.all_recipes)), '];'): + for r in isa.all_recipes: + fmt.comment(r.name) + with fmt.indented('RecipeConstraints {', '},'): + emit_operand_constraints(r.ins, 'ins', fmt) + emit_operand_constraints(r.outs, 'outs', fmt) + + +def emit_operand_constraints(seq, field, fmt): + # type: (Sequence[OperandConstraint], str, srcgen.Formatter) -> None + """ + Emit a struct field initializer for an array of operand constraints. + """ + if len(seq) == 0: + fmt.line('{}: &[],'.format(field)) + return + with fmt.indented('{}: &['.format(field), '],'): + for cons in seq: + with fmt.indented('OperandConstraint {', '},'): + if isinstance(cons, RegClass): + fmt.line('kind: ConstraintKind::Reg,') + fmt.line('regclass: {},'.format(cons)) + elif isinstance(cons, Register): + fmt.line( + 'kind: ConstraintKind::FixedReg({}),' + .format(cons.unit)) + fmt.line('regclass: {},'.format(cons.regclass)) + else: + raise AssertionError( + 'Unsupported constraint {}'.format(cons)) + + def gen_isa(isa, fmt): # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. @@ -446,6 +496,7 @@ def gen_isa(isa, fmt): cpumode, level1_tables[cpumode], level1_offt, fmt) emit_recipe_names(isa, fmt) + emit_recipe_constraints(isa, fmt) def generate(isas, out_dir): diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index ae8d5b3add..fe0618d6cc 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,6 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm +from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit # instructions have 11 as the two low bits, with bits 6:2 determining the base @@ -67,9 +68,11 @@ def OP32(funct3, funct7): # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) -R = EncRecipe('R', Binary) +R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) # R-type with an immediate shift amount instead of rs2. -Rshamt = EncRecipe('Rshamt', BinaryImm) +Rshamt = EncRecipe('Rshamt', BinaryImm, ins=GPR, outs=GPR) -I = EncRecipe('I', BinaryImm, instp=IsSignedInt(BinaryImm.imm, 12)) +I = EncRecipe( + 'I', BinaryImm, ins=GPR, outs=GPR, + instp=IsSignedInt(BinaryImm.imm, 12)) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index e40362a32f..3c3ffae695 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -4,5 +4,6 @@ use ir::InstructionData; use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 617f52f43e..63f6293516 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -70,4 +70,8 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index dbe65c4e8f..92b5ad58c3 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -4,5 +4,6 @@ use ir::InstructionData; use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index a4367a878b..2c2b98437b 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -63,4 +63,8 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs new file mode 100644 index 0000000000..bdd2bea958 --- /dev/null +++ b/lib/cretonne/src/isa/constraints.rs @@ -0,0 +1,66 @@ +//! Register constraints for instruction operands. +//! +//! An encoding recipe specifies how an instruction is encoded as binary machine code, but it only +//! works if the operands and results satisfy certain constraints. Constraints on immediate +//! operands are checked by instruction predicates when the recipe is chosen. +//! +//! It is the register allocator's job to make sure that the register constraints on value operands +//! are satisfied. + +use isa::{RegClass, RegUnit}; + +/// Register constraint for a single value operand or instruction result. +pub struct OperandConstraint { + /// The kind of constraint. + pub kind: ConstraintKind, + + /// The register class of the operand. + /// + /// This applies to all kinds of constraints, but with slightly different meaning. + pub regclass: RegClass, +} + +/// The different kinds of operand constraints. +pub enum ConstraintKind { + /// This operand or result must be a register from the given register class. + Reg, + + /// This operand or result must be a fixed register. + /// + /// The constraint's `regclass` field is the top-level register class containing the fixed + /// register. + FixedReg(RegUnit), + + /// This result value must use the same register as an input value operand. Input operands + /// can't be tied. + /// + /// The associated number is the index of the input value operand this result is tied to. + /// + /// The constraint's `regclass` field is the top-level register class containing the tied + /// operand's register class. + Tied(u8), + + /// This operand must be a value in a stack slot. + /// + /// The constraint's `regclass` field is the register class that would normally be used to load + /// and store values of this type. + Stack, +} + +/// Constraints for an encoding recipe. +pub struct RecipeConstraints { + /// Constraints for the instruction's fixed value operands. + /// + /// If the instruction takes a variable number of operands, the register constraints for those + /// operands must be computed dynamically. + /// + /// - For branches and jumps, EBB arguments must match the expectations of the destination EBB. + /// - For calls and returns, the calling convention ABI specifies constraints. + pub ins: &'static [OperandConstraint], + + /// Constraints for the instruction's fixed results. + /// + /// If the instruction produces a variable number of results, it's probably a call and the + /// constraints must be derived from the calling convention ABI. + pub outs: &'static [OperandConstraint], +} diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 842629b058..0b01f481f2 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -4,5 +4,6 @@ use ir::InstructionData; use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index acad76743b..13b77e1dd2 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -70,4 +70,8 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 6d22d5dd71..0bd9e5e9fa 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -42,6 +42,8 @@ pub use isa::encoding::Encoding; pub use isa::registers::{RegInfo, RegUnit, RegClass}; +pub use isa::constraints::RecipeConstraints; + use settings; use ir::{InstructionData, DataFlowGraph}; @@ -52,6 +54,7 @@ pub mod arm64; pub mod registers; mod encoding; mod enc_tables; +mod constraints; /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. @@ -140,11 +143,17 @@ pub trait TargetIsa { fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result; /// Get a static array of names associated with encoding recipes in this ISA. Encoding recipes - /// are numbered starting from 0, corresponding to indexes into th name array. + /// are numbered starting from 0, corresponding to indexes into the name array. /// /// This is just used for printing and parsing encodings in the textual IL format. fn recipe_names(&self) -> &'static [&'static str]; + /// Get a static array of value operand constraints associated with encoding recipes in this + /// ISA. + /// + /// The constraints describe which registers can be used with an encoding recipe. + fn recipe_constraints(&self) -> &'static [RecipeConstraints]; + /// Create an object that can display an ISA-dependent encoding properly. fn display_enc(&self, enc: Encoding) -> encoding::DisplayEncoding { encoding::DisplayEncoding { diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index f32a313c92..2538911a25 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -5,6 +5,8 @@ use ir::instructions::InstructionFormat; use ir::types; use predicates; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; +use super::registers::*; // Include the generated encoding tables: // - `LEVEL1_RV32` diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 762df2249c..34b2a43c99 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -70,6 +70,10 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } #[cfg(test)] From 672e4abd7e619470433a2b286681bdb1a4423b69 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 13:57:43 -0800 Subject: [PATCH 485/968] Compute register class intersections. Ensure that the set of register classes is closed under intersection. Provide a RegClass::intersect() method which finds the register class representing the intersection of two classes. Generate a bit-mask of subclasses for each register class to be used by the intersect() method. Ensure that register classes are sorted topologically. This is also used by the intersect() method. --- lib/cretonne/meta/cdsl/isa.py | 13 +++ lib/cretonne/meta/cdsl/registers.py | 108 ++++++++++++++++++- lib/cretonne/meta/gen_registers.py | 21 ++-- lib/cretonne/src/isa/intel/registers.rs | 15 ++- lib/cretonne/src/isa/registers.rs | 54 ++++++++++ lib/cretonne/src/regalloc/allocatable_set.rs | 2 + 6 files changed, 200 insertions(+), 13 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 8e1423315c..0065e4e907 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -51,6 +51,7 @@ class TargetISA(object): """ self._collect_encoding_recipes() self._collect_predicates() + self._collect_regclasses() return self def _collect_encoding_recipes(self): @@ -96,6 +97,18 @@ class TargetISA(object): if enc.isap: self.settings.number_predicate(enc.isap) + def _collect_regclasses(self): + """ + Collect and number register classes. + + Every register class needs a unique index, and the classes need to be + topologically ordered. + """ + rc_index = 0 + for bank in self.regbanks: + bank.finish_regclasses(rc_index) + rc_index += len(bank.classes) + class CPUMode(object): """ diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 828e6a2704..99dd453aec 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -26,8 +26,11 @@ from . import is_power_of_two, next_power_of_two try: - from typing import Sequence # noqa + from typing import Sequence, Tuple # noqa from .isa import TargetISA # noqa + # A tuple uniquely identifying a register class inside a register bank. + # (count, width, start) + RCTup = Tuple[int, int, int] except ImportError: pass @@ -90,6 +93,63 @@ class RegBank(object): return ('RegBank({}, units={}, first_unit={})' .format(self.name, self.units, self.first_unit)) + def finish_regclasses(self, first_index): + # type: (int) -> None + """ + Assign indexes to the register classes in this bank, starting from + `first_index`. + + Verify that the set of register classes satisfies: + + 1. Closed under intersection: The intersection of any two register + classes in the set is either empty or identical to a member of the + set. + 2. There are no identical classes under different names. + 3. Classes are sorted topologically such that all subclasses have a + higher index that the superclass. + + We could reorder classes topologically here instead of just enforcing + the order, but the ordering tends to fall out naturally anyway. + """ + cmap = dict() # type: Dict[RCTup, RegClass] + + for idx, rc in enumerate(self.classes): + # All register classes must be given a name. + assert rc.name, "Anonymous register class found" + + # Assign a unique index. + assert rc.index is None + rc.index = idx + first_index + + # Check for duplicates. + tup = rc.rctup() + if tup in cmap: + raise AssertionError( + '{} and {} are identical register classes' + .format(rc, cmap[tup])) + cmap[tup] = rc + + # Check intersections and topological order. + for idx, rc1 in enumerate(self.classes): + for rc2 in self.classes[0:idx]: + itup = rc1.intersect(rc2) + if itup is None: + continue + if itup not in cmap: + raise AssertionError( + 'intersection of {} and {} missing' + .format(rc1, rc2)) + irc = cmap[itup] + # rc1 > rc2, so rc2 can't be the sub-class. + if irc is rc2: + raise AssertionError( + 'Bad topological order: {}/{}' + .format(rc1, rc2)) + if irc is rc1: + # The intersection of rc1 and rc2 is rc1, so it must be a + # sub-class. + rc2.subclasses.append(rc1) + class RegClass(object): """ @@ -111,10 +171,14 @@ class RegClass(object): def __init__(self, bank, count=None, width=1, start=0): # type: (RegBank, int, int, int) -> None self.name = None # type: str + self.index = None # type: int self.bank = bank self.start = start self.width = width + # This is computed later in `finish_regclasses()`. + self.subclasses = list() # type: List[RegClass] + assert width > 0 assert start >= 0 and start < bank.units @@ -127,13 +191,43 @@ class RegClass(object): def __str__(self): return self.name + def rctup(self): + # type: () -> RCTup + """ + Get a tuple that uniquely identifies the registers in this class. + + The tuple can be used as a dictionary key to ensure that there are no + duplicate register classes. + """ + return (self.count, self.width, self.start) + + def intersect(self, other): + # type: (RegClass) -> RCTup + """ + Get a tuple representing the intersction of two register classes. + + Returns `None` if the two classes are disjoint. + """ + if self.width != other.width: + return None + s_end = self.start + self.count * self.width + o_end = other.start + other.count * other.width + if self.start >= o_end or other.start >= s_end: + return None + + # We have an overlap. + start = max(self.start, other.start) + end = min(s_end, o_end) + count = (end - start) // self.width + assert count > 0 + return (count, self.width, start) + def __getitem__(self, sliced): """ Create a sub-class of a register class using slice notation. The slice indexes refer to allocations in the parent register class, not register units. """ - print(repr(sliced)) assert isinstance(sliced, slice), "RegClass slicing can't be 1 reg" # We could add strided sub-classes if needed. assert sliced.step is None, 'Subclass striding not supported' @@ -167,6 +261,16 @@ class RegClass(object): return mask + def subclass_mask(self): + # type: () -> int + """ + Compute a bit-mask of subclasses, including self. + """ + m = 1 << self.index + for rc in self.subclasses: + m |= 1 << rc.index + return m + @staticmethod def extract_names(globs): """ diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index d3afe3ca4c..c535f44c22 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -28,15 +28,16 @@ def gen_regbank(regbank, fmt): fmt.line('prefix: "{}",'.format(regbank.prefix)) -def gen_regclass(idx, rc, fmt): - # type: (int, RegClass, srcgen.Formatter) -> None +def gen_regclass(rc, fmt): + # type: (RegClass, srcgen.Formatter) -> None """ Emit a static data definition for a register class. """ fmt.comment(rc.name) with fmt.indented('RegClassData {', '},'): - fmt.line('index: {},'.format(idx)) + fmt.line('index: {},'.format(rc.index)) fmt.line('width: {},'.format(rc.width)) + fmt.line('subclasses: 0x{:x},'.format(rc.subclass_mask())) mask = ', '.join('0x{:08x}'.format(x) for x in rc.mask()) fmt.line('mask: [{}],'.format(mask)) @@ -62,15 +63,15 @@ def gen_isa(isa, fmt): with fmt.indented( 'const CLASSES: [RegClassData; {}] = ['.format(len(rcs)), '];'): for idx, rc in enumerate(rcs): - gen_regclass(idx, rc, fmt) + assert idx == rc.index + gen_regclass(rc, fmt) # Emit constants referencing the register classes. - for idx, rc in enumerate(rcs): - if rc.name: - fmt.line('#[allow(dead_code)]') - fmt.line( - 'pub const {}: RegClass = &CLASSES[{}];' - .format(rc.name, idx)) + for rc in rcs: + fmt.line('#[allow(dead_code)]') + fmt.line( + 'pub const {}: RegClass = &CLASSES[{}];' + .format(rc.name, rc.index)) def generate(isas, out_dir): diff --git a/lib/cretonne/src/isa/intel/registers.rs b/lib/cretonne/src/isa/intel/registers.rs index 82c5b9886e..14b4050d94 100644 --- a/lib/cretonne/src/isa/intel/registers.rs +++ b/lib/cretonne/src/isa/intel/registers.rs @@ -6,7 +6,7 @@ include!(concat!(env!("OUT_DIR"), "/registers-intel.rs")); #[cfg(test)] mod tests { - use super::INFO; + use super::*; use isa::RegUnit; #[test] @@ -46,4 +46,17 @@ mod tests { assert_eq!(uname(16), "%xmm0"); assert_eq!(uname(31), "%xmm15"); } + + #[test] + fn regclasses() { + assert_eq!(GPR.intersect(GPR), Some(GPR.into())); + assert_eq!(GPR.intersect(ABCD), Some(ABCD.into())); + assert_eq!(GPR.intersect(FPR), None); + assert_eq!(ABCD.intersect(GPR), Some(ABCD.into())); + assert_eq!(ABCD.intersect(ABCD), Some(ABCD.into())); + assert_eq!(ABCD.intersect(FPR), None); + assert_eq!(FPR.intersect(FPR), Some(FPR.into())); + assert_eq!(FPR.intersect(GPR), None); + assert_eq!(FPR.intersect(ABCD), None); + } } diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 48173c50bd..b223a3b5b0 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -1,5 +1,6 @@ //! Data structures describing the registers in an ISA. +use entity_map::EntityRef; use std::fmt; /// Register units are the smallest units of register allocation. @@ -106,11 +107,59 @@ pub struct RegClassData { /// How many register units to allocate per register. pub width: u8, + /// Bit-mask of sub-classes of this register class, including itself. + /// + /// Bits correspond to RC indexes. + pub subclasses: u32, + /// Mask of register units in the class. If `width > 1`, the mask only has a bit set for the /// first register unit in each allocatable register. pub mask: RegUnitMask, } +impl RegClassData { + /// Get the register class corresponding to the intersection of `self` and `other`. + /// + /// This register class is guaranteed to exist if the register classes overlap. If the register + /// classes don't overlap, returns `None`. + pub fn intersect(&self, other: RegClass) -> Option { + // Compute the set of common subclasses. + let mask = self.subclasses & other.subclasses; + + if mask == 0 { + // No overlap. + None + } else { + // Register class indexes are topologically ordered, so the largest common subclass has + // the smallest index. + Some(RegClassIndex(mask.trailing_zeros() as u8)) + } + } +} + +/// A small reference to a register class. +/// +/// Use this when storing register classes in compact data structures. The `RegInfo::rc()` method +/// can be used to get the real register class reference back. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RegClassIndex(u8); + +impl EntityRef for RegClassIndex { + fn new(idx: usize) -> Self { + RegClassIndex(idx as u8) + } + + fn index(self) -> usize { + self.0 as usize + } +} + +impl From for RegClassIndex { + fn from(rc: RegClass) -> Self { + RegClassIndex(rc.index) + } +} + /// Information about the registers in an ISA. /// /// The `RegUnit` data structure collects all relevant static information about the registers in an @@ -143,6 +192,11 @@ impl RegInfo { reginfo: self, } } + + /// Get the register class corresponding to `idx`. + pub fn rc(&self, idx: RegClassIndex) -> RegClass { + &self.classes[idx.index()] + } } /// Temporary object that holds enough information to print a register unit. diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 34145cd01a..f2ef0102d4 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -120,11 +120,13 @@ mod tests { const GPR: RegClass = &RegClassData { index: 0, width: 1, + subclasses: 0, mask: [0xf0000000, 0x0000000f, 0], }; const DPR: RegClass = &RegClassData { index: 0, width: 2, + subclasses: 0, mask: [0x50000000, 0x0000000a, 0], }; From 2932d41f183a17e7dace0b334232a79df98643ae Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:12:36 -0800 Subject: [PATCH 486/968] Run Python checks from test-all.sh The Python style enforcements are easy to miss otherwise. --- lib/cretonne/meta/check.sh | 12 +++--------- test-all.sh | 3 +++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh index 2077716eb7..655092e6cb 100755 --- a/lib/cretonne/meta/check.sh +++ b/lib/cretonne/meta/check.sh @@ -4,29 +4,23 @@ cd $(dirname "$0") runif() { if command -v "$1" > /dev/null; then - echo "=== $1 ===" + echo " === $1 ===" "$@" else echo "$1 not found" fi } -# Check Python sources for Python 3 compatibility using pylint. -# -# Install pylint with 'pip install pylint'. -runif pylint --py3k --reports=no -- *.py cdsl base cretonne isa - # Style linting. runif flake8 . # Type checking. runif mypy --py2 build.py -echo "=== Python unit tests ===" -python -m unittest discover +# Python unit tests. +runif python -m unittest discover # Then run the unit tests again with Python 3. # We get deprecation warnings about assertRaisesRegexp which was renamed in # Python 3, but there doesn't seem to be an easy workaround. runif python3 -Wignore:Deprecation -m unittest discover - diff --git a/test-all.sh b/test-all.sh index f55464ce3b..bf2270efb6 100755 --- a/test-all.sh +++ b/test-all.sh @@ -40,6 +40,9 @@ else echo "If a newer version of rustfmt is available, update this script." fi +banner "Python checks" +$topdir/lib/cretonne/meta/check.sh + PKGS="cretonne cretonne-reader cretonne-tools filecheck" cd "$topdir" for PKG in $PKGS From 42a0c27b246abca0d79b7db21a8aedba135e30cd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:20:22 -0800 Subject: [PATCH 487/968] Install mypy and flake8 in Travis environment. --- .travis.yml | 5 ++++- test-all.sh | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 034d0e6c55..7bd682fcfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,8 @@ rust: - stable - beta - nightly +install: pip install --upgrade mypy flake8 script: ./test-all.sh -cache: cargo +cache: + - cargo + - pip diff --git a/test-all.sh b/test-all.sh index bf2270efb6..ef85cd19ee 100755 --- a/test-all.sh +++ b/test-all.sh @@ -40,7 +40,7 @@ else echo "If a newer version of rustfmt is available, update this script." fi -banner "Python checks" +banner $(python --version 2>&1) $topdir/lib/cretonne/meta/check.sh PKGS="cretonne cretonne-reader cretonne-tools filecheck" From c111361e19ca8b60e9e6d2de46765f87f9cfe76d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:26:28 -0800 Subject: [PATCH 488/968] Install Python packages without Travis root user. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7bd682fcfc..417d67855b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ rust: - stable - beta - nightly -install: pip install --upgrade mypy flake8 +install: pip install --user --upgrade mypy flake8 script: ./test-all.sh cache: - cargo From 8635aedc20ee1230511d6a8a22c8070b771ef1a4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:32:52 -0800 Subject: [PATCH 489/968] Use Python 3.6 in Travis builds --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 417d67855b..1f6844c672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ rust: - stable - beta - nightly +python: "3.6" install: pip install --user --upgrade mypy flake8 script: ./test-all.sh cache: From 27e735b028bb6563f736e7df6a97913b887931e3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:45:41 -0800 Subject: [PATCH 490/968] Pull in a python3 Ubuntu package for Travis CI. Then use pip3 to install dependencies. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f6844c672..1910397b6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,11 @@ rust: - stable - beta - nightly -python: "3.6" -install: pip install --user --upgrade mypy flake8 +addons: + apt: + packages: + - python3-pip +install: pip3 install --user --upgrade mypy flake8 script: ./test-all.sh cache: - cargo From f2b567b83f8e30d94813b1ad14187574e5bbac18 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:51:48 -0800 Subject: [PATCH 491/968] The python3-pip package does not exist on Ubuntu 12.04 LTS. Try to go via python3-setuptools instead. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1910397b6c..ebaf2199f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,10 @@ rust: addons: apt: packages: - - python3-pip -install: pip3 install --user --upgrade mypy flake8 + - python3-setuptools +install: + - easy_install3 --user pip + - python3 -m pip install --user --upgrade mypy flake8 script: ./test-all.sh cache: - cargo From 188ffb9881bdab7b52f6bf853c2e66c93de4bbc1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 14:59:48 -0800 Subject: [PATCH 492/968] Doesn't work with 12.02 LTS's Python 3.2. Try switching to Trusty to get a newer Python 3. --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ebaf2199f7..38f42b15b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,14 @@ rust: - stable - beta - nightly +dist: trusty +sudo: false addons: apt: packages: - - python3-setuptools + - python3-pip install: - - easy_install3 --user pip - - python3 -m pip install --user --upgrade mypy flake8 + - pip3 install --user --upgrade mypy flake8 script: ./test-all.sh cache: - cargo From c132b8c328c74687da3b7da38b0ed456559985ad Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 15:17:27 -0800 Subject: [PATCH 493/968] Upgrade to rustfmt 0.7.1 --- lib/cretonne/src/isa/registers.rs | 10 ++++----- lib/reader/src/sourcemap.rs | 34 +++++++++++++++---------------- src/filetest/concurrent.rs | 6 ++---- test-all.sh | 2 +- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index b223a3b5b0..f8c2ee5731 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -66,12 +66,10 @@ impl RegBank { } } } - .and_then(|offset| { - if offset < self.units { - Some(offset + self.first_unit) - } else { - None - } + .and_then(|offset| if offset < self.units { + Some(offset + self.first_unit) + } else { + None }) } diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 26d874817d..369e732e90 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -62,25 +62,23 @@ impl SourceMap { /// Look up an entity by source name. /// Returns the entity reference corresponding to `name`, if it exists. pub fn lookup_str(&self, name: &str) -> Option { - split_entity_name(name).and_then(|(ent, num)| { - match ent { - "v" => { - Value::direct_with_number(num) - .and_then(|v| self.get_value(v)) - .map(AnyEntity::Value) - } - "vx" => { - Value::table_with_number(num) - .and_then(|v| self.get_value(v)) - .map(AnyEntity::Value) - } - "ebb" => Ebb::with_number(num).and_then(|e| self.get_ebb(e)).map(AnyEntity::Ebb), - "ss" => self.get_ss(num).map(AnyEntity::StackSlot), - "sig" => self.get_sig(num).map(AnyEntity::SigRef), - "fn" => self.get_fn(num).map(AnyEntity::FuncRef), - "jt" => self.get_jt(num).map(AnyEntity::JumpTable), - _ => None, + split_entity_name(name).and_then(|(ent, num)| match ent { + "v" => { + Value::direct_with_number(num) + .and_then(|v| self.get_value(v)) + .map(AnyEntity::Value) } + "vx" => { + Value::table_with_number(num) + .and_then(|v| self.get_value(v)) + .map(AnyEntity::Value) + } + "ebb" => Ebb::with_number(num).and_then(|e| self.get_ebb(e)).map(AnyEntity::Ebb), + "ss" => self.get_ss(num).map(AnyEntity::StackSlot), + "sig" => self.get_sig(num).map(AnyEntity::SigRef), + "fn" => self.get_fn(num).map(AnyEntity::FuncRef), + "jt" => self.get_jt(num).map(AnyEntity::JumpTable), + _ => None, }) } diff --git a/src/filetest/concurrent.rs b/src/filetest/concurrent.rs index 30abb844d4..e0afc8bead 100644 --- a/src/filetest/concurrent.rs +++ b/src/filetest/concurrent.rs @@ -96,10 +96,8 @@ impl ConcurrentRunner { fn heartbeat_thread(replies: Sender) -> thread::JoinHandle<()> { thread::Builder::new() .name("heartbeat".to_string()) - .spawn(move || { - while replies.send(Reply::Tick).is_ok() { - thread::sleep(Duration::from_secs(1)); - } + .spawn(move || while replies.send(Reply::Tick).is_ok() { + thread::sleep(Duration::from_secs(1)); }) .unwrap() } diff --git a/test-all.sh b/test-all.sh index ef85cd19ee..8035703488 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.6.3" +RUSTFMT_VERSION="0.7.1" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" From 5b71ec922a3257190a918af9d5a622859404e685 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 15:35:58 -0800 Subject: [PATCH 494/968] Add pip files to the cache. --- .travis.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 38f42b15b9..f7b0f62d64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,18 @@ language: rust rust: - - stable - - beta - - nightly + - stable + - beta + - nightly dist: trusty sudo: false addons: - apt: - packages: - - python3-pip + apt: + packages: + - python3-pip install: - - pip3 install --user --upgrade mypy flake8 + - pip3 install --user --upgrade mypy flake8 script: ./test-all.sh cache: - - cargo - - pip + cargo: true + directories: + - $HOME/.cache/pip From 6e33173fce77d2d1f262f6f1e25a92e6b44a8bd5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 15:42:16 -0800 Subject: [PATCH 495/968] Install rustfmt when running under Travis CI. The built rustfmt should be cached. --- test-all.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-all.sh b/test-all.sh index 8035703488..aebec1a872 100755 --- a/test-all.sh +++ b/test-all.sh @@ -35,6 +35,12 @@ RUSTFMT_VERSION="0.7.1" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" $topdir/format-all.sh --write-mode=diff +elif [ -n "$TRAVIS" ]; then + # We're running under Travis CI. + # Install rustfmt, it will be cached for the next build. + echo "Installing rustfmt v$RUSTFMT_VERSION." + cargo install --force --vers="$RUSTFMT_VERSION" rustfmt + $topdir/format-all.sh --write-mode=diff else echo "Please install rustfmt v$RUSTFMT_VERSION to verify formatting." echo "If a newer version of rustfmt is available, update this script." From 0d3990c3945cd1ef0ceeeeea7b84044de2d96236 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 15:57:43 -0800 Subject: [PATCH 496/968] Make sure we can find rustfmt. --- format-all.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/format-all.sh b/format-all.sh index 7d51b7d343..d9e97f1026 100755 --- a/format-all.sh +++ b/format-all.sh @@ -8,6 +8,9 @@ set -e cd $(dirname "$0") src=$(pwd) +# Make sure we can find rustfmt. +export PATH="$PATH:$HOME/.cargo/bin" + for crate in $(find "$src" -name Cargo.toml); do cd $(dirname "$crate") cargo fmt -- "$@" From 7e54cdb4f55482d74619f87f54c8cee575db813f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 25 Jan 2017 16:35:28 -0800 Subject: [PATCH 497/968] Stop testing on nightly rust The nightly compiler isn't able to compile rustfmt in 10 minutes. This causes Travis CI to terminate the build. We keep testing on beta and stable. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f7b0f62d64..1986553bf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: rust rust: - stable - beta - - nightly dist: trusty sudo: false addons: From a395f01b3eafe36beeadf744e337ec1f30208eb6 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Fri, 27 Jan 2017 18:42:05 +0100 Subject: [PATCH 498/968] Fix some typos in the documentation These were found by the spellchecker. --- docs/langref.rst | 2 +- docs/regalloc.rst | 2 +- docs/testing.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 40cc65dbe3..5bd59b18d3 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -882,7 +882,7 @@ Glossary - Type and flags of each argument. - Type and flags of each return value. - Not all function atributes are part of the signature. For example, a + Not all function attributes are part of the signature. For example, a function that never returns could be marked as ``noreturn``, but that is not necessary to know when calling it, so it is just an attribute, and not part of the signature. diff --git a/docs/regalloc.rst b/docs/regalloc.rst index 1d662e9a63..75477e7375 100644 --- a/docs/regalloc.rst +++ b/docs/regalloc.rst @@ -206,7 +206,7 @@ top-down order, and each value define by the instruction is assigned an available register. With this iteration order, every value that is live at an instruction has already been assigned to a register. -This coloring algorith works if the following condition holds: +This coloring algorithm works if the following condition holds: At every instruction, consider the values live through the instruction. No matter how the live values have been assigned to registers, there must be diff --git a/docs/testing.rst b/docs/testing.rst index d52b180c99..a452dc1b58 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -139,7 +139,7 @@ run will also have the RISC-V specific flag ``supports_m`` disabled. Filecheck --------- -Many of the test commands bescribed below use *filecheck* to verify their +Many of the test commands described below use *filecheck* to verify their output. Filecheck is a Rust implementation of the LLVM tool of the same name. See the :file:`lib/filecheck` `documentation `_ for details of its syntax. From 3c4d54c4bd2c514fe75134bd3ddb3f2df960e5c2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 26 Jan 2017 14:51:49 -0800 Subject: [PATCH 499/968] Implement value affinities for register allocation. An SSA value is usually biased towards a specific register class or a stack slot, depending on the constraints of the instructions using it. Represent this bias as an Affinity enum, and implement a merging algorithm for updating an affinity to satisfy a new constraint. Affinities will be computed as part of the liveness analysis. This is not implemented yet. --- .gitignore | 1 + lib/cretonne/src/isa/constraints.rs | 1 + lib/cretonne/src/isa/mod.rs | 4 +- lib/cretonne/src/isa/registers.rs | 6 +++ lib/cretonne/src/regalloc/affinity.rs | 68 ++++++++++++++++++++++++++ lib/cretonne/src/regalloc/liverange.rs | 15 ++++-- lib/cretonne/src/regalloc/mod.rs | 2 + 7 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 lib/cretonne/src/regalloc/affinity.rs diff --git a/.gitignore b/.gitignore index 9ceaac3ed5..765772864e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tags target Cargo.lock +.*.rustfmt diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index bdd2bea958..43a0ddb271 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -21,6 +21,7 @@ pub struct OperandConstraint { } /// The different kinds of operand constraints. +#[derive(Clone, Copy, PartialEq, Eq)] pub enum ConstraintKind { /// This operand or result must be a register from the given register class. Reg, diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 0bd9e5e9fa..ee33bb4c47 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -41,8 +41,8 @@ //! concurrent function compilations. pub use isa::encoding::Encoding; -pub use isa::registers::{RegInfo, RegUnit, RegClass}; -pub use isa::constraints::RecipeConstraints; +pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; +pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind}; use settings; use ir::{InstructionData, DataFlowGraph}; diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index f8c2ee5731..5e89110284 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -133,6 +133,12 @@ impl RegClassData { Some(RegClassIndex(mask.trailing_zeros() as u8)) } } + + /// Returns true if `other` is a subclass of this register class. + /// A register class is considerd to be a subclass of itself. + pub fn has_subclass>(&self, other: RCI) -> bool { + self.subclasses & (1 << other.into().0) != 0 + } } /// A small reference to a register class. diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs new file mode 100644 index 0000000000..529828759e --- /dev/null +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -0,0 +1,68 @@ +//! Value affinity for register allocation. +//! +//! An SSA value's affinity is a hint used to guide the register allocator. It specifies the class +//! of allocation that is likely to cause the least amount of fixup moves in order to satisfy +//! instruction operand constraints. +//! +//! For values that want to be in registers, the affinity hint includes a register class or +//! subclass. This is just a hint, and the register allocator is allowed to pick a register from a +//! larger register class instead. + +use isa::{RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; + +/// Preferred register allocation for an SSA value. +#[derive(Clone, Copy)] +pub enum Affinity { + /// Don't care. This value can go anywhere. + Any, + + /// This value should be placed in a spill slot on the stack. + Stack, + + /// This value prefers a register from the given register class. + Reg(RegClassIndex), +} + +impl Default for Affinity { + fn default() -> Self { + Affinity::Any + } +} + +impl Affinity { + /// Create an affinity that satisfies a single constraint. + /// + /// This will never create the indifferent `Affinity::Any`. + /// Use the `Default` implementation for that. + pub fn new(constraint: &OperandConstraint) -> Affinity { + if constraint.kind == ConstraintKind::Stack { + Affinity::Stack + } else { + Affinity::Reg(constraint.regclass.into()) + } + } + + /// Merge an operand constraint into this affinity. + /// + /// Note that this does not guarantee that the register allocator will pick a register that + /// satisfies the constraint. + pub fn merge(&mut self, constraint: &OperandConstraint, reg_info: &RegInfo) { + match *self { + Affinity::Any => *self = Affinity::new(constraint), + Affinity::Reg(rc) => { + // If the preferred register class is a subclass of the constraint, there's no need + // to change anything. + if constraint.kind != ConstraintKind::Stack && + !constraint.regclass.has_subclass(rc) { + // If the register classes don't overlap, `intersect` returns `None`, and we + // just keep our previous affinity. + if let Some(subclass) = constraint.regclass.intersect(reg_info.rc(rc)) { + // This constraint shrinks our preferred register class. + *self = Affinity::Reg(subclass); + } + } + } + Affinity::Stack => {} + } + } +} diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index e192efdc4d..67667a3a47 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -73,9 +73,9 @@ //! 3. A numerical order by EBB number. Performant because it doesn't need to indirect through the //! `ProgramOrder` for comparisons. //! -//! These orderings will cause small differences in coalescing opportinities, but all of them would +//! These orderings will cause small differences in coalescing opportunities, but all of them would //! do a decent job of compressing a long live range. The numerical order might be preferable -//! beacuse: +//! because: //! //! - It has better performance because EBB numbers can be compared directly without any table //! lookups. @@ -92,7 +92,7 @@ //! //! Coalescing is an important compression technique because some live ranges can span thousands of //! EBBs. We can represent that by switching to a sorted `Vec` representation where -//! an `[Ebb, Inst]` pair represents a coalesced range, while an `Inst` entry without a preceeding +//! an `[Ebb, Inst]` pair represents a coalesced range, while an `Inst` entry without a preceding //! `Ebb` entry represents a single live-in interval. //! //! This representation is more compact for a live range with many uncoalesced live-in intervals. @@ -109,6 +109,7 @@ use std::cmp::Ordering; use ir::{Inst, Ebb, Value, ProgramPoint, ProgramOrder}; +use regalloc::affinity::Affinity; use sparse_map::SparseMapValue; /// Global live range of a single SSA value. @@ -143,6 +144,9 @@ pub struct LiveRange { /// This member can't be modified in case the live range is stored in a `SparseMap`. value: Value, + /// The preferred register allocation for this value. + pub affinity: Affinity, + /// The instruction or EBB header where this value is defined. def_begin: ProgramPoint, @@ -167,7 +171,7 @@ pub struct LiveRange { /// An additional contiguous interval of a global live range. /// /// This represents a live-in interval for a single EBB, or a coalesced set of live-in intervals -/// for contiguous EBBs where all but the last live-in inteval covers the whole EBB. +/// for contiguous EBBs where all but the last live-in interval covers the whole EBB. /// struct Interval { /// Interval starting point. @@ -205,6 +209,7 @@ impl LiveRange { let def = def.into(); LiveRange { value: value, + affinity: Default::default(), def_begin: def, def_end: def, liveins: Vec::new(), @@ -238,7 +243,7 @@ impl LiveRange { /// is live-in to `ebb`, extending to `to`. Return true. /// /// The return value can be used to detect if we just learned that the value is live-in to - /// `ebb`. This can trigger recursive extensions in `ebb`'s CFG precedessor blocks. + /// `ebb`. This can trigger recursive extensions in `ebb`'s CFG predecessor blocks. pub fn extend_in_ebb(&mut self, ebb: Ebb, to: Inst, order: &PO) -> bool { // First check if we're extending the def interval. // diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 9e0f7b820d..bd1c34cd23 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -5,3 +5,5 @@ pub mod liverange; pub mod liveness; pub mod allocatable_set; + +mod affinity; From 0ada419fe7eb9bf7c6e1e1fba1f0104a1bfd615c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 27 Jan 2017 14:31:14 -0800 Subject: [PATCH 500/968] Add entity lists. Like a vector, but with a tiny footprint, and allocated from a pool so all memory can be released very quickly. --- lib/cretonne/src/entity_list.rs | 519 ++++++++++++++++++++++++++++++++ lib/cretonne/src/lib.rs | 1 + 2 files changed, 520 insertions(+) create mode 100644 lib/cretonne/src/entity_list.rs diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs new file mode 100644 index 0000000000..6ec23e39e2 --- /dev/null +++ b/lib/cretonne/src/entity_list.rs @@ -0,0 +1,519 @@ +//! Small lists of entity references. +//! +//! This module defines an `EntityList` type which provides similar functionality to `Vec`, +//! but with some important differences in the implementation: +//! +//! 1. Memory is allocated from a `ListPool` instead of the global heap. +//! 2. The footprint of an entity list is 4 bytes, compared with the 24 bytes for `Vec`. +//! 3. An entity list doesn't implement `Drop`, leaving it to the pool to manage memory. +//! +//! The list pool is intended to be used as a LIFO allocator. After building up a larger data +//! structure with many list references, the whole thing can be discarded quickly by clearing the +//! pool. +//! +//! # Safety +//! +//! Entity lists are not as safe to use as `Vec`, but they never jeopardize Rust's memory safety +//! guarantees. These are the problems to be aware of: +//! +//! - If you lose track of an entity list, it's memory won't be recycled until the pool is cleared. +//! This can cause the pool to grow very large with leaked lists. +//! - If entity lists are used after their pool is cleared, they may contain garbage data, and +//! modifying them may corrupt other lists in the pool. +//! - If an entity list is used with two different pool instances, both pools are likely to become +//! corrupted. +//! +//! # Implementation +//! +//! The `EntityList` itself is designed to have the smallest possible footprint. This is important +//! because it is used inside very compact data structures like `InstructionData`. The list +//! contains only a 32-bit index into the pool's memory vector, pointing to the first element of +//! the list. +//! +//! The pool is just a single `Vec` containing all of the allocated lists. Each list is +//! represented as three contiguous parts: +//! +//! 1. The number of elements in the list. +//! 2. The list elements. +//! 3. Excess capacity elements. +//! +//! The total size of the three parts is always a power of two, and the excess capacity is always +//! as small as possible. This means that shrinking a list may cause the excess capacity to shrink +//! if a smaller power-of-two size becomes available. +//! +//! Both growing and shrinking a list may cause it to be reallocated in the pool vector. +//! +//! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is +//! reserved for the empty list which isn't allocated in the vector. + +use std::marker::PhantomData; + +use entity_map::EntityRef; + +/// A small list of entity references allocated from a pool. +/// +/// All of the list methods that take a pool reference must be given the same pool reference every +/// time they are called. Otherwise data structures will be corrupted. +pub struct EntityList { + index: u32, + unused: PhantomData, +} + +/// Create an empty list. +impl Default for EntityList { + fn default() -> Self { + EntityList { + index: 0, + unused: PhantomData, + } + } +} + +/// A memory pool for storing lists of `T`. +pub struct ListPool { + // The main array containing the lists. + data: Vec, + + // Heads of the free lists, one for each size class. + free: Vec, +} + +/// Lists are allocated in sizes that are powers of two, starting from 4. +/// Each power of two is assigned a size class number, so the size is `4 << SizeClass`. +type SizeClass = u8; + +/// Get the size of a given size class. The size includes the length field, so the maximum list +/// length is one less than the class size. +fn sclass_size(sclass: SizeClass) -> usize { + 4 << sclass +} + +/// Get the size class to use for a given list length. +/// This always leaves room for the length element in addition to the list elements. +fn sclass_for_length(len: usize) -> SizeClass { + 30 - (len as u32 | 3).leading_zeros() as SizeClass +} + +/// Is `len` the minimum length in its size class? +fn is_sclass_min_length(len: usize) -> bool { + len > 3 && len.is_power_of_two() +} + +impl ListPool { + /// Create a new list pool. + pub fn new() -> ListPool { + ListPool { + data: Vec::new(), + free: Vec::new(), + } + } + + /// Clear the pool, forgetting about all lists that use it. + /// + /// This invalidates any existing entity lists that used this pool to allocate memory. + /// + /// The pool's memory is not released to the operating system, but kept around for faster + /// allocation in the future. + pub fn clear(&mut self) { + self.data.clear(); + self.free.clear(); + } + + /// Read the length of a list field, if it exists. + fn len_of(&self, list: &EntityList) -> Option { + let idx = list.index as usize; + // `idx` points at the list elements. The list length is encoded in the element immediately + // before the list elements. + // + // The `wrapping_sub` handles the special case 0, which is the empty list. This way, the + // cost of the bounds check that we have to pay anyway is co-opted to handle the special + // case of the empty list. + self.data.get(idx.wrapping_sub(1)).map(|len| len.index()) + } + + /// Allocate a storage block with a size given by `sclass`. + /// + /// Returns the first index of an available segment of `self.data` containing + /// `sclass_size(sclass)` elements. + fn alloc(&mut self, sclass: SizeClass) -> usize { + // First try the free list for this size class. + match self.free.get(sclass as usize).cloned() { + Some(head) if head > 0 => { + // The free list pointers are offset by 1, using 0 to terminate the list. + // A block on the free list has two entries: `[ 0, next ]`. + // The 0 is where the length field would be stored for a block in use. + // The free list heads and the next pointer point at the `next` field. + self.free[sclass as usize] = self.data[head].index(); + head - 1 + } + _ => { + // Nothing on the free list. Allocate more memory. + let offset = self.data.len(); + // We don't want to mess around with uninitialized data. + // Just fill it up with nulls. + self.data.resize(offset + sclass_size(sclass), T::new(0)); + offset + } + } + } + + /// Free a storage block with a size given by `sclass`. + /// + /// This must be a block that was previously allocated by `alloc()` with the same size class. + fn free(&mut self, block: usize, sclass: SizeClass) { + let sclass = sclass as usize; + + // Make sure we have a free-list head for `sclass`. + if self.free.len() <= sclass { + self.free.resize(sclass + 1, 0); + } + + // Make sure the length field is cleared. + self.data[block] = T::new(0); + // Insert the block on the free list which is a single linked list. + self.data[block + 1] = T::new(self.free[sclass]); + self.free[sclass] = block + 1 + } + + /// Returns two mutable slices representing the two requested blocks. + /// + /// The two returned slices can be longer than the blocks. Each block is located at the front + /// the the respective slice. + fn mut_slices(&mut self, block0: usize, block1: usize) -> (&mut [T], &mut [T]) { + if block0 < block1 { + let (s0, s1) = self.data.split_at_mut(block1); + (&mut s0[block0..], s1) + } else { + let (s1, s0) = self.data.split_at_mut(block0); + (s0, &mut s1[block1..]) + } + } + + /// Reallocate a block to a different size class. + /// + /// Copy `elems_to_copy` elements from the old to the new block. + fn realloc(&mut self, + block: usize, + from_sclass: SizeClass, + to_sclass: SizeClass, + elems_to_copy: usize) + -> usize { + assert!(elems_to_copy <= sclass_size(from_sclass)); + assert!(elems_to_copy <= sclass_size(to_sclass)); + let new_block = self.alloc(to_sclass); + + if elems_to_copy > 0 { + let (old, new) = self.mut_slices(block, new_block); + (&mut new[0..elems_to_copy]).copy_from_slice(&old[0..elems_to_copy]); + } + + self.free(block, from_sclass); + new_block + } +} + +impl EntityList { + /// Returns `true` if the list has a length of 0. + pub fn is_empty(&self) -> bool { + // 0 is a magic value for the empty list. Any list in the pool array must have a positive + // length. + self.index == 0 + } + + /// Get the number of elements in the list. + pub fn len(&self, pool: &ListPool) -> usize { + // Both the empty list and any invalidated old lists will return `None`. + pool.len_of(self).unwrap_or(0) + } + + /// Get the list as a slice. + pub fn as_slice<'a>(&'a self, pool: &'a ListPool) -> &'a [T] { + let idx = self.index as usize; + match pool.len_of(self) { + None => &[], + Some(len) => &pool.data[idx..idx + len], + } + } + + /// Get a single element from the list. + pub fn get(&self, index: usize, pool: &ListPool) -> Option { + self.as_slice(pool).get(index).cloned() + } + + /// Get the list as a mutable slice. + pub fn as_mut_slice<'a>(&'a mut self, pool: &'a mut ListPool) -> &'a mut [T] { + let idx = self.index as usize; + match pool.len_of(self) { + None => &mut [], + Some(len) => &mut pool.data[idx..idx + len], + } + } + + /// Get a mutable reference to a single element from the list. + pub fn get_mut<'a>(&'a mut self, index: usize, pool: &'a mut ListPool) -> Option<&'a mut T> { + self.as_mut_slice(pool).get_mut(index) + } + + /// Removes all elements from the list. + /// + /// The memory used by the list is put back in the pool. + pub fn clear(&mut self, pool: &mut ListPool) { + let idx = self.index as usize; + match pool.len_of(self) { + None => assert_eq!(idx, 0, "Invalid pool"), + Some(len) => pool.free(idx - 1, sclass_for_length(len)), + } + // Switch back to the empty list representation which has no storage. + self.index = 0; + } + + /// Appends an element to the back of the list. + pub fn push(&mut self, element: T, pool: &mut ListPool) { + let idx = self.index as usize; + match pool.len_of(self) { + None => { + // This is an empty list. Allocate a block and set length=1. + assert_eq!(idx, 0, "Invalid pool"); + let block = pool.alloc(sclass_for_length(1)); + pool.data[block] = T::new(1); + pool.data[block + 1] = element; + self.index = (block + 1) as u32; + } + Some(len) => { + // Do we need to reallocate? + let new_len = len + 1; + let block; + if is_sclass_min_length(new_len) { + // Reallocate, preserving length + all old elements. + let sclass = sclass_for_length(len); + block = pool.realloc(idx - 1, sclass, sclass + 1, len + 1); + self.index = (block + 1) as u32; + } else { + block = idx - 1; + } + pool.data[block + new_len] = element; + pool.data[block] = T::new(new_len); + } + } + } + + /// Appends multiple elements to the back of the list. + pub fn extend(&mut self, elements: I, pool: &mut ListPool) + where I: IntoIterator + { + // TODO: use `size_hint()` to reduce reallocations. + for x in elements { + self.push(x, pool); + } + } + + /// Inserts an element as position `index` in the list, shifting all elements after it to the + /// right. + pub fn insert(&mut self, index: usize, element: T, pool: &mut ListPool) { + // Increase size by 1. + self.push(element, pool); + + // Move tail elements. + let seq = self.as_mut_slice(pool); + if index < seq.len() { + let tail = &mut seq[index..]; + for i in (1..tail.len()).rev() { + tail[i] = tail[i - 1]; + } + tail[0] = element; + } else { + assert_eq!(index, seq.len()); + } + } + + /// Removes the element at position `index` from the list. + pub fn remove(&mut self, index: usize, pool: &mut ListPool) { + let len; + { + let seq = self.as_mut_slice(pool); + len = seq.len(); + assert!(index < len); + + // Copy elements down. + for i in index..len - 1 { + seq[i] = seq[i + 1]; + } + } + + // Check if we deleted the last element. + if len == 1 { + self.clear(pool); + return; + } + + // Do we need to reallocate to a smaller size class? + let mut block = self.index as usize - 1; + if is_sclass_min_length(len) { + let sclass = sclass_for_length(len); + block = pool.realloc(block, sclass, sclass - 1, len); + self.index = (block + 1) as u32; + } + + // Finally adjust the length. + pool.data[block] = T::new(len - 1); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::{sclass_size, sclass_for_length}; + use ir::Inst; + use entity_map::EntityRef; + + #[test] + fn size_classes() { + assert_eq!(sclass_size(0), 4); + assert_eq!(sclass_for_length(0), 0); + assert_eq!(sclass_for_length(1), 0); + assert_eq!(sclass_for_length(2), 0); + assert_eq!(sclass_for_length(3), 0); + assert_eq!(sclass_for_length(4), 1); + assert_eq!(sclass_for_length(7), 1); + assert_eq!(sclass_for_length(8), 2); + assert_eq!(sclass_size(1), 8); + for l in 0..300 { + assert!(sclass_size(sclass_for_length(l)) >= l + 1); + } + } + + #[test] + fn block_allocator() { + let mut pool = ListPool::::new(); + let b1 = pool.alloc(0); + let b2 = pool.alloc(1); + let b3 = pool.alloc(0); + assert_ne!(b1, b2); + assert_ne!(b1, b3); + assert_ne!(b2, b3); + pool.free(b2, 1); + let b2a = pool.alloc(1); + let b2b = pool.alloc(1); + assert_ne!(b2a, b2b); + // One of these should reuse the freed block. + assert!(b2a == b2 || b2b == b2); + + // Check the free lists for a size class smaller than the largest seen so far. + pool.free(b1, 0); + pool.free(b3, 0); + let b1a = pool.alloc(0); + let b3a = pool.alloc(0); + assert_ne!(b1a, b3a); + assert!(b1a == b1 || b1a == b3); + assert!(b3a == b1 || b3a == b3); + } + + #[test] + fn empty_list() { + let pool = &mut ListPool::::new(); + let mut list = EntityList::::default(); + { + let ilist = &list; + assert!(ilist.is_empty()); + assert_eq!(ilist.len(pool), 0); + assert_eq!(ilist.as_slice(pool), &[]); + assert_eq!(ilist.get(0, pool), None); + assert_eq!(ilist.get(100, pool), None); + } + assert_eq!(list.as_mut_slice(pool), &[]); + assert_eq!(list.get_mut(0, pool), None); + assert_eq!(list.get_mut(100, pool), None); + + list.clear(pool); + assert!(list.is_empty()); + assert_eq!(list.len(pool), 0); + assert_eq!(list.as_slice(pool), &[]); + } + + #[test] + fn push() { + let pool = &mut ListPool::::new(); + let mut list = EntityList::::default(); + + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let i3 = Inst::new(3); + let i4 = Inst::new(4); + + list.push(i1, pool); + assert_eq!(list.len(pool), 1); + assert!(!list.is_empty()); + assert_eq!(list.as_slice(pool), &[i1]); + assert_eq!(list.get(0, pool), Some(i1)); + assert_eq!(list.get(1, pool), None); + + list.push(i2, pool); + assert_eq!(list.len(pool), 2); + assert!(!list.is_empty()); + assert_eq!(list.as_slice(pool), &[i1, i2]); + assert_eq!(list.get(0, pool), Some(i1)); + assert_eq!(list.get(1, pool), Some(i2)); + assert_eq!(list.get(2, pool), None); + + list.push(i3, pool); + assert_eq!(list.len(pool), 3); + assert!(!list.is_empty()); + assert_eq!(list.as_slice(pool), &[i1, i2, i3]); + assert_eq!(list.get(0, pool), Some(i1)); + assert_eq!(list.get(1, pool), Some(i2)); + assert_eq!(list.get(2, pool), Some(i3)); + assert_eq!(list.get(3, pool), None); + + // This triggers a reallocation. + list.push(i4, pool); + assert_eq!(list.len(pool), 4); + assert!(!list.is_empty()); + assert_eq!(list.as_slice(pool), &[i1, i2, i3, i4]); + assert_eq!(list.get(0, pool), Some(i1)); + assert_eq!(list.get(1, pool), Some(i2)); + assert_eq!(list.get(2, pool), Some(i3)); + assert_eq!(list.get(3, pool), Some(i4)); + assert_eq!(list.get(4, pool), None); + + list.extend([i1, i1, i2, i2, i3, i3, i4, i4].iter().cloned(), pool); + assert_eq!(list.len(pool), 12); + assert_eq!(list.as_slice(pool), + &[i1, i2, i3, i4, i1, i1, i2, i2, i3, i3, i4, i4]); + } + + #[test] + fn insert_remove() { + let pool = &mut ListPool::::new(); + let mut list = EntityList::::default(); + + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let i3 = Inst::new(3); + let i4 = Inst::new(4); + + list.insert(0, i4, pool); + assert_eq!(list.as_slice(pool), &[i4]); + + list.insert(0, i3, pool); + assert_eq!(list.as_slice(pool), &[i3, i4]); + + list.insert(2, i2, pool); + assert_eq!(list.as_slice(pool), &[i3, i4, i2]); + + list.insert(2, i1, pool); + assert_eq!(list.as_slice(pool), &[i3, i4, i1, i2]); + + list.remove(3, pool); + assert_eq!(list.as_slice(pool), &[i3, i4, i1]); + + list.remove(2, pool); + assert_eq!(list.as_slice(pool), &[i3, i4]); + + list.remove(0, pool); + assert_eq!(list.as_slice(pool), &[i4]); + + list.remove(0, pool); + assert_eq!(list.as_slice(pool), &[]); + assert!(list.is_empty()); + } +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 25feb3c0db..436e2829e3 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -14,6 +14,7 @@ pub mod isa; pub mod cfg; pub mod dominator_tree; pub mod entity_map; +pub mod entity_list; pub mod sparse_map; pub mod settings; pub mod verifier; From 4293bed745ad1e01967648e9263107595e806886 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Feb 2017 11:25:27 -0800 Subject: [PATCH 501/968] Fix a dead code warning from the new Rust compiler. On ISAs with no instruction predicates, just emit an unimplemented!() stub for the check_instp() function. It is unlikely that a finished ISA will not have any instruction predicates. --- lib/cretonne/meta/gen_encoding.py | 12 ++++++++++-- lib/cretonne/src/isa/arm32/enc_tables.rs | 1 - lib/cretonne/src/isa/arm64/enc_tables.rs | 1 - lib/cretonne/src/isa/intel/enc_tables.rs | 1 - lib/cretonne/src/isa/riscv/enc_tables.rs | 1 - 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 30d1ed3370..3d4a00c4cc 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -75,7 +75,7 @@ def emit_instp(instp, fmt): """ iform = instp.predicate_context() - # Which fiels do we need in the InstructionData pattern match? + # Which fields do we need in the InstructionData pattern match? if iform.boxed_storage: fields = 'ref data' else: @@ -99,10 +99,18 @@ def emit_instps(instps, fmt): """ if not instps: - fmt.line('#[allow(unused_variables)]') + # If the ISA has no predicates, just emit a stub. + with fmt.indented( + 'pub fn check_instp(_: &InstructionData, _: u16) ' + + '-> bool {', '}'): + fmt.line('unimplemented!()') + return + with fmt.indented( 'pub fn check_instp(inst: &InstructionData, instp_idx: u16) ' + '-> bool {', '}'): + # The matches emitted by `emit_instp` need this. + fmt.line('use ir::instructions::InstructionFormat;') with fmt.indented('match instp_idx {', '}'): for instp in instps: emit_instp(instp, fmt) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index 3c3ffae695..c464d92f23 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -1,7 +1,6 @@ //! Encoding tables for ARM32 ISA. use ir::InstructionData; -use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::constraints::*; diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index 92b5ad58c3..064a00f2e2 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -1,7 +1,6 @@ //! Encoding tables for ARM64 ISA. use ir::InstructionData; -use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::constraints::*; diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 0b01f481f2..a29394a3a9 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,7 +1,6 @@ //! Encoding tables for Intel ISAs. use ir::InstructionData; -use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::constraints::*; diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 2538911a25..f02ccf80ba 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -1,7 +1,6 @@ //! Encoding tables for RISC-V. use ir::{Opcode, InstructionData}; -use ir::instructions::InstructionFormat; use ir::types; use predicates; use isa::enc_tables::{Level1Entry, Level2Entry}; From 4ae7fd2a372806cdc1b4cfdcb37512b2071bda0f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Feb 2017 12:28:07 -0800 Subject: [PATCH 502/968] Speling. --- lib/cretonne/src/cfg.rs | 19 +++++----- lib/cretonne/src/constant_hash.rs | 2 +- lib/cretonne/src/dominator_tree.rs | 11 +++--- lib/cretonne/src/entity_list.rs | 2 +- lib/cretonne/src/entity_map.rs | 4 +- lib/cretonne/src/ir/builder.rs | 4 +- lib/cretonne/src/ir/dfg.rs | 20 +++++----- lib/cretonne/src/ir/entities.rs | 6 +-- lib/cretonne/src/ir/function.rs | 2 +- lib/cretonne/src/ir/immediates.rs | 10 ++--- lib/cretonne/src/ir/instructions.rs | 23 ++++++----- lib/cretonne/src/ir/jumptable.rs | 2 +- lib/cretonne/src/ir/layout.rs | 12 +++--- lib/cretonne/src/ir/types.rs | 4 +- lib/cretonne/src/legalizer.rs | 4 +- lib/cretonne/src/predicates.rs | 4 +- lib/cretonne/src/regalloc/allocatable_set.rs | 8 ++-- lib/cretonne/src/regalloc/liveness.rs | 40 ++++++++++---------- lib/cretonne/src/regalloc/liverange.rs | 20 +++++----- lib/cretonne/src/sparse_map.rs | 4 +- lib/cretonne/src/verifier.rs | 16 ++++---- lib/cretonne/src/write.rs | 5 ++- 22 files changed, 112 insertions(+), 110 deletions(-) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index f68d8bcbf4..5466355812 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -1,8 +1,9 @@ //! A control flow graph represented as mappings of extended basic blocks to their predecessors -//! and successors. Successors are represented as extended basic blocks while predecessors are -//! represented by basic blocks. -//! BasicBlocks are denoted by tuples of EBB and branch/jump instructions. Each predecessor -//! tuple corresponds to the end of a basic block. +//! and successors. +//! +//! Successors are represented as extended basic blocks while predecessors are represented by basic +//! blocks. Basic blocks are denoted by tuples of EBB and branch/jump instructions. Each +//! predecessor tuple corresponds to the end of a basic block. //! //! ```c //! Ebb0: @@ -19,8 +20,8 @@ //! jmp Ebb2 ; end of basic block //! ``` //! -//! Here Ebb1 and Ebb2 would each have a single predecessor denoted as (Ebb0, `brz vx, Ebb1`) -//! and (Ebb0, `jmp Ebb2`) respectively. +//! Here `Ebb1` and `Ebb2` would each have a single predecessor denoted as `(Ebb0, brz)` +//! and `(Ebb0, jmp Ebb2)` respectively. use ir::{Function, Inst, Ebb}; use ir::instructions::BranchInfo; @@ -91,7 +92,7 @@ impl ControlFlowGraph { &self.data[ebb].successors } - /// Return [reachable] ebbs in postorder. + /// Return [reachable] ebbs in post-order. pub fn postorder_ebbs(&self) -> Vec { let entry_block = match self.entry_block { None => { @@ -111,7 +112,7 @@ impl ControlFlowGraph { // This is a white node. Mark it as gray. grey.insert(node); stack.push(node); - // Get any children we’ve never seen before. + // Get any children we've never seen before. for child in self.get_successors(node) { if !grey.contains(child) { stack.push(child.clone()); @@ -125,7 +126,7 @@ impl ControlFlowGraph { postorder } - /// An iterator across all of the ebbs stored in the cfg. + /// An iterator across all of the ebbs stored in the CFG. pub fn ebbs(&self) -> Keys { self.data.keys() } diff --git a/lib/cretonne/src/constant_hash.rs b/lib/cretonne/src/constant_hash.rs index f76fde7fb7..08cc2944c8 100644 --- a/lib/cretonne/src/constant_hash.rs +++ b/lib/cretonne/src/constant_hash.rs @@ -67,7 +67,7 @@ mod tests { #[test] fn basic() { - // c.f. meta/constant_hash.py tests. + // c.f. `meta/constant_hash.py` tests. assert_eq!(simple_hash("Hello"), 0x2fa70c01); assert_eq!(simple_hash("world"), 0x5b0c31d5); } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index f940d3c910..eaec78f367 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -52,7 +52,7 @@ impl DominatorTree { self.nodes[ebb].idom.into() } - /// Compare two EBBs relative to a reverse pst-order traversal of the control-flow graph. + /// Compare two EBBs relative to a reverse post-order traversal of the control-flow graph. /// /// Return `Ordering::Less` if `a` comes before `b` in the RPO. pub fn rpo_cmp(&self, a: Ebb, b: Ebb) -> Ordering { @@ -124,7 +124,7 @@ impl DominatorTree { pub fn new(func: &Function, cfg: &ControlFlowGraph) -> DominatorTree { let mut domtree = DominatorTree { nodes: EntityMap::with_capacity(func.dfg.num_ebbs()) }; - // We'll be iterating over a reverse postorder of the CFG. + // We'll be iterating over a reverse post-order of the CFG. // This vector only contains reachable EBBs. let mut postorder = cfg.postorder_ebbs(); @@ -154,8 +154,8 @@ impl DominatorTree { } } - // Now that we have RPO numbers for everything and initial idom estimates, iterate until - // convergence. + // Now that we have RPO numbers for everything and initial immediate dominator estimates, + // iterate until convergence. // // If the function is free of irreducible control flow, this will exit after one iteration. let mut changed = true; @@ -173,7 +173,8 @@ impl DominatorTree { domtree } - // Compute the idom for `ebb` using the current `idom` states for the reachable nodes. + // Compute the immediate dominator for `ebb` using the current `idom` states for the reachable + // nodes. fn compute_idom(&self, ebb: Ebb, cfg: &ControlFlowGraph, layout: &Layout) -> Inst { // Get an iterator with just the reachable predecessors to `ebb`. // Note that during the first pass, `is_reachable` returns false for blocks that haven't diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 6ec23e39e2..d94795c902 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -178,7 +178,7 @@ impl ListPool { /// Returns two mutable slices representing the two requested blocks. /// /// The two returned slices can be longer than the blocks. Each block is located at the front - /// the the respective slice. + /// of the respective slice. fn mut_slices(&mut self, block0: usize, block1: usize) -> (&mut [T], &mut [T]) { if block0 < block1 { let (s0, s1) = self.data.split_at_mut(block1); diff --git a/lib/cretonne/src/entity_map.rs b/lib/cretonne/src/entity_map.rs index 33d355e9f1..f9ac258f92 100644 --- a/lib/cretonne/src/entity_map.rs +++ b/lib/cretonne/src/entity_map.rs @@ -139,7 +139,7 @@ impl EntityMap self.elems.resize(n, V::default()); } - /// Ensure that `k` is a valid key but adding default entries if necesssary. + /// Ensure that `k` is a valid key but adding default entries if necessary. /// /// Return a mutable reference to the corresponding entry. pub fn ensure(&mut self, k: K) -> &mut V { @@ -202,7 +202,7 @@ impl Iterator for Keys mod tests { use super::*; - // EntityRef impl for testing. + // `EntityRef` impl for testing. #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct E(u32); diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index ad3f8e22fe..639b640c20 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -13,7 +13,7 @@ use ir::condcodes::{IntCC, FloatCC}; /// /// The `InstBuilderBase` trait provides the basic functionality required by the methods of the /// generated `InstBuilder` trait. These methods should not normally be used directly. Use the -/// methods in the `InstBuilder trait instead. +/// methods in the `InstBuilder` trait instead. /// /// Any data type that implements `InstBuilderBase` also gets all the methods of the `InstBuilder` /// trait. @@ -104,7 +104,7 @@ impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { /// /// If the old instruction still has secondary result values attached, it is assumed that the new /// instruction produces the same number and types of results. The old secondary values are -/// preserved. If the replacemant instruction format does not support multiple results, the builder +/// preserved. If the replacement instruction format does not support multiple results, the builder /// panics. It is a bug to leave result values dangling. /// /// If the old instruction was capable of producing secondary results, but the values have been diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index a01a4dd654..94117095a7 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -12,7 +12,7 @@ use packed_option::PackedOption; use std::ops::{Index, IndexMut}; use std::u16; -/// A data flow graph defines all instuctions and extended basic blocks in a function as well as +/// A data flow graph defines all instructions and extended basic blocks in a function as well as /// the data flow dependencies between them. The DFG also tracks values which can be either /// instruction results or EBB arguments. /// @@ -213,7 +213,7 @@ impl DataFlowGraph { original: original, }; } else { - panic!("Cannot change dirrect value {} into an alias", dest); + panic!("Cannot change direct value {} into an alias", dest); } } } @@ -328,7 +328,7 @@ impl DataFlowGraph { // Additional values form a linked list starting from the second result value. Generate // the list backwards so we don't have to modify value table entries in place. (This - // causes additional result values to be numbered backwards which is not the aestetic + // causes additional result values to be numbered backwards which is not the aesthetic // choice, but since it is only visible in extremely rare instructions with 3+ results, // we don't care). let mut head = None; @@ -498,7 +498,7 @@ impl DataFlowGraph { return num as usize + 1; } } - panic!("inconsistent value table entry for EBB arg"); + panic!("inconsistent value table entry for EBB argument"); } } } @@ -514,7 +514,7 @@ impl DataFlowGraph { next: None.into(), }); match self.ebbs[ebb].last_arg.expand() { - // If last_arg is `None`, we're adding the first EBB argument. + // If last_argument is `None`, we're adding the first EBB argument. None => { self.ebbs[ebb].first_arg = val.into(); } @@ -525,11 +525,11 @@ impl DataFlowGraph { if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] { *next = val.into(); } else { - panic!("inconsistent value table entry for EBB arg"); + panic!("inconsistent value table entry for EBB argument"); } } ExpandedValue::Direct(_) => { - panic!("inconsistent value table entry for EBB arg") + panic!("inconsistent value table entry for EBB argument") } } } @@ -556,7 +556,7 @@ impl DataFlowGraph { struct EbbData { // First argument to this EBB, or `None` if the block has no arguments. // - // The arguments are all ValueData::Argument entries that form a linked list from `first_arg` + // The arguments are all `ValueData::Argument` entries that form a linked list from `first_arg` // to `last_arg`. first_arg: PackedOption, @@ -680,14 +680,14 @@ mod tests { _ => panic!(), }; - // Detach the 'c' value from iadd. + // Detach the 'c' value from `iadd`. { let mut vals = dfg.detach_secondary_results(iadd); assert_eq!(vals.next(), Some(c)); assert_eq!(vals.next(), None); } - // Replace iadd_cout with a normal iadd and an icmp. + // Replace `iadd_cout` with a normal `iadd` and an `icmp`. dfg.replace(iadd).iadd(v1, arg0); let c2 = dfg.ins(pos).icmp(IntCC::UnsignedLessThan, s, v1); dfg.change_to_alias(c, c2); diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 69a9902151..51ca70ad7a 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -65,7 +65,7 @@ pub struct Ebb(u32); entity_impl!(Ebb, "ebb"); impl Ebb { - /// Create a new EBB reference from its number. This corresponds to the ebbNN representation. + /// Create a new EBB reference from its number. This corresponds to the `ebbNN` representation. /// /// This method is for use by the parser. pub fn with_number(n: u32) -> Option { @@ -90,7 +90,7 @@ pub enum ExpandedValue { impl Value { /// Create a `Direct` value from its number representation. - /// This is the number in the vNN notation. + /// This is the number in the `vNN` notation. /// /// This method is for use by the parser. pub fn direct_with_number(n: u32) -> Option { @@ -104,7 +104,7 @@ impl Value { } /// Create a `Table` value from its number representation. - /// This is the number in the vxNN notation. + /// This is the number in the `vxNN` notation. /// /// This method is for use by the parser. pub fn table_with_number(n: u32) -> Option { diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index db6c811b5a..2828104daa 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -60,7 +60,7 @@ impl Function { } } - /// Create a new empty, anomymous function. + /// Create a new empty, anonymous function. pub fn new() -> Function { Self::with_name_signature(FunctionName::default(), Signature::new()) } diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index e2fc0c446d..bc44e6c022 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -62,7 +62,7 @@ impl Display for Imm64 { impl FromStr for Imm64 { type Err = &'static str; - // Parse a decimal or hexadecimal Imm64, formatted as above. + // Parse a decimal or hexadecimal `Imm64`, formatted as above. fn from_str(s: &str) -> Result { let mut value: u64 = 0; let mut digits = 0; @@ -149,7 +149,7 @@ pub struct Ieee64(f64); // Format a floating point number in a way that is reasonably human-readable, and that can be // converted back to binary without any rounding issues. The hexadecimal formatting of normal and -// subnormal numbers is compatible with C99 and the printf "%a" format specifier. The NaN and Inf +// subnormal numbers is compatible with C99 and the `printf "%a"` format specifier. The NaN and Inf // formats are not supported by C99. // // The encoding parameters are: @@ -380,7 +380,7 @@ impl Ieee32 { Ieee32(x) } - /// Construct Ieee32 immediate from raw bits. + /// Construct `Ieee32` immediate from raw bits. pub fn from_bits(x: u32) -> Ieee32 { Ieee32(unsafe { mem::transmute(x) }) } @@ -410,7 +410,7 @@ impl Ieee64 { Ieee64(x) } - /// Construct Ieee64 immediate from raw bits. + /// Construct `Ieee64` immediate from raw bits. pub fn from_bits(x: u64) -> Ieee64 { Ieee64(unsafe { mem::transmute(x) }) } @@ -496,7 +496,7 @@ mod tests { "Negative number too small for Imm64"); parse_ok::("18446744073709551615", "-1"); parse_ok::("-9223372036854775808", "0x8000_0000_0000_0000"); - // Overflow both the checked_add and checked_mul. + // Overflow both the `checked_add` and `checked_mul`. parse_err::("18446744073709551616", "Too large decimal Imm64"); parse_err::("184467440737095516100", "Too large decimal Imm64"); parse_err::("-9223372036854775809", diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index f25c58d197..17a0ef2c27 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -57,9 +57,9 @@ impl Opcode { } } -// This trait really belongs in lib/reader where it is used by the .cton file parser, but since it -// critically depends on the `opcode_name()` function which is needed here anyway, it lives in this -// module. This also saves us from running the build script twice to generate code for the two +// This trait really belongs in lib/reader where it is used by the `.cton` file parser, but since +// it critically depends on the `opcode_name()` function which is needed here anyway, it lives in +// this module. This also saves us from running the build script twice to generate code for the two // separate crates. impl FromStr for Opcode { type Err = &'static str; @@ -141,7 +141,7 @@ pub enum InstructionData { arg: Value, imm: Imm64, }, - // Same as BinaryImm, but the immediate is the lhs operand. + // Same as `BinaryImm`, but the immediate is the left-hand-side operand. BinaryImmRev { opcode: Opcode, ty: Type, @@ -246,7 +246,7 @@ impl VariableArgs { } } -// Coerce VariableArgs into a &[Value] slice. +// Coerce `VariableArgs` into a `&[Value]` slice. impl Deref for VariableArgs { type Target = [Value]; @@ -311,7 +311,7 @@ impl Display for TernaryOverflowData { } /// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit -/// in the allowed InstructionData size. +/// in the allowed `InstructionData` size. #[derive(Clone, Debug)] pub struct JumpData { /// Jump destination EBB. @@ -331,7 +331,7 @@ impl Display for JumpData { } /// Payload data for branch instructions. These need to carry lists of EBB arguments that won't fit -/// in the allowed InstructionData size. +/// in the allowed `InstructionData` size. #[derive(Clone, Debug)] pub struct BranchData { /// Value argument controlling the branch. @@ -702,11 +702,10 @@ mod tests { #[test] fn instruction_data() { use std::mem; - // The size of the InstructionData enum is important for performance. It should not exceed - // 16 bytes. Use `Box` out-of-line payloads for instruction formats that require - // more space than that. - // It would be fine with a data structure smaller than 16 bytes, but what are the odds of - // that? + // The size of the `InstructionData` enum is important for performance. It should not + // exceed 16 bytes. Use `Box` out-of-line payloads for instruction formats that + // require more space than that. It would be fine with a data structure smaller than 16 + // bytes, but what are the odds of that? assert_eq!(mem::size_of::(), 16); } diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index 824e2dcf23..7103485094 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -33,7 +33,7 @@ impl JumpTableData { /// Set a table entry. /// - /// The table will grow as needed to fit 'idx'. + /// The table will grow as needed to fit `idx`. pub fn set_entry(&mut self, idx: usize, dest: Ebb) { // Resize table to fit `idx`. if idx >= self.table.len() { diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 2a8d1ba4ac..f914142c7e 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -573,10 +573,10 @@ impl<'f> DoubleEndedIterator for Insts<'f> { /// /// A `Cursor` represents a position in a function layout where instructions can be inserted and /// removed. It can be used to iterate through the instructions of a function while editing them at -/// the same time. A normal instruction iterator can't do this since it holds an immutable refernce -/// to the Layout. +/// the same time. A normal instruction iterator can't do this since it holds an immutable +/// reference to the Layout. /// -/// When new instructions are added, the cursor can either apend them to an EBB or insert them +/// When new instructions are added, the cursor can either append them to an EBB or insert them /// before the current instruction. pub struct Cursor<'f> { layout: &'f mut Layout, @@ -592,7 +592,7 @@ pub enum CursorPosition { /// New instructions will be inserted *before* the current instruction. At(Inst), /// Cursor is before the beginning of an EBB. No instructions can be inserted. Calling - /// `next_inst()` wil move to the first instruction in the EBB. + /// `next_inst()` will move to the first instruction in the EBB. Before(Ebb), /// Cursor is pointing after the end of an EBB. /// New instructions will be appended to the EBB. @@ -851,7 +851,7 @@ impl<'f> Cursor<'f> { /// - If pointing at the bottom of an EBB, the new instruction is appended to the EBB. /// - Otherwise panic. /// - /// In either case, the cursor is not moved, such that repeates calls to `insert_inst()` causes + /// In either case, the cursor is not moved, such that repeated calls to `insert_inst()` causes /// instructions to appear in insertion order in the EBB. pub fn insert_inst(&mut self, inst: Inst) { use self::CursorPosition::*; @@ -1263,7 +1263,7 @@ mod tests { assert_eq!(cur.prev_ebb(), None); } - // Check ProgramOrder. + // Check `ProgramOrder`. assert_eq!(layout.cmp(e2, e2), Ordering::Equal); assert_eq!(layout.cmp(e2, i2), Ordering::Less); assert_eq!(layout.cmp(i3, i2), Ordering::Greater); diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index c001c9f3c7..cdd2207aad 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -43,7 +43,7 @@ impl Type { Type(self.0 & 0x0f) } - /// Get log2 of the number of bits in a lane. + /// Get log_2 of the number of bits in a lane. pub fn log2_lane_bits(self) -> u8 { match self.lane_type() { B1 => 0, @@ -157,7 +157,7 @@ impl Type { } } - /// Get log2 of the number of lanes in this SIMD vector type. + /// Get log_2 of the number of lanes in this SIMD vector type. /// /// All SIMD types have a lane count that is a power of two and no larger than 256, so this /// will be a number in the range 0-8. diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index a83485b5e2..015bfb9b30 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -69,8 +69,8 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { } } -// Include legalization patterns that were generated by gen_legalizer.py from the XForms in -// meta/cretonne/legalize.py. +// Include legalization patterns that were generated by `gen_legalizer.py` from the `XForms` in +// `meta/cretonne/legalize.py`. // // Concretely, this defines private functions `narrow()`, and `expand()`. include!(concat!(env!("OUT_DIR"), "/legalizer.rs")); diff --git a/lib/cretonne/src/predicates.rs b/lib/cretonne/src/predicates.rs index a0e50402bf..748a75e23a 100644 --- a/lib/cretonne/src/predicates.rs +++ b/lib/cretonne/src/predicates.rs @@ -20,7 +20,7 @@ pub fn is_signed_int>(x: T, wd: u8, sc: u8) -> bool { #[allow(dead_code)] pub fn is_unsigned_int>(x: T, wd: u8, sc: u8) -> bool { let u = x.into() as u64; - // Bitmask of the permitted bits. + // Bit-mask of the permitted bits. let m = (1 << wd) - (1 << sc); u == (u & m) } @@ -40,7 +40,7 @@ mod tests { assert!(is_signed_int(x2, 2, 0)); assert!(!is_signed_int(x2, 2, 1)); - // u32 doesn't sign-extend when converted to i64. + // `u32` doesn't sign-extend when converted to `i64`. assert!(!is_signed_int(x3, 8, 0)); assert!(is_unsigned_int(x1, 1, 0)); diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index f2ef0102d4..2c608b0a16 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -38,7 +38,7 @@ impl AllocatableSet { AllocatableSet { avail: [!0; 3] } } - /// Returns `true` if the spoecified register is available. + /// Returns `true` if the specified register is available. pub fn is_avail(&self, rc: RegClass, reg: RegUnit) -> bool { let (idx, bits) = bitmask(rc, reg); (self.avail[idx] & bits) == bits @@ -71,7 +71,7 @@ impl AllocatableSet { for idx in 0..self.avail.len() { // If a single unit in a register is unavailable, the whole register can't be used. // If a register straddles a word boundary, it will be marked as unavailable. - // There's an assertion in cdsl/registers.py to check for that. + // There's an assertion in `cdsl/registers.py` to check for that. for i in 0..rc.width { rsi.regs[idx] &= self.avail[idx] >> i; } @@ -102,7 +102,7 @@ impl Iterator for RegSetIter { return Some(unit); } - // How many register units was there in the word? This is a constant 32 for u32 etc. + // How many register units was there in the word? This is a constant 32 for `u32` etc. unit_offset += 8 * size_of_val(word) as RegUnit; } @@ -134,7 +134,7 @@ mod tests { fn put_and_take() { let mut regs = AllocatableSet::new(); - // GPR has units 28-36. + // `GPR` has units 28-36. assert_eq!(regs.iter(GPR).count(), 8); assert_eq!(regs.iter(DPR).collect::>(), [28, 30, 33, 35]); diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index c7646ed701..2e6cb78db4 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -8,8 +8,8 @@ //! //! The primary consumer of the liveness analysis is the SSA coloring pass which goes through each //! EBB and assigns a register to the defined values. This algorithm needs to maintain a set of the -//! curently live values as it is iterating down the instructions in the EBB. It asks the following -//! questions: +//! currently live values as it is iterating down the instructions in the EBB. It asks the +//! following questions: //! //! - What is the set of live values at the entry to the EBB? //! - When moving past a use of a value, is that value still alive in the EBB, or was that the last @@ -18,7 +18,7 @@ //! //! The set of `LiveRange` instances can answer these questions through their `def_local_end` and //! `livein_local_end` queries. The coloring algorithm visits EBBs in a topological order of the -//! dominator tree, so it can compute the set of live values at the begining of an EBB by starting +//! dominator tree, so it can compute the set of live values at the beginning of an EBB by starting //! from the set of live values at the dominating branch instruction and filtering it with //! `livein_local_end`. These sets do not need to be stored in the liveness analysis. //! @@ -32,11 +32,11 @@ //! A number of different liveness analysis algorithms exist, so it is worthwhile to look at a few //! alternatives. //! -//! ## Dataflow equations +//! ## Data-flow equations //! //! The classic *live variables analysis* that you will find in all compiler books from the //! previous century does not depend on SSA form. It is typically implemented by iteratively -//! solving dataflow equations on bitvectors of variables. The result is a live-out bitvector of +//! solving data-flow equations on bit-vectors of variables. The result is a live-out bit-vector of //! variables for every basic block in the program. //! //! This algorithm has some disadvantages that makes us look elsewhere: @@ -44,18 +44,18 @@ //! - Quadratic memory use. We need a bit per variable per basic block in the function. //! - Sparse representation. In practice, the majority of SSA values never leave their basic block, //! and those that do span basic blocks rarely span a large number of basic blocks. This makes -//! the bitvectors quite sparse. -//! - Traditionally, the dataflow equations were solved for real program *variables* which does not -//! include temporaries used in evaluating expressions. We have an SSA form program which blurs -//! the distinction between temporaries and variables. This makes the quadratic memory problem -//! worse because there are many more SSA values than there was variables in the original +//! the bit-vectors quite sparse. +//! - Traditionally, the data-flow equations were solved for real program *variables* which does +//! not include temporaries used in evaluating expressions. We have an SSA form program which +//! blurs the distinction between temporaries and variables. This makes the quadratic memory +//! problem worse because there are many more SSA values than there was variables in the original //! program, and we don't know a priori which SSA values leave their basic block. //! - Missing last-use information. For values that are not live-out of a basic block, we would //! need to store information about the last use in the block somewhere. LLVM stores this //! information as a 'kill bit' on the last use in the IR. Maintaining these kill bits has been a //! source of problems for LLVM's register allocator. //! -//! Dataflow equations can detect when a variable is used uninitialized, and they can handle +//! Data-flow equations can detect when a variable is used uninitialized, and they can handle //! multiple definitions of the same variable. We don't need this generality since we already have //! a program in SSA form. //! @@ -83,7 +83,7 @@ //! The iterative SSA form reconstruction can be skipped if the depth-first search only encountered //! one SSA value. //! -//! This algorithm has some advantages compared to the dataflow equations: +//! This algorithm has some advantages compared to the data-flow equations: //! //! - The live ranges of local virtual registers are computed very quickly without ever traversing //! the CFG. The memory needed to store these live ranges is independent of the number of basic @@ -106,11 +106,11 @@ //! was presented at CGO 2008: //! //! > Boissinot, B., Hack, S., Grund, D., de Dinechin, B. D., & Rastello, F. (2008). *Fast Liveness -//! Checking for SSA-Form Programs.* CGO. +//! Checking for SSA-Form Programs.* CGO. //! -//! This analysis uses a global precomputation that only depends on the CFG of the function. It +//! This analysis uses a global pre-computation that only depends on the CFG of the function. It //! then allows liveness queries for any (value, program point) pair. Each query traverses the use -//! chain of the value and performs lookups in the precomputed bitvectors. +//! chain of the value and performs lookups in the precomputed bit-vectors. //! //! I did not seriously consider this analysis for Cretonne because: //! @@ -118,8 +118,8 @@ //! - Popular variables like the `this` pointer in a C++ method can have very large use chains. //! Traversing such a long use chain on every liveness lookup has the potential for some nasty //! quadratic behavior in unfortunate cases. -//! - It says "fast" in the title, but the paper only claims to be 16% faster than a dataflow based -//! approach, which isn't that impressive. +//! - It says "fast" in the title, but the paper only claims to be 16% faster than a data-flow +//! based approach, which isn't that impressive. //! //! Nevertheless, the property of only depending in the CFG structure is very useful. If Cretonne //! gains use chains, this approach would be worth a proper evaluation. @@ -171,7 +171,7 @@ //! - Related values should be stored on the same cache line. The current sparse set implementation //! does a decent job of that. //! - For global values, the list of live-in intervals is very likely to fit on a single cache -//! line. These lists are very likely ot be found in L2 cache at least. +//! line. These lists are very likely to be found in L2 cache at least. //! //! There is some room for improvement. @@ -271,10 +271,10 @@ impl Liveness { self.worklist.push(ebb); } - // The worklist contains those EBBs where we have learned that the value needs to be + // The work list contains those EBBs where we have learned that the value needs to be // live-in. // - // This algorithm bcomes a depth-first traversal up the CFG, enumerating all paths through + // This algorithm becomes a depth-first traversal up the CFG, enumerating all paths through // the CFG from the existing live range to `ebb`. // // Extend the live range as we go. The live range itself also serves as a visited set since diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 67667a3a47..c132a82478 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -45,13 +45,13 @@ //! handle *early clobbers* which are output registers that are not allowed to alias any input //! registers. //! -//! If i1 < i2 < i3 are program points, we have: +//! If `i1 < i2 < i3` are program points, we have: //! -//! - i1-i2 and i1-i3 interfere because the intervals overlap. -//! - i1-i2 and i2-i3 don't interfere. -//! - i1-i3 and i2-i2 do interfere because the dead def would clobber the register. -//! - i1-i2 and i2-i2 don't interfere. -//! - i2-i3 and i2-i2 do interfere. +//! - `i1-i2` and `i1-i3` interfere because the intervals overlap. +//! - `i1-i2` and `i2-i3` don't interfere. +//! - `i1-i3` and `i2-i2` do interfere because the dead def would clobber the register. +//! - `i1-i2` and `i2-i2` don't interfere. +//! - `i2-i3` and `i2-i2` do interfere. //! //! Because of this behavior around interval end points, live range interference is not completely //! equivalent to mathematical intersection of open or half-open intervals. @@ -415,7 +415,7 @@ mod tests { } } - // Singleton ProgramOrder for tests below. + // Singleton `ProgramOrder` for tests below. const PO: &'static ProgOrder = &ProgOrder {}; #[test] @@ -441,7 +441,7 @@ mod tests { assert!(lr.is_local()); assert_eq!(lr.def(), e2.into()); assert_eq!(lr.def_local_end(), e2.into()); - // The def interval of an EBB arg does not count as live-in. + // The def interval of an EBB argument does not count as live-in. assert_eq!(lr.livein_local_end(e2, PO), None); PO.validate(&lr); } @@ -478,8 +478,8 @@ mod tests { let i13 = Inst::new(13); let mut lr = LiveRange::new(v0, e10); - // Extending a dead EBB arg in its own block should not indicate that a live-in interval - // was created. + // Extending a dead EBB argument in its own block should not indicate that a live-in + // interval was created. assert_eq!(lr.extend_in_ebb(e10, i12, PO), false); PO.validate(&lr); assert!(!lr.is_dead()); diff --git a/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs index 48844c026a..3a1b4dbdb6 100644 --- a/lib/cretonne/src/sparse_map.rs +++ b/lib/cretonne/src/sparse_map.rs @@ -27,7 +27,7 @@ //! about creating new mappings to the default value. It doesn't distinguish clearly between an //! unmapped key and one that maps to the default value. `SparseMap` does not require `Default` //! values, and it tracks accurately if a key has been mapped or not. -//! - Iterating over the contants of an `EntityMap` is linear in the size of the *key space*, while +//! - Iterating over the contents of an `EntityMap` is linear in the size of the *key space*, while //! iterating over a `SparseMap` is linear in the number of elements in the mapping. This is an //! advantage precisely when the mapping is sparse. //! - `SparseMap::clear()` is constant time and super-fast. `EntityMap::clear()` is linear in the @@ -45,7 +45,7 @@ use std::u32; /// All values stored in a `SparseMap` must keep track of their own key in the map and implement /// this trait to provide access to the key. pub trait SparseMapValue { - /// Get the key of this sparse map value. This key is not alowed to change while the value + /// Get the key of this sparse map value. This key is not allowed to change while the value /// is a member of the map. fn key(&self) -> K; } diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index f0a4b66b32..0076933934 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -3,18 +3,18 @@ //! //! EBB integrity //! -//! - All instructions reached from the ebb_insts iterator must belong to -//! the EBB as reported by inst_ebb(). +//! - All instructions reached from the `ebb_insts` iterator must belong to +//! the EBB as reported by `inst_ebb()`. //! - Every EBB must end in a terminator instruction, and no other instruction //! can be a terminator. -//! - Every value in the ebb_args iterator belongs to the EBB as reported by value_ebb. +//! - Every value in the `ebb_args` iterator belongs to the EBB as reported by `value_ebb`. //! //! Instruction integrity //! //! - The instruction format must match the opcode. //! TODO: //! - All result values must be created for multi-valued instructions. -//! - Instructions with no results must have a VOID first_type(). +//! - Instructions with no results must have a VOID `first_type()`. //! - All referenced entities must exist. (Values, EBBs, stack slots, ...) //! //! SSA form @@ -33,7 +33,7 @@ //! - Compare input and output values against the opcode's type constraints. //! For polymorphic opcodes, determine the controlling type variable first. //! - Branches and jumps must pass arguments to destination EBBs that match the -//! expected types excatly. The number of arguments must match. +//! expected types exactly. The number of arguments must match. //! - All EBBs in a jump_table must take no arguments. //! - Function calls are type checked against their signature. //! - The entry block must take arguments that match the signature of the current @@ -44,10 +44,10 @@ //! Ad hoc checking //! //! - Stack slot loads and stores must be in-bounds. -//! - Immediate constraints for certain opcodes, like udiv_imm v3, 0. +//! - Immediate constraints for certain opcodes, like `udiv_imm v3, 0`. //! - Extend / truncate instructions have more type constraints: Source type can't be //! larger / smaller than result type. -//! - Insertlane and extractlane instructions have immediate lane numbers that must be in +//! - `Insertlane` and `extractlane` instructions have immediate lane numbers that must be in //! range for their polymorphic type. //! - Swizzle and shuffle instructions take a variable number of lane arguments. The number //! of arguments must match the destination type, and the lane indexes must be in range. @@ -76,7 +76,7 @@ impl Display for Error { /// Verifier result. pub type Result = result::Result; -// Create an `Err` variant of `Result` from a location and `format!` args. +// Create an `Err` variant of `Result` from a location and `format!` arguments. macro_rules! err { ( $loc:expr, $msg:expr ) => { Err(Error { diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index d8db064cd6..8dfe955e42 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -43,7 +43,8 @@ fn write_preamble(w: &mut Write, func: &Function) -> result::Result try!(writeln!(w, " {} = {}", ss, func.stack_slots[ss])); } - // Write out all signatures before functions since function decls can refer to signatures. + // Write out all signatures before functions since function declarations can refer to + // signatures. for sig in func.dfg.signatures.keys() { any = true; try!(writeln!(w, " {} = signature{}", sig, func.dfg.signatures[sig])); @@ -93,7 +94,7 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { try!(write_arg(w, func, arg)); } } - // Remaining args. + // Remaining arguments. for arg in args { try!(write!(w, ", ")); try!(write_arg(w, func, arg)); From 8ca61b2a24bb4739ca96fe6734f7a89c94fcadfd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Feb 2017 15:06:05 -0800 Subject: [PATCH 503/968] Compute register affinities during liveness analysis. Each live range has an affinity hint containing the preferred register class (or stack slot). Compute the affinity by merging the constraints of the def and all uses. --- lib/cretonne/src/regalloc/liveness.rs | 162 +++++++++++++++---------- lib/cretonne/src/regalloc/liverange.rs | 15 ++- 2 files changed, 105 insertions(+), 72 deletions(-) diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 2e6cb78db4..fe43b6962c 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -175,39 +175,87 @@ //! //! There is some room for improvement. -use regalloc::liverange::LiveRange; -use ir::{Function, Value, Inst, Ebb, ProgramPoint}; -use ir::dfg::{DataFlowGraph, ValueDef}; use cfg::ControlFlowGraph; +use ir::dfg::ValueDef; +use ir::{Function, Value, Inst, Ebb}; +use isa::{TargetIsa, RecipeConstraints}; +use regalloc::liverange::LiveRange; +use regalloc::affinity::Affinity; use sparse_map::SparseMap; /// A set of live ranges, indexed by value number. -struct LiveRangeSet(SparseMap); +type LiveRangeSet = SparseMap; -impl LiveRangeSet { - pub fn new() -> LiveRangeSet { - LiveRangeSet(SparseMap::new()) +/// Get a mutable reference to the live range for `value`. +/// Create it if necessary. +fn get_or_create<'a>(lrset: &'a mut LiveRangeSet, + value: Value, + func: &Function, + recipe_constraints: &[RecipeConstraints]) + -> &'a mut LiveRange { + // It would be better to use `get_mut()` here, but that leads to borrow checker fighting + // which can probably only be resolved by non-lexical lifetimes. + // https://github.com/rust-lang/rfcs/issues/811 + if lrset.get(value).is_none() { + // Create a live range for value. We need the program point that defines it. + let def; + let affinity; + match func.dfg.value_def(value) { + ValueDef::Res(inst, rnum) => { + def = inst.into(); + // Initialize the affinity from the defining instruction's result constraints. + // Don't do this for call return values which are always tied to a single register. + affinity = recipe_constraints.get(func.encodings[inst].recipe()) + .and_then(|rc| rc.outs.get(rnum)) + .map(Affinity::new) + .unwrap_or_default(); + } + ValueDef::Arg(ebb, _) => { + def = ebb.into(); + // Don't apply any affinity to EBB arguments. + // They could be in a register or on the stack. + affinity = Default::default(); + } + }; + lrset.insert(LiveRange::new(value, def, affinity)); + } + lrset.get_mut(value).unwrap() +} + +/// Extend the live range for `value` so it reaches `to` which must live in `ebb`. +fn extend_to_use(lr: &mut LiveRange, + ebb: Ebb, + to: Inst, + worklist: &mut Vec, + func: &Function, + cfg: &ControlFlowGraph) { + // This is our scratch working space, and we'll leave it empty when we return. + assert!(worklist.is_empty()); + + // Extend the range locally in `ebb`. + // If there already was a live interval in that block, we're done. + if lr.extend_in_ebb(ebb, to, &func.layout) { + worklist.push(ebb); } - pub fn clear(&mut self) { - self.0.clear(); - } - - /// Get a mutable reference to the live range for `value`. - /// Create it if necessary. - pub fn get_or_create(&mut self, value: Value, dfg: &DataFlowGraph) -> &mut LiveRange { - // It would be better to use `get_mut()` here, but that leads to borrow checker fighting - // which can probably only be resolved by non-lexical lifetimes. - // https://github.com/rust-lang/rfcs/issues/811 - if self.0.get(value).is_none() { - // Create a live range for value. We need the program point that defines it. - let def: ProgramPoint = match dfg.value_def(value) { - ValueDef::Res(inst, _) => inst.into(), - ValueDef::Arg(ebb, _) => ebb.into(), - }; - self.0.insert(LiveRange::new(value, def)); + // The work list contains those EBBs where we have learned that the value needs to be + // live-in. + // + // This algorithm becomes a depth-first traversal up the CFG, enumerating all paths through the + // CFG from the existing live range to `ebb`. + // + // Extend the live range as we go. The live range itself also serves as a visited set since + // `extend_in_ebb` will never return true twice for the same EBB. + // + while let Some(livein) = worklist.pop() { + // We've learned that the value needs to be live-in to the `livein` EBB. + // Make sure it is also live at all predecessor branches to `livein`. + for &(pred, branch) in cfg.get_predecessors(livein) { + if lr.extend_in_ebb(pred, branch, &func.layout) { + // This predecessor EBB also became live-in. We need to process it later. + worklist.push(pred); + } } - self.0.get_mut(value).unwrap() } } @@ -238,56 +286,42 @@ impl Liveness { /// Compute the live ranges of all SSA values used in `func`. /// This clears out any existing analysis stored in this data structure. - pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) { + pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, isa: &TargetIsa) { self.ranges.clear(); + // Get ISA data structures used for computing live range affinities. + let recipe_constraints = isa.recipe_constraints(); + let reg_info = isa.register_info(); + // The liveness computation needs to visit all uses, but the order doesn't matter. // TODO: Perhaps this traversal of the function could be combined with a dead code // elimination pass if we visit a post-order of the dominator tree? // TODO: Resolve value aliases while we're visiting instructions? for ebb in func.layout.ebbs() { for inst in func.layout.ebb_insts(ebb) { - func.dfg[inst].each_arg(|arg| self.extend_to_use(arg, ebb, inst, func, cfg)); - } - } - } + // The instruction encoding is used to compute affinities. + let recipe = func.encodings[inst].recipe(); + // Iterator of constraints, one per value operand. + // TODO: Should we fail here if the instruction doesn't have a valid encoding? + let mut operand_constraints = + recipe_constraints.get(recipe).map(|c| c.ins).unwrap_or(&[]).iter(); - /// Extend the live range for `value` so it reaches `to` which must live in `ebb`. - fn extend_to_use(&mut self, - value: Value, - ebb: Ebb, - to: Inst, - func: &Function, - cfg: &ControlFlowGraph) { - // Get the live range, create it as a dead range if necessary. - let lr = self.ranges.get_or_create(value, &func.dfg); + func.dfg[inst].each_arg(|arg| { + // Get the live range, create it as a dead range if necessary. + let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints); - // This is our scratch working space, and we'll leave it empty when we return. - assert!(self.worklist.is_empty()); + // Extend the live range to reach this use. + extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg); - // Extend the range locally in `ebb`. - // If there already was a live interval in that block, we're done. - if lr.extend_in_ebb(ebb, to, &func.layout) { - self.worklist.push(ebb); - } - - // The work list contains those EBBs where we have learned that the value needs to be - // live-in. - // - // This algorithm becomes a depth-first traversal up the CFG, enumerating all paths through - // the CFG from the existing live range to `ebb`. - // - // Extend the live range as we go. The live range itself also serves as a visited set since - // `extend_in_ebb` will never return true twice for the same EBB. - // - while let Some(livein) = self.worklist.pop() { - // We've learned that the value needs to be live-in to the `livein` EBB. - // Make sure it is also live at all predecessor branches to `livein`. - for &(pred, branch) in cfg.get_predecessors(livein) { - if lr.extend_in_ebb(pred, branch, &func.layout) { - // This predecessor EBB also became live-in. We need to process it later. - self.worklist.push(pred); - } + // Apply operand constraint, ignoring any variable arguments after the fixed + // operands described by `operand_constraints`. Variable arguments are either + // EBB arguments or call/return ABI arguments. EBB arguments need to be + // resolved by the coloring algorithm, and ABI constraints require specific + // registers or stack slots which the affinities don't model anyway. + if let Some(constraint) = operand_constraints.next() { + lr.affinity.merge(constraint, reg_info); + } + }); } } } diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index c132a82478..4441bfcda5 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -205,11 +205,10 @@ impl LiveRange { /// Create a new live range for `value` defined at `def`. /// /// The live range will be created as dead, but it can be extended with `extend_in_ebb()`. - pub fn new>(value: Value, def: PP) -> LiveRange { - let def = def.into(); + pub fn new(value: Value, def: ProgramPoint, affinity: Affinity) -> LiveRange { LiveRange { value: value, - affinity: Default::default(), + affinity: affinity, def_begin: def, def_end: def, liveins: Vec::new(), @@ -423,7 +422,7 @@ mod tests { let v0 = Value::new(0); let i1 = Inst::new(1); let e2 = Ebb::new(2); - let lr = LiveRange::new(v0, i1); + let lr = LiveRange::new(v0, i1.into(), Default::default()); assert!(lr.is_dead()); assert!(lr.is_local()); assert_eq!(lr.def(), i1.into()); @@ -436,7 +435,7 @@ mod tests { fn dead_arg_range() { let v0 = Value::new(0); let e2 = Ebb::new(2); - let lr = LiveRange::new(v0, e2); + let lr = LiveRange::new(v0, e2.into(), Default::default()); assert!(lr.is_dead()); assert!(lr.is_local()); assert_eq!(lr.def(), e2.into()); @@ -453,7 +452,7 @@ mod tests { let i11 = Inst::new(11); let i12 = Inst::new(12); let i13 = Inst::new(13); - let mut lr = LiveRange::new(v0, i11); + let mut lr = LiveRange::new(v0, i11.into(), Default::default()); assert_eq!(lr.extend_in_ebb(e10, i13, PO), false); PO.validate(&lr); @@ -476,7 +475,7 @@ mod tests { let i11 = Inst::new(11); let i12 = Inst::new(12); let i13 = Inst::new(13); - let mut lr = LiveRange::new(v0, e10); + let mut lr = LiveRange::new(v0, e10.into(), Default::default()); // Extending a dead EBB argument in its own block should not indicate that a live-in // interval was created. @@ -510,7 +509,7 @@ mod tests { let i21 = Inst::new(21); let i22 = Inst::new(22); let i23 = Inst::new(23); - let mut lr = LiveRange::new(v0, i11); + let mut lr = LiveRange::new(v0, i11.into(), Default::default()); assert_eq!(lr.extend_in_ebb(e10, i12, PO), false); From 73202bb3dc22489af98295c2e20d4704df3851d7 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 14 Feb 2017 14:04:03 +0200 Subject: [PATCH 504/968] Coalesce live range intervals in adjacent EBBs LiveRanges represent the live-in range of a value as a sorted list of intervals. Each interval starts at an EBB and continues to an instruction. Before this commit, the LiveRange would store an interval for each EBB. This commit changes the representation such that intervals continuing from one EBB to another are coalesced into one. Fixes #37. --- lib/cretonne/src/ir/layout.rs | 12 +++ lib/cretonne/src/ir/progpoint.rs | 5 ++ lib/cretonne/src/regalloc/liverange.rs | 109 ++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index f914142c7e..50dfa61f4e 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -102,6 +102,13 @@ impl ProgramOrder for Layout { let b_seq = self.seq(b); a_seq.cmp(&b_seq) } + + fn is_ebb_gap(&self, inst: Inst, ebb: Ebb) -> bool { + let i = &self.insts[inst]; + let e = &self.ebbs[ebb]; + + i.next.is_none() && i.ebb == e.prev + } } // Private methods for dealing with sequence numbers. @@ -1267,5 +1274,10 @@ mod tests { assert_eq!(layout.cmp(e2, e2), Ordering::Equal); assert_eq!(layout.cmp(e2, i2), Ordering::Less); assert_eq!(layout.cmp(i3, i2), Ordering::Greater); + + assert_eq!(layout.is_ebb_gap(i1, e2), true); + assert_eq!(layout.is_ebb_gap(i3, e1), true); + assert_eq!(layout.is_ebb_gap(i1, e1), false); + assert_eq!(layout.is_ebb_gap(i2, e1), false); } } diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index 83c565fca7..7d57ab8cb9 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -94,6 +94,11 @@ pub trait ProgramOrder { fn cmp(&self, a: A, b: B) -> cmp::Ordering where A: Into, B: Into; + + /// Is the range from `inst` to `ebb` just the gap between consecutive EBBs? + /// + /// This returns true if `inst` is the terminator in the EBB immediately before `ebb`. + fn is_ebb_gap(&self, inst: Inst, ebb: Ebb) -> bool; } #[cfg(test)] diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 4441bfcda5..a2d7a8f32e 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -173,6 +173,7 @@ pub struct LiveRange { /// This represents a live-in interval for a single EBB, or a coalesced set of live-in intervals /// for contiguous EBBs where all but the last live-in interval covers the whole EBB. /// +#[derive(Copy, Clone)] struct Interval { /// Interval starting point. /// @@ -265,18 +266,57 @@ impl LiveRange { Ok(n) => { // We have an interval that contains `ebb`, so we can simply extend it. self.liveins[n].extend_to(to, order); - // TODO: Check if this interval can be coalesced with the `n+1` one. + + // If `to` is the terminator and the value lives in the successor EBB, + // coalesce the two intervals. + if let Some(next) = self.liveins.get(n + 1).cloned() { + if order.is_ebb_gap(to, next.begin) { + self.liveins[n].extend_to(next.end, order); + self.liveins.remove(n + 1); + } + } + false } Err(n) => { - // Insert a new live-in interval at `n`. - // TODO: Check if this interval can be coalesced with the `n-1` or `n+1` one (or - // both). - self.liveins.insert(n, - Interval { - begin: ebb, - end: to, - }); + // Insert a new live-in interval at `n`, or coalesce to predecessor or successor + // if possible. + + // Determine if the new live-in range touches the predecessor or successor range + // and can therefore be coalesced to them. + let (coalesce_prev, coalesce_next) = { + let prev = n.checked_sub(1).and_then(|i| self.liveins.get(i)); + let next = self.liveins.get(n); + + (prev.map_or(false, |prev| order.is_ebb_gap(prev.end, ebb)), + next.map_or(false, |next| order.is_ebb_gap(to, next.begin))) + }; + + match (coalesce_prev, coalesce_next) { + // Extend predecessor interval to cover new and successor intervals + (true, true) => { + let end = self.liveins[n].end; + self.liveins[n - 1].extend_to(end, order); + self.liveins.remove(n); + } + // Extend predecessor interval to cover new interval + (true, false) => { + self.liveins[n - 1].extend_to(to, order); + } + // Extend successor interval to cover new interval + (false, true) => { + self.liveins[n].begin = ebb; + } + // Cannot coalesce; insert new interval + (false, false) => { + self.liveins.insert(n, + Interval { + begin: ebb, + end: to, + }); + } + } + true } } @@ -344,7 +384,8 @@ mod tests { // Dummy program order which simply compares indexes. // It is assumed that EBBs have indexes that are multiples of 10, and instructions have indexes - // in between. + // in between. `is_ebb_gap` assumes that terminator instructions have indexes of the form + // ebb * 10 + 1. This is used in the coalesce test. struct ProgOrder {} impl ProgramOrder for ProgOrder { @@ -363,6 +404,10 @@ mod tests { let ib = idx(b.into()); ia.cmp(&ib) } + + fn is_ebb_gap(&self, inst: Inst, ebb: Ebb) -> bool { + inst.index() % 10 == 1 && ebb.index() / 10 == inst.index() / 10 + 1 + } } impl ProgOrder { @@ -528,5 +573,47 @@ mod tests { assert_eq!(lr.livein_local_end(e20, PO), Some(i23)); } - // TODO: Add more tests that exercise the binary search and the coalescing algorithm. + #[test] + fn coalesce() { + let v0 = Value::new(0); + let i11 = Inst::new(11); + let e20 = Ebb::new(20); + let i21 = Inst::new(21); + let e30 = Ebb::new(30); + let i31 = Inst::new(31); + let e40 = Ebb::new(40); + let i41 = Inst::new(41); + let mut lr = LiveRange::new(v0, i11.into(), Default::default()); + + assert_eq!(lr.extend_in_ebb(e30, i31, PO), true); + assert_eq!(lr.liveins.len(), 1); + + // Coalesce to previous + assert_eq!(lr.extend_in_ebb(e40, i41, PO), true); + assert_eq!(lr.liveins.len(), 1); + assert_eq!(lr.liveins[0].begin, e30); + assert_eq!(lr.liveins[0].end, i41); + + // Coalesce to next + assert_eq!(lr.extend_in_ebb(e20, i21, PO), true); + assert_eq!(lr.liveins.len(), 1); + assert_eq!(lr.liveins[0].begin, e20); + assert_eq!(lr.liveins[0].end, i41); + + let mut lr = LiveRange::new(v0, i11.into(), Default::default()); + + assert_eq!(lr.extend_in_ebb(e40, i41, PO), true); + assert_eq!(lr.liveins.len(), 1); + + assert_eq!(lr.extend_in_ebb(e20, i21, PO), true); + assert_eq!(lr.liveins.len(), 2); + + // Coalesce to previous and next + assert_eq!(lr.extend_in_ebb(e30, i31, PO), true); + assert_eq!(lr.liveins.len(), 1); + assert_eq!(lr.liveins[0].begin, e20); + assert_eq!(lr.liveins[0].end, i41); + } + + // TODO: Add more tests that exercise the binary search algorithm. } From 6567eca21ec3448e513f94185699875a9bcfcd57 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 6 Feb 2017 18:06:37 +0100 Subject: [PATCH 505/968] Add a partition_slice function. Partition the elements in a mutable slice according to a predicate. --- lib/cretonne/src/partition_slice.rs | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 lib/cretonne/src/partition_slice.rs diff --git a/lib/cretonne/src/partition_slice.rs b/lib/cretonne/src/partition_slice.rs new file mode 100644 index 0000000000..9626b5fd37 --- /dev/null +++ b/lib/cretonne/src/partition_slice.rs @@ -0,0 +1,75 @@ +//! Rearrange the elements in a slice according to a predicate. + +/// Rearrange the elements of the mutable slice `s` such that elements where `p(t)` is true precede +/// the elements where `p(t)` is false. +/// +/// The order of elements is not preserved, unless the slice is already partitioned. +/// +/// Returns the number of elements where `p(t)` is true. +pub fn partition_slice<'a, T: 'a, F>(s: &'a mut [T], mut p: F) -> usize + where F: FnMut(&T) -> bool +{ + // Count the length of the prefix where `p` returns true. + let mut count = match s.iter().position(|t| !p(t)) { + Some(t) => t, + None => return s.len(), + }; + + // Swap remaining `true` elements into place. + // + // This actually preserves the order of the `true` elements, but the `false` elements get + // shuffled. + for i in count + 1..s.len() { + if p(&s[i]) { + s.swap(count, i); + count += 1; + } + } + + count +} + +#[cfg(test)] +mod tests { + use super::partition_slice; + + fn check(x: &[u32], want: &[u32]) { + assert_eq!(x.len(), want.len()); + let want_count = want.iter().cloned().filter(|&x| x % 10 == 0).count(); + let mut v = Vec::new(); + v.extend(x.iter().cloned()); + let count = partition_slice(&mut v[..], |&x| x % 10 == 0); + assert_eq!(v, want); + assert_eq!(count, want_count); + } + + #[test] + fn empty() { + check(&[], &[]); + } + + #[test] + fn singles() { + check(&[0], &[0]); + check(&[1], &[1]); + check(&[10], &[10]); + } + + #[test] + fn doubles() { + check(&[0, 0], &[0, 0]); + check(&[0, 5], &[0, 5]); + check(&[5, 0], &[0, 5]); + check(&[5, 4], &[5, 4]); + } + + #[test] + fn longer() { + check(&[1, 2, 3], &[1, 2, 3]); + check(&[1, 2, 10], &[10, 2, 1]); // Note: 2, 1 order not required. + check(&[1, 10, 2], &[10, 1, 2]); // Note: 1, 2 order not required. + check(&[1, 20, 10], &[20, 10, 1]); + check(&[1, 20, 3, 10], &[20, 10, 3, 1]); + check(&[20, 3, 10, 1], &[20, 10, 3, 1]); + } +} From e3480987bd320583f0f8a0035854e589fd7ffb3e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 6 Feb 2017 16:54:29 +0100 Subject: [PATCH 506/968] Live Value Tracker. Keep track of which values are live and dead as we move through the instructions in an EBB. --- lib/cretonne/src/lib.rs | 1 + .../src/regalloc/live_value_tracker.rs | 253 ++++++++++++++++++ lib/cretonne/src/regalloc/liveness.rs | 5 + lib/cretonne/src/regalloc/mod.rs | 1 + 4 files changed, 260 insertions(+) create mode 100644 lib/cretonne/src/regalloc/live_value_tracker.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 436e2829e3..bfd986949a 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -25,4 +25,5 @@ mod constant_hash; mod predicates; mod legalizer; mod ref_slice; +mod partition_slice; mod packed_option; diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs new file mode 100644 index 0000000000..02af0969d1 --- /dev/null +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -0,0 +1,253 @@ +//! Track which values are live in an EBB with instruction granularity. +//! +//! The `LiveValueTracker` keeps track of the set of live SSA values at each instruction in an EBB. +//! The sets of live values are computed on the fly as the tracker is moved from instruction to +//! instruction, starting at the EBB header. + +use dominator_tree::DominatorTree; +use entity_list::{EntityList, ListPool}; +use ir::instructions::BranchInfo; +use ir::{Inst, Ebb, Value, DataFlowGraph, ProgramOrder, ExpandedProgramPoint}; +use partition_slice::partition_slice; +use regalloc::liveness::Liveness; + +use std::collections::HashMap; + +type ValueList = EntityList; + +/// Compute and track live values throughout an EBB. +pub struct LiveValueTracker { + /// The set of values that are live at the current program point. + live: LiveValueVec, + + /// Saved set of live values for every jump and branch that can potentially be an immediate + /// dominator of an EBB. + /// + /// This is the set of values that are live *before* the branch. + idom_sets: HashMap, + + /// Memory pool for the live sets. + idom_pool: ListPool, +} + +/// Information about a value that is live at the current program point. +pub struct LiveValue { + /// The live value. + pub value: Value, + + /// The local ending point of the live range in the current EBB, as returned by + /// `LiveRange::def_local_end()` or `LiveRange::livein_local_end()`. + pub endpoint: Inst, +} + +struct LiveValueVec { + /// The set of values that are live at the current program point. + values: Vec, + + /// How many values at the front of `values` are known to be live after `inst`? + /// + /// This is used to pass a much smaller slice to `partition_slice` when its called a second + /// time for the same instruction. + live_prefix: Option<(Inst, usize)>, +} + +impl LiveValueVec { + fn new() -> LiveValueVec { + LiveValueVec { + values: Vec::new(), + live_prefix: None, + } + } + + /// Add a new live value to `values`. + fn push(&mut self, value: Value, endpoint: Inst) { + self.values.push(LiveValue { + value: value, + endpoint: endpoint, + }); + } + + /// Remove all elements. + fn clear(&mut self) { + self.values.clear(); + self.live_prefix = None; + } + + /// Make sure that the values killed by `next_inst` are moved to the end of the `values` + /// vector. + /// + /// Returns the number of values that will be live after `next_inst`. + fn live_after(&mut self, next_inst: Inst) -> usize { + // How many values at the front of the vector are already known to survive `next_inst`? + // We don't need to pass this prefix to `partition_slice()` + let keep = match self.live_prefix { + Some((i, prefix)) if i == next_inst => prefix, + _ => 0, + }; + + // Move the remaining surviving values to the front partition of the vector. + let prefix = keep + partition_slice(&mut self.values[keep..], |v| v.endpoint != next_inst); + + // Remember the new prefix length in case we get called again for the same `next_inst`. + self.live_prefix = Some((next_inst, prefix)); + prefix + } + + /// Remove the values killed by `next_inst`. + fn remove_kill_values(&mut self, next_inst: Inst) { + let keep = self.live_after(next_inst); + self.values.truncate(keep); + } +} + +impl LiveValueTracker { + /// Create a new blank tracker. + pub fn new() -> LiveValueTracker { + LiveValueTracker { + live: LiveValueVec::new(), + idom_sets: HashMap::new(), + idom_pool: ListPool::new(), + } + } + + /// Clear all cached information. + pub fn clear(&mut self) { + self.live.clear(); + self.idom_sets.clear(); + self.idom_pool.clear(); + } + + /// Get the set of currently live values. + /// + /// Between calls to `process_inst()` and `drop_dead()`, this includes both values killed and + /// defined by the current instruction. + pub fn live(&self) -> &[LiveValue] { + &self.live.values + } + + /// Move the current position to the top of `ebb`. + /// + /// This depends on the stored live value set at `ebb`'s immediate dominator, so that must have + /// been visited first. + pub fn ebb_top(&mut self, + ebb: Ebb, + dfg: &DataFlowGraph, + liveness: &Liveness, + program_order: &PO, + domtree: &DominatorTree) { + // Start over, compute the set of live values at the top of the EBB from two sources: + // + // 1. Values that were live before `ebb`'s immediate dominator, filtered for those that are + // actually live-in. + // 2. Arguments to `ebb` that are not dead. + // + self.live.clear(); + + // Compute the live-in values. Start by filtering the set of values that were live before + // the immediate dominator. Just use the empty set if there's no immediate dominator (i.e., + // the entry block or an unreachable block). + if let Some(idom) = domtree.idom(ebb) { + // If the immediate dominator exits, we must have a stored list for it. This is a + // requirement to the order EBBs are visited: All dominators must have been processed + // before the current EBB. + let idom_live_list = + self.idom_sets.get(&idom).expect("No stored live set for dominator"); + // Get just the values that are live-in to `ebb`. + for &value in idom_live_list.as_slice(&self.idom_pool) { + let lr = liveness.get(value) + .expect("Immediate dominator value has no live range"); + + // Check if this value is live-in here. + if let Some(endpoint) = lr.livein_local_end(ebb, program_order) { + self.live.push(value, endpoint); + } + } + } + + // Now add all the live arguments to `ebb`. + for value in dfg.ebb_args(ebb) { + let lr = liveness.get(value).expect("EBB argument value has no live range"); + assert_eq!(lr.def(), ebb.into()); + match lr.def_local_end().into() { + ExpandedProgramPoint::Inst(endpoint) => { + self.live.push(value, endpoint); + } + ExpandedProgramPoint::Ebb(local_ebb) => { + // This is a dead EBB argument which is not even live into the first + // instruction in the EBB. We can ignore it. + assert_eq!(local_ebb, + ebb, + "EBB argument live range ends at wrong EBB header"); + } + } + } + } + + /// Prepare to move past `inst`. + /// + /// Determine the set of already live values that are killed by `inst`, and add the new defined + /// values to the tracked set. + /// + /// Returns `(kills, defs)` as a pair of slices. The `defs` slice is guaranteed to be in the + /// same order as `inst`'s results, and includes dead defines. The order of `kills` is + /// arbitrary. + /// + /// The `drop_dead()` method must be called next to actually remove the dead values from the + /// tracked set after the two returned slices are no longer needed. + pub fn process_inst(&mut self, + inst: Inst, + dfg: &DataFlowGraph, + liveness: &Liveness) + -> (&[LiveValue], &[LiveValue]) { + // Save a copy of the live values before any branches or jumps that could be somebody's + // immediate dominator. + match dfg[inst].analyze_branch() { + BranchInfo::NotABranch => {} + _ => self.save_idom_live_set(inst), + } + + // Move killed values to the end of the vector. + // Don't remove them yet, `drop_dead()` will do that. + let first_kill = self.live.live_after(inst); + + // Add the values defined by `inst`. + let first_def = self.live.values.len(); + for value in dfg.inst_results(inst) { + let lr = liveness.get(value).expect("Instruction result has no live range"); + assert_eq!(lr.def(), inst.into()); + match lr.def_local_end().into() { + ExpandedProgramPoint::Inst(endpoint) => { + self.live.push(value, endpoint); + } + ExpandedProgramPoint::Ebb(ebb) => { + panic!("Instruction result live range can't end at {}", ebb); + } + } + } + + (&self.live.values[first_kill..first_def], &self.live.values[first_def..]) + } + + /// Drop the values that are now dead after moving past `inst`. + /// + /// This removes both live values that were killed by `inst` and dead defines on `inst` itself. + /// + /// This must be called after `process_inst(inst)` and before proceeding to the next + /// instruction. + pub fn drop_dead(&mut self, inst: Inst) { + // Remove both live values that were killed by `inst` and dead defines from `inst`. + self.live.remove_kill_values(inst); + } + + /// Save the current set of live values so it is associated with `idom`. + fn save_idom_live_set(&mut self, idom: Inst) { + let values = self.live.values.iter().map(|lv| lv.value); + let pool = &mut self.idom_pool; + // If there already is a set saved for `idom`, just keep it. + self.idom_sets.entry(idom).or_insert_with(|| { + let mut list = ValueList::default(); + list.extend(values, pool); + list + }); + } +} diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index fe43b6962c..e34ceda915 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -284,6 +284,11 @@ impl Liveness { } } + /// Get the live range for `value`, if it exists. + pub fn get(&self, value: Value) -> Option<&LiveRange> { + self.ranges.get(value) + } + /// Compute the live ranges of all SSA values used in `func`. /// This clears out any existing analysis stored in this data structure. pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, isa: &TargetIsa) { diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index bd1c34cd23..902e7aabb0 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -5,5 +5,6 @@ pub mod liverange; pub mod liveness; pub mod allocatable_set; +pub mod live_value_tracker; mod affinity; From 71ec92281ced60cfe7638cadc7ee153126deb187 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Feb 2017 15:52:44 -0800 Subject: [PATCH 507/968] Add a Layout::next_ebb() method. This lets us iterate over the blocks in a function without holding a reference to the layout. --- lib/cretonne/src/ir/layout.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 50dfa61f4e..d30f336780 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -348,6 +348,11 @@ impl Layout { pub fn entry_block(&self) -> Option { self.first_ebb } + + /// Get the block following `ebb` in the layout order. + pub fn next_ebb(&self, ebb: Ebb) -> Option { + self.ebbs[ebb].next.expand() + } } #[derive(Clone, Debug, Default)] From efab6d0214f543e5f97782d9a53712f05c603b8e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Feb 2017 15:54:33 -0800 Subject: [PATCH 508/968] Return slices of live-ins and arguments from ebb_top(). The coloring algorithm needs to process these two types of live values differently, so we may as well provide the needed info. --- lib/cretonne/src/regalloc/live_value_tracker.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 02af0969d1..cdbfebe286 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -129,12 +129,17 @@ impl LiveValueTracker { /// /// This depends on the stored live value set at `ebb`'s immediate dominator, so that must have /// been visited first. + /// + /// Returns `(liveins, args)` as a pair or slices. The first slice is the set of live-in values + /// from the immediate dominator. The second slice is the set of `ebb` arguments that are live. + /// Dead arguments with no uses are ignored and not added to the set. pub fn ebb_top(&mut self, ebb: Ebb, dfg: &DataFlowGraph, liveness: &Liveness, program_order: &PO, - domtree: &DominatorTree) { + domtree: &DominatorTree) + -> (&[LiveValue], &[LiveValue]) { // Start over, compute the set of live values at the top of the EBB from two sources: // // 1. Values that were live before `ebb`'s immediate dominator, filtered for those that are @@ -165,6 +170,7 @@ impl LiveValueTracker { } // Now add all the live arguments to `ebb`. + let first_arg = self.live.values.len(); for value in dfg.ebb_args(ebb) { let lr = liveness.get(value).expect("EBB argument value has no live range"); assert_eq!(lr.def(), ebb.into()); @@ -181,6 +187,8 @@ impl LiveValueTracker { } } } + + self.live.values.split_at(first_arg) } /// Prepare to move past `inst`. From 8d7756c06b4e24675068335020f41bae391342d5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Feb 2017 15:55:47 -0800 Subject: [PATCH 509/968] Add a contains_key method to SparseMap. --- lib/cretonne/src/sparse_map.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs index 3a1b4dbdb6..ad49ac49fa 100644 --- a/lib/cretonne/src/sparse_map.rs +++ b/lib/cretonne/src/sparse_map.rs @@ -126,6 +126,11 @@ impl SparseMap None } + /// Return `true` if the map contains a value corresponding to `key`. + pub fn contains_key(&self, key: K) -> bool { + self.get(key).is_some() + } + /// Insert a value into the map. /// /// If the map did not have this key present, `None` is returned. From 23614675363ae8695d8a9b17ffc1ef57af2d9422 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Feb 2017 16:00:33 -0800 Subject: [PATCH 510/968] Return RegInfo by value from TargetIsa::register_info(). The struct is just a pair of static references, and we don't need the double indirection. --- lib/cretonne/src/isa/arm32/mod.rs | 4 ++-- lib/cretonne/src/isa/arm64/mod.rs | 4 ++-- lib/cretonne/src/isa/intel/mod.rs | 4 ++-- lib/cretonne/src/isa/mod.rs | 2 +- lib/cretonne/src/isa/registers.rs | 1 + lib/cretonne/src/isa/riscv/mod.rs | 4 ++-- lib/cretonne/src/regalloc/liveness.rs | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 63f6293516..63e9516a13 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -49,8 +49,8 @@ impl TargetIsa for Isa { &self.shared_flags } - fn register_info(&self) -> &RegInfo { - ®isters::INFO + fn register_info(&self) -> RegInfo { + registers::INFO.clone() } fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 2c2b98437b..4d00b1d49e 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -42,8 +42,8 @@ impl TargetIsa for Isa { &self.shared_flags } - fn register_info(&self) -> &RegInfo { - ®isters::INFO + fn register_info(&self) -> RegInfo { + registers::INFO.clone() } fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 13b77e1dd2..533c264e3d 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -49,8 +49,8 @@ impl TargetIsa for Isa { &self.shared_flags } - fn register_info(&self) -> &RegInfo { - ®isters::INFO + fn register_info(&self) -> RegInfo { + registers::INFO.clone() } fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index ee33bb4c47..af689a3947 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -132,7 +132,7 @@ pub trait TargetIsa { fn flags(&self) -> &settings::Flags; /// Get a data structure describing the registers in this ISA. - fn register_info(&self) -> &RegInfo; + fn register_info(&self) -> RegInfo; /// Encode an instruction after determining it is legal. /// diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 5e89110284..588fc0b14d 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -168,6 +168,7 @@ impl From for RegClassIndex { /// /// The `RegUnit` data structure collects all relevant static information about the registers in an /// ISA. +#[derive(Clone)] pub struct RegInfo { /// All register banks, ordered by their `first_unit`. The register banks are disjoint, but /// there may be holes of unused register unit numbers between banks due to alignment. diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 34b2a43c99..b997316733 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -49,8 +49,8 @@ impl TargetIsa for Isa { &self.shared_flags } - fn register_info(&self) -> &RegInfo { - ®isters::INFO + fn register_info(&self) -> RegInfo { + registers::INFO.clone() } fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index e34ceda915..da4b2568f9 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -324,7 +324,7 @@ impl Liveness { // resolved by the coloring algorithm, and ABI constraints require specific // registers or stack slots which the affinities don't model anyway. if let Some(constraint) = operand_constraints.next() { - lr.affinity.merge(constraint, reg_info); + lr.affinity.merge(constraint, ®_info); } }); } From e182bfa9adff305ab6a3a3c733131b11057435e4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Feb 2017 13:53:01 -0800 Subject: [PATCH 511/968] Cache the affinity in LiveValue. Most of the register allocator algorithms will only have to look at the currently live values as presented by LiveValueTracker. Many also need the value's affinity which is stored in the LiveRange associated with the value. Save the extra table lookup by caching the affinity value inside LiveValue. --- lib/cretonne/src/regalloc/live_value_tracker.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index cdbfebe286..838c5b6bb9 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -9,6 +9,7 @@ use entity_list::{EntityList, ListPool}; use ir::instructions::BranchInfo; use ir::{Inst, Ebb, Value, DataFlowGraph, ProgramOrder, ExpandedProgramPoint}; use partition_slice::partition_slice; +use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; use std::collections::HashMap; @@ -38,6 +39,12 @@ pub struct LiveValue { /// The local ending point of the live range in the current EBB, as returned by /// `LiveRange::def_local_end()` or `LiveRange::livein_local_end()`. pub endpoint: Inst, + + /// The affinity of the value as represented in its `LiveRange`. + /// + /// This value is simply a copy of the affinity stored in the live range. We copy it because + /// almost all users of `LiveValue` need to look at it. + pub affinity: Affinity, } struct LiveValueVec { @@ -60,10 +67,11 @@ impl LiveValueVec { } /// Add a new live value to `values`. - fn push(&mut self, value: Value, endpoint: Inst) { + fn push(&mut self, value: Value, endpoint: Inst, affinity: Affinity) { self.values.push(LiveValue { value: value, endpoint: endpoint, + affinity: affinity, }); } @@ -164,7 +172,7 @@ impl LiveValueTracker { // Check if this value is live-in here. if let Some(endpoint) = lr.livein_local_end(ebb, program_order) { - self.live.push(value, endpoint); + self.live.push(value, endpoint, lr.affinity); } } } @@ -176,7 +184,7 @@ impl LiveValueTracker { assert_eq!(lr.def(), ebb.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { - self.live.push(value, endpoint); + self.live.push(value, endpoint, lr.affinity); } ExpandedProgramPoint::Ebb(local_ebb) => { // This is a dead EBB argument which is not even live into the first @@ -225,7 +233,7 @@ impl LiveValueTracker { assert_eq!(lr.def(), inst.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { - self.live.push(value, endpoint); + self.live.push(value, endpoint, lr.affinity); } ExpandedProgramPoint::Ebb(ebb) => { panic!("Instruction result live range can't end at {}", ebb); From 3072728d375e88e428a1313bc36177bd44533b5c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 16 Feb 2017 13:56:58 -0800 Subject: [PATCH 512/968] Give register classes a name. This is just for better error messages etc. --- lib/cretonne/meta/gen_registers.py | 2 +- lib/cretonne/src/isa/registers.rs | 3 +++ lib/cretonne/src/regalloc/allocatable_set.rs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index c535f44c22..e36434f4d6 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -33,8 +33,8 @@ def gen_regclass(rc, fmt): """ Emit a static data definition for a register class. """ - fmt.comment(rc.name) with fmt.indented('RegClassData {', '},'): + fmt.line('name: "{}",'.format(rc.name)) fmt.line('index: {},'.format(rc.index)) fmt.line('width: {},'.format(rc.width)) fmt.line('subclasses: 0x{:x},'.format(rc.subclass_mask())) diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 588fc0b14d..c785ab2ac9 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -99,6 +99,9 @@ pub type RegClass = &'static RegClassData; /// A register class can be a subset of another register class. The top-level register classes are /// disjoint. pub struct RegClassData { + /// The name of the register class. + pub name: &'static str, + /// The index of this class in the ISA's RegInfo description. pub index: u8, diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 2c608b0a16..39058c04e4 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -118,12 +118,14 @@ mod tests { // Register classes for testing. const GPR: RegClass = &RegClassData { + name: "GPR", index: 0, width: 1, subclasses: 0, mask: [0xf0000000, 0x0000000f, 0], }; const DPR: RegClass = &RegClassData { + name: "DPR", index: 0, width: 2, subclasses: 0, From 518d30b3793b57182a01afeca65879709da832ea Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Feb 2017 11:57:32 -0800 Subject: [PATCH 513/968] Add a compilation context struct. This will provide main entry points for compiling functions, and it serves as a place for keeping data structures that should be preserved between function compilations to reduce allocator thrashing. So far, Context is just basic scaffolding. More to be added. --- lib/cretonne/src/context.rs | 23 +++++++++++++++++++++++ lib/cretonne/src/lib.rs | 24 +++++++++++++----------- 2 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 lib/cretonne/src/context.rs diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs new file mode 100644 index 0000000000..2360302ea2 --- /dev/null +++ b/lib/cretonne/src/context.rs @@ -0,0 +1,23 @@ +//! Cretonne compilation context and main entry point. +//! +//! When compiling many small functions, it is important to avoid repeatedly allocating and +//! deallocating the data structures needed for compilation. The `Context` struct is used to hold +//! on to memory allocations between function compilations. + +use ir::Function; + +/// Persistent data structures and compilation pipeline. +pub struct Context { + /// The function we're compiling. + pub func: Function, +} + +impl Context { + /// Allocate a new compilation context. + /// + /// The returned instance should be reused for compiling multiple functions in order to avoid + /// needless allocator thrashing. + pub fn new() -> Context { + Context { func: Function::new() } + } +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index bfd986949a..47339a39ca 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -2,28 +2,30 @@ #![deny(missing_docs)] +pub use context::Context; +pub use legalizer::legalize_function; pub use verifier::verify_function; pub use write::write_function; -pub use legalizer::legalize_function; /// Version number of the cretonne crate. pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub mod ir; -pub mod isa; pub mod cfg; pub mod dominator_tree; -pub mod entity_map; pub mod entity_list; -pub mod sparse_map; -pub mod settings; -pub mod verifier; +pub mod entity_map; +pub mod ir; +pub mod isa; pub mod regalloc; +pub mod settings; +pub mod sparse_map; +pub mod verifier; -mod write; mod constant_hash; -mod predicates; +mod context; mod legalizer; -mod ref_slice; -mod partition_slice; mod packed_option; +mod partition_slice; +mod predicates; +mod ref_slice; +mod write; From 793b3a140aba0eda5aa69435f9aebabbe03bd61a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Feb 2017 12:05:27 -0800 Subject: [PATCH 514/968] Return slices instead of &Vec references. We Don't need to expose the internal control flow graph representation. --- lib/cretonne/src/cfg.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index 5466355812..cfb29844d0 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -83,12 +83,12 @@ impl ControlFlowGraph { } /// Get the CFG predecessor basic blocks to `ebb`. - pub fn get_predecessors(&self, ebb: Ebb) -> &Vec { + pub fn get_predecessors(&self, ebb: Ebb) -> &[BasicBlock] { &self.data[ebb].predecessors } /// Get the CFG successors to `ebb`. - pub fn get_successors(&self, ebb: Ebb) -> &Vec { + pub fn get_successors(&self, ebb: Ebb) -> &[Ebb] { &self.data[ebb].successors } From 0f29fc7a526ea561f0e1180e6e2fb050529859f8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Feb 2017 12:16:48 -0800 Subject: [PATCH 515/968] Make the ControlFlowGraph reusable. Move the flow graph computation into a compute method which can be called with multiple functions. This allows us to reuse the ControlFlowGraph memory and keep an instance in the Context. --- lib/cretonne/src/cfg.rs | 39 ++++++++++++++++++++---------- lib/cretonne/src/context.rs | 9 ++++++- lib/cretonne/src/dominator_tree.rs | 4 +-- src/filetest/domtree.rs | 2 +- src/print_cfg.rs | 2 +- tests/cfg_traversal.rs | 2 +- 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index cfb29844d0..9b0f3f15e7 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -50,31 +50,44 @@ pub struct ControlFlowGraph { } impl ControlFlowGraph { - /// During initialization mappings will be generated for any existing - /// blocks within the CFG's associated function. - pub fn new(func: &Function) -> ControlFlowGraph { + /// Allocate a new blank control flow graph. + pub fn new() -> ControlFlowGraph { + ControlFlowGraph { + entry_block: None, + data: EntityMap::new(), + } + } - let mut cfg = ControlFlowGraph { - data: EntityMap::with_capacity(func.dfg.num_ebbs()), - entry_block: func.layout.entry_block(), - }; + /// Allocate and compute the control flow graph for `func`. + pub fn with_function(func: &Function) -> ControlFlowGraph { + let mut cfg = ControlFlowGraph::new(); + cfg.compute(func); + cfg + } + + /// Compute the control flow graph of `func`. + /// + /// This will clear and overwrite any information already stored in this data structure. + pub fn compute(&mut self, func: &Function) { + self.entry_block = func.layout.entry_block(); + self.data.clear(); + self.data.resize(func.dfg.num_ebbs()); for ebb in &func.layout { for inst in func.layout.ebb_insts(ebb) { match func.dfg[inst].analyze_branch() { BranchInfo::SingleDest(dest, _) => { - cfg.add_edge((ebb, inst), dest); + self.add_edge((ebb, inst), dest); } BranchInfo::Table(jt) => { for (_, dest) in func.jump_tables[jt].entries() { - cfg.add_edge((ebb, inst), dest); + self.add_edge((ebb, inst), dest); } } BranchInfo::NotABranch => {} } } } - cfg } fn add_edge(&mut self, from: BasicBlock, to: Ebb) { @@ -140,7 +153,7 @@ mod tests { #[test] fn empty() { let func = Function::new(); - let cfg = ControlFlowGraph::new(&func); + let cfg = ControlFlowGraph::with_function(&func); assert_eq!(None, cfg.ebbs().next()); } @@ -154,7 +167,7 @@ mod tests { func.layout.append_ebb(ebb1); func.layout.append_ebb(ebb2); - let cfg = ControlFlowGraph::new(&func); + let cfg = ControlFlowGraph::with_function(&func); let nodes = cfg.ebbs().collect::>(); assert_eq!(nodes.len(), 3); @@ -194,7 +207,7 @@ mod tests { cur.insert_ebb(ebb2); } - let cfg = ControlFlowGraph::new(&func); + let cfg = ControlFlowGraph::with_function(&func); let ebb0_predecessors = cfg.get_predecessors(ebb0); let ebb1_predecessors = cfg.get_predecessors(ebb1); diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 2360302ea2..8a94dd83fc 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -4,12 +4,16 @@ //! deallocating the data structures needed for compilation. The `Context` struct is used to hold //! on to memory allocations between function compilations. +use cfg::ControlFlowGraph; use ir::Function; /// Persistent data structures and compilation pipeline. pub struct Context { /// The function we're compiling. pub func: Function, + + /// The control flow graph of `func`. + pub cfg: ControlFlowGraph, } impl Context { @@ -18,6 +22,9 @@ impl Context { /// The returned instance should be reused for compiling multiple functions in order to avoid /// needless allocator thrashing. pub fn new() -> Context { - Context { func: Function::new() } + Context { + func: Function::new(), + cfg: ControlFlowGraph::new(), + } } } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index eaec78f367..02c9cecc84 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -203,7 +203,7 @@ mod test { #[test] fn empty() { let func = Function::new(); - let cfg = ControlFlowGraph::new(&func); + let cfg = ControlFlowGraph::with_function(&func); let dtree = DominatorTree::new(&func, &cfg); assert_eq!(0, dtree.nodes.keys().count()); } @@ -238,7 +238,7 @@ mod test { cur.insert_ebb(ebb0); } - let cfg = ControlFlowGraph::new(&func); + let cfg = ControlFlowGraph::with_function(&func); let dt = DominatorTree::new(&func, &cfg); assert_eq!(func.layout.entry_block().unwrap(), ebb3); diff --git a/src/filetest/domtree.rs b/src/filetest/domtree.rs index 0a570e3253..72a4293df7 100644 --- a/src/filetest/domtree.rs +++ b/src/filetest/domtree.rs @@ -40,7 +40,7 @@ impl SubTest for TestDomtree { // Extract our own dominator tree from fn run(&self, func: Cow, context: &Context) -> Result<()> { let func = func.borrow(); - let cfg = ControlFlowGraph::new(func); + let cfg = ControlFlowGraph::with_function(func); let domtree = DominatorTree::new(func, &cfg); // Build an expected domtree from the source annotations. diff --git a/src/print_cfg.rs b/src/print_cfg.rs index 858ef4de23..ca6c9219f7 100644 --- a/src/print_cfg.rs +++ b/src/print_cfg.rs @@ -33,7 +33,7 @@ impl<'a> CFGPrinter<'a> { pub fn new(func: &'a Function) -> CFGPrinter<'a> { CFGPrinter { func: func, - cfg: ControlFlowGraph::new(func), + cfg: ControlFlowGraph::with_function(func), } } diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index 2bdeab2347..453e0f83b7 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -8,7 +8,7 @@ use self::cretonne::entity_map::EntityMap; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &parse_functions(function_source).unwrap()[0]; - let cfg = ControlFlowGraph::new(&func); + let cfg = ControlFlowGraph::with_function(&func); let ebbs = ebb_order.iter() .map(|n| Ebb::with_number(*n).unwrap()) .collect::>(); From fa205d049dcb13d271839b34a61b4fa8f2c23de3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Feb 2017 13:09:41 -0800 Subject: [PATCH 516/968] Make the DominatorTree reusable. Add a compute() method which can recompute a dominator tree for different functions. Add a dominator tree data to the cretonne::Context. --- lib/cretonne/src/context.rs | 11 +++++++++ lib/cretonne/src/dominator_tree.rs | 38 ++++++++++++++++++++---------- src/filetest/domtree.rs | 2 +- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 8a94dd83fc..31a1569d5f 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -5,6 +5,7 @@ //! on to memory allocations between function compilations. use cfg::ControlFlowGraph; +use dominator_tree::DominatorTree; use ir::Function; /// Persistent data structures and compilation pipeline. @@ -14,6 +15,9 @@ pub struct Context { /// The control flow graph of `func`. pub cfg: ControlFlowGraph, + + /// Dominator tree for `func`. + pub domtree: DominatorTree, } impl Context { @@ -25,6 +29,13 @@ impl Context { Context { func: Function::new(), cfg: ControlFlowGraph::new(), + domtree: DominatorTree::new(), } } + + /// Recompute the control flow graph and dominator tree. + pub fn flowgraph(&mut self) { + self.cfg.compute(&self.func); + self.domtree.compute(&self.func, &self.cfg); + } } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 02c9cecc84..fd2c6cfb50 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -119,10 +119,24 @@ impl DominatorTree { } impl DominatorTree { + /// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a + /// function. + pub fn new() -> DominatorTree { + DominatorTree { nodes: EntityMap::new() } + } + + /// Allocate and compute a dominator tree. + pub fn with_function(func: &Function, cfg: &ControlFlowGraph) -> DominatorTree { + let mut domtree = DominatorTree::new(); + domtree.compute(func, cfg); + domtree + } + /// Build a dominator tree from a control flow graph using Keith D. Cooper's /// "Simple, Fast Dominator Algorithm." - pub fn new(func: &Function, cfg: &ControlFlowGraph) -> DominatorTree { - let mut domtree = DominatorTree { nodes: EntityMap::with_capacity(func.dfg.num_ebbs()) }; + pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) { + self.nodes.clear(); + self.nodes.resize(func.dfg.num_ebbs()); // We'll be iterating over a reverse post-order of the CFG. // This vector only contains reachable EBBs. @@ -132,12 +146,12 @@ impl DominatorTree { // The last block visited in a post-order traversal must be the entry block. let entry_block = match postorder.pop() { Some(ebb) => ebb, - None => return domtree, + None => return, }; assert_eq!(Some(entry_block), func.layout.entry_block()); // Do a first pass where we assign RPO numbers to all reachable nodes. - domtree.nodes[entry_block].rpo_number = 1; + self.nodes[entry_block].rpo_number = 1; for (rpo_idx, &ebb) in postorder.iter().rev().enumerate() { // Update the current node and give it an RPO number. // The entry block got 1, the rest start at 2. @@ -148,8 +162,8 @@ impl DominatorTree { // // Due to the nature of the post-order traversal, every node we visit will have at // least one predecessor that has previously been visited during this RPO. - domtree.nodes[ebb] = DomNode { - idom: domtree.compute_idom(ebb, cfg, &func.layout).into(), + self.nodes[ebb] = DomNode { + idom: self.compute_idom(ebb, cfg, &func.layout).into(), rpo_number: rpo_idx as u32 + 2, } } @@ -162,15 +176,13 @@ impl DominatorTree { while changed { changed = false; for &ebb in postorder.iter().rev() { - let idom = domtree.compute_idom(ebb, cfg, &func.layout).into(); - if domtree.nodes[ebb].idom != idom { - domtree.nodes[ebb].idom = idom; + let idom = self.compute_idom(ebb, cfg, &func.layout).into(); + if self.nodes[ebb].idom != idom { + self.nodes[ebb].idom = idom; changed = true; } } } - - domtree } // Compute the immediate dominator for `ebb` using the current `idom` states for the reachable @@ -204,7 +216,7 @@ mod test { fn empty() { let func = Function::new(); let cfg = ControlFlowGraph::with_function(&func); - let dtree = DominatorTree::new(&func, &cfg); + let dtree = DominatorTree::with_function(&func, &cfg); assert_eq!(0, dtree.nodes.keys().count()); } @@ -239,7 +251,7 @@ mod test { } let cfg = ControlFlowGraph::with_function(&func); - let dt = DominatorTree::new(&func, &cfg); + let dt = DominatorTree::with_function(&func, &cfg); assert_eq!(func.layout.entry_block().unwrap(), ebb3); assert_eq!(dt.idom(ebb3), None); diff --git a/src/filetest/domtree.rs b/src/filetest/domtree.rs index 72a4293df7..efa4ee857b 100644 --- a/src/filetest/domtree.rs +++ b/src/filetest/domtree.rs @@ -41,7 +41,7 @@ impl SubTest for TestDomtree { fn run(&self, func: Cow, context: &Context) -> Result<()> { let func = func.borrow(); let cfg = ControlFlowGraph::with_function(func); - let domtree = DominatorTree::new(func, &cfg); + let domtree = DominatorTree::with_function(func, &cfg); // Build an expected domtree from the source annotations. let mut expected = HashMap::new(); From 706720eba930b6891016143f1c25fcc975eb2531 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 18 Feb 2017 10:22:00 -0800 Subject: [PATCH 517/968] Improve assertion text for missing live ranges. --- lib/cretonne/src/regalloc/live_value_tracker.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 838c5b6bb9..bcbbb62fe1 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -229,7 +229,10 @@ impl LiveValueTracker { // Add the values defined by `inst`. let first_def = self.live.values.len(); for value in dfg.inst_results(inst) { - let lr = liveness.get(value).expect("Instruction result has no live range"); + let lr = match liveness.get(value) { + Some(lr) => lr, + None => panic!("{} result {} has no live range", dfg[inst].opcode(), value), + }; assert_eq!(lr.def(), inst.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { From 74eb13c17a6e648b58117ff6188674f2a0e85764 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sat, 18 Feb 2017 10:22:27 -0800 Subject: [PATCH 518/968] Create live ranges for dead defs. When the liveness pass implements dead code elimination, missing live ranges can be used to indicate unused values that it may be possible to remove. But even then, we may have to keep dead defs around if the instruction has side effects or other live defs. --- lib/cretonne/src/regalloc/liveness.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index da4b2568f9..a8591e8597 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -304,6 +304,13 @@ impl Liveness { // TODO: Resolve value aliases while we're visiting instructions? for ebb in func.layout.ebbs() { for inst in func.layout.ebb_insts(ebb) { + // Make sure we have created live ranges for dead defs. + // TODO: When we implement DCE, we can use the absence of a live range to indicate + // an unused value. + for def in func.dfg.inst_results(inst) { + get_or_create(&mut self.ranges, def, func, recipe_constraints); + } + // The instruction encoding is used to compute affinities. let recipe = func.encodings[inst].recipe(); // Iterator of constraints, one per value operand. From 20ff2f0025f6c74a4fa276b0cc189886d6fb1d3a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Feb 2017 13:05:17 -0800 Subject: [PATCH 519/968] Add a return_reg instruction to the base instruction set. Register-style return is used by all RISC architectures, so it is natural to have a shared instruction representation. --- docs/langref.rst | 1 + lib/cretonne/meta/base/formats.py | 2 +- lib/cretonne/meta/base/instructions.py | 23 +++++++++++++++++++++-- lib/cretonne/src/ir/instructions.rs | 26 ++++++++++++++++++++++++++ lib/cretonne/src/write.rs | 7 +++++++ lib/reader/src/parser.rs | 20 +++++++++++++++++++- 6 files changed, 75 insertions(+), 4 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 5bd59b18d3..d7bffd870a 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -384,6 +384,7 @@ preamble`: .. autoinst:: call .. autoinst:: x_return +.. autoinst:: return_reg This simple example illustrates direct function calls and signatures:: diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 421913b0e4..89704623fd 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -52,7 +52,7 @@ IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True, boxed_storage=True) Return = InstructionFormat(VARIABLE_ARGS, boxed_storage=True) - +ReturnReg = InstructionFormat(VALUE, VARIABLE_ARGS, boxed_storage=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index eb98d175ef..1693763468 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -18,7 +18,7 @@ GROUP = InstructionGroup("base", "Shared base instruction set") Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) iB = TypeVar('iB', 'A scalar integer type', ints=True) -iPtr = TypeVar('iB', 'An integer address type', ints=(32, 64)) +iAddr = TypeVar('iAddr', 'An integer address type', ints=(32, 64)) Testable = TypeVar( 'Testable', 'A scalar boolean or integer type', ints=True, bools=True) @@ -113,6 +113,25 @@ x_return = Instruction( """, ins=rvals, is_terminator=True) +raddr = Operand('raddr', iAddr, doc='Return address') + +return_reg = Instruction( + 'return_reg', r""" + Return from the function to a return address held in a register. + + Unconditionally transfer control to the calling function, passing the + provided return values. The list of return values must match the + function signature's return types. + + This instruction should only be used by ISA-specific epilogue lowering + code. It is equivalent to :inst:`return`, but the return address is + provided explicitly in a register. This style of return instruction is + used by RISC architectures such as ARM and RISC-V. A normal + :inst:`return` will be legalized into this instruction on these + architectures. + """, + ins=(raddr, rvals), is_terminator=True) + FN = Operand( 'FN', entities.func_ref, @@ -130,7 +149,7 @@ call = Instruction( outs=rvals) SIG = Operand('SIG', entities.sig_ref, doc='function signature') -callee = Operand('callee', iPtr, doc='address of function to call') +callee = Operand('callee', iAddr, doc='address of function to call') call_indirect = Instruction( 'call_indirect', r""" diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 17a0ef2c27..d778ddb9a3 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -222,6 +222,11 @@ pub enum InstructionData { ty: Type, data: Box, }, + ReturnReg { + opcode: Opcode, + ty: Type, + data: Box, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to @@ -406,6 +411,27 @@ pub struct ReturnData { pub varargs: VariableArgs, } +/// Payload of a return instruction. +#[derive(Clone, Debug)] +pub struct ReturnRegData { + /// Return address. + pub arg: Value, + /// Dynamically sized array containing return values. + pub varargs: VariableArgs, +} + +impl ReturnRegData { + /// Get references to the arguments. + pub fn arguments(&self) -> [&[Value]; 2] { + [ref_slice(&self.arg), &self.varargs] + } + + /// Get mutable references to the arguments. + pub fn arguments_mut(&mut self) -> [&mut [Value]; 2] { + [ref_slice_mut(&mut self.arg), &mut self.varargs] + } +} + /// Analyzing an instruction. /// /// Avoid large matches on instruction formats by using the methods defined here to examine diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 8dfe955e42..7a328a6d30 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -239,6 +239,13 @@ fn write_instruction(w: &mut Write, writeln!(w, " {}", data.varargs) } } + ReturnReg { ref data, .. } => { + if data.varargs.is_empty() { + writeln!(w, "{}", data.arg) + } else { + writeln!(w, "{}, {}", data.arg, data.varargs) + } + } } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 9decd562b0..8651e09d4d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -16,7 +16,7 @@ use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, TernaryOverflowData, JumpData, BranchData, CallData, - IndirectCallData, ReturnData}; + IndirectCallData, ReturnData, ReturnRegData}; use cretonne::isa; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -197,6 +197,11 @@ impl Context { InstructionData::Return { ref mut data, .. } => { try!(self.map.rewrite_values(&mut data.varargs, loc)); } + + InstructionData::ReturnReg { ref mut data, .. } => { + try!(self.map.rewrite_value(&mut data.arg, loc)); + try!(self.map.rewrite_values(&mut data.varargs, loc)); + } } } } @@ -1321,6 +1326,19 @@ impl<'a> Parser<'a> { data: Box::new(ReturnData { varargs: args }), } } + InstructionFormat::ReturnReg => { + let raddr = try!(self.match_value("expected SSA value return addr operand")); + try!(self.match_token(Token::Comma, "expected ',' between operands")); + let args = try!(self.parse_value_list()); + InstructionData::ReturnReg { + opcode: opcode, + ty: VOID, + data: Box::new(ReturnRegData { + arg: raddr, + varargs: args, + }), + } + } InstructionFormat::BranchTable => { let arg = try!(self.match_value("expected SSA value operand")); try!(self.match_token(Token::Comma, "expected ',' between operands")); From a7d24ab1dcc87d6ea04216b76407dd7c109abe4a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Feb 2017 16:18:45 -0800 Subject: [PATCH 520/968] Compute the controlling type variable accurately. Some polymorphic instructions don't return the controlling type variable, so it has to be computed from the designated operand instead. - Add a requires_typevar_operand() method to the operand constraints which indicates that. - Add a ctrl_typevar(dfg) method to InstructionData which computes the controlling type variable correctly, and returns VOID for monomorphic instructions. - Use ctrl_typevar(dfg) to drive the level-1 encoding table lookups. --- lib/cretonne/meta/gen_instr.py | 16 ++++++++++-- lib/cretonne/src/ir/instructions.rs | 40 +++++++++++++++++++++++++++-- lib/cretonne/src/isa/arm32/mod.rs | 4 +-- lib/cretonne/src/isa/arm64/mod.rs | 4 +-- lib/cretonne/src/isa/intel/mod.rs | 4 +-- lib/cretonne/src/isa/riscv/mod.rs | 4 +-- 6 files changed, 60 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index d05bef96df..67ab23bda9 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -417,10 +417,20 @@ def gen_type_constraints(fmt, instrs): get_constraint(i.ins[idx], ctrl_typevar, type_sets)) offset = operand_seqs.add(constraints) fixed_results = len(i.value_results) + # Can the controlling type variable be inferred from the designated + # operand? use_typevar_operand = i.is_polymorphic and i.use_typevar_operand + # Can the controlling type variable be inferred from the result? + use_result = (fixed_results > 0 and + i.outs[i.value_results[0]].typevar != ctrl_typevar) + # Are we required to use the designated operand instead of the + # result? + requires_typevar_operand = use_typevar_operand and not use_result fmt.comment( - '{}: fixed_results={}, use_typevar_operand={}' - .format(i.camel_name, fixed_results, use_typevar_operand)) + ('{}: fixed_results={}, use_typevar_operand={}, ' + + 'requires_typevar_operand={}') + .format(i.camel_name, fixed_results, use_typevar_operand, + requires_typevar_operand)) fmt.comment('Constraints={}'.format(constraints)) if i.is_polymorphic: fmt.comment( @@ -430,6 +440,8 @@ def gen_type_constraints(fmt, instrs): flags = fixed_results if use_typevar_operand: flags |= 8 + if requires_typevar_operand: + flags |= 0x10 with fmt.indented('OpcodeConstraints {', '},'): fmt.line('flags: {:#04x},'.format(flags)) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index d778ddb9a3..95311d48d8 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -14,6 +14,7 @@ use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::*; use ir::types; +use ir::DataFlowGraph; use ref_slice::*; use packed_option::PackedOption; @@ -492,6 +493,27 @@ impl InstructionData { _ => CallInfo::NotACall, } } + + /// Get the controlling type variable, or `VOID` if this instruction isn't polymorphic. + /// + /// In most cases, the controlling type variable is the same as the first result type, but some + /// opcodes require us to read the type of the designated type variable operand from `dfg`. + pub fn ctrl_typevar(&self, dfg: &DataFlowGraph) -> Type { + let constraints = self.opcode().constraints(); + + if !constraints.is_polymorphic() { + types::VOID + } else if constraints.requires_typevar_operand() { + // Not all instruction formats have a designated operand, but in that case + // `requires_typevar_operand()` should never be true. + dfg.value_type(self.typevar_operand() + .expect("Instruction format doesn't have a designated operand, bad opcode.")) + } else { + // For locality of reference, we prefer to get the controlling type variable from + // `idata` itself, when possible. + self.first_type() + } + } } /// Information about branch and jump instructions. @@ -537,8 +559,12 @@ pub struct OpcodeConstraints { /// Bit 3: /// This opcode is polymorphic and the controlling type variable can be inferred from the /// designated input operand. This is the `typevar_operand` index given to the - /// `InstructionFormat` meta language object. When bit 0 is not set, the controlling type - /// variable must be the first output value instead. + /// `InstructionFormat` meta language object. When this bit is not set, the controlling + /// type variable must be the first output value instead. + /// + /// Bit 4: + /// This opcode is polymorphic and the controlling type variable does *not* appear as the + /// first result type. flags: u8, /// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. @@ -559,6 +585,16 @@ impl OpcodeConstraints { (self.flags & 0x8) != 0 } + /// Is it necessary to look at the designated value input operand in order to determine the + /// controlling type variable, or is it good enough to use the first return type? + /// + /// Most polymorphic instructions produce a single result with the type of the controlling type + /// variable. A few polymorphic instructions either don't produce any results, or produce + /// results with a fixed type. These instructions return `true`. + pub fn requires_typevar_operand(self) -> bool { + (self.flags & 0x10) != 0 + } + /// Get the number of *fixed* result values produced by this opcode. /// This does not include `variable_args` produced by calls. pub fn fixed_results(self) -> usize { diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 63e9516a13..9af02883e3 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -53,8 +53,8 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { - lookup_enclist(inst.first_type(), + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 4d00b1d49e..50f54524a9 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -46,8 +46,8 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { - lookup_enclist(inst.first_type(), + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), &enc_tables::LEVEL1_A64[..], &enc_tables::LEVEL2[..]) diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 533c264e3d..e5c99521e3 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -53,8 +53,8 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { - lookup_enclist(inst.first_type(), + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index b997316733..8b27a46119 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -53,8 +53,8 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, _: &DataFlowGraph, inst: &InstructionData) -> Result { - lookup_enclist(inst.first_type(), + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) From 62334b26b4436cf03d798cc9ebe850bc5ed4a68b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Feb 2017 15:17:33 -0800 Subject: [PATCH 521/968] Add return_reg encodings for RISC-V. --- filetests/isa/riscv/encoding.cton | 4 +++- lib/cretonne/meta/isa/riscv/encodings.py | 11 ++++++++++- lib/cretonne/meta/isa/riscv/recipes.py | 13 ++++++++++++- lib/cretonne/src/write.rs | 4 ++-- lib/reader/src/parser.rs | 9 ++++++--- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/filetests/isa/riscv/encoding.cton b/filetests/isa/riscv/encoding.cton index 2ba06007bb..f6defe27bf 100644 --- a/filetests/isa/riscv/encoding.cton +++ b/filetests/isa/riscv/encoding.cton @@ -15,5 +15,7 @@ ebb0(v1: i32, v2: i32): ; check: [R#10c] ; sameln: $v12 = imul - return + return_reg v1 + ; check: [Iret#19] + ; sameln: return_reg } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 0fc3a3ee8d..bf84c468b0 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -4,7 +4,7 @@ RISC-V Encodings. from __future__ import absolute_import from base import instructions as base from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32, R, Rshamt, I +from .recipes import OPIMM, OPIMM32, OP, OP32, JALR, R, Rshamt, I, Iret from .settings import use_m # Basic arithmetic binary instructions are encoded in an R-type instruction. @@ -52,3 +52,12 @@ for inst, inst_imm, f3, f7 in [ RV32.enc(base.imul.i32, R, OP(0b000, 0b0000001), isap=use_m) RV64.enc(base.imul.i64, R, OP(0b000, 0b0000001), isap=use_m) RV64.enc(base.imul.i32, R, OP32(0b000, 0b0000001), isap=use_m) + +# Control flow. + +# Returns are a special case of JALR. +# Note: Return stack predictors will only recognize this as a return when the +# return address is provided in `x1`. We may want a special encoding to enforce +# that. +RV32.enc(base.return_reg.i32, Iret, JALR()) +RV64.enc(base.return_reg.i64, Iret, JALR()) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index fe0618d6cc..ea7e019eb4 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -11,7 +11,7 @@ instruction formats described in the reference: from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt -from base.formats import Binary, BinaryImm +from base.formats import Binary, BinaryImm, ReturnReg from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -40,6 +40,12 @@ def BRANCH(funct3): return 0b11000 | (funct3 << 5) +def JALR(funct3=0): + # type: (int) -> int + assert funct3 <= 0b111 + return 0b11001 | (funct3 << 5) + + def OPIMM(funct3, funct7=0): # type: (int, int) -> int assert funct3 <= 0b111 @@ -76,3 +82,8 @@ Rshamt = EncRecipe('Rshamt', BinaryImm, ins=GPR, outs=GPR) I = EncRecipe( 'I', BinaryImm, ins=GPR, outs=GPR, instp=IsSignedInt(BinaryImm.imm, 12)) + +# I-type encoding for `jalr` as a return instruction. We won't use the +# immediate offset. +# The variable return values are not encoded. +Iret = EncRecipe('Iret', ReturnReg, ins=GPR, outs=()) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 7a328a6d30..cb5fb8a24f 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -241,9 +241,9 @@ fn write_instruction(w: &mut Write, } ReturnReg { ref data, .. } => { if data.varargs.is_empty() { - writeln!(w, "{}", data.arg) + writeln!(w, " {}", data.arg) } else { - writeln!(w, "{}, {}", data.arg, data.varargs) + writeln!(w, " {}, {}", data.arg, data.varargs) } } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 8651e09d4d..f44cfd4dc8 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1327,9 +1327,12 @@ impl<'a> Parser<'a> { } } InstructionFormat::ReturnReg => { - let raddr = try!(self.match_value("expected SSA value return addr operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let args = try!(self.parse_value_list()); + let raddr = try!(self.match_value("expected SSA value return address operand")); + let args = if self.optional(Token::Comma) { + try!(self.parse_value_list()) + } else { + VariableArgs::new() + }; InstructionData::ReturnReg { opcode: opcode, ty: VOID, From 3d0240d2449eb1d5b405dc8cf634fdb32c275ebd Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Wed, 22 Feb 2017 14:48:57 +0000 Subject: [PATCH 522/968] Documentation fix for what appears to be a minor copy-paste mistake. --- lib/cretonne/meta/base/instructions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 1693763468..9cec7d9d58 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -254,8 +254,8 @@ fill = Instruction( 'fill', r""" Load a register value from a stack slot. - This instruction behaves exactly like :inst:`copy`, but the input - value is assigned to a spill slot. + This instruction behaves exactly like :inst:`copy`, but creates a new + SSA value for the spilled input value. """, ins=x, outs=a) From a4e4776087ac368ee52c8d256478464c1f16b66c Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Wed, 22 Feb 2017 16:13:48 +0000 Subject: [PATCH 523/968] Removed the Opcode::NotAnOpcode variant, replaced its uses with Option, and used the NonZero optimization to maintain the small 1-byte size of an optional Opcode. --- lib/cretonne/meta/gen_encoding.py | 4 ++-- lib/cretonne/meta/gen_instr.py | 23 ++++++++++++++++------- lib/cretonne/src/ir/instructions.rs | 22 ++++++++-------------- lib/cretonne/src/isa/enc_tables.rs | 9 ++------- lib/cretonne/src/verifier.rs | 2 +- lib/reader/src/parser.rs | 2 +- 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 3d4a00c4cc..c2849c1d6f 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -364,12 +364,12 @@ def emit_level2_hashtables(level2_hashtables, offt, level2_doc, fmt): if entry: fmt.line( 'Level2Entry ' + - '{{ opcode: Opcode::{}, offset: {:#08x} }},' + '{{ opcode: Some(Opcode::{}), offset: {:#08x} }},' .format(entry.inst.camel_name, entry.offset)) else: fmt.line( 'Level2Entry ' + - '{ opcode: Opcode::NotAnOpcode, offset: 0 },') + '{ opcode: None, offset: 0 },') def emit_level1_hashtable(cpumode, level1, offt, fmt): diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 67ab23bda9..1e83a1da2d 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -254,9 +254,13 @@ def gen_opcodes(groups, fmt): fmt.doc_comment('All instructions from all supported ISAs are present.') fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') instrs = [] + + # We explicitly set the discriminant of the first variant to 1, which + # allows us to take advantage of the NonZero optimization, meaning that + # wrapping enums can use the 0 discriminant instead of increasing the size + # if the whole type, and so SIZEOF(Option>) == SIZEOF(Opcode) + is_first_opcode = True with fmt.indented('pub enum Opcode {', '}'): - fmt.doc_comment('An invalid opcode.') - fmt.line('NotAnOpcode,') for g in groups: for i in g.instructions: instrs.append(i) @@ -269,7 +273,13 @@ def gen_opcodes(groups, fmt): 'Type inferred from {}.' .format(i.ins[i.format.typevar_operand])) # Enum variant itself. - fmt.line(i.camel_name + ',') + if is_first_opcode: + fmt.doc_comment('We explicitly set this to 1 to allow the NonZero optimization,') + fmt.doc_comment('meaning that SIZEOF(Option) == SIZEOF(Opcode)') + fmt.line(i.camel_name + ' = 1,') + is_first_opcode = False + else: + fmt.line(i.camel_name + ',') fmt.line() with fmt.indented('impl Opcode {', '}'): @@ -318,7 +328,6 @@ def gen_opcodes(groups, fmt): # Generate a private opcode_name function. with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): with fmt.indented('match opc {', '}'): - fmt.line('Opcode::NotAnOpcode => "",') for i in instrs: fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) fmt.line() @@ -328,13 +337,13 @@ def gen_opcodes(groups, fmt): instrs, lambda i: constant_hash.simple_hash(i.name)) with fmt.indented( - 'const OPCODE_HASH_TABLE: [Opcode; {}] = [' + 'const OPCODE_HASH_TABLE: [Option; {}] = [' .format(len(hash_table)), '];'): for i in hash_table: if i is None: - fmt.line('Opcode::NotAnOpcode,') + fmt.line('None,') else: - fmt.format('Opcode::{},', i.camel_name) + fmt.format('Some(Opcode::{}),', i.camel_name) fmt.line() return instrs diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 95311d48d8..73d49e7246 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -43,12 +43,8 @@ impl Display for Opcode { impl Opcode { /// Get the instruction format for this opcode. - pub fn format(self) -> Option { - if self == Opcode::NotAnOpcode { - None - } else { - Some(OPCODE_FORMAT[self as usize - 1]) - } + pub fn format(self) -> InstructionFormat { + OPCODE_FORMAT[self as usize - 1] } /// Get the constraint descriptor for this opcode. @@ -69,23 +65,21 @@ impl FromStr for Opcode { fn from_str(s: &str) -> Result { use constant_hash::{Table, simple_hash, probe}; - impl<'a> Table<&'a str> for [Opcode] { + impl<'a> Table<&'a str> for [Option] { fn len(&self) -> usize { self.len() } fn key(&self, idx: usize) -> Option<&'a str> { - if self[idx] == Opcode::NotAnOpcode { - None - } else { - Some(opcode_name(self[idx])) - } + self[idx].map(opcode_name) } } - match probe::<&str, [Opcode]>(&OPCODE_HASH_TABLE, s, simple_hash(s)) { + match probe::<&str, [Option]>(&OPCODE_HASH_TABLE, s, simple_hash(s)) { None => Err("Unknown opcode"), - Some(i) => Ok(OPCODE_HASH_TABLE[i]), + // We unwrap here because probe() should have ensured that the entry + // at this index is not None. + Some(i) => Ok(OPCODE_HASH_TABLE[i].unwrap()), } } } diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index d0a371dbc4..dd4e67875f 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -53,7 +53,7 @@ impl + Copy> Table for [Level1Entry] { /// /// Empty entries are encoded with a `NotAnOpcode` `opcode` field. pub struct Level2Entry + Copy> { - pub opcode: Opcode, + pub opcode: Option, pub offset: OffT, } @@ -63,12 +63,7 @@ impl + Copy> Table for [Level2Entry] { } fn key(&self, idx: usize) -> Option { - let opc = self[idx].opcode; - if opc != Opcode::NotAnOpcode { - Some(opc) - } else { - None - } + self[idx].opcode } } diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 0076933934..ea5941006e 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -149,7 +149,7 @@ impl<'a> Verifier<'a> { let inst_data = &self.func.dfg[inst]; // The instruction format matches the opcode - if inst_data.opcode().format() != Some(InstructionFormat::from(inst_data)) { + if inst_data.opcode().format() != InstructionFormat::from(inst_data) { return err!(inst, "instruction opcode doesn't match instruction format"); } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f44cfd4dc8..204b78c9e5 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1088,7 +1088,7 @@ impl<'a> Parser<'a> { // Parse the operands following the instruction opcode. // This depends on the format of the opcode. fn parse_inst_operands(&mut self, ctx: &Context, opcode: Opcode) -> Result { - Ok(match opcode.format().unwrap() { + Ok(match opcode.format() { InstructionFormat::Nullary => { InstructionData::Nullary { opcode: opcode, From 75a426a81212a8d8d98f2d7bb121836f50a36874 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Wed, 22 Feb 2017 17:07:07 +0000 Subject: [PATCH 524/968] Fix test case that I missed before. --- lib/cretonne/src/ir/instructions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 73d49e7246..216e88b883 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -742,7 +742,7 @@ mod tests { assert!(x != y); y = Opcode::Iadd; assert_eq!(x, y); - assert_eq!(x.format(), Some(InstructionFormat::Binary)); + assert_eq!(x.format(), InstructionFormat::Binary); assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm"); assert_eq!(Opcode::IaddImm.to_string(), "iadd_imm"); From 81a6e343ad884578a8f97c9b181b3529758c3d5f Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Wed, 22 Feb 2017 18:07:14 +0000 Subject: [PATCH 525/968] Add assertion that the NonZero optimization works on Option. --- lib/cretonne/src/ir/instructions.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 216e88b883..839219fc2a 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -736,6 +736,8 @@ mod tests { #[test] fn opcodes() { + use std::mem; + let x = Opcode::Iadd; let mut y = Opcode::Isub; @@ -753,6 +755,12 @@ mod tests { assert_eq!("iadd\0".parse::(), Err("Unknown opcode")); assert_eq!("".parse::(), Err("Unknown opcode")); assert_eq!("\0".parse::(), Err("Unknown opcode")); + + // Opcode is a single byte, and because Option originally came to 2 bytes, early on + // Opcode included a variant NotAnOpcode to avoid the unnecessary bloat. Since then the Rust + // compiler has brought in NonZero optimization, meaning that an enum not using the 0 value + // can be optional for no size cost. We want to ensure Option remains small. + assert_eq!(mem::size_of::(), mem::size_of::>()); } #[test] From 2ec7412a81ca6a06d6674991916941bd689019ac Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Wed, 22 Feb 2017 18:10:09 +0000 Subject: [PATCH 526/968] Removed unnecessary documentation. --- lib/cretonne/meta/gen_instr.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 1e83a1da2d..8cf89e7654 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -274,8 +274,6 @@ def gen_opcodes(groups, fmt): .format(i.ins[i.format.typevar_operand])) # Enum variant itself. if is_first_opcode: - fmt.doc_comment('We explicitly set this to 1 to allow the NonZero optimization,') - fmt.doc_comment('meaning that SIZEOF(Option) == SIZEOF(Opcode)') fmt.line(i.camel_name + ' = 1,') is_first_opcode = False else: From 329e51ac4fb3fadcd66b3d3369a3fd4465c0247e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Feb 2017 13:26:52 -0800 Subject: [PATCH 527/968] SSA register coloring pass. This is a bare-bones outline of the SSA coloring pass. Many features are missing, including: - Handling instruction operand constraints beyond simple register classes. - Handling ABI requirements for function arguments and return values. - Generating shuffle code for EBB arguments. --- lib/cretonne/src/isa/constraints.rs | 1 + lib/cretonne/src/regalloc/coloring.rs | 337 ++++++++++++++++++++++++++ lib/cretonne/src/regalloc/mod.rs | 1 + 3 files changed, 339 insertions(+) create mode 100644 lib/cretonne/src/regalloc/coloring.rs diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index 43a0ddb271..2b8700ec4a 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -49,6 +49,7 @@ pub enum ConstraintKind { } /// Constraints for an encoding recipe. +#[derive(Clone)] pub struct RecipeConstraints { /// Constraints for the instruction's fixed value operands. /// diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs new file mode 100644 index 0000000000..11018f664d --- /dev/null +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -0,0 +1,337 @@ +//! Register allocator coloring pass. +//! +//! The coloring pass assigns a physical register to every SSA value with a register affinity, +//! under the assumption that the register pressure has been lowered sufficiently by spilling and +//! splitting. +//! +//! # Preconditions +//! +//! The coloring pass doesn't work on arbitrary code. Certain preconditions must be satisfied: +//! +//! 1. All instructions must be legalized and assigned an encoding. The encoding recipe guides the +//! register assignments and provides exact constraints. +//! +//! 2. Instructions with tied operands must be in a coloring-friendly state. Specifically, the +//! values used by the tied operands must be killed by the instruction. This can be achieved by +//! inserting a `copy` to a new value immediately before the two-address instruction. +//! +//! 3. The register pressure must be lowered sufficiently by inserting spill code. Register +//! operands are allowed to read spilled values, but each such instance must be counted as using +//! a register. +//! +//! # Iteration order +//! +//! The SSA property guarantees that whenever the live range of two values overlap, one of the +//! values will be live at the definition point of the other value. If we visit the instructions in +//! a topological order relative to the dominance relation, we can assign colors to the values +//! defined by the instruction and only consider the colors of other values that are live at the +//! instruction. +//! +//! The topological order of instructions inside an EBB is simply the layout order, starting from +//! the EBB header. A topological order of the EBBs can only visit an EBB once its immediate +//! dominator has been visited. +//! +//! There are many valid topological orders of the EBBs, and the specific order can affect which +//! coloring hints are satisfied and which are broken. +//! + +use entity_map::EntityMap; +use dominator_tree::DominatorTree; +use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph}; +use isa::{TargetIsa, RegInfo, Encoding, RecipeConstraints, ConstraintKind}; +use regalloc::affinity::Affinity; +use regalloc::allocatable_set::AllocatableSet; +use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; +use regalloc::liveness::Liveness; +use sparse_map::SparseSet; + + +/// Data structures for the coloring pass. +/// +/// These are scratch space data structures that can be reused between invocations. +pub struct Coloring { + /// Set of visited EBBs. + visited: SparseSet, + + /// Stack of EBBs to be visited next. + stack: Vec, +} + +/// Bundle of references that the coloring algorithm needs. +/// +/// Some of the needed mutable references are passed around as explicit function arguments so we +/// can avoid many fights with the borrow checker over mutable borrows of `self`. This includes the +/// `Function` and `LiveValueTracker` references. +/// +/// Immutable context information and mutable references that don't need to be borrowed across +/// method calls should go in this struct. +struct Context<'a> { + // Cached ISA information. + // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. + reginfo: RegInfo, + recipe_constraints: &'a [RecipeConstraints], + + // References to contextual data structures we need. + domtree: &'a DominatorTree, + liveness: &'a mut Liveness, + + // Pristine set of registers that the allocator can use. + // This set remains immutable, we make clones. + usable_regs: AllocatableSet, +} + +impl Coloring { + /// Allocate scratch space data structures for the coloring pass. + pub fn new() -> Coloring { + Coloring { + visited: SparseSet::new(), + stack: Vec::new(), + } + } + + /// Run the coloring algorithm over `func`. + pub fn run(&mut self, + isa: &TargetIsa, + func: &mut Function, + domtree: &DominatorTree, + liveness: &mut Liveness, + tracker: &mut LiveValueTracker) { + let mut ctx = Context { + reginfo: isa.register_info(), + recipe_constraints: isa.recipe_constraints(), + domtree: domtree, + liveness: liveness, + // TODO: Ask the target ISA about reserved registers etc. + usable_regs: AllocatableSet::new(), + }; + ctx.run(self, func, tracker) + } +} + +impl<'a> Context<'a> { + /// Run the coloring algorithm. + fn run(&mut self, data: &mut Coloring, func: &mut Function, tracker: &mut LiveValueTracker) { + // Just visit blocks in layout order, letting `process_ebb` enforce a topological ordering. + // TODO: Once we have a loop tree, we could visit hot blocks first. + let mut next = func.layout.entry_block(); + while let Some(ebb) = next { + self.process_ebb(ebb, data, func, tracker); + next = func.layout.next_ebb(ebb); + } + } + + /// Process `ebb`, but only after ensuring that the immediate dominator has been processed. + /// + /// This method can be called with the most desired order of visiting the EBBs. It will convert + /// that order into a valid topological order by visiting dominators first. + fn process_ebb(&mut self, + mut ebb: Ebb, + data: &mut Coloring, + func: &mut Function, + tracker: &mut LiveValueTracker) { + // The stack is just a scratch space for this algorithm. We leave it empty when returning. + assert!(data.stack.is_empty()); + + // Trace up the dominator tree until we reach a dominator that has already been visited. + while data.visited.insert(ebb).is_none() { + data.stack.push(ebb); + match self.domtree.idom(ebb) { + Some(idom) => ebb = func.layout.inst_ebb(idom).expect("idom not in layout"), + None => break, + } + } + + // Pop off blocks in topological order. + while let Some(ebb) = data.stack.pop() { + self.visit_ebb(ebb, func, tracker); + } + } + + /// Visit `ebb`, assuming that the immediate dominator has already been visited. + fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + let mut regs = self.visit_ebb_header(ebb, func, tracker); + + // Now go through the instructions in `ebb` and color the values they define. + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(ebb); + while let Some(inst) = pos.next_inst() { + let encoding = func.encodings[inst]; + assert!(encoding.is_legal(), "Illegal: {}", func.dfg[inst].opcode()); + self.visit_inst(inst, + encoding, + &mut pos, + &mut func.dfg, + tracker, + &mut regs, + &mut func.locations); + tracker.drop_dead(inst); + } + + } + + /// Visit the `ebb` header. + /// + /// Initialize the set of live registers and color the arguments to `ebb`. + fn visit_ebb_header(&self, + ebb: Ebb, + func: &mut Function, + tracker: &mut LiveValueTracker) + -> AllocatableSet { + // Reposition the live value tracker and deal with the EBB arguments. + let (liveins, args) = + tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + + // The live-ins have already been assigned a register. Reconstruct the allocatable set. + let mut regs = self.livein_regs(liveins, func); + + // TODO: Arguments to the entry block are pre-colored by the ABI. We should probably call + // a whole other function for that case. + self.color_args(args, &mut regs, &mut func.locations); + + regs + } + + /// Initialize a set of allocatable registers from the values that are live-in to a block. + /// These values must already be colored when the dominating blocks were processed. + fn livein_regs(&self, liveins: &[LiveValue], func: &Function) -> AllocatableSet { + // Start from the registers that are actually usable. We don't want to include any reserved + // registers in the set. + let mut regs = self.usable_regs.clone(); + + for lv in liveins { + let value = lv.value; + let affinity = self.liveness.get(value).expect("No live range for live-in").affinity; + if let Affinity::Reg(rc_index) = affinity { + let regclass = self.reginfo.rc(rc_index); + match func.locations[value] { + ValueLoc::Reg(regunit) => regs.take(regclass, regunit), + ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value), + ValueLoc::Stack(ss) => { + panic!("Live-in {} is in {}, should be register", value, ss) + } + } + } + } + + regs + } + + /// Color the live arguments to the current block. + /// + /// It is assumed that any live-in register values have already been taken out of the register + /// set. + fn color_args(&self, + args: &[LiveValue], + regs: &mut AllocatableSet, + locations: &mut EntityMap) { + for lv in args { + // Only look at the register arguments. + if let Affinity::Reg(rc_index) = lv.affinity { + let regclass = self.reginfo.rc(rc_index); + // TODO: Fall back to a top-level super-class. Sub-classes are only hints. + let regunit = regs.iter(regclass).next().expect("Out of registers for arguments"); + regs.take(regclass, regunit); + *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + } + } + } + + /// Color the values defined by `inst` and insert any necessary shuffle code to satisfy + /// instruction constraints. + /// + /// Update `regs` to reflect the allocated registers after `inst`, including removing any dead + /// or killed values from the set. + fn visit_inst(&self, + inst: Inst, + encoding: Encoding, + _pos: &mut Cursor, + dfg: &mut DataFlowGraph, + tracker: &mut LiveValueTracker, + regs: &mut AllocatableSet, + locations: &mut EntityMap) { + // First update the live value tracker with this instruction. + // Get lists of values that are killed and defined by `inst`. + let (kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + + // Get the operand constraints for `inst` that we are trying to satisfy. + let constraints = self.recipe_constraints[encoding.recipe()].clone(); + + // Get rid of the killed values. + for lv in kills { + if let Affinity::Reg(rc_index) = lv.affinity { + let regclass = self.reginfo.rc(rc_index); + if let ValueLoc::Reg(regunit) = locations[lv.value] { + regs.free(regclass, regunit); + } + } + } + + // Process the defined values with fixed constraints. + // TODO: Handle constraints on call return values. + assert_eq!(defs.len(), + constraints.outs.len(), + "Can't handle variable results"); + for (lv, opcst) in defs.iter().zip(constraints.outs) { + match lv.affinity { + // This value should go in a register. + Affinity::Reg(rc_index) => { + // The preferred register class is not a requirement. + let pref_rc = self.reginfo.rc(rc_index); + match opcst.kind { + ConstraintKind::Reg => { + // This is a standard register constraint. The preferred register class + // should have been computed as a subclass of the hard constraint of + // the def. + assert!(opcst.regclass.has_subclass(rc_index), + "{} preference {} is not compatible with the definition \ + constraint {}", + lv.value, + pref_rc.name, + opcst.regclass.name); + // Try to grab a register from the preferred class, but fall back to + // the actual constraint if we have to. + let regunit = regs.iter(pref_rc) + .next() + .or_else(|| regs.iter(opcst.regclass).next()) + .expect("Ran out of registers"); + regs.take(opcst.regclass, regunit); + *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + } + ConstraintKind::Tied(arg_index) => { + // This def must use the same register as a fixed instruction argument. + let loc = locations[dfg[inst].arguments()[0][arg_index as usize]]; + *locations.ensure(lv.value) = loc; + // Mark the reused register. It's not really clear if we support tied + // stack operands. We could do that for some Intel read-modify-write + // encodings. + if let ValueLoc::Reg(regunit) = loc { + // This is going to assert out unless the incoming value at + // `arg_index` was killed. Tied operands must be fixed to + // ensure that before running the coloring pass. + regs.take(opcst.regclass, regunit); + } + } + ConstraintKind::FixedReg(_regunit) => unimplemented!(), + ConstraintKind::Stack => { + panic!("{}:{} should be a stack value", lv.value, pref_rc.name) + } + } + } + Affinity::Stack => unimplemented!(), + Affinity::Any => unimplemented!(), + } + } + + // Get rid of the dead defs. + for lv in defs { + if lv.endpoint == inst { + if let Affinity::Reg(rc_index) = lv.affinity { + let regclass = self.reginfo.rc(rc_index); + if let ValueLoc::Reg(regunit) = locations[lv.value] { + regs.free(regclass, regunit); + } + } + } + } + } +} diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 902e7aabb0..19bb350499 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -6,5 +6,6 @@ pub mod liverange; pub mod liveness; pub mod allocatable_set; pub mod live_value_tracker; +pub mod coloring; mod affinity; From 83571028d951ef1ef6cafa500e15fd90147e250f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Feb 2017 10:55:47 -0800 Subject: [PATCH 528/968] Add a register allocation context module. Collect the data structures that hang around between function compilations. Provide a main entry point to the register allocator passes. --- lib/cretonne/src/context.rs | 22 +++++++++++ lib/cretonne/src/regalloc/context.rs | 57 +++++++++++++++++++++++++++ lib/cretonne/src/regalloc/liveness.rs | 2 +- lib/cretonne/src/regalloc/mod.rs | 3 ++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 lib/cretonne/src/regalloc/context.rs diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 31a1569d5f..ce1e8b50ff 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -3,10 +3,18 @@ //! When compiling many small functions, it is important to avoid repeatedly allocating and //! deallocating the data structures needed for compilation. The `Context` struct is used to hold //! on to memory allocations between function compilations. +//! +//! The context does not hold a `TargetIsa` instance which has to be provided as an argument +//! instead. This is because an ISA instance is immutable and can be used by multiple compilation +//! contexts concurrently. Typically, you would have one context per compilation thread and only a +//! single ISA instance. use cfg::ControlFlowGraph; use dominator_tree::DominatorTree; use ir::Function; +use isa::TargetIsa; +use legalize_function; +use regalloc; /// Persistent data structures and compilation pipeline. pub struct Context { @@ -18,6 +26,9 @@ pub struct Context { /// Dominator tree for `func`. pub domtree: DominatorTree, + + /// Register allocation context. + pub regalloc: regalloc::Context, } impl Context { @@ -30,12 +41,23 @@ impl Context { func: Function::new(), cfg: ControlFlowGraph::new(), domtree: DominatorTree::new(), + regalloc: regalloc::Context::new(), } } + /// Run the legalizer for `isa` on the function. + pub fn legalize(&mut self, isa: &TargetIsa) { + legalize_function(&mut self.func, isa); + } + /// Recompute the control flow graph and dominator tree. pub fn flowgraph(&mut self) { self.cfg.compute(&self.func); self.domtree.compute(&self.func, &self.cfg); } + + /// Run the register allocator. + pub fn regalloc(&mut self, isa: &TargetIsa) { + self.regalloc.run(isa, &mut self.func, &self.cfg, &self.domtree); + } } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs new file mode 100644 index 0000000000..75863fbc63 --- /dev/null +++ b/lib/cretonne/src/regalloc/context.rs @@ -0,0 +1,57 @@ +//! Register allocator context. +//! +//! The `Context` struct contains data structures that should be preserved across invocations of +//! the register allocator algorithm. This doesn't preserve any data between functions, but it +//! avoids allocating data structures independently for each function begin compiled. + +use dominator_tree::DominatorTree; +use ir::Function; +use regalloc::coloring::Coloring; +use regalloc::live_value_tracker::LiveValueTracker; +use regalloc::liveness::Liveness; +use isa::TargetIsa; +use cfg::ControlFlowGraph; + +/// Persistent memory allocations for register allocation. +pub struct Context { + liveness: Liveness, + tracker: LiveValueTracker, + coloring: Coloring, +} + +impl Context { + /// Create a new context for register allocation. + /// + /// This context should be reused for multiple functions in order to avoid repeated memory + /// allocations. + pub fn new() -> Context { + Context { + liveness: Liveness::new(), + tracker: LiveValueTracker::new(), + coloring: Coloring::new(), + } + } + + /// Allocate registers in `func`. + /// + /// After register allocation, all values in `func` have been assigned to a register or stack + /// location that is consistent with instruction encoding constraints. + pub fn run(&mut self, + isa: &TargetIsa, + func: &mut Function, + cfg: &ControlFlowGraph, + domtree: &DominatorTree) { + // `Liveness` and `Coloring` are self-clearing. + // Tracker state (dominator live sets) is actually reused between the spilling and coloring + // phases. + self.tracker.clear(); + + // First pass: Liveness analysis. + self.liveness.compute(isa, func, cfg); + + // TODO: Second pass: Spilling. + + // Third pass: Reload and coloring. + self.coloring.run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + } +} diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index a8591e8597..524b50e8ea 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -291,7 +291,7 @@ impl Liveness { /// Compute the live ranges of all SSA values used in `func`. /// This clears out any existing analysis stored in this data structure. - pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, isa: &TargetIsa) { + pub fn compute(&mut self, isa: &TargetIsa, func: &Function, cfg: &ControlFlowGraph) { self.ranges.clear(); // Get ISA data structures used for computing live range affinities. diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 19bb350499..fb19286670 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -9,3 +9,6 @@ pub mod live_value_tracker; pub mod coloring; mod affinity; +mod context; + +pub use self::context::Context; From ccda0a192c42bc9c1028128b83132d22ffce8166 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 22 Feb 2017 11:41:30 -0800 Subject: [PATCH 529/968] Also write out register assignments in write_instruction. The value locations appear after the encodings: > [R#0c,%x2] v0 = iadd vx0, vx1 > [Iret#19] return_reg v0 --- lib/cretonne/src/write.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index cb5fb8a24f..933ba8822e 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -4,7 +4,7 @@ //! equivalent textual representation. This textual representation can be read back by the //! `cretonne-reader` crate. -use ir::{Function, Ebb, Inst, Value, Type}; +use ir::{Function, Ebb, Inst, Value, Type, ValueLoc}; use isa::TargetIsa; use std::fmt::{Result, Error, Write}; use std::result; @@ -172,7 +172,19 @@ fn write_instruction(w: &mut Write, if let Some(enc) = func.encodings.get(inst).cloned() { let mut s = String::with_capacity(16); if let Some(isa) = isa { - try!(write!(s, "[{}]", isa.display_enc(enc))); + try!(write!(s, "[{}", isa.display_enc(enc))); + // Write value locations, if we have them. + if !func.locations.is_empty() { + let regs = isa.register_info(); + for r in func.dfg.inst_results(inst) { + match func.locations[r] { + ValueLoc::Unassigned => write!(s, ",-")?, + ValueLoc::Reg(ru) => write!(s, ",{}", regs.display_regunit(ru))?, + ValueLoc::Stack(ss) => write!(s, ",{}", ss)?, + } + } + } + try!(write!(s, "]")); } else { try!(write!(s, "[{}]", enc)); } From 4ba5cfeed3cfe447106c7c2f0f934cda25a9c9bf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Feb 2017 17:07:08 -0800 Subject: [PATCH 530/968] Add a 'regalloc' filetest command. Run functions through the register allocator, and then filecheck. --- docs/testing.rst | 14 +++++++++ filetests/regalloc/basic.cton | 12 ++++++++ src/filetest/mod.rs | 11 ++++--- src/filetest/regalloc.rs | 55 +++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 filetests/regalloc/basic.cton create mode 100644 src/filetest/regalloc.rs diff --git a/docs/testing.rst b/docs/testing.rst index a452dc1b58..db027f8fbe 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -300,3 +300,17 @@ Legalize each function for the specified target ISA and run the resulting function through filecheck. This test command can be used to validate the encodings selected for legal instructions as well as the instruction transformations performed by the legalizer. + +`test regalloc` +--------------- + +Test the register allocator. + +First, each function is legalized for the specified target ISA. This is +required for register allocation since the instruction encodings provide +register class constraints to the register allocator. + +Second, the register allocator is run on the function, inserting spill code and +assigning registers and stack slots to all values. + +The resulting function is then run through filecheck. diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton new file mode 100644 index 0000000000..00e204f9b1 --- /dev/null +++ b/filetests/regalloc/basic.cton @@ -0,0 +1,12 @@ +test regalloc + +; We can add more ISAs once they have defined encodings. +isa riscv + +function add(i32, i32) { +ebb0(v1: i32, v2: i32): + v3 = iadd v1, v2 +; check: [R#0c,%x0] +; sameln: iadd + return_reg v3 +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 28a9b94d45..41b89948a1 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -12,12 +12,14 @@ use print_cfg; use filetest::runner::TestRunner; pub mod subtest; -mod runner; -mod runone; + mod concurrent; mod domtree; -mod verifier; mod legalizer; +mod regalloc; +mod runner; +mod runone; +mod verifier; /// The result of running the test in a file. pub type TestResult = Result; @@ -49,7 +51,7 @@ pub fn run(verbose: bool, files: Vec) -> CommandResult { /// Create a new subcommand trait object to match `parsed.command`. /// /// This function knows how to create all of the possible `test ` commands that can appear in -/// a .cton test file. +/// a `.cton` test file. fn new_subtest(parsed: &TestCommand) -> subtest::Result> { match parsed.command { "cat" => cat::subtest(parsed), @@ -57,6 +59,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "domtree" => domtree::subtest(parsed), "verifier" => verifier::subtest(parsed), "legalizer" => legalizer::subtest(parsed), + "regalloc" => regalloc::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs new file mode 100644 index 0000000000..af9dbb2b23 --- /dev/null +++ b/src/filetest/regalloc.rs @@ -0,0 +1,55 @@ +//! Test command for testing the register allocator. +//! +//! The `regalloc` test command runs each function through the register allocator after ensuring +//! that all instructions are legal for the target. +//! +//! The resulting function is sent to `filecheck`. + +use std::borrow::Cow; +use cretonne::{self, write_function}; +use cretonne::ir::Function; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result, run_filecheck}; + +struct TestRegalloc; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "regalloc"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestRegalloc)) + } +} + +impl SubTest for TestRegalloc { + fn name(&self) -> Cow { + Cow::from("regalloc") + } + + fn is_mutating(&self) -> bool { + true + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let isa = context.isa.expect("register allocator needs an ISA"); + + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + // TODO: Should we have an option to skip legalization? + comp_ctx.legalize(isa); + + comp_ctx.flowgraph(); + comp_ctx.regalloc(isa); + + let mut text = String::new(); + try!(write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())); + run_filecheck(&text, context) + } +} From 0b310017b703d3369fdbf32bd6114e76e078afa4 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 23 Feb 2017 00:28:19 +0000 Subject: [PATCH 531/968] Lexer can now scan names, hex sequences, brackets and minus signs. --- lib/reader/src/lexer.rs | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 04801b2644..7e33e8ecd0 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -22,6 +22,9 @@ pub enum Token<'a> { RPar, // ')' LBrace, // '{' RBrace, // '}' + LBracket, // '[' + RBracket, // ']' + Minus, // '-' Comma, // ',' Dot, // '.' Colon, // ':' @@ -36,6 +39,8 @@ pub enum Token<'a> { JumpTable(u32), // jt2 FuncRef(u32), // fn2 SigRef(u32), // sig2 + Name(&'a str), // %9arbitrary_alphanum, %x3, %0, %function ... + HexSequence(&'a str), // #89AF Identifier(&'a str), // Unrecognized identifier (opcode, enumerator, ...) } @@ -222,6 +227,13 @@ impl<'a> Lexer<'a> { // Skip a leading sign. if self.lookahead == Some('-') { self.next_ch(); + + if let Some(c) = self.lookahead { + // If the next character won't parse as a number, we conservatively return Token::Minus + if !c.is_alphanumeric() && c != '.' { + return token(Token::Minus, loc); + } + } } // Check for NaNs with payloads. @@ -326,6 +338,39 @@ impl<'a> Lexer<'a> { } } + fn scan_name(&mut self) -> Result, LocatedError> { + let loc = self.loc(); + let begin = self.pos; + + assert!(self.lookahead == Some('%')); + + while let Some(c) = self.next_ch() { + if !c.is_alphanumeric() && c != '_' { + break; + } + } + + let end = self.pos; + token(Token::Name(&self.source[begin..end]), loc) + } + + fn scan_hex_sequence(&mut self) -> Result, LocatedError> { + let loc = self.loc(); + let begin = self.pos; + + assert!(self.lookahead == Some('#')); + + while let Some(c) = self.next_ch() { + match c { + 'a'...'f' | 'A'...'F' | '0'...'9' => {}, + _ => break, + } + } + + let end = self.pos; + token(Token::HexSequence(&self.source[begin..end]), loc) + } + /// Get the next token or a lexical error. /// /// Return None when the end of the source is encountered. @@ -339,6 +384,8 @@ impl<'a> Lexer<'a> { Some(')') => Some(self.scan_char(Token::RPar)), Some('{') => Some(self.scan_char(Token::LBrace)), Some('}') => Some(self.scan_char(Token::RBrace)), + Some('[') => Some(self.scan_char(Token::LBracket)), + Some(']') => Some(self.scan_char(Token::RBracket)), Some(',') => Some(self.scan_char(Token::Comma)), Some('.') => Some(self.scan_char(Token::Dot)), Some(':') => Some(self.scan_char(Token::Colon)), @@ -352,6 +399,8 @@ impl<'a> Lexer<'a> { } Some(ch) if ch.is_digit(10) => Some(self.scan_number()), Some(ch) if ch.is_alphabetic() => Some(self.scan_word()), + Some('%') => Some(self.scan_name()), + Some('#') => Some(self.scan_hex_sequence()), Some(ch) if ch.is_whitespace() => { self.next_ch(); continue; From a35d946bc495b8f0f86cbbf3d90003ac55d1ff20 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 23 Feb 2017 01:07:25 +0000 Subject: [PATCH 532/968] Added tests, some fixes. --- lib/reader/src/lexer.rs | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 7e33e8ecd0..474b8d04e0 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -7,6 +7,7 @@ use std::str::CharIndices; use std::u16; +use std::ascii::AsciiExt; use cretonne::ir::types; use cretonne::ir::{Value, Ebb}; use error::Location; @@ -340,12 +341,12 @@ impl<'a> Lexer<'a> { fn scan_name(&mut self) -> Result, LocatedError> { let loc = self.loc(); - let begin = self.pos; + let begin = self.pos + 1; assert!(self.lookahead == Some('%')); while let Some(c) = self.next_ch() { - if !c.is_alphanumeric() && c != '_' { + if !(c.is_ascii() && c.is_alphanumeric() || c == '_') { break; } } @@ -356,14 +357,13 @@ impl<'a> Lexer<'a> { fn scan_hex_sequence(&mut self) -> Result, LocatedError> { let loc = self.loc(); - let begin = self.pos; + let begin = self.pos + 1; assert!(self.lookahead == Some('#')); while let Some(c) = self.next_ch() { - match c { - 'a'...'f' | 'A'...'F' | '0'...'9' => {}, - _ => break, + if !char::is_digit(c, 16) { + break; } } @@ -478,7 +478,7 @@ mod tests { assert_eq!(lex.next(), None); // Scan a comment after an invalid char. - let mut lex = Lexer::new("#; hello"); + let mut lex = Lexer::new("$; hello"); assert_eq!(lex.next(), error(Error::InvalidChar, 1)); assert_eq!(lex.next(), token(Token::Comment("; hello"), 1)); assert_eq!(lex.next(), None); @@ -535,4 +535,27 @@ mod tests { assert_eq!(lex.next(), token(Token::Identifier("f32x5"), 1)); assert_eq!(lex.next(), None); } + + #[test] + fn lex_hex_sequences() { + let mut lex = Lexer::new("#0 #DEADbeef123 #789"); + + assert_eq!(lex.next(), token(Token::HexSequence("0"), 1)); + assert_eq!(lex.next(), token(Token::HexSequence("DEADbeef123"), 1)); + assert_eq!(lex.next(), token(Token::HexSequence("789"), 1)); + } + + #[test] + fn lex_names() { + let mut lex = Lexer::new("%0 %x3 %function %123_abc %ss0 %v3 %ebb11 %_"); + + assert_eq!(lex.next(), token(Token::Name("0"), 1)); + assert_eq!(lex.next(), token(Token::Name("x3"), 1)); + assert_eq!(lex.next(), token(Token::Name("function"), 1)); + assert_eq!(lex.next(), token(Token::Name("123_abc"), 1)); + assert_eq!(lex.next(), token(Token::Name("ss0"), 1)); + assert_eq!(lex.next(), token(Token::Name("v3"), 1)); + assert_eq!(lex.next(), token(Token::Name("ebb11"), 1)); + assert_eq!(lex.next(), token(Token::Name("_"), 1)); + } } From bf26fffc924fdff0a845dc7fe7c72c451dafc24f Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 23 Feb 2017 01:29:32 +0000 Subject: [PATCH 533/968] Shortened comment to pass lint. --- lib/reader/src/lexer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 474b8d04e0..caf32ed252 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -230,7 +230,7 @@ impl<'a> Lexer<'a> { self.next_ch(); if let Some(c) = self.lookahead { - // If the next character won't parse as a number, we conservatively return Token::Minus + // If the next character won't parse as a number, we return Token::Minus if !c.is_alphanumeric() && c != '.' { return token(Token::Minus, loc); } From afd42cf9c2a338a7718a5a03440539642f52327c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 24 Feb 2017 10:33:08 -0800 Subject: [PATCH 534/968] Convert try! to ? in extfunc.rs --- lib/cretonne/src/ir/extfunc.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index a47ff4ad7a..ae012e7530 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -34,9 +34,9 @@ fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { match args.split_first() { None => {} Some((first, rest)) => { - try!(write!(f, "{}", first)); + write!(f, "{}", first)?; for arg in rest { - try!(write!(f, ", {}", arg)); + write!(f, ", {}", arg)?; } } } @@ -45,12 +45,12 @@ fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { impl Display for Signature { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "(")); - try!(write_list(f, &self.argument_types)); - try!(write!(f, ")")); + write!(f, "(")?; + write_list(f, &self.argument_types)?; + write!(f, ")")?; if !self.return_types.is_empty() { - try!(write!(f, " -> ")); - try!(write_list(f, &self.return_types)); + write!(f, " -> ")?; + write_list(f, &self.return_types)?; } Ok(()) } @@ -83,14 +83,14 @@ impl ArgumentType { impl Display for ArgumentType { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "{}", self.value_type)); + write!(f, "{}", self.value_type)?; match self.extension { ArgumentExtension::None => {} - ArgumentExtension::Uext => try!(write!(f, " uext")), - ArgumentExtension::Sext => try!(write!(f, " sext")), + ArgumentExtension::Uext => write!(f, " uext")?, + ArgumentExtension::Sext => write!(f, " sext")?, } if self.inreg { - try!(write!(f, " inreg")); + write!(f, " inreg")?; } Ok(()) } From b51cf57e3982d9c204e5fe2b59a3eeb28205ea9a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 24 Feb 2017 11:04:31 -0800 Subject: [PATCH 535/968] Add a section about implementation limits. Fix a few other minor issues with the documentation. --- docs/Makefile | 2 +- docs/langref.rst | 43 +++++++++++++++++++++++-- lib/cretonne/meta/isa/arm32/__init__.py | 2 +- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index b2443f68b7..8cec8c36e6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -58,7 +58,7 @@ html: @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." autohtml: html - $(SPHINXABUILD) -z ../lib/cretonne/meta --ignore '*.swp' -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXABUILD) -z ../lib/cretonne/meta --ignore '.*.sw?' -b html -E $(ALLSPHINXOPTS) $(BUILDDIR)/html dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml diff --git a/docs/langref.rst b/docs/langref.rst index d7bffd870a..5e6d999b09 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -80,7 +80,7 @@ into Cretonne :term:`IL` contains multiple assignments to the same variables. Such variables can be presented to Cretonne as :term:`stack slot`\s instead. Stack slots are accessed with the :inst:`stack_store` and :inst:`stack_load` instructions which behave more like variable accesses in a typical programming -language. Cretonne can perform the necessary dataflow analysis to convert stack +language. Cretonne can perform the necessary data-flow analysis to convert stack slots to SSA form. .. _value-types: @@ -459,7 +459,7 @@ accesses may trap, or they may work. Sometimes, operating systems catch alignment traps and emulate the misaligned memory access. On target architectures like x86 that don't check alignment, Cretonne expands -the aligntrap flag into a conditional trap instruction:: +the `aligntrap` flag into a conditional trap instruction:: v5 = load.i32 v1, 4, align(4), aligntrap ; Becomes: @@ -854,6 +854,45 @@ group. Target ISAs may define further instructions in their own instruction groups. +Implementation limits +===================== + +Cretonne's intermediate representation imposes some limits on the size of +functions and the number of entities allowed. If these limits are exceeded, the +implementation will panic. + +Number of instructions in a function + At most :math:`2^{31} - 1`. + +Number of EBBs in a function + At most :math:`2^{31} - 1`. + + Every EBB needs at least a terminator instruction anyway. + +Number of secondary values in a function + At most :math:`2^{31} - 1`. + + Secondary values are any SSA values that are not the first result of an + instruction. + +Other entities declared in the preamble + At most :math:`2^{32} - 1`. + + This covers things like stack slots, jump tables, external functions, and + function signatures, etc. + +Number of arguments to an EBB + At most :math:`2^{16}`. + +Number of arguments to a function + At most :math:`2^{16}`. + + This follows from the limit on arguments to the entry EBB. Note that + Cretonne may add a handful of ABI register arguments as function signatures + are lowered. This is for representing things like the link register, the + incoming frame pointer, and callee-saved registers that are saved in the + prologue. + Glossary ======== diff --git a/lib/cretonne/meta/isa/arm32/__init__.py b/lib/cretonne/meta/isa/arm32/__init__.py index d2de00667a..9e0ae5a7e1 100644 --- a/lib/cretonne/meta/isa/arm32/__init__.py +++ b/lib/cretonne/meta/isa/arm32/__init__.py @@ -1,6 +1,6 @@ """ ARM 32-bit Architecture ----------------------- +----------------------- This target ISA generates code for ARMv7 and ARMv8 CPUs in 32-bit mode (AArch32). We support both ARM and Thumb2 instruction encodings. From 15e0822ac393fdfa5e55de77695e3eaad640e0fe Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 24 Feb 2017 12:04:46 -0800 Subject: [PATCH 536/968] Add an ArgumentLoc data type. This will be used to amend function signatures with ABI lowering information. --- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/ir/valueloc.rs | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index f4b2a337b4..c14473e9f5 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -23,7 +23,7 @@ pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; -pub use ir::valueloc::ValueLoc; +pub use ir::valueloc::{ValueLoc, ArgumentLoc}; pub use ir::dfg::{DataFlowGraph, ValueDef}; pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index 3526c58190..ea7dea39d9 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -22,3 +22,37 @@ impl Default for ValueLoc { ValueLoc::Unassigned } } + +/// Function argument location. +/// +/// The ABI specifies how arguments are passed to a function, and where return values appear after +/// the call. Just like a `ValueLoc`, function arguments can be passed in registers or on the +/// stack. +/// +/// Function arguments on the stack are accessed differently for the incoming arguments to the +/// current function and the outgoing arguments to a called external function. For this reason, +/// the location of stack arguments is described as an offset into the array of function arguments +/// on the stack. +/// +/// An `ArgumentLoc` can be translated to a `ValueLoc` only when we know if we're talking about an +/// incoming argument or an outgoing argument. +/// +/// - For stack arguments, different `StackSlot` entities are used to represent incoming and +/// outgoing arguments. +/// - For register arguments, there is usually no difference, but if we ever add support for a +/// register-window ISA like SPARC, register arguments would also need to be translated. +#[derive(Copy, Clone, Debug)] +pub enum ArgumentLoc { + /// This argument has not been assigned to a location yet. + Unassigned, + /// Argument is passed in a register. + Reg(RegUnit), + /// Argument is passed on the stack, at the given byte offset into the argument array. + Stack(u32), +} + +impl Default for ArgumentLoc { + fn default() -> Self { + ArgumentLoc::Unassigned + } +} From c8be39fa9d06ccf7fb5765c125c16672cebce992 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 24 Feb 2017 13:43:04 -0800 Subject: [PATCH 537/968] Add ABI annotations to function signatures. Specify the location of arguments as well as the size of stack argument array needed. The ABI annotations are optional, just like the value locations. Remove the Eq implementation for Signature which was only used by a single parser test. --- docs/langref.rst | 5 +++ lib/cretonne/src/ir/extfunc.rs | 60 +++++++++++++++++++++++++++++++--- lib/reader/src/parser.rs | 11 +++---- 3 files changed, 65 insertions(+), 11 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 5e6d999b09..23cc225d7f 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -893,6 +893,11 @@ Number of arguments to a function incoming frame pointer, and callee-saved registers that are saved in the prologue. +Size of function call arguments on the stack + At most :math:`2^{32} - 1` bytes. + + This is probably not possible to achieve given the limit on the number of + arguments, except by requiring extremely large offsets for stack arguments. Glossary ======== diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index ae012e7530..5c4d1d1c71 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -5,19 +5,30 @@ //! //! This module declares the data types used to represent external functions and call signatures. +use ir::{Type, FunctionName, SigRef, ArgumentLoc}; +use std::cmp; use std::fmt::{self, Display, Formatter}; -use ir::{Type, FunctionName, SigRef}; /// Function signature. /// /// The function signature describes the types of arguments and return values along with other /// details that are needed to call a function correctly. -#[derive(Clone, PartialEq, Eq, Debug)] +/// +/// A signature can optionally include ISA-specific ABI information which specifies exactly how +/// arguments and return values are passed. +#[derive(Clone, Debug)] pub struct Signature { /// Types of the arguments passed to the function. pub argument_types: Vec, /// Types returned from the function. pub return_types: Vec, + + /// When the signature has been legalized to a specific ISA, this holds the size of the + /// argument array on the stack. Before legalization, this is `None`. + /// + /// This can be computed from the legalized `argument_types` array as the maximum (offset plus + /// byte size) of the `ArgumentLoc::Stack(offset)` argument. + pub argument_bytes: Option, } impl Signature { @@ -26,8 +37,24 @@ impl Signature { Signature { argument_types: Vec::new(), return_types: Vec::new(), + argument_bytes: None, } } + + /// Compute the size of the stack arguments and mark signature as legalized. + /// + /// Even if there are no stack arguments, this will set `argument_types` to `Some(0)` instead + /// of `None`. This indicates that the signature has been legalized. + pub fn compute_argument_bytes(&mut self) { + let bytes = self.argument_types + .iter() + .filter_map(|arg| match arg.location { + ArgumentLoc::Stack(offset) => Some(offset + arg.value_type.bits() as u32 / 8), + _ => None, + }) + .fold(0, cmp::max); + self.argument_bytes = Some(bytes); + } } fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { @@ -60,7 +87,7 @@ impl Display for Signature { /// /// This describes the value type being passed to or from a function along with flags that affect /// how the argument is passed. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, Debug)] pub struct ArgumentType { /// Type of the argument value. pub value_type: Type, @@ -68,6 +95,10 @@ pub struct ArgumentType { pub extension: ArgumentExtension, /// Place this argument in a register if possible. pub inreg: bool, + + /// ABI-specific location of this argument, or `Unassigned` for arguments that have not yet + /// been legalized. + pub location: ArgumentLoc, } impl ArgumentType { @@ -77,6 +108,7 @@ impl ArgumentType { value_type: vt, extension: ArgumentExtension::None, inreg: false, + location: Default::default(), } } } @@ -92,7 +124,13 @@ impl Display for ArgumentType { if self.inreg { write!(f, " inreg")?; } - Ok(()) + + // This really needs a `&TargetAbi` so we can print register units correctly. + match self.location { + ArgumentLoc::Reg(ru) => write!(f, " [%{}]", ru), + ArgumentLoc::Stack(offset) => write!(f, " [{}]", offset), + ArgumentLoc::Unassigned => Ok(()), + } } } @@ -154,5 +192,19 @@ mod tests { assert_eq!(sig.to_string(), "(i32, i32x4) -> f32"); sig.return_types.push(ArgumentType::new(B8)); assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8"); + + // Test the offset computation algorithm. + assert_eq!(sig.argument_bytes, None); + sig.argument_types[1].location = ArgumentLoc::Stack(8); + sig.compute_argument_bytes(); + // An `i32x4` at offset 8 requires a 24-byte argument array. + assert_eq!(sig.argument_bytes, Some(24)); + // Order does not matter. + sig.argument_types[0].location = ArgumentLoc::Stack(24); + sig.compute_argument_bytes(); + assert_eq!(sig.argument_bytes, Some(28)); + + // Writing ABI-annotated signatures. + assert_eq!(sig.to_string(), "(i32 [24], i32x4 [8]) -> f32, b8"); } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 204b78c9e5..47ff73a89c 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1360,7 +1360,7 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::ir::{ArgumentType, ArgumentExtension}; + use cretonne::ir::ArgumentExtension; use cretonne::ir::types; use cretonne::ir::entities::AnyEntity; use testfile::{Details, Comment}; @@ -1371,12 +1371,9 @@ mod tests { fn argument_type() { let mut p = Parser::new("i32 sext"); let arg = p.parse_argument_type().unwrap(); - assert_eq!(arg, - ArgumentType { - value_type: types::I32, - extension: ArgumentExtension::Sext, - inreg: false, - }); + assert_eq!(arg.value_type, types::I32); + assert_eq!(arg.extension, ArgumentExtension::Sext); + assert_eq!(arg.inreg, false); let Error { location, message } = p.parse_argument_type().unwrap_err(); assert_eq!(location.line_number, 1); assert_eq!(message, "expected argument type"); From 7459fee71a85e3cb12507b451308136c03e363b5 Mon Sep 17 00:00:00 2001 From: rep-nop Date: Sat, 25 Feb 2017 22:12:33 -0500 Subject: [PATCH 538/968] Converts all try! macros to ? syntax. Fixes #46 --- lib/cretonne/meta/gen_settings.py | 10 +- lib/cretonne/src/ir/funcname.rs | 4 +- lib/cretonne/src/ir/immediates.rs | 6 +- lib/cretonne/src/ir/instructions.rs | 12 +- lib/cretonne/src/ir/jumptable.rs | 8 +- lib/cretonne/src/settings.rs | 12 +- lib/cretonne/src/verifier.rs | 4 +- lib/cretonne/src/write.rs | 56 +++--- lib/filecheck/src/checker.rs | 16 +- lib/filecheck/src/explain.rs | 18 +- lib/filecheck/src/pattern.rs | 10 +- lib/reader/src/parser.rs | 296 ++++++++++++++-------------- lib/reader/src/sourcemap.rs | 2 +- lib/reader/src/testcommand.rs | 4 +- src/cat.rs | 6 +- src/filetest/legalizer.rs | 2 +- src/filetest/regalloc.rs | 2 +- src/filetest/runone.rs | 14 +- src/filetest/subtest.rs | 12 +- src/print_cfg.rs | 26 +-- src/rsfilecheck.rs | 16 +- src/utils.rs | 4 +- 22 files changed, 270 insertions(+), 270 deletions(-) diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 3f435c8fc3..6429333d28 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -171,13 +171,13 @@ def gen_display(sgrp, fmt): with fmt.indented( 'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {', '}'): - fmt.line('try!(writeln!(f, "[{}]"));'.format(sgrp.name)) + fmt.line('writeln!(f, "[{}]")?;'.format(sgrp.name)) with fmt.indented('for d in &DESCRIPTORS {', '}'): - fmt.line('try!(write!(f, "{} = ", d.name));') + fmt.line('write!(f, "{} = ", d.name)?;') fmt.line( - 'try!(TEMPLATE.format_toml_value(d.detail,' + - 'self.bytes[d.offset as usize], f));') - fmt.line('try!(writeln!(f, ""));') + 'TEMPLATE.format_toml_value(d.detail,' + + 'self.bytes[d.offset as usize], f)?;') + fmt.line('writeln!(f, "")?;') fmt.line('Ok(())') diff --git a/lib/cretonne/src/ir/funcname.rs b/lib/cretonne/src/ir/funcname.rs index d84b25ac2b..0074c97502 100644 --- a/lib/cretonne/src/ir/funcname.rs +++ b/lib/cretonne/src/ir/funcname.rs @@ -42,9 +42,9 @@ fn needs_quotes(name: &str) -> bool { impl fmt::Display for FunctionName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if needs_quotes(&self.0) { - try!(f.write_char('"')); + f.write_char('"')?; for c in self.0.chars().flat_map(char::escape_default) { - try!(f.write_char(c)); + f.write_char(c)?; } f.write_char('"') } else { diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index bc44e6c022..5673af1163 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -49,10 +49,10 @@ impl Display for Imm64 { // 0xffff_ffff_fff8_4400 // let mut pos = (64 - x.leading_zeros() - 1) & 0xf0; - try!(write!(f, "0x{:04x}", (x >> pos) & 0xffff)); + write!(f, "0x{:04x}", (x >> pos) & 0xffff)?; while pos > 0 { pos -= 16; - try!(write!(f, "_{:04x}", (x >> pos) & 0xffff)); + write!(f, "_{:04x}", (x >> pos) & 0xffff)?; } Ok(()) } @@ -178,7 +178,7 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { // All formats share the leading sign. if sign_bit != 0 { - try!(write!(f, "-")); + write!(f, "-")?; } if e_bits == 0 { diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 839219fc2a..c4ac3b0c20 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -265,9 +265,9 @@ impl Display for VariableArgs { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { for (i, val) in self.0.iter().enumerate() { if i == 0 { - try!(write!(fmt, "{}", val)); + write!(fmt, "{}", val)?; } else { - try!(write!(fmt, ", {}", val)); + write!(fmt, ", {}", val)?; } } Ok(()) @@ -289,9 +289,9 @@ pub struct UnaryImmVectorData { impl Display for UnaryImmVectorData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "#")); + write!(f, "#")?; for b in &self.imm { - try!(write!(f, "{:02x}", b)); + write!(f, "{:02x}", b)?; } Ok(()) } @@ -356,9 +356,9 @@ impl BranchData { impl Display for BranchData { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "{}, {}", self.arg, self.destination)); + write!(f, "{}, {}", self.arg, self.destination)?; if !self.varargs.is_empty() { - try!(write!(f, "({})", self.varargs)); + write!(f, "({})", self.varargs)?; } Ok(()) } diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index 7103485094..29da4456bc 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -97,14 +97,14 @@ impl<'a> Iterator for Entries<'a> { impl Display for JumpTableData { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { match self.table.first().and_then(|e| e.expand()) { - None => try!(write!(fmt, "jump_table 0")), - Some(first) => try!(write!(fmt, "jump_table {}", first)), + None => write!(fmt, "jump_table 0")?, + Some(first) => write!(fmt, "jump_table {}", first)?, } for dest in self.table.iter().skip(1).map(|e| e.expand()) { match dest { - None => try!(write!(fmt, ", 0")), - Some(ebb) => try!(write!(fmt, ", {}", ebb)), + None => write!(fmt, ", 0")?, + Some(ebb) => write!(fmt, ", {}", ebb)?, } } Ok(()) diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 30ecff68e4..0d81c38dce 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -103,7 +103,7 @@ fn parse_enum_value(value: &str, choices: &[&str]) -> Result { impl Configurable for Builder { fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { use self::detail::Detail; - let (offset, detail) = try!(self.lookup(name)); + let (offset, detail) = self.lookup(name)?; if let Detail::Bool { bit } = detail { self.set_bit(offset, bit, value); Ok(()) @@ -114,17 +114,17 @@ impl Configurable for Builder { fn set(&mut self, name: &str, value: &str) -> Result<()> { use self::detail::Detail; - let (offset, detail) = try!(self.lookup(name)); + let (offset, detail) = self.lookup(name)?; match detail { Detail::Bool { bit } => { - self.set_bit(offset, bit, try!(parse_bool_value(value))); + self.set_bit(offset, bit, parse_bool_value(value))?; } Detail::Num => { - self.bytes[offset] = try!(value.parse().map_err(|_| Error::BadValue)); + self.bytes[offset] = value.parse().map_err(|_| Error::BadValue)?; } Detail::Enum { last, enumerators } => { - self.bytes[offset] = try!(parse_enum_value(value, - self.template.enums(last, enumerators))); + self.bytes[offset] = parse_enum_value(value, + self.template.enums(last, enumerators))?; } } Ok(()) diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index ea5941006e..2610475759 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -159,8 +159,8 @@ impl<'a> Verifier<'a> { pub fn run(&self) -> Result<()> { for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { - try!(self.ebb_integrity(ebb, inst)); - try!(self.instruction_integrity(inst)); + self.ebb_integrity(ebb, inst)?; + self.instruction_integrity(inst)?; } } Ok(()) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 933ba8822e..eca9909961 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -12,14 +12,14 @@ use std::result; /// Write `func` to `w` as equivalent text. /// Use `isa` to emit ISA-dependent annotations. pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) -> Result { - try!(write_spec(w, func)); - try!(writeln!(w, " {{")); - let mut any = try!(write_preamble(w, func)); + write_spec(w, func)?; + writeln!(w, " {{")?; + let mut any = write_preamble(w, func)?; for ebb in &func.layout { if any { - try!(writeln!(w, "")); + writeln!(w, "")?; } - try!(write_ebb(w, func, isa, ebb)); + write_ebb(w, func, isa, ebb)?; any = true; } writeln!(w, "}}") @@ -40,24 +40,24 @@ fn write_preamble(w: &mut Write, func: &Function) -> result::Result for ss in func.stack_slots.keys() { any = true; - try!(writeln!(w, " {} = {}", ss, func.stack_slots[ss])); + writeln!(w, " {} = {}", ss, func.stack_slots[ss])?; } // Write out all signatures before functions since function declarations can refer to // signatures. for sig in func.dfg.signatures.keys() { any = true; - try!(writeln!(w, " {} = signature{}", sig, func.dfg.signatures[sig])); + writeln!(w, " {} = signature{}", sig, func.dfg.signatures[sig])?; } for fnref in func.dfg.ext_funcs.keys() { any = true; - try!(writeln!(w, " {} = {}", fnref, func.dfg.ext_funcs[fnref])); + writeln!(w, " {} = {}", fnref, func.dfg.ext_funcs[fnref])?; } for jt in func.jump_tables.keys() { any = true; - try!(writeln!(w, " {} = {}", jt, func.jump_tables[jt])); + writeln!(w, " {} = {}", jt, func.jump_tables[jt])?; } Ok(any) @@ -83,29 +83,29 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // If we're writing encoding annotations, shift by 20. if !func.encodings.is_empty() { - try!(write!(w, " ")); + write!(w, " ")?; } let mut args = func.dfg.ebb_args(ebb); match args.next() { None => return writeln!(w, "{}:", ebb), Some(arg) => { - try!(write!(w, "{}(", ebb)); - try!(write_arg(w, func, arg)); + write!(w, "{}(", ebb)?; + write_arg(w, func, arg)?; } } // Remaining arguments. for arg in args { - try!(write!(w, ", ")); - try!(write_arg(w, func, arg)); + write!(w, ", ")?; + write_arg(w, func, arg)?; } writeln!(w, "):") } pub fn write_ebb(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, ebb: Ebb) -> Result { - try!(write_ebb_header(w, func, ebb)); + write_ebb_header(w, func, ebb)?; for inst in func.layout.ebb_insts(ebb) { - try!(write_instruction(w, func, isa, inst)); + write_instruction(w, func, isa, inst)?; } Ok(()) } @@ -151,7 +151,7 @@ fn write_value_aliases(w: &mut Write, func: &Function, inst: Inst, indent: usize for &arg in func.dfg[inst].arguments().iter().flat_map(|x| x.iter()) { let resolved = func.dfg.resolve_aliases(arg); if resolved != arg { - try!(writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)); + writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)?; } } Ok(()) @@ -166,13 +166,13 @@ fn write_instruction(w: &mut Write, let indent = if func.encodings.is_empty() { 4 } else { 24 }; // Value aliases come out on lines before the instruction using them. - try!(write_value_aliases(w, func, inst, indent)); + write_value_aliases(w, func, inst, indent)?; // Write out encoding info. if let Some(enc) = func.encodings.get(inst).cloned() { let mut s = String::with_capacity(16); if let Some(isa) = isa { - try!(write!(s, "[{}", isa.display_enc(enc))); + write!(s, "[{}", isa.display_enc(enc))?; // Write value locations, if we have them. if !func.locations.is_empty() { let regs = isa.register_info(); @@ -184,15 +184,15 @@ fn write_instruction(w: &mut Write, } } } - try!(write!(s, "]")); + write!(s, "]")?; } else { - try!(write!(s, "[{}]", enc)); + write!(s, "[{}]", enc)?; } // Align instruction following ISA annotation to col 24. - try!(write!(w, "{:23} ", s)); + write!(w, "{:23} ", s)?; } else { // No annotations, simply indent. - try!(write!(w, "{1:0$}", indent, "")); + write!(w, "{1:0$}", indent, "")?; } // Write out the result values, if any. @@ -200,21 +200,21 @@ fn write_instruction(w: &mut Write, for r in func.dfg.inst_results(inst) { if !has_results { has_results = true; - try!(write!(w, "{}", r)); + write!(w, "{}", r)?; } else { - try!(write!(w, ", {}", r)); + write!(w, ", {}", r)?; } } if has_results { - try!(write!(w, " = ")); + write!(w, " = ")?; } // Then the opcode, possibly with a '.type' suffix. let opcode = func.dfg[inst].opcode(); match type_suffix(func, inst) { - Some(suf) => try!(write!(w, "{}.{}", opcode, suf)), - None => try!(write!(w, "{}", opcode)), + Some(suf) => write!(w, "{}.{}", opcode, suf)?, + None => write!(w, "{}", opcode)?, } // Then the operands, depending on format. diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index a2af0815e7..2474742fb1 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -38,7 +38,7 @@ impl Directive { } // All other commands are followed by a pattern. - let pat = try!(rest.parse()); + let pat = rest.parse()?; match cmd { "check" => Ok(Directive::Check(pat)), @@ -99,7 +99,7 @@ impl CheckerBuilder { pub fn directive(&mut self, l: &str) -> Result { match self.linerx.captures(l) { Some(caps) => { - self.directives.push(try!(Directive::new(caps))); + self.directives.push(Directive::new(caps)?); Ok(true) } None => Ok(false), @@ -112,7 +112,7 @@ impl CheckerBuilder { /// This method can be used to parse a whole test file containing multiple directives. pub fn text(&mut self, t: &str) -> Result<&mut Self> { for caps in self.linerx.captures_iter(t) { - self.directives.push(try!(Directive::new(caps))); + self.directives.push(Directive::new(caps)?); } Ok(self) } @@ -154,7 +154,7 @@ impl Checker { /// Explain how directives are matched against the input text. pub fn explain(&self, text: &str, vars: &VariableMap) -> Result<(bool, String)> { let mut expl = Explainer::new(text); - let success = try!(self.run(text, vars, &mut expl)); + let success = self.run(text, vars, &mut expl)?; expl.finish(); Ok((success, expl.to_string())) } @@ -178,7 +178,7 @@ impl Checker { // The `not:` directives test the same range as `unordered:` directives. In // particular, if they refer to defined variables, their range is restricted to // the text following the match that defined the variable. - nots.push((dct_idx, state.unordered_begin(pat), try!(pat.resolve(&state)))); + nots.push((dct_idx, state.unordered_begin(pat), pat.resolve(&state)?)); continue; } Directive::Regex(ref var, ref rx) => { @@ -192,7 +192,7 @@ impl Checker { }; // Check if `pat` matches in `range`. state.recorder.directive(dct_idx); - if let Some((match_begin, match_end)) = try!(state.match_positive(pat, range)) { + if let Some((match_begin, match_end)) = state.match_positive(pat, range)? { if let &Directive::Unordered(_) = dct { // This was an unordered unordered match. // Keep track of the largest matched position, but leave `last_ordered` alone. @@ -318,7 +318,7 @@ impl<'a> State<'a> { // Search for `pat` in `range`, return the range matched. // After a positive match, update variable definitions, if any. fn match_positive(&mut self, pat: &Pattern, range: MatchRange) -> Result> { - let rx = try!(pat.resolve(self)); + let rx = pat.resolve(self)?; let txt = &self.text[range.0..range.1]; let defs = pat.defs(); let matched_range = if defs.is_empty() { @@ -382,7 +382,7 @@ impl Display for Directive { impl Display for Checker { fn fmt(&self, f: &mut Formatter) -> fmt::Result { for (idx, dir) in self.directives.iter().enumerate() { - try!(write!(f, "#{} {}", idx, dir)); + write!(f, "#{} {}", idx, dir)?; } Ok(()) } diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs index 53dd4003a7..825f162079 100644 --- a/lib/filecheck/src/explain.rs +++ b/lib/filecheck/src/explain.rs @@ -89,34 +89,34 @@ impl<'a> Display for Explainer<'a> { .map(|d| nextln + d + 1) .unwrap_or(self.text.len()); assert!(newln > nextln); - try!(writeln!(f, "> {}", &self.text[nextln..newln - 1])); + writeln!(f, "> {}", &self.text[nextln..newln - 1])?; curln = nextln; nextln = newln; } // Emit ~~~ under the part of the match in curln. if m.is_match { - try!(write!(f, " ")); + write!(f, " ")?; let mend = min(m.range.1, nextln - 1); for pos in curln..mend { - try!(if pos < m.range.0 { + if pos < m.range.0 { write!(f, " ") } else if pos == m.range.0 { write!(f, "^") } else { write!(f, "~") - }); + }?; } - try!(writeln!(f, "")); + writeln!(f, "")?; } // Emit the match message itself. - try!(writeln!(f, + writeln!(f, "{} #{}{}: {}", if m.is_match { "Matched" } else { "Missed" }, m.directive, if m.is_not { " not" } else { "" }, - m.regex)); + m.regex)?; // Emit any variable definitions. if let Ok(found) = self.vardefs.binary_search_by_key(&m.directive, |v| v.directive) { @@ -128,14 +128,14 @@ impl<'a> Display for Explainer<'a> { if d.directive != m.directive { break; } - try!(writeln!(f, "Define {}={}", d.varname, d.value)); + writeln!(f, "Define {}={}", d.varname, d.value)?; } } } // Emit trailing lines. for line in self.text[nextln..].lines() { - try!(writeln!(f, "> {}", line)); + writeln!(f, "> {}", line)?; } Ok(()) } diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs index 030758d41e..9f54a80cf6 100644 --- a/lib/filecheck/src/pattern.rs +++ b/lib/filecheck/src/pattern.rs @@ -147,7 +147,7 @@ impl Pattern { let def = if varname.is_empty() { None } else { - Some(try!(self.add_def(&varname))) + Some(self.add_def(&varname)?) }; // Match `$(var=$PAT)`. @@ -270,7 +270,7 @@ impl FromStr for Pattern { let mut pat = Pattern::new(); let mut pos = 0; while pos < s.len() { - let (part, len) = try!(pat.parse_part(&s[pos..])); + let (part, len) = pat.parse_part(&s[pos..])?; if let Some(v) = part.ref_var() { if pat.defines_var(v) { return Err(Error::Backref(format!("unsupported back-reference to '${}' \ @@ -353,7 +353,7 @@ impl Pattern { } } - Ok(try!(RegexBuilder::new(&out).multi_line(true).compile())) + Ok(RegexBuilder::new(&out).multi_line(true).compile()?) } } @@ -361,7 +361,7 @@ impl Display for Pattern { fn fmt(&self, f: &mut Formatter) -> fmt::Result { for part in &self.parts { use self::Part::*; - try!(match *part { + match *part { Text(ref txt) if txt == "" => write!(f, "$()"), Text(ref txt) if txt == "$" => write!(f, "$$"), Text(ref txt) => write!(f, "{}", txt), @@ -374,7 +374,7 @@ impl Display for Pattern { write!(f, "$({}={})", defvar, litrx) } DefVar { def, ref var } => write!(f, "$({}=${})", self.defs[def], var), - }); + }?; } Ok(()) } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 47ff73a89c..e9a5a26a02 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -42,9 +42,9 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { parser.gather_comments(AnyEntity::Function); Ok(TestFile { commands: parser.parse_test_commands(), - isa_spec: try!(parser.parse_isa_specs()), + isa_spec: parser.parse_isa_specs()?, preamble_comments: parser.take_comments(), - functions: try!(parser.parse_function_list()), + functions: parser.parse_function_list()?, }) } @@ -155,7 +155,7 @@ impl Context { InstructionData::BinaryImmRev { ref mut arg, .. } | InstructionData::ExtractLane { ref mut arg, .. } | InstructionData::BranchTable { ref mut arg, .. } => { - try!(self.map.rewrite_value(arg, loc)); + self.map.rewrite_value(arg, loc)?; } InstructionData::Binary { ref mut args, .. } | @@ -163,44 +163,44 @@ impl Context { InstructionData::InsertLane { ref mut args, .. } | InstructionData::IntCompare { ref mut args, .. } | InstructionData::FloatCompare { ref mut args, .. } => { - try!(self.map.rewrite_values(args, loc)); + self.map.rewrite_values(args, loc)?; } InstructionData::Ternary { ref mut args, .. } => { - try!(self.map.rewrite_values(args, loc)); + self.map.rewrite_values(args, loc)?; } InstructionData::TernaryOverflow { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.args, loc)); + self.map.rewrite_values(&mut data.args, loc)?; } InstructionData::Jump { ref mut data, .. } => { - try!(self.map.rewrite_ebb(&mut data.destination, loc)); - try!(self.map.rewrite_values(&mut data.varargs, loc)); + self.map.rewrite_ebb(&mut data.destination, loc)?; + self.map.rewrite_values(&mut data.varargs, loc)?; } InstructionData::Branch { ref mut data, .. } => { - try!(self.map.rewrite_value(&mut data.arg, loc)); - try!(self.map.rewrite_ebb(&mut data.destination, loc)); - try!(self.map.rewrite_values(&mut data.varargs, loc)); + self.map.rewrite_value(&mut data.arg, loc)?; + self.map.rewrite_ebb(&mut data.destination, loc)?; + self.map.rewrite_values(&mut data.varargs, loc)?; } InstructionData::Call { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.varargs, loc)); + self.map.rewrite_values(&mut data.varargs, loc)?; } InstructionData::IndirectCall { ref mut data, .. } => { - try!(self.map.rewrite_value(&mut data.arg, loc)); - try!(self.map.rewrite_values(&mut data.varargs, loc)); + self.map.rewrite_value(&mut data.arg, loc)?; + self.map.rewrite_values(&mut data.varargs, loc)?; } InstructionData::Return { ref mut data, .. } => { - try!(self.map.rewrite_values(&mut data.varargs, loc)); + self.map.rewrite_values(&mut data.varargs, loc)?; } InstructionData::ReturnReg { ref mut data, .. } => { - try!(self.map.rewrite_value(&mut data.arg, loc)); - try!(self.map.rewrite_values(&mut data.varargs, loc)); + self.map.rewrite_value(&mut data.arg, loc)?; + self.map.rewrite_values(&mut data.varargs, loc)?; } } } @@ -211,7 +211,7 @@ impl Context { let loc = jt.into(); for ebb_ref in self.function.jump_tables[jt].as_mut_slice() { if let Some(mut ebb) = ebb_ref.expand() { - try!(self.map.rewrite_ebb(&mut ebb, loc)); + self.map.rewrite_ebb(&mut ebb, loc)?; // Convert back to a packed option. *ebb_ref = ebb.into(); } @@ -480,9 +480,9 @@ impl<'a> Parser<'a> { match command { "set" => { last_set_loc = Some(self.loc); - try!(isaspec::parse_options(self.consume_line().trim().split_whitespace(), + isaspec::parse_options(self.consume_line().trim().split_whitespace(), &mut flag_builder, - &self.loc)); + &self.loc)?; } "isa" => { last_set_loc = None; @@ -501,7 +501,7 @@ impl<'a> Parser<'a> { Some(b) => b, }; // Apply the ISA-specific settings to `isa_builder`. - try!(isaspec::parse_options(words, &mut isa_builder, &self.loc)); + isaspec::parse_options(words, &mut isa_builder, &self.loc)?; // Construct a trait object with the aggregrate settings. isas.push(isa_builder.finish(settings::Flags::new(&flag_builder))); @@ -526,7 +526,7 @@ impl<'a> Parser<'a> { pub fn parse_function_list(&mut self) -> Result)>> { let mut list = Vec::new(); while self.token().is_some() { - list.push(try!(self.parse_function())); + list.push(self.parse_function()?); } Ok(list) } @@ -542,17 +542,17 @@ impl<'a> Parser<'a> { self.comments.clear(); self.gather_comments(AnyEntity::Function); - let (location, name, sig) = try!(self.parse_function_spec()); + let (location, name, sig) = self.parse_function_spec()?; let mut ctx = Context::new(Function::with_name_signature(name, sig)); // function ::= function-spec * "{" preamble function-body "}" - try!(self.match_token(Token::LBrace, "expected '{' before function body")); + self.match_token(Token::LBrace, "expected '{' before function body")?; // function ::= function-spec "{" * preamble function-body "}" - try!(self.parse_preamble(&mut ctx)); + self.parse_preamble(&mut ctx)?; // function ::= function-spec "{" preamble * function-body "}" - try!(self.parse_function_body(&mut ctx)); + self.parse_function_body(&mut ctx)?; // function ::= function-spec "{" preamble function-body * "}" - try!(self.match_token(Token::RBrace, "expected '}' after function body")); + self.match_token(Token::RBrace, "expected '}' after function body")?; // Collect any comments following the end of the function, then stop gathering comments. self.gather_comments(AnyEntity::Function); @@ -561,7 +561,7 @@ impl<'a> Parser<'a> { // Rewrite references to values and EBBs after parsing everything to allow forward // references. - try!(ctx.rewrite_references()); + ctx.rewrite_references()?; let details = Details { location: location, @@ -577,14 +577,14 @@ impl<'a> Parser<'a> { // function-spec ::= * "function" name signature // fn parse_function_spec(&mut self) -> Result<(Location, FunctionName, Signature)> { - try!(self.match_identifier("function", "expected 'function'")); + self.match_identifier("function", "expected 'function'")?; let location = self.loc; // function-spec ::= "function" * name signature - let name = try!(self.parse_function_name()); + let name = self.parse_function_name()?; // function-spec ::= "function" name * signature - let sig = try!(self.parse_signature()); + let sig = self.parse_signature()?; Ok((location, name, sig)) } @@ -610,14 +610,14 @@ impl<'a> Parser<'a> { fn parse_signature(&mut self) -> Result { let mut sig = Signature::new(); - try!(self.match_token(Token::LPar, "expected function signature: ( args... )")); + self.match_token(Token::LPar, "expected function signature: ( args... )")?; // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] if self.token() != Some(Token::RPar) { - sig.argument_types = try!(self.parse_argument_list()); + sig.argument_types = self.parse_argument_list()?; } - try!(self.match_token(Token::RPar, "expected ')' after function arguments")); + self.match_token(Token::RPar, "expected ')' after function arguments")?; if self.optional(Token::Arrow) { - sig.return_types = try!(self.parse_argument_list()); + sig.return_types = self.parse_argument_list()?; } // TBD: calling convention. @@ -633,12 +633,12 @@ impl<'a> Parser<'a> { let mut list = Vec::new(); // arglist ::= * arg { "," arg } - list.push(try!(self.parse_argument_type())); + list.push(self.parse_argument_type()?); // arglist ::= arg * { "," arg } while self.optional(Token::Comma) { // arglist ::= arg { "," * arg } - list.push(try!(self.parse_argument_type())); + list.push(self.parse_argument_type()?); } Ok(list) @@ -647,7 +647,7 @@ impl<'a> Parser<'a> { // Parse a single argument type with flags. fn parse_argument_type(&mut self) -> Result { // arg ::= * type { flag } - let mut arg = ArgumentType::new(try!(self.match_type("expected argument type"))); + let mut arg = ArgumentType::new(self.match_type("expected argument type")?); // arg ::= type * { flag } while let Some(Token::Identifier(s)) = self.token() { @@ -674,7 +674,7 @@ impl<'a> Parser<'a> { // The parsed decls are added to `ctx` rather than returned. fn parse_preamble(&mut self, ctx: &mut Context) -> Result<()> { loop { - try!(match self.token() { + match self.token() { Some(Token::StackSlot(..)) => { self.gather_comments(ctx.function.stack_slots.next_key()); self.parse_stack_slot_decl() @@ -697,7 +697,7 @@ impl<'a> Parser<'a> { } // More to come.. _ => return Ok(()), - }); + }?; } } @@ -705,12 +705,12 @@ impl<'a> Parser<'a> { // // stack-slot-decl ::= * StackSlot(ss) "=" "stack_slot" Bytes {"," stack-slot-flag} fn parse_stack_slot_decl(&mut self) -> Result<(u32, StackSlotData)> { - let number = try!(self.match_ss("expected stack slot number: ss«n»")); - try!(self.match_token(Token::Equal, "expected '=' in stack_slot decl")); - try!(self.match_identifier("stack_slot", "expected 'stack_slot'")); + let number = self.match_ss("expected stack slot number: ss«n»")?; + self.match_token(Token::Equal, "expected '=' in stack_slot decl")?; + self.match_identifier("stack_slot", "expected 'stack_slot'")?; // stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag} - let bytes: i64 = try!(self.match_imm64("expected byte-size in stack_slot decl")).into(); + let bytes: i64 = self.match_imm64("expected byte-size in stack_slot decl")?.into(); if bytes < 0 { return err!(self.loc, "negative stack slot size"); } @@ -728,10 +728,10 @@ impl<'a> Parser<'a> { // signature-decl ::= SigRef(sigref) "=" "signature" signature // fn parse_signature_decl(&mut self) -> Result<(u32, Signature)> { - let number = try!(self.match_sig("expected signature number: sig«n»")); - try!(self.match_token(Token::Equal, "expected '=' in signature decl")); - try!(self.match_identifier("signature", "expected 'signature'")); - let data = try!(self.parse_signature()); + let number = self.match_sig("expected signature number: sig«n»")?; + self.match_token(Token::Equal, "expected '=' in signature decl")?; + self.match_identifier("signature", "expected 'signature'")?; + let data = self.parse_signature()?; Ok((number, data)) } @@ -746,12 +746,12 @@ impl<'a> Parser<'a> { // signature which must be declared first. // fn parse_function_decl(&mut self, ctx: &mut Context) -> Result<(u32, ExtFuncData)> { - let number = try!(self.match_fn("expected function number: fn«n»")); - try!(self.match_token(Token::Equal, "expected '=' in function decl")); + let number = self.match_fn("expected function number: fn«n»")?; + self.match_token(Token::Equal, "expected '=' in function decl")?; let data = match self.token() { Some(Token::Identifier("function")) => { - let (loc, name, sig) = try!(self.parse_function_spec()); + let (loc, name, sig) = self.parse_function_spec()?; let sigref = ctx.function.dfg.signatures.push(sig); ctx.map.def_entity(sigref.into(), &loc).expect("duplicate SigRef entities created"); ExtFuncData { @@ -760,9 +760,9 @@ impl<'a> Parser<'a> { } } Some(Token::SigRef(sig_src)) => { - let sig = try!(ctx.get_sig(sig_src, &self.loc)); + let sig = ctx.get_sig(sig_src, &self.loc)?; self.consume(); - let name = try!(self.parse_function_name()); + let name = self.parse_function_name()?; ExtFuncData { name: name, signature: sig, @@ -777,15 +777,15 @@ impl<'a> Parser<'a> { // // jump-table-decl ::= * JumpTable(jt) "=" "jump_table" jt-entry {"," jt-entry} fn parse_jump_table_decl(&mut self) -> Result<(u32, JumpTableData)> { - let number = try!(self.match_jt()); - try!(self.match_token(Token::Equal, "expected '=' in jump_table decl")); - try!(self.match_identifier("jump_table", "expected 'jump_table'")); + let number = self.match_jt()?; + self.match_token(Token::Equal, "expected '=' in jump_table decl")?; + self.match_identifier("jump_table", "expected 'jump_table'")?; let mut data = JumpTableData::new(); // jump-table-decl ::= JumpTable(jt) "=" "jump_table" * jt-entry {"," jt-entry} for idx in 0usize.. { - if let Some(dest) = try!(self.parse_jump_table_entry()) { + if let Some(dest) = self.parse_jump_table_entry()? { data.set_entry(idx, dest); } if !self.optional(Token::Comma) { @@ -821,7 +821,7 @@ impl<'a> Parser<'a> { // fn parse_function_body(&mut self, ctx: &mut Context) -> Result<()> { while self.token() != Some(Token::RBrace) { - try!(self.parse_extended_basic_block(ctx)); + self.parse_extended_basic_block(ctx)?; } Ok(()) } @@ -832,14 +832,14 @@ impl<'a> Parser<'a> { // ebb-header ::= Ebb(ebb) [ebb-args] ":" // fn parse_extended_basic_block(&mut self, ctx: &mut Context) -> Result<()> { - let ebb_num = try!(self.match_ebb("expected EBB header")); - let ebb = try!(ctx.add_ebb(ebb_num, &self.loc)); + let ebb_num = self.match_ebb("expected EBB header")?; + let ebb = ctx.add_ebb(ebb_num, &self.loc)?; self.gather_comments(ebb); if !self.optional(Token::Colon) { // ebb-header ::= Ebb(ebb) [ * ebb-args ] ":" - try!(self.parse_ebb_args(ctx, ebb)); - try!(self.match_token(Token::Colon, "expected ':' after EBB arguments")); + self.parse_ebb_args(ctx, ebb)?; + self.match_token(Token::Colon, "expected ':' after EBB arguments")?; } // extended-basic-block ::= ebb-header * { instruction } @@ -848,7 +848,7 @@ impl<'a> Parser<'a> { Some(Token::Identifier(_)) => true, _ => false, } { - try!(self.parse_instruction(ctx, ebb)); + self.parse_instruction(ctx, ebb)?; } Ok(()) @@ -860,19 +860,19 @@ impl<'a> Parser<'a> { // ebb-args ::= * "(" ebb-arg { "," ebb-arg } ")" fn parse_ebb_args(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { // ebb-args ::= * "(" ebb-arg { "," ebb-arg } ")" - try!(self.match_token(Token::LPar, "expected '(' before EBB arguments")); + self.match_token(Token::LPar, "expected '(' before EBB arguments")?; // ebb-args ::= "(" * ebb-arg { "," ebb-arg } ")" - try!(self.parse_ebb_arg(ctx, ebb)); + self.parse_ebb_arg(ctx, ebb)?; // ebb-args ::= "(" ebb-arg * { "," ebb-arg } ")" while self.optional(Token::Comma) { // ebb-args ::= "(" ebb-arg { "," * ebb-arg } ")" - try!(self.parse_ebb_arg(ctx, ebb)); + self.parse_ebb_arg(ctx, ebb)?; } // ebb-args ::= "(" ebb-arg { "," ebb-arg } * ")" - try!(self.match_token(Token::RPar, "expected ')' after EBB arguments")); + self.match_token(Token::RPar, "expected ')' after EBB arguments")?; Ok(()) } @@ -883,12 +883,12 @@ impl<'a> Parser<'a> { // fn parse_ebb_arg(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { // ebb-arg ::= * Value(vx) ":" Type(t) - let vx = try!(self.match_value("EBB argument must be a value")); + let vx = self.match_value("EBB argument must be a value")?; let vx_location = self.loc; // ebb-arg ::= Value(vx) * ":" Type(t) - try!(self.match_token(Token::Colon, "expected ':' after EBB argument")); + self.match_token(Token::Colon, "expected ':' after EBB argument")?; // ebb-arg ::= Value(vx) ":" * Type(t) - let t = try!(self.match_type("expected EBB argument type")); + let t = self.match_type("expected EBB argument type")?; // Allocate the EBB argument and add the mapping. let value = ctx.function.dfg.append_ebb_arg(ebb, t); ctx.map.def_value(vx, value, &vx_location) @@ -915,10 +915,10 @@ impl<'a> Parser<'a> { // inst-results ::= Value(v) * { "," Value(vx) } while self.optional(Token::Comma) { // inst-results ::= Value(v) { "," * Value(vx) } - results.push(try!(self.match_value("expected result value"))); + results.push(self.match_value("expected result value")?); } - try!(self.match_token(Token::Equal, "expected '=' before opcode")); + self.match_token(Token::Equal, "expected '=' before opcode")?; } // instruction ::= [inst-results "="] * Opcode(opc) ["." Type] ... @@ -936,20 +936,20 @@ impl<'a> Parser<'a> { // Look for a controlling type variable annotation. // instruction ::= [inst-results "="] Opcode(opc) * ["." Type] ... let explicit_ctrl_type = if self.optional(Token::Dot) { - Some(try!(self.match_type("expected type after 'opcode.'"))) + Some(self.match_type("expected type after 'opcode.'")?) } else { None }; // instruction ::= [inst-results "="] Opcode(opc) ["." Type] * ... - let inst_data = try!(self.parse_inst_operands(ctx, opcode)); + let inst_data = self.parse_inst_operands(ctx, opcode)?; // We're done parsing the instruction now. // // We still need to check that the number of result values in the source matches the opcode // or function call signature. We also need to create values with the right type for all // the instruction results. - let ctrl_typevar = try!(self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data)); + let ctrl_typevar = self.infer_typevar(ctx, opcode, explicit_ctrl_type, &inst_data)?; let inst = ctx.function.dfg.make_inst(inst_data); let num_results = ctx.function.dfg.make_inst_results(inst, ctrl_typevar); ctx.function.layout.append_inst(inst, ebb); @@ -1046,7 +1046,7 @@ impl<'a> Parser<'a> { V: Iterator { for (src, val) in results.zip(new_results) { - try!(map.def_value(src, val, &self.loc)); + map.def_value(src, val, &self.loc)?; } Ok(()) } @@ -1066,7 +1066,7 @@ impl<'a> Parser<'a> { } while self.optional(Token::Comma) { - args.push(try!(self.match_value("expected value in argument list"))); + args.push(self.match_value("expected value in argument list")?); } Ok(args) @@ -1078,9 +1078,9 @@ impl<'a> Parser<'a> { return Ok(VariableArgs::new()); } - let args = try!(self.parse_value_list()); + let args = self.parse_value_list()?; - try!(self.match_token(Token::RPar, "expected ')' after arguments")); + self.match_token(Token::RPar, "expected ')' after arguments")?; Ok(args) } @@ -1099,28 +1099,28 @@ impl<'a> Parser<'a> { InstructionData::Unary { opcode: opcode, ty: VOID, - arg: try!(self.match_value("expected SSA value operand")), + arg: self.match_value("expected SSA value operand")?, } } InstructionFormat::UnaryImm => { InstructionData::UnaryImm { opcode: opcode, ty: VOID, - imm: try!(self.match_imm64("expected immediate integer operand")), + imm: self.match_imm64("expected immediate integer operand")?, } } InstructionFormat::UnaryIeee32 => { InstructionData::UnaryIeee32 { opcode: opcode, ty: VOID, - imm: try!(self.match_ieee32("expected immediate 32-bit float operand")), + imm: self.match_ieee32("expected immediate 32-bit float operand")?, } } InstructionFormat::UnaryIeee64 => { InstructionData::UnaryIeee64 { opcode: opcode, ty: VOID, - imm: try!(self.match_ieee64("expected immediate 64-bit float operand")), + imm: self.match_ieee64("expected immediate 64-bit float operand")?, } } InstructionFormat::UnaryImmVector => { @@ -1131,13 +1131,13 @@ impl<'a> Parser<'a> { opcode: opcode, ty: VOID, second_result: None.into(), - arg: try!(self.match_value("expected SSA value operand")), + arg: self.match_value("expected SSA value operand")?, } } InstructionFormat::Binary => { - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value second operand")); + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; InstructionData::Binary { opcode: opcode, ty: VOID, @@ -1145,9 +1145,9 @@ impl<'a> Parser<'a> { } } InstructionFormat::BinaryImm => { - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_imm64("expected immediate integer second operand")); + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_imm64("expected immediate integer second operand")?; InstructionData::BinaryImm { opcode: opcode, ty: VOID, @@ -1156,9 +1156,9 @@ impl<'a> Parser<'a> { } } InstructionFormat::BinaryImmRev => { - let lhs = try!(self.match_imm64("expected immediate integer first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value second operand")); + let lhs = self.match_imm64("expected immediate integer first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; InstructionData::BinaryImmRev { opcode: opcode, ty: VOID, @@ -1167,9 +1167,9 @@ impl<'a> Parser<'a> { } } InstructionFormat::BinaryOverflow => { - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value second operand")); + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; InstructionData::BinaryOverflow { opcode: opcode, ty: VOID, @@ -1180,11 +1180,11 @@ impl<'a> Parser<'a> { InstructionFormat::Ternary => { // Names here refer to the `select` instruction. // This format is also use by `fma`. - let ctrl_arg = try!(self.match_value("expected SSA value control operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let true_arg = try!(self.match_value("expected SSA value true operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let false_arg = try!(self.match_value("expected SSA value false operand")); + let ctrl_arg = self.match_value("expected SSA value control operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let true_arg = self.match_value("expected SSA value true operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let false_arg = self.match_value("expected SSA value false operand")?; InstructionData::Ternary { opcode: opcode, ty: VOID, @@ -1193,11 +1193,11 @@ impl<'a> Parser<'a> { } InstructionFormat::TernaryOverflow => { // Names here refer to the `iadd_carry` instruction. - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value second operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let cin = try!(self.match_value("expected SSA value third operand")); + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let cin = self.match_value("expected SSA value third operand")?; InstructionData::TernaryOverflow { opcode: opcode, ty: VOID, @@ -1207,8 +1207,8 @@ impl<'a> Parser<'a> { } InstructionFormat::Jump => { // Parse the destination EBB number. Don't translate source to local numbers yet. - let ebb_num = try!(self.match_ebb("expected jump destination EBB")); - let args = try!(self.parse_opt_value_list()); + let ebb_num = self.match_ebb("expected jump destination EBB")?; + let args = self.parse_opt_value_list()?; InstructionData::Jump { opcode: opcode, ty: VOID, @@ -1219,10 +1219,10 @@ impl<'a> Parser<'a> { } } InstructionFormat::Branch => { - let ctrl_arg = try!(self.match_value("expected SSA value control operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let ebb_num = try!(self.match_ebb("expected branch destination EBB")); - let args = try!(self.parse_opt_value_list()); + let ctrl_arg = self.match_value("expected SSA value control operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let ebb_num = self.match_ebb("expected branch destination EBB")?; + let args = self.parse_opt_value_list()?; InstructionData::Branch { opcode: opcode, ty: VOID, @@ -1234,11 +1234,11 @@ impl<'a> Parser<'a> { } } InstructionFormat::InsertLane => { - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let lane = try!(self.match_uimm8("expected lane number")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value last operand")); + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let lane = self.match_uimm8("expected lane number")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value last operand")?; InstructionData::InsertLane { opcode: opcode, ty: VOID, @@ -1247,9 +1247,9 @@ impl<'a> Parser<'a> { } } InstructionFormat::ExtractLane => { - let arg = try!(self.match_value("expected SSA value last operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let lane = try!(self.match_uimm8("expected lane number")); + let arg = self.match_value("expected SSA value last operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let lane = self.match_uimm8("expected lane number")?; InstructionData::ExtractLane { opcode: opcode, ty: VOID, @@ -1258,11 +1258,11 @@ impl<'a> Parser<'a> { } } InstructionFormat::IntCompare => { - let cond = try!(self.match_enum("expected intcc condition code")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value second operand")); + let cond = self.match_enum("expected intcc condition code")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; InstructionData::IntCompare { opcode: opcode, ty: VOID, @@ -1271,11 +1271,11 @@ impl<'a> Parser<'a> { } } InstructionFormat::FloatCompare => { - let cond = try!(self.match_enum("expected floatcc condition code")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let lhs = try!(self.match_value("expected SSA value first operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let rhs = try!(self.match_value("expected SSA value second operand")); + let cond = self.match_enum("expected floatcc condition code")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; InstructionData::FloatCompare { opcode: opcode, ty: VOID, @@ -1284,11 +1284,11 @@ impl<'a> Parser<'a> { } } InstructionFormat::Call => { - let func_ref = try!(self.match_fn("expected function reference") - .and_then(|num| ctx.get_fn(num, &self.loc))); - try!(self.match_token(Token::LPar, "expected '(' before arguments")); - let args = try!(self.parse_value_list()); - try!(self.match_token(Token::RPar, "expected ')' after arguments")); + let func_ref = self.match_fn("expected function reference") + .and_then(|num| ctx.get_fn(num, &self.loc))?; + self.match_token(Token::LPar, "expected '(' before arguments")?; + let args = self.parse_value_list()?; + self.match_token(Token::RPar, "expected ')' after arguments")?; InstructionData::Call { opcode: opcode, ty: VOID, @@ -1300,13 +1300,13 @@ impl<'a> Parser<'a> { } } InstructionFormat::IndirectCall => { - let sig_ref = try!(self.match_sig("expected signature reference") - .and_then(|num| ctx.get_sig(num, &self.loc))); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let callee = try!(self.match_value("expected SSA value callee operand")); - try!(self.match_token(Token::LPar, "expected '(' before arguments")); - let args = try!(self.parse_value_list()); - try!(self.match_token(Token::RPar, "expected ')' after arguments")); + let sig_ref = self.match_sig("expected signature reference") + .and_then(|num| ctx.get_sig(num, &self.loc))?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let callee = self.match_value("expected SSA value callee operand")?; + self.match_token(Token::LPar, "expected '(' before arguments")?; + let args = self.parse_value_list()?; + self.match_token(Token::RPar, "expected ')' after arguments")?; InstructionData::IndirectCall { opcode: opcode, ty: VOID, @@ -1319,7 +1319,7 @@ impl<'a> Parser<'a> { } } InstructionFormat::Return => { - let args = try!(self.parse_value_list()); + let args = self.parse_value_list()?; InstructionData::Return { opcode: opcode, ty: VOID, @@ -1327,9 +1327,9 @@ impl<'a> Parser<'a> { } } InstructionFormat::ReturnReg => { - let raddr = try!(self.match_value("expected SSA value return address operand")); + let raddr = self.match_value("expected SSA value return address operand")?; let args = if self.optional(Token::Comma) { - try!(self.parse_value_list()) + self.parse_value_list()? } else { VariableArgs::new() }; @@ -1343,9 +1343,9 @@ impl<'a> Parser<'a> { } } InstructionFormat::BranchTable => { - let arg = try!(self.match_value("expected SSA value operand")); - try!(self.match_token(Token::Comma, "expected ',' between operands")); - let table = try!(self.match_jt().and_then(|num| ctx.get_jt(num, &self.loc))); + let arg = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let table = self.match_jt().and_then(|num| ctx.get_jt(num, &self.loc))?; InstructionData::BranchTable { opcode: opcode, ty: VOID, diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 369e732e90..dda6420220 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -121,7 +121,7 @@ impl SourceMap { /// Rewrite a slice of value references. pub fn rewrite_values(&self, vals: &mut [Value], loc: AnyEntity) -> Result<()> { for val in vals { - try!(self.rewrite_value(val, loc)); + self.rewrite_value(val, loc)?; } Ok(()) } diff --git a/lib/reader/src/testcommand.rs b/lib/reader/src/testcommand.rs index 012c7033e1..6d9dd9969e 100644 --- a/lib/reader/src/testcommand.rs +++ b/lib/reader/src/testcommand.rs @@ -47,9 +47,9 @@ impl<'a> TestCommand<'a> { impl<'a> Display for TestCommand<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - try!(write!(f, "{}", self.command)); + write!(f, "{}", self.command)?; for opt in &self.options { - try!(write!(f, " {}", opt)); + write!(f, " {}", opt)?; } writeln!(f, "") } diff --git a/src/cat.rs b/src/cat.rs index 14871b07c2..443c37dadc 100644 --- a/src/cat.rs +++ b/src/cat.rs @@ -15,14 +15,14 @@ pub fn run(files: Vec) -> CommandResult { if i != 0 { println!(""); } - try!(cat_one(f)) + cat_one(f)? } Ok(()) } fn cat_one(filename: String) -> CommandResult { - let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); - let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); + let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; + let items = parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))?; for (idx, func) in items.into_iter().enumerate() { if idx != 0 { diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index ea53a703b7..b7c3345436 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -39,7 +39,7 @@ impl SubTest for TestLegalizer { legalize_function(&mut func, isa); let mut text = String::new(); - try!(write_function(&mut text, &func, Some(isa)).map_err(|e| e.to_string())); + write_function(&mut text, &func, Some(isa)).map_err(|e| e.to_string())?; run_filecheck(&text, context) } } diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index af9dbb2b23..90796c1b85 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -49,7 +49,7 @@ impl SubTest for TestRegalloc { comp_ctx.regalloc(isa); let mut text = String::new(); - try!(write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())); + write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; run_filecheck(&text, context) } } diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index d84f0f7041..06e4cfce6b 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -18,14 +18,14 @@ use filetest::subtest::{SubTest, Context, Result}; /// If running this test causes a panic, it will propagate as normal. pub fn run(path: &Path) -> TestResult { let started = time::Instant::now(); - let buffer = try!(read_to_string(path).map_err(|e| e.to_string())); - let testfile = try!(parse_test(&buffer).map_err(|e| e.to_string())); + let buffer = read_to_string(path).map_err(|e| e.to_string())?; + let testfile = parse_test(&buffer).map_err(|e| e.to_string())?; if testfile.functions.is_empty() { return Err("no functions found".to_string()); } // Parse the test commands. - let mut tests = try!(testfile.commands.iter().map(new_subtest).collect::>>()); + let mut tests = testfile.commands.iter().map(new_subtest).collect::>>()?; // Flags to use for those tests that don't need an ISA. // This is the cumulative effect of all the `set` commands in the file. @@ -39,7 +39,7 @@ pub fn run(path: &Path) -> TestResult { tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); // Expand the tests into (test, flags, isa) tuples. - let mut tuples = try!(test_tuples(&tests, &testfile.isa_spec, flags)); + let mut tuples = test_tuples(&tests, &testfile.isa_spec, flags)?; // Isolate the last test in the hope that this is the only mutating test. // If so, we can completely avoid cloning functions. @@ -58,11 +58,11 @@ pub fn run(path: &Path) -> TestResult { }; for tuple in &tuples { - try!(run_one_test(*tuple, Cow::Borrowed(&func), &mut context)); + run_one_test(*tuple, Cow::Borrowed(&func), &mut context)?; } // Run the last test with an owned function which means it won't need to clone it before // mutating. - try!(run_one_test(last_tuple, Cow::Owned(func), &mut context)); + run_one_test(last_tuple, Cow::Owned(func), &mut context)?; } @@ -108,7 +108,7 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { - try!(verify_function(&func).map_err(|e| e.to_string())); + verify_function(&func).map_err(|e| e.to_string())?; context.verified = true; } diff --git a/src/filetest/subtest.rs b/src/filetest/subtest.rs index f78ac7a6b1..8ba3528a6c 100644 --- a/src/filetest/subtest.rs +++ b/src/filetest/subtest.rs @@ -72,13 +72,13 @@ impl<'a> filecheck::VariableMap for Context<'a> { /// Run filecheck on `text`, using directives extracted from `context`. pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { - let checker = try!(build_filechecker(context)); - if try!(checker.check(&text, context).map_err(|e| format!("filecheck: {}", e))) { + let checker = build_filechecker(context)?; + if checker.check(&text, context).map_err(|e| format!("filecheck: {}", e))? { Ok(()) } else { // Filecheck mismatch. Emit an explanation as output. - let (_, explain) = try!(checker.explain(&text, context) - .map_err(|e| format!("explain: {}", e))); + let (_, explain) = checker.explain(&text, context) + .map_err(|e| format!("explain: {}", e))?; Err(format!("filecheck failed:\n{}{}", checker, explain)) } } @@ -88,10 +88,10 @@ pub fn build_filechecker(context: &Context) -> Result { let mut builder = CheckerBuilder::new(); // Preamble comments apply to all functions. for comment in context.preamble_comments { - try!(builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))); + builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))?; } for comment in &context.details.comments { - try!(builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))); + builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))?; } let checker = builder.finish(); if checker.is_empty() { diff --git a/src/print_cfg.rs b/src/print_cfg.rs index ca6c9219f7..d8261dad10 100644 --- a/src/print_cfg.rs +++ b/src/print_cfg.rs @@ -19,7 +19,7 @@ pub fn run(files: Vec) -> CommandResult { if i != 0 { println!(""); } - try!(print_cfg(f)) + print_cfg(f)? } Ok(()) } @@ -39,37 +39,37 @@ impl<'a> CFGPrinter<'a> { /// Write the CFG for this function to `w`. pub fn write(&self, w: &mut Write) -> Result { - try!(self.header(w)); - try!(self.ebb_nodes(w)); - try!(self.cfg_connections(w)); + self.header(w)?; + self.ebb_nodes(w)?; + self.cfg_connections(w)?; writeln!(w, "}}") } fn header(&self, w: &mut Write) -> Result { - try!(writeln!(w, "digraph {} {{", self.func.name)); + writeln!(w, "digraph {} {{", self.func.name)?; if let Some(entry) = self.func.layout.entry_block() { - try!(writeln!(w, " {{rank=min; {}}}", entry)); + writeln!(w, " {{rank=min; {}}}", entry)?; } Ok(()) } fn ebb_nodes(&self, w: &mut Write) -> Result { for ebb in &self.func.layout { - try!(write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)); + write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)?; // Add all outgoing branch instructions to the label. for inst in self.func.layout.ebb_insts(ebb) { let idata = &self.func.dfg[inst]; match idata.analyze_branch() { BranchInfo::SingleDest(dest, _) => { - try!(write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)) + write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)? } BranchInfo::Table(table) => { - try!(write!(w, " | <{}>{} {}", inst, idata.opcode(), table)) + write!(w, " | <{}>{} {}", inst, idata.opcode(), table)? } BranchInfo::NotABranch => {} } } - try!(writeln!(w, "}}\"]")) + writeln!(w, "}}\"]")? } Ok(()) } @@ -77,7 +77,7 @@ impl<'a> CFGPrinter<'a> { fn cfg_connections(&self, w: &mut Write) -> Result { for ebb in &self.func.layout { for &(parent, inst) in self.cfg.get_predecessors(ebb) { - try!(writeln!(w, " {}:{} -> {}", parent, inst, ebb)); + writeln!(w, " {}:{} -> {}", parent, inst, ebb)?; } } Ok(()) @@ -91,8 +91,8 @@ impl<'a> Display for CFGPrinter<'a> { } fn print_cfg(filename: String) -> CommandResult { - let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); - let items = try!(parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))); + let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; + let items = parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))?; for (idx, func) in items.into_iter().enumerate() { if idx != 0 { diff --git a/src/rsfilecheck.rs b/src/rsfilecheck.rs index 5ff6c596d1..9794e10813 100644 --- a/src/rsfilecheck.rs +++ b/src/rsfilecheck.rs @@ -7,7 +7,7 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { if files.is_empty() { return Err("No check files".to_string()); } - let checker = try!(read_checkfile(&files[0])); + let checker = read_checkfile(&files[0])?; if checker.is_empty() { return Err(format!("{}: no filecheck directives found", files[0])); } @@ -18,11 +18,11 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } let mut buffer = String::new(); - try!(io::stdin().read_to_string(&mut buffer).map_err(|e| format!("stdin: {}", e))); + io::stdin().read_to_string(&mut buffer).map_err(|e| format!("stdin: {}", e))?; if verbose { - let (success, explain) = try!(checker.explain(&buffer, NO_VARIABLES) - .map_err(|e| e.to_string())); + let (success, explain) = checker.explain(&buffer, NO_VARIABLES) + .map_err(|e| e.to_string())?; print!("{}", explain); if success { println!("OK"); @@ -30,18 +30,18 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } else { Err("Check failed".to_string()) } - } else if try!(checker.check(&buffer, NO_VARIABLES).map_err(|e| e.to_string())) { + } else if checker.check(&buffer, NO_VARIABLES).map_err(|e| e.to_string())? { Ok(()) } else { - let (_, explain) = try!(checker.explain(&buffer, NO_VARIABLES).map_err(|e| e.to_string())); + let (_, explain) = checker.explain(&buffer, NO_VARIABLES).map_err(|e| e.to_string())?; print!("{}", explain); Err("Check failed".to_string()) } } fn read_checkfile(filename: &str) -> Result { - let buffer = try!(read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))); + let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; let mut builder = CheckerBuilder::new(); - try!(builder.text(&buffer).map_err(|e| format!("{}: {}", filename, e))); + builder.text(&buffer).map_err(|e| format!("{}: {}", filename, e))?; Ok(builder.finish()) } diff --git a/src/utils.rs b/src/utils.rs index f89bc46b43..9cbe20b4bd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,9 +6,9 @@ use std::io::{Result, Read}; /// Read an entire file into a string. pub fn read_to_string>(path: P) -> Result { - let mut file = try!(File::open(path)); + let mut file = File::open(path)?; let mut buffer = String::new(); - try!(file.read_to_string(&mut buffer)); + file.read_to_string(&mut buffer)?; Ok(buffer) } From 9f8748ee086351b4e14fc4219fe5793a553949cd Mon Sep 17 00:00:00 2001 From: rep-nop Date: Sat, 25 Feb 2017 22:21:03 -0500 Subject: [PATCH 539/968] Fixes formatting for settings.rs --- lib/cretonne/src/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 0d81c38dce..5ffe5dc6a7 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -124,7 +124,7 @@ impl Configurable for Builder { } Detail::Enum { last, enumerators } => { self.bytes[offset] = parse_enum_value(value, - self.template.enums(last, enumerators))?; + self.template.enums(last, enumerators))?; } } Ok(()) From 7edbc90d5e0c9aa65e7e66f25b76b8648a471d02 Mon Sep 17 00:00:00 2001 From: rep-nop Date: Sat, 25 Feb 2017 23:56:16 -0500 Subject: [PATCH 540/968] Ran rustfmt --- lib/filecheck/src/explain.rs | 10 +++++----- lib/reader/src/parser.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs index 825f162079..75fe6f9286 100644 --- a/lib/filecheck/src/explain.rs +++ b/lib/filecheck/src/explain.rs @@ -112,11 +112,11 @@ impl<'a> Display for Explainer<'a> { // Emit the match message itself. writeln!(f, - "{} #{}{}: {}", - if m.is_match { "Matched" } else { "Missed" }, - m.directive, - if m.is_not { " not" } else { "" }, - m.regex)?; + "{} #{}{}: {}", + if m.is_match { "Matched" } else { "Missed" }, + m.directive, + if m.is_not { " not" } else { "" }, + m.regex)?; // Emit any variable definitions. if let Ok(found) = self.vardefs.binary_search_by_key(&m.directive, |v| v.directive) { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index e9a5a26a02..bd6fd8fce8 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -481,8 +481,8 @@ impl<'a> Parser<'a> { "set" => { last_set_loc = Some(self.loc); isaspec::parse_options(self.consume_line().trim().split_whitespace(), - &mut flag_builder, - &self.loc)?; + &mut flag_builder, + &self.loc)?; } "isa" => { last_set_loc = None; From 077f39d8daae07988c103b200797b3edbb95c1ea Mon Sep 17 00:00:00 2001 From: rep-nop Date: Sun, 26 Feb 2017 00:14:05 -0500 Subject: [PATCH 541/968] Fixes error on propagating a Result<()> with the `?` operator --- lib/cretonne/src/settings.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 5ffe5dc6a7..23ff7d688d 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -117,7 +117,9 @@ impl Configurable for Builder { let (offset, detail) = self.lookup(name)?; match detail { Detail::Bool { bit } => { - self.set_bit(offset, bit, parse_bool_value(value))?; + // Cannot currently propagate Result<()> up on functions returning () + // with the `?` operator + self.set_bit(offset, bit, parse_bool_value(value)?); } Detail::Num => { self.bytes[offset] = value.parse().map_err(|_| Error::BadValue)?; From 8616db60dc5a6e19dbcc177c84d75e56d4af8993 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 27 Feb 2017 15:03:06 -0800 Subject: [PATCH 542/968] Require a current Rust version. Rust 1.12 did work at one point, but Travis is testing against the current stable and beta releases, so that is the only versions we can claim to support. Fixes #51. --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 5656547e49..4a46f9267c 100644 --- a/README.rst +++ b/README.rst @@ -42,8 +42,9 @@ Building Cretonne ----------------- Cretonne is using the Cargo package manager format. First, ensure you have -installed `rust 1.12.0` or above. Then, change the working directory to your -clone of cretonne and run:: +installed a current stable rust (stable, beta, and nightly should all work, but +only stable and beta are tested consistently). Then, change the working +directory to your clone of cretonne and run:: cargo build From 6d3ee32f7b7cd02cbef285069d8a8dff9bbfa71a Mon Sep 17 00:00:00 2001 From: Davide Italiano Date: Tue, 28 Feb 2017 12:39:50 -0800 Subject: [PATCH 543/968] [EntityList] Fix typo. No functional change. --- lib/cretonne/src/entity_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index d94795c902..b2c75bd9ed 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -16,7 +16,7 @@ //! Entity lists are not as safe to use as `Vec`, but they never jeopardize Rust's memory safety //! guarantees. These are the problems to be aware of: //! -//! - If you lose track of an entity list, it's memory won't be recycled until the pool is cleared. +//! - If you lose track of an entity list, its memory won't be recycled until the pool is cleared. //! This can cause the pool to grow very large with leaked lists. //! - If entity lists are used after their pool is cleared, they may contain garbage data, and //! modifying them may corrupt other lists in the pool. From 8c5a69eb4727d4fc09d02cdc8c15f89e4aa6ec7e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Mar 2017 09:04:29 -0800 Subject: [PATCH 544/968] Fixed for mypy 0.501. The List and Dict types are no longer implicitly available. They must be imported from typing. Type annotations must appear before the doc comment in a function. Also fix type errors in these functions that weren't detected before. --- lib/cretonne/meta/cdsl/ast.py | 14 +++++++------- lib/cretonne/meta/cdsl/formats.py | 2 +- lib/cretonne/meta/cdsl/instructions.py | 2 +- lib/cretonne/meta/cdsl/isa.py | 2 +- lib/cretonne/meta/cdsl/registers.py | 2 +- lib/cretonne/meta/cdsl/types.py | 5 +++++ lib/cretonne/meta/cdsl/xform.py | 2 +- lib/cretonne/meta/gen_instr.py | 7 +++++++ lib/cretonne/meta/gen_registers.py | 2 +- lib/cretonne/meta/isa/__init__.py | 5 +++++ lib/cretonne/meta/srcgen.py | 2 +- 11 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index ddcc702575..931cc1e3a2 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -143,22 +143,22 @@ class Var(Expr): def is_input(self): # type: () -> bool """Is this an input value to the src pattern?""" - return not self.src_def and not self.dst_def + return self.src_def is None and self.dst_def is None def is_output(self): - """Is this an output value, defined in both src and dst patterns?""" # type: () -> bool - return self.src_def and self.dst_def + """Is this an output value, defined in both src and dst patterns?""" + return self.src_def is not None and self.dst_def is not None def is_intermediate(self): - """Is this an intermediate value, defined only in the src pattern?""" # type: () -> bool - return self.src_def and not self.dst_def + """Is this an intermediate value, defined only in the src pattern?""" + return self.src_def is not None and self.dst_def is None def is_temp(self): - """Is this a temp value, defined only in the dst pattern?""" # type: () -> bool - return not self.src_def and self.dst_def + """Is this a temp value, defined only in the dst pattern?""" + return self.src_def is None and self.dst_def is not None def get_typevar(self): # type: () -> TypeVar diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 32f4463ae0..974ca80541 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -6,7 +6,7 @@ from .operands import Operand # noqa # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: - from typing import Tuple, Union, Any, Sequence, Iterable # noqa + from typing import Dict, List, Tuple, Union, Any, Sequence, Iterable # noqa except ImportError: pass diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 84968c1b6b..4040e5338b 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -6,7 +6,7 @@ from .operands import Operand from .formats import InstructionFormat try: - from typing import Union, Sequence + from typing import Union, Sequence, List # noqa # List of operands for ins/outs: OpList = Union[Sequence[Operand], Operand] MaybeBoundInst = Union['Instruction', 'BoundInstruction'] diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 0065e4e907..7e4c09e58d 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -6,7 +6,7 @@ from .registers import RegClass, Register # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: - from typing import Tuple, Union, Any, Iterable, Sequence, TYPE_CHECKING # noqa + from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, TYPE_CHECKING # noqa from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa from .predicates import Predicate, FieldPredicate # noqa from .settings import SettingGroup # noqa diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 99dd453aec..003ce6379c 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -26,7 +26,7 @@ from . import is_power_of_two, next_power_of_two try: - from typing import Sequence, Tuple # noqa + from typing import Sequence, Tuple, List, Dict # noqa from .isa import TargetISA # noqa # A tuple uniquely identifying a register class inside a register bank. # (count, width, start) diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index 62334b7dd7..f96c5a2ef0 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -2,6 +2,11 @@ from __future__ import absolute_import import math +try: + from typing import Dict, List # noqa +except ImportError: + pass + # ValueType instances (i8, i32, ...) are provided in the cretonne.types module. class ValueType(object): diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index ee6710a79f..deca293e50 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -5,7 +5,7 @@ from __future__ import absolute_import from .ast import Def, Var, Apply try: - from typing import Union, Iterator, Sequence, Iterable # noqa + from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa from .ast import Expr # noqa DefApply = Union[Def, Apply] except ImportError: diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 8cf89e7654..166967698e 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -14,6 +14,13 @@ from cdsl.instructions import Instruction # noqa from cdsl.operands import Operand # noqa from cdsl.typevar import TypeVar # noqa +# The typing module is only required by mypy, and we don't use these imports +# outside type comments. +try: + from typing import List # noqa +except ImportError: + pass + def gen_formats(fmt): # type: (srcgen.Formatter) -> None diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index e36434f4d6..7dfdc45d00 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -6,7 +6,7 @@ from __future__ import absolute_import import srcgen try: - from typing import Sequence # noqa + from typing import Sequence, List # noqa from cdsl.isa import TargetISA # noqa from cdsl.registers import RegBank, RegClass # noqa except ImportError: diff --git a/lib/cretonne/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py index 8f623d18bb..bad91c5c90 100644 --- a/lib/cretonne/meta/isa/__init__.py +++ b/lib/cretonne/meta/isa/__init__.py @@ -9,6 +9,11 @@ from __future__ import absolute_import from cdsl.isa import TargetISA # noqa from . import riscv, intel, arm32, arm64 +try: + from typing import List # noqa +except ImportError: + pass + def all_isas(): # type: () -> List[TargetISA] diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py index 75b102f534..e87c16a577 100644 --- a/lib/cretonne/meta/srcgen.py +++ b/lib/cretonne/meta/srcgen.py @@ -11,7 +11,7 @@ import os import re try: - from typing import Any # noqa + from typing import Any, List # noqa except ImportError: pass From 00772fb6c40eeee12508a3e01e62c4c09f094f96 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Mar 2017 10:12:49 -0800 Subject: [PATCH 545/968] Add {ValueLoc,ArgumentLoc}::display(). These functions return an object containing the necessary ISA info to print a ValueLoc or ArgumentLoc correctly. --- lib/cretonne/src/ir/valueloc.rs | 61 ++++++++++++++++++++++++++++++++- lib/cretonne/src/write.rs | 8 ++--- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index ea7dea39d9..db1ee2baf2 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -3,8 +3,9 @@ //! The register allocator assigns every SSA value to either a register or a stack slot. This //! assignment is represented by a `ValueLoc` object. -use isa::RegUnit; +use isa::{RegInfo, RegUnit}; use ir::StackSlot; +use std::fmt; /// Value location. #[derive(Copy, Clone, Debug)] @@ -23,6 +24,35 @@ impl Default for ValueLoc { } } +impl ValueLoc { + /// Return an object that can display this value location, using the register info from the + /// target ISA. + pub fn display<'a, R: Into>>(self, regs: R) -> DisplayValueLoc<'a> { + DisplayValueLoc(self, regs.into()) + } +} + +/// Displaying a `ValueLoc` correctly requires the associated `RegInfo` from the target ISA. +/// Without the register info, register units are simply show as numbers. +/// +/// The `DisplayValueLoc` type can display the contained `ValueLoc`. +pub struct DisplayValueLoc<'a>(ValueLoc, Option<&'a RegInfo>); + +impl<'a> fmt::Display for DisplayValueLoc<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + ValueLoc::Unassigned => write!(f, "-"), + ValueLoc::Reg(ru) => { + match self.1 { + Some(regs) => write!(f, "{}", regs.display_regunit(ru)), + None => write!(f, "%{}", ru), + } + } + ValueLoc::Stack(ss) => write!(f, "{}", ss), + } + } +} + /// Function argument location. /// /// The ABI specifies how arguments are passed to a function, and where return values appear after @@ -56,3 +86,32 @@ impl Default for ArgumentLoc { ArgumentLoc::Unassigned } } + +impl ArgumentLoc { + /// Return an object that can display this argument location, using the register info from the + /// target ISA. + pub fn display<'a, R: Into>>(self, regs: R) -> DisplayArgumentLoc<'a> { + DisplayArgumentLoc(self, regs.into()) + } +} + +/// Displaying a `ArgumentLoc` correctly requires the associated `RegInfo` from the target ISA. +/// Without the register info, register units are simply show as numbers. +/// +/// The `DisplayArgumentLoc` type can display the contained `ArgumentLoc`. +pub struct DisplayArgumentLoc<'a>(ArgumentLoc, Option<&'a RegInfo>); + +impl<'a> fmt::Display for DisplayArgumentLoc<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + ArgumentLoc::Unassigned => write!(f, "-"), + ArgumentLoc::Reg(ru) => { + match self.1 { + Some(regs) => write!(f, "{}", regs.display_regunit(ru)), + None => write!(f, "%{}", ru), + } + } + ArgumentLoc::Stack(offset) => write!(f, "{}", offset), + } + } +} diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index eca9909961..957f8a036d 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -4,7 +4,7 @@ //! equivalent textual representation. This textual representation can be read back by the //! `cretonne-reader` crate. -use ir::{Function, Ebb, Inst, Value, Type, ValueLoc}; +use ir::{Function, Ebb, Inst, Value, Type}; use isa::TargetIsa; use std::fmt::{Result, Error, Write}; use std::result; @@ -177,11 +177,7 @@ fn write_instruction(w: &mut Write, if !func.locations.is_empty() { let regs = isa.register_info(); for r in func.dfg.inst_results(inst) { - match func.locations[r] { - ValueLoc::Unassigned => write!(s, ",-")?, - ValueLoc::Reg(ru) => write!(s, ",{}", regs.display_regunit(ru))?, - ValueLoc::Stack(ss) => write!(s, ",{}", ss)?, - } + write!(s, ",{}", func.locations[r].display(®s))? } } write!(s, "]")?; From cb3e503f07ab81f7d7a6a11a320a91157cc93a2f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 3 Mar 2017 10:34:29 -0800 Subject: [PATCH 546/968] Use ISA information to display function signatures. The argument locations contains register unit references that we want to display with their correct names. --- lib/cretonne/src/ir/extfunc.rs | 73 +++++++++++++++++++++++---------- lib/cretonne/src/ir/function.rs | 2 +- lib/cretonne/src/ir/valueloc.rs | 8 ++++ lib/cretonne/src/write.rs | 23 +++++++---- 4 files changed, 77 insertions(+), 29 deletions(-) diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 5c4d1d1c71..9f81d11442 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -6,8 +6,9 @@ //! This module declares the data types used to represent external functions and call signatures. use ir::{Type, FunctionName, SigRef, ArgumentLoc}; +use isa::RegInfo; use std::cmp; -use std::fmt::{self, Display, Formatter}; +use std::fmt; /// Function signature. /// @@ -55,34 +56,51 @@ impl Signature { .fold(0, cmp::max); self.argument_bytes = Some(bytes); } + + /// Return an object that can display `self` with correct register names. + pub fn display<'a, R: Into>>(&'a self, regs: R) -> DisplaySignature<'a> { + DisplaySignature(self, regs.into()) + } } -fn write_list(f: &mut Formatter, args: &Vec) -> fmt::Result { +/// Wrapper type capable of displaying a `Signature` with correct register names. +pub struct DisplaySignature<'a>(&'a Signature, Option<&'a RegInfo>); + +fn write_list(f: &mut fmt::Formatter, + args: &Vec, + regs: Option<&RegInfo>) + -> fmt::Result { match args.split_first() { None => {} Some((first, rest)) => { - write!(f, "{}", first)?; + write!(f, "{}", first.display(regs))?; for arg in rest { - write!(f, ", {}", arg)?; + write!(f, ", {}", arg.display(regs))?; } } } Ok(()) } -impl Display for Signature { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { +impl<'a> fmt::Display for DisplaySignature<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "(")?; - write_list(f, &self.argument_types)?; + write_list(f, &self.0.argument_types, self.1)?; write!(f, ")")?; - if !self.return_types.is_empty() { + if !self.0.return_types.is_empty() { write!(f, " -> ")?; - write_list(f, &self.return_types)?; + write_list(f, &self.0.return_types, self.1)?; } Ok(()) } } +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.display(None).fmt(f) + } +} + /// Function argument or return value type. /// /// This describes the value type being passed to or from a function along with flags that affect @@ -111,26 +129,39 @@ impl ArgumentType { location: Default::default(), } } + + /// Return an object that can display `self` with correct register names. + pub fn display<'a, R: Into>>(&'a self, regs: R) -> DisplayArgumentType<'a> { + DisplayArgumentType(self, regs.into()) + } } -impl Display for ArgumentType { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.value_type)?; - match self.extension { +/// Wrapper type capable of displaying an `ArgumentType` with correct register names. +pub struct DisplayArgumentType<'a>(&'a ArgumentType, Option<&'a RegInfo>); + +impl<'a> fmt::Display for DisplayArgumentType<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0.value_type)?; + match self.0.extension { ArgumentExtension::None => {} ArgumentExtension::Uext => write!(f, " uext")?, ArgumentExtension::Sext => write!(f, " sext")?, } - if self.inreg { + if self.0.inreg { write!(f, " inreg")?; } - // This really needs a `&TargetAbi` so we can print register units correctly. - match self.location { - ArgumentLoc::Reg(ru) => write!(f, " [%{}]", ru), - ArgumentLoc::Stack(offset) => write!(f, " [{}]", offset), - ArgumentLoc::Unassigned => Ok(()), + if self.0.location.is_assigned() { + write!(f, " [{}]", self.0.location.display(self.1))?; } + + Ok(()) + } +} + +impl fmt::Display for ArgumentType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.display(None).fmt(f) } } @@ -159,8 +190,8 @@ pub struct ExtFuncData { pub signature: SigRef, } -impl Display for ExtFuncData { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { +impl fmt::Display for ExtFuncData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", self.signature, self.name) } } diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 2828104daa..b1ae83dc28 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -20,7 +20,7 @@ pub struct Function { pub name: FunctionName, /// Signature of this function. - signature: Signature, + pub signature: Signature, /// Stack slots allocated in this function. pub stack_slots: EntityMap, diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index db1ee2baf2..253c5ceb33 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -88,6 +88,14 @@ impl Default for ArgumentLoc { } impl ArgumentLoc { + /// Is this an assigned location? (That is, not `Unassigned`). + pub fn is_assigned(&self) -> bool { + match self { + &ArgumentLoc::Unassigned => false, + _ => true, + } + } + /// Return an object that can display this argument location, using the register info from the /// target ISA. pub fn display<'a, R: Into>>(self, regs: R) -> DisplayArgumentLoc<'a> { diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 957f8a036d..40b625d6e0 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -5,16 +5,19 @@ //! `cretonne-reader` crate. use ir::{Function, Ebb, Inst, Value, Type}; -use isa::TargetIsa; +use isa::{TargetIsa, RegInfo}; use std::fmt::{Result, Error, Write}; use std::result; /// Write `func` to `w` as equivalent text. /// Use `isa` to emit ISA-dependent annotations. pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) -> Result { - write_spec(w, func)?; + let regs = isa.map(TargetIsa::register_info); + let regs = regs.as_ref(); + + write_spec(w, func, regs)?; writeln!(w, " {{")?; - let mut any = write_preamble(w, func)?; + let mut any = write_preamble(w, func, regs)?; for ebb in &func.layout { if any { writeln!(w, "")?; @@ -31,11 +34,14 @@ pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) - // // ====--------------------------------------------------------------------------------------====// -fn write_spec(w: &mut Write, func: &Function) -> Result { - write!(w, "function {}{}", func.name, func.own_signature()) +fn write_spec(w: &mut Write, func: &Function, regs: Option<&RegInfo>) -> Result { + write!(w, "function {}{}", func.name, func.signature.display(regs)) } -fn write_preamble(w: &mut Write, func: &Function) -> result::Result { +fn write_preamble(w: &mut Write, + func: &Function, + regs: Option<&RegInfo>) + -> result::Result { let mut any = false; for ss in func.stack_slots.keys() { @@ -47,7 +53,10 @@ fn write_preamble(w: &mut Write, func: &Function) -> result::Result // signatures. for sig in func.dfg.signatures.keys() { any = true; - writeln!(w, " {} = signature{}", sig, func.dfg.signatures[sig])?; + writeln!(w, + " {} = signature{}", + sig, + func.dfg.signatures[sig].display(regs))?; } for fnref in func.dfg.ext_funcs.keys() { From 408395db256db4a51c30647e0784e5633d1253ce Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 1 Mar 2017 11:53:52 -0800 Subject: [PATCH 547/968] Add a legalize_signature method to TargetIsa. This entry point will be used for controlling ABI conventions when legalizing. Provide an empty implementation for RISC-V and let the other ISAs crash in legalization. This is just the scaffolding. We still need to: - Rewrite the entry block arguments to match the legalized signature. - Rewrite call and return instructions. - Implement the legalize_signature() function for all ISAs. - Add shared generic types to help with the legalize_signature() functions. --- lib/cretonne/src/ir/function.rs | 5 ----- lib/cretonne/src/isa/mod.rs | 22 +++++++++++++++++++++- lib/cretonne/src/isa/riscv/abi.rs | 12 ++++++++++++ lib/cretonne/src/isa/riscv/mod.rs | 8 +++++++- lib/cretonne/src/legalizer.rs | 14 ++++++++++++++ 5 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 lib/cretonne/src/isa/riscv/abi.rs diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index b1ae83dc28..b0115a70e3 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -64,11 +64,6 @@ impl Function { pub fn new() -> Function { Self::with_name_signature(FunctionName::default(), Signature::new()) } - - /// Get the signature of this function. - pub fn own_signature(&self) -> &Signature { - &self.signature - } } impl Display for Function { diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index af689a3947..37684addb1 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -45,7 +45,7 @@ pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind}; use settings; -use ir::{InstructionData, DataFlowGraph}; +use ir::{InstructionData, DataFlowGraph, Signature}; pub mod riscv; pub mod intel; @@ -161,4 +161,24 @@ pub trait TargetIsa { recipe_names: self.recipe_names(), } } + + /// Legalize a function signature. + /// + /// This is used to legalize both the signature of the function being compiled and any called + /// functions. The signature should be modified by adding `ArgumentLoc` annotations to all + /// arguments and return values. + /// + /// Arguments with types that are not supported by the ABI can be expanded into multiple + /// arguments: + /// + /// - Integer types that are too large to fit in a register can be broken into multiple + /// arguments of a smaller integer type. + /// - Floating point types can be bit-cast to an integer type of the same size, and possible + /// broken into smaller integer types. + /// - Vector types can be bit-cast and broken down into smaller vectors or scalars. + /// + /// The legalizer will adapt argument and return values as necessary at all ABI boundaries. + fn legalize_signature(&self, _sig: &mut Signature) { + unimplemented!() + } } diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs new file mode 100644 index 0000000000..7025bf8c0e --- /dev/null +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -0,0 +1,12 @@ +//! RISC-V ABI implementation. +//! +//! This module implements the RISC-V calling convention through the primary `legalize_signature()` +//! entry point. + +use ir::Signature; +use settings as shared_settings; + +/// Legalize `sig` for RISC-V. +pub fn legalize_signature(_sig: &mut Signature, _flags: &shared_settings::Flags) { + // TODO: Actually do something. +} diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 8b27a46119..220f062278 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -1,6 +1,7 @@ //! RISC-V Instruction Set Architecture. pub mod settings; +mod abi; mod enc_tables; mod registers; @@ -8,7 +9,7 @@ use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; -use ir::{InstructionData, DataFlowGraph}; +use ir::{InstructionData, DataFlowGraph, Signature}; #[allow(dead_code)] struct Isa { @@ -74,6 +75,11 @@ impl TargetIsa for Isa { fn recipe_constraints(&self) -> &'static [RecipeConstraints] { &enc_tables::RECIPE_CONSTRAINTS } + + fn legalize_signature(&self, sig: &mut Signature) { + // We can pass in `self.isa_flags` too, if we need it. + abi::legalize_signature(sig, &self.shared_flags) + } } #[cfg(test)] diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 015bfb9b30..3d57c73b95 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -23,6 +23,8 @@ use isa::{TargetIsa, Legalize}; /// - Fill out `func.encodings`. /// pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { + legalize_signatures(func, isa); + // TODO: This is very simplified and incomplete. func.encodings.resize(func.dfg.num_insts()); let mut pos = Cursor::new(&mut func.layout); @@ -74,3 +76,15 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { // // Concretely, this defines private functions `narrow()`, and `expand()`. include!(concat!(env!("OUT_DIR"), "/legalizer.rs")); + +/// Legalize all the function signatures in `func`. +/// +/// This changes all signatures to be ABI-compliant with full `ArgumentLoc` annotations. It doesn't +/// change the entry block arguments, calls, or return instructions, so this can leave the function +/// in a state with type discrepancies. +fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { + isa.legalize_signature(&mut func.signature); + for sig in func.dfg.signatures.keys() { + isa.legalize_signature(&mut func.dfg.signatures[sig]); + } +} From e84a4e41a0de6aa18e075257a62cfc68598378c4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 2 Mar 2017 16:01:56 -0800 Subject: [PATCH 548/968] Implement legalize_signature for RISC-V. Add an abi module with code that is probably useful to all ISAs when implementing this function. Add a unit() method to RegClassData which can be used to index the register units in a class. --- filetests/isa/riscv/abi.cton | 23 ++++++ lib/cretonne/meta/gen_registers.py | 1 + lib/cretonne/src/abi.rs | 72 +++++++++++++++++++ lib/cretonne/src/isa/registers.rs | 9 +++ lib/cretonne/src/isa/riscv/abi.rs | 76 ++++++++++++++++++-- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/regalloc/allocatable_set.rs | 2 + 7 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 filetests/isa/riscv/abi.cton create mode 100644 lib/cretonne/src/abi.rs diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton new file mode 100644 index 0000000000..adb2dfed07 --- /dev/null +++ b/filetests/isa/riscv/abi.cton @@ -0,0 +1,23 @@ +; Test the legalization of function signatures. +test legalizer +isa riscv + +function f(i32) { + sig0 = signature(i32) -> i32 + +; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + + sig1 = signature(i64) -> b1 +; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + +; The i64 argument must go in an even-odd register pair. + sig2 = signature(f32, i64) -> f64 +; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + +; Spilling into the stack args. + sig3 = signature(f64, f64, f64, f64, f64, f64, f64, i64) -> f64 +; check: sig3 = signature(f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] + +ebb0(v0: i32): + return_reg v0 +} diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index 7dfdc45d00..b1532761a6 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -37,6 +37,7 @@ def gen_regclass(rc, fmt): fmt.line('name: "{}",'.format(rc.name)) fmt.line('index: {},'.format(rc.index)) fmt.line('width: {},'.format(rc.width)) + fmt.line('first: {},'.format(rc.bank.first_unit + rc.start)) fmt.line('subclasses: 0x{:x},'.format(rc.subclass_mask())) mask = ', '.join('0x{:08x}'.format(x) for x in rc.mask()) fmt.line('mask: [{}],'.format(mask)) diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs new file mode 100644 index 0000000000..897133bc76 --- /dev/null +++ b/lib/cretonne/src/abi.rs @@ -0,0 +1,72 @@ +//! Common helper code for ABI lowering. +//! +//! This module provides functions and data structures that are useful for implementing the +//! `TargetIsa::legalize_signature()` method. + +use ir::{ArgumentLoc, ArgumentType, Type}; + +/// Legalization action to perform on a single argument or return value. +/// +/// An argument may go through a sequence of legalization steps before it reaches the final +/// `Assign` action. +pub enum ArgAction { + /// Assign the argument to the given location. + Assign(ArgumentLoc), + + /// Split the argument into smaller parts, then call again. + /// + /// This action can split an integer type into two smaller integer arguments, or it can split a + /// SIMD vector into halves. + /// + /// Floating point scalar types can't be split. + Split, +} + +/// Common trait for assigning arguments to registers or stack locations. +/// +/// This will be implemented by individual ISAs. +pub trait ArgAssigner { + /// Pick an assignment action for function argument (or return value) `arg`. + fn assign(&mut self, arg: &ArgumentType) -> ArgAction; +} + +/// Legalize the arguments in `args` using the given argument assigner. +/// +/// This function can be used for both arguments and return values. +pub fn legalize_args(args: &mut Vec, aa: &mut AA) { + // Iterate over the arguments. + // We may need to mutate the vector in place, so don't use a normal iterator, and clone the + // argument to avoid holding a reference. + let mut argno = 0; + while let Some(arg) = args.get(argno).cloned() { + // Leave the pre-assigned arguments alone. + // We'll assume that they don't interfere with our assignments. + if arg.location.is_assigned() { + argno += 1; + continue; + } + + match aa.assign(&arg) { + // Assign argument to a location and move on to the next one. + ArgAction::Assign(loc) => { + args[argno].location = loc; + argno += 1; + } + // Split this argument into two smaller ones. Then revisit both. + ArgAction::Split => { + let new_arg = ArgumentType { value_type: split_type(arg.value_type), ..arg }; + args[argno].value_type = new_arg.value_type; + args.insert(argno + 1, new_arg); + } + } + } +} + +/// Given a value type that isn't legal, compute a replacement type half the size. +fn split_type(ty: Type) -> Type { + if ty.is_int() { + ty.half_width().expect("Integer type too small to split") + } else { + ty.half_vector().expect("Can only split integers and vectors") + } +} diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index c785ab2ac9..c51413eb20 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -108,6 +108,9 @@ pub struct RegClassData { /// How many register units to allocate per register. pub width: u8, + /// The first register unit in this class. + pub first: RegUnit, + /// Bit-mask of sub-classes of this register class, including itself. /// /// Bits correspond to RC indexes. @@ -142,6 +145,12 @@ impl RegClassData { pub fn has_subclass>(&self, other: RCI) -> bool { self.subclasses & (1 << other.into().0) != 0 } + + /// Get a specific register unit in this class. + pub fn unit(&self, offset: usize) -> RegUnit { + let uoffset = offset * self.width as usize; + self.first + uoffset as RegUnit + } } /// A small reference to a register class. diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 7025bf8c0e..5c9630f8f4 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -2,11 +2,79 @@ //! //! This module implements the RISC-V calling convention through the primary `legalize_signature()` //! entry point. +//! +//! This doesn't support the soft-float ABI at the moment. -use ir::Signature; +use abi::{ArgAction, ArgAssigner, legalize_args}; +use ir::{Signature, ArgumentType, ArgumentLoc}; +use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; -/// Legalize `sig` for RISC-V. -pub fn legalize_signature(_sig: &mut Signature, _flags: &shared_settings::Flags) { - // TODO: Actually do something. +struct Args { + pointer_bits: u16, + pointer_bytes: u32, + regs: u32, + offset: u32, +} + +impl Args { + fn new(bits: u16) -> Args { + Args { + pointer_bits: bits, + pointer_bytes: bits as u32 / 8, + regs: 0, + offset: 0, + } + } +} + +impl ArgAssigner for Args { + fn assign(&mut self, arg: &ArgumentType) -> ArgAction { + fn align(value: u32, to: u32) -> u32 { + (value + to - 1) & !(to - 1) + } + + let ty = arg.value_type; + + // Check for a legal type. + // RISC-V doesn't have SIMD at all, so break all vectors down. + if !ty.is_scalar() { + return ArgAction::Split; + } + + // Large integers and booleans are broken down to fit in a register. + if !ty.is_float() && ty.bits() > self.pointer_bits { + // Align registers and stack to a multiple of two pointers. + self.regs = align(self.regs, 2); + self.offset = align(self.offset, 2 * self.pointer_bytes); + return ArgAction::Split; + } + + if self.regs < 8 { + // Assign to a register. + let reg = if ty.is_float() { + FPR.unit(10 + self.regs as usize) + } else { + GPR.unit(10 + self.regs as usize) + }; + self.regs += 1; + ArgAction::Assign(ArgumentLoc::Reg(reg)) + } else { + // Assign a stack location. + let loc = ArgumentLoc::Stack(self.offset); + self.offset += self.pointer_bytes; + ArgAction::Assign(loc) + } + } +} + +/// Legalize `sig` for RISC-V. +pub fn legalize_signature(sig: &mut Signature, flags: &shared_settings::Flags) { + let bits = if flags.is_64bit() { 64 } else { 32 }; + + let mut args = Args::new(bits); + legalize_args(&mut sig.argument_types, &mut args); + + let mut rets = Args::new(bits); + legalize_args(&mut sig.return_types, &mut rets); } diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 47339a39ca..2839ecfc1f 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -21,6 +21,7 @@ pub mod settings; pub mod sparse_map; pub mod verifier; +mod abi; mod constant_hash; mod context; mod legalizer; diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 39058c04e4..40ef3c3636 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -121,6 +121,7 @@ mod tests { name: "GPR", index: 0, width: 1, + first: 28, subclasses: 0, mask: [0xf0000000, 0x0000000f, 0], }; @@ -128,6 +129,7 @@ mod tests { name: "DPR", index: 0, width: 2, + first: 28, subclasses: 0, mask: [0x50000000, 0x0000000a, 0], }; From 5185cce1e9f5ca4a0961c74fa50b04c16f90c58e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 6 Mar 2017 14:51:59 -0800 Subject: [PATCH 549/968] Add take_ebb_args(), put_ebb_arg() method pair. These two methods can be use to rewrite the argument values to an EBB. In particular, we need to rewrite the arguments to the entry block to be compatible with a legalized function signature. Reuse the put_ebb_arg() method in the implementation of append_ebb_arg(). --- lib/cretonne/src/ir/dfg.rs | 111 ++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 25 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 94117095a7..593d3a7907 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -505,36 +505,13 @@ impl DataFlowGraph { /// Append an argument with type `ty` to `ebb`. pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { - let num_args = self.num_ebb_args(ebb); - assert!(num_args <= u16::MAX as usize, "Too many arguments to EBB"); let val = self.make_value(ValueData::Arg { ty: ty, ebb: ebb, - num: num_args as u16, + num: 0, next: None.into(), }); - match self.ebbs[ebb].last_arg.expand() { - // If last_argument is `None`, we're adding the first EBB argument. - None => { - self.ebbs[ebb].first_arg = val.into(); - } - Some(last_arg) => { - match last_arg.expand() { - // Append to linked list of arguments. - ExpandedValue::Table(idx) => { - if let ValueData::Arg { ref mut next, .. } = self.extended_values[idx] { - *next = val.into(); - } else { - panic!("inconsistent value table entry for EBB argument"); - } - } - ExpandedValue::Direct(_) => { - panic!("inconsistent value table entry for EBB argument") - } - } - } - } - self.ebbs[ebb].last_arg = val.into(); + self.put_ebb_arg(ebb, val); val } @@ -545,6 +522,74 @@ impl DataFlowGraph { cur: self.ebbs[ebb].first_arg.into(), } } + + /// Given a value that is known to be an EBB argument, return the next EBB argument. + pub fn next_ebb_arg(&self, arg: Value) -> Option { + if let ExpandedValue::Table(index) = arg.expand() { + if let ValueData::Arg { next, .. } = self.extended_values[index] { + return next.into(); + } + } + panic!("{} is not an EBB argument", arg); + } + + /// Detach all the arguments from `ebb` and return the first one. + /// + /// The whole list of detached arguments can be traversed with `next_ebb_arg`. This method does + /// not return a `Values` iterator since it is often necessary to mutate the DFG while + /// processing the list of arguments. + /// + /// This is a quite low-level operation. Sensible things to do with the detached EBB arguments + /// is to put them back on the same EBB with `put_ebb_arg()` or change them into aliases + /// with `change_to_alias()`. + pub fn take_ebb_args(&mut self, ebb: Ebb) -> Option { + let first = self.ebbs[ebb].first_arg.into(); + self.ebbs[ebb].first_arg = None.into(); + self.ebbs[ebb].last_arg = None.into(); + first + } + + /// Append an existing argument value to `ebb`. + /// + /// The appended value should already be an EBB argument belonging to `ebb`, but it can't be + /// attached. In practice, this means that it should be one of the values returned from + /// `take_ebb_args()`. + /// + /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. + pub fn put_ebb_arg(&mut self, ebb: Ebb, arg: Value) { + let arg_num = match self.ebbs[ebb].last_arg.map(|v| v.expand()) { + // If last_argument is `None`, we're adding the first EBB argument. + None => { + self.ebbs[ebb].first_arg = arg.into(); + 0 + } + // Append to the linked list of arguments. + Some(ExpandedValue::Table(idx)) => { + if let ValueData::Arg { ref mut next, num, .. } = self.extended_values[idx] { + *next = arg.into(); + assert!(num < u16::MAX, "Too many arguments to EBB"); + num + 1 + } else { + panic!("inconsistent value table entry for EBB argument"); + } + } + _ => panic!("inconsistent value table entry for EBB argument"), + }; + self.ebbs[ebb].last_arg = arg.into(); + + // Now update `arg` itself. + let arg_ebb = ebb; + if let ExpandedValue::Table(idx) = arg.expand() { + if let ValueData::Arg { ref mut num, ebb, ref mut next, .. } = + self.extended_values[idx] { + *num = arg_num; + *next = None.into(); + assert_eq!(arg_ebb, ebb, "{} should already belong to EBB", arg); + return; + } + } + panic!("{} must be an EBB argument value", arg); + } } // Contents of an extended basic block. @@ -630,6 +675,9 @@ mod tests { assert_eq!(ebb.to_string(), "ebb0"); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb).next(), None); + assert_eq!(dfg.take_ebb_args(ebb), None); + assert_eq!(dfg.num_ebb_args(ebb), 0); + assert_eq!(dfg.ebb_args(ebb).next(), None); let arg1 = dfg.append_ebb_arg(ebb, types::F32); assert_eq!(arg1.to_string(), "vx0"); @@ -653,6 +701,19 @@ mod tests { assert_eq!(dfg.value_def(arg2), ValueDef::Arg(ebb, 1)); assert_eq!(dfg.value_type(arg1), types::F32); assert_eq!(dfg.value_type(arg2), types::I16); + + // Swap the two EBB arguments. + let take1 = dfg.take_ebb_args(ebb).unwrap(); + assert_eq!(dfg.num_ebb_args(ebb), 0); + assert_eq!(dfg.ebb_args(ebb).next(), None); + let take2 = dfg.next_ebb_arg(take1).unwrap(); + assert_eq!(take1, arg1); + assert_eq!(take2, arg2); + assert_eq!(dfg.next_ebb_arg(take2), None); + dfg.put_ebb_arg(ebb, take2); + let arg3 = dfg.append_ebb_arg(ebb, types::I32); + dfg.put_ebb_arg(ebb, take1); + assert_eq!(dfg.ebb_args(ebb).collect::>(), [take2, arg3, take1]); } #[test] From 5d266acb5f1bda662fe4595ddb75468ca9a21fcd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 6 Mar 2017 13:57:36 -0800 Subject: [PATCH 550/968] Legalize entry block arguments to match ABI types. Insert conversion code that reconstructs the original function argument types from the legalized ABI signature. Add abi::legalize_abi_value(). This function is used when adapting code to a legalized function signature. --- filetests/isa/riscv/abi.cton | 11 ++ filetests/isa/riscv/legalize-i64.cton | 16 +-- lib/cretonne/src/abi.rs | 156 +++++++++++++++++++++++--- lib/cretonne/src/ir/types.rs | 11 ++ lib/cretonne/src/isa/riscv/abi.rs | 6 +- lib/cretonne/src/legalizer.rs | 115 ++++++++++++++++++- 6 files changed, 288 insertions(+), 27 deletions(-) diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index adb2dfed07..e3bafb83e1 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -2,6 +2,9 @@ test legalizer isa riscv +; regex: V=v\d+ +; regex: VX=vx\d+ + function f(i32) { sig0 = signature(i32) -> i32 @@ -21,3 +24,11 @@ function f(i32) { ebb0(v0: i32): return_reg v0 } + +function int_split_args(i64) -> i64 { +ebb0(v0: i64): +; check: $ebb0($(v0l=$VX): i32, $(v0h=$VX): i32): +; check: iconcat_lohi $v0l, $v0h + v1 = iadd_imm v0, 1 + return v0 +} diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index 41bc2e2947..fb8d412d8c 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -10,8 +10,8 @@ ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#ec ; sameln: $(v3l=$V) = band $v1l, $v2l ; check: [R#ec @@ -23,8 +23,8 @@ ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#cc ; sameln: $(v3l=$V) = bor $v1l, $v2l ; check: [R#cc @@ -36,8 +36,8 @@ ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#8c ; sameln: $(v3l=$V) = bxor $v1l, $v2l ; check: [R#8c @@ -52,8 +52,8 @@ ebb0(v1: i64, v2: i64): v3 = iadd v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi $v2 +; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi +; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi ; check: [R#0c ; sameln: $(v3l=$V) = iadd $v1l, $v2l ; check: $(c=$V) = icmp ult, $v3l, $v1l diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs index 897133bc76..8d02de4aca 100644 --- a/lib/cretonne/src/abi.rs +++ b/lib/cretonne/src/abi.rs @@ -3,23 +3,65 @@ //! This module provides functions and data structures that are useful for implementing the //! `TargetIsa::legalize_signature()` method. -use ir::{ArgumentLoc, ArgumentType, Type}; +use ir::{ArgumentLoc, ArgumentType, ArgumentExtension, Type}; +use std::cmp::Ordering; -/// Legalization action to perform on a single argument or return value. +/// Legalization action to perform on a single argument or return value when converting a +/// signature. /// /// An argument may go through a sequence of legalization steps before it reaches the final /// `Assign` action. +#[derive(Clone, Copy)] pub enum ArgAction { /// Assign the argument to the given location. Assign(ArgumentLoc), - /// Split the argument into smaller parts, then call again. + /// Convert the argument, then call again. /// /// This action can split an integer type into two smaller integer arguments, or it can split a /// SIMD vector into halves. - /// - /// Floating point scalar types can't be split. - Split, + Convert(ValueConversion), +} + +/// Legalization action to be applied to a value that is being passed to or from a legalized ABI. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ValueConversion { + /// Split an integer types into low and high parts, using `isplit_lohi`. + IntSplit, + + /// Split a vector type into halves with identical lane types. + VectorSplit, + + /// Bit-cast to an integer type of the same size. + IntBits, + + /// Sign-extend integer value to the required type. + Sext(Type), + + /// Unsigned zero-extend value to the required type. + Uext(Type), +} + +impl ValueConversion { + /// Apply this conversion to a type, return the converted type. + pub fn apply(self, ty: Type) -> Type { + match self { + ValueConversion::IntSplit => ty.half_width().expect("Integer type too small to split"), + ValueConversion::VectorSplit => ty.half_vector().expect("Not a vector"), + ValueConversion::IntBits => Type::int(ty.bits()).expect("Bad integer size"), + ValueConversion::Sext(nty) => nty, + ValueConversion::Uext(nty) => nty, + } + } + + /// Is this a split conversion that results in two arguments? + pub fn is_split(self) -> bool { + match self { + ValueConversion::IntSplit => true, + ValueConversion::VectorSplit => true, + _ => false, + } + } } /// Common trait for assigning arguments to registers or stack locations. @@ -53,20 +95,104 @@ pub fn legalize_args(args: &mut Vec, aa: &mut AA) argno += 1; } // Split this argument into two smaller ones. Then revisit both. - ArgAction::Split => { - let new_arg = ArgumentType { value_type: split_type(arg.value_type), ..arg }; + ArgAction::Convert(conv) => { + let new_arg = ArgumentType { value_type: conv.apply(arg.value_type), ..arg }; args[argno].value_type = new_arg.value_type; - args.insert(argno + 1, new_arg); + if conv.is_split() { + args.insert(argno + 1, new_arg); + } } } } } -/// Given a value type that isn't legal, compute a replacement type half the size. -fn split_type(ty: Type) -> Type { - if ty.is_int() { - ty.half_width().expect("Integer type too small to split") - } else { - ty.half_vector().expect("Can only split integers and vectors") +/// Determine the right action to take when passing a `have` value type to a call signature where +/// the next argument is `arg` which has a different value type. +/// +/// The signature legalization process in `legalize_args` above can replace a single argument value +/// with multiple arguments of smaller types. It can also change the type of an integer argument to +/// a larger integer type, requiring the smaller value to be sign- or zero-extended. +/// +/// The legalizer needs to repair the values at all ABI boundaries: +/// +/// - Incoming function arguments to the entry EBB. +/// - Function arguments passed to a call. +/// - Return values from a call. +/// - Return values passed to a return instruction. +/// +/// The `legalize_abi_value` function helps the legalizer with the process. When the legalizer +/// needs to pass a pre-legalized `have` argument, but the ABI argument `arg` has a different value +/// type, `legalize_abi_value(have, arg)` tells the legalizer how to create the needed value type +/// for the argument. +/// +/// It may be necessary to call `legalize_abi_value` more than once for a given argument before the +/// desired argument type appears. This will happen when a vector or integer type needs to be split +/// more than once, for example. +pub fn legalize_abi_value(have: Type, arg: &ArgumentType) -> ValueConversion { + let have_bits = have.bits(); + let arg_bits = arg.value_type.bits(); + + match have_bits.cmp(&arg_bits) { + // We have fewer bits than the ABI argument. + Ordering::Less => { + assert!(have.is_int() && arg.value_type.is_int(), + "Can only extend integer values"); + match arg.extension { + ArgumentExtension::Uext => ValueConversion::Uext(arg.value_type), + ArgumentExtension::Sext => ValueConversion::Sext(arg.value_type), + _ => panic!("No argument extension specified"), + } + } + // We have the same number of bits as the argument. + Ordering::Equal => { + // This must be an integer vector that is split and then extended. + assert!(arg.value_type.is_int()); + assert!(!have.is_scalar()); + ValueConversion::VectorSplit + } + // We have more bits than the argument. + Ordering::Greater => { + if have.is_scalar() { + if have.is_float() { + // Convert a float to int so it can be split the next time. + // ARM would do this to pass an `f64` in two registers. + ValueConversion::IntBits + } else { + ValueConversion::IntSplit + } + } else { + ValueConversion::VectorSplit + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::types; + use ir::ArgumentType; + + #[test] + fn legalize() { + let mut arg = ArgumentType::new(types::I32); + + assert_eq!(legalize_abi_value(types::I64X2, &arg), + ValueConversion::VectorSplit); + assert_eq!(legalize_abi_value(types::I64, &arg), + ValueConversion::IntSplit); + + // Vector of integers is broken down, then sign-extended. + arg.extension = ArgumentExtension::Sext; + assert_eq!(legalize_abi_value(types::I16X4, &arg), + ValueConversion::VectorSplit); + assert_eq!(legalize_abi_value(types::I16.by(2).unwrap(), &arg), + ValueConversion::VectorSplit); + assert_eq!(legalize_abi_value(types::I16, &arg), + ValueConversion::Sext(types::I32)); + + // 64-bit float is split as an integer. + assert_eq!(legalize_abi_value(types::F64, &arg), + ValueConversion::IntBits); } } diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index cdd2207aad..2391ca6072 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -67,6 +67,17 @@ impl Type { } } + /// Get an integer type with the requested number of bits. + pub fn int(bits: u16) -> Option { + match bits { + 8 => Some(I8), + 16 => Some(I16), + 32 => Some(I32), + 64 => Some(I64), + _ => None, + } + } + /// Get a type with the same number of lanes as this type, but with the lanes replaced by /// booleans of the same size. /// diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 5c9630f8f4..7813a59cef 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -5,7 +5,7 @@ //! //! This doesn't support the soft-float ABI at the moment. -use abi::{ArgAction, ArgAssigner, legalize_args}; +use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; use ir::{Signature, ArgumentType, ArgumentLoc}; use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; @@ -39,7 +39,7 @@ impl ArgAssigner for Args { // Check for a legal type. // RISC-V doesn't have SIMD at all, so break all vectors down. if !ty.is_scalar() { - return ArgAction::Split; + return ArgAction::Convert(ValueConversion::VectorSplit); } // Large integers and booleans are broken down to fit in a register. @@ -47,7 +47,7 @@ impl ArgAssigner for Args { // Align registers and stack to a multiple of two pointers. self.regs = align(self.regs, 2); self.offset = align(self.offset, 2 * self.pointer_bytes); - return ArgAction::Split; + return ArgAction::Convert(ValueConversion::IntSplit); } if self.regs < 8 { diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 3d57c73b95..885a034084 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -13,7 +13,9 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. -use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; +use abi::{legalize_abi_value, ValueConversion}; +use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder, Ebb, Type, Value, + ArgumentType}; use ir::condcodes::IntCC; use isa::{TargetIsa, Legalize}; @@ -87,4 +89,115 @@ fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { for sig in func.dfg.signatures.keys() { isa.legalize_signature(&mut func.dfg.signatures[sig]); } + + if let Some(entry) = func.layout.entry_block() { + legalize_entry_arguments(func, entry); + } +} + +/// Legalize the entry block arguments after `func`'s signature has been legalized. +/// +/// The legalized signature may contain more arguments than the original signature, and the +/// argument types have been changed. This function goes through the arguments to the entry EBB and +/// replaces them with arguments of the right type for the ABI. +/// +/// The original entry EBB arguments are computed from the new ABI arguments by code inserted at +/// the top of the entry block. +fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { + // Insert position for argument conversion code. + // We want to insert instructions before the first instruction in the entry block. + // If the entry block is empty, append instructions to it instead. + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(entry); + pos.next_inst(); + + // Keep track of the argument types in the ABI-legalized signature. + let abi_types = &func.signature.argument_types; + let mut abi_arg = 0; + + // Process the EBB arguments one at a time, possibly replacing one argument with multiple new + // ones. We do this by detaching the entry EBB arguments first. + let mut next_arg = func.dfg.take_ebb_args(entry); + while let Some(arg) = next_arg { + // Get the next argument before we mutate `arg`. + next_arg = func.dfg.next_ebb_arg(arg); + + let arg_type = func.dfg.value_type(arg); + if arg_type == abi_types[abi_arg].value_type { + // No value translation is necessary, this argument matches the ABI type. + // Just use the original EBB argument value. This is the most common case. + func.dfg.put_ebb_arg(entry, arg); + abi_arg += 1; + } else { + // Compute the value we want for `arg` from the legalized ABI arguments. + let converted = convert_from_abi(&mut func.dfg, + &mut pos, + entry, + &mut abi_arg, + abi_types, + arg_type); + // The old `arg` is no longer an attached EBB argument, but there are probably still + // uses of the value. Make it an alias to the converted value. + func.dfg.change_to_alias(arg, converted); + } + } +} + +/// Compute original value of type `ty` from the legalized ABI arguments beginning at `abi_arg`. +/// +/// Update `abi_arg` to reflect the ABI arguments consumed and return the computed value. +fn convert_from_abi(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + entry: Ebb, + abi_arg: &mut usize, + abi_types: &[ArgumentType], + ty: Type) + -> Value { + // Terminate the recursion when we get the desired type. + if ty == abi_types[*abi_arg].value_type { + return dfg.append_ebb_arg(entry, ty); + } + + // Reconstruct how `ty` was legalized into the argument at `abi_arg`. + let conversion = legalize_abi_value(ty, &abi_types[*abi_arg]); + + // The conversion describes value to ABI argument. We implement the reverse conversion here. + match conversion { + // Construct a `ty` by concatenating two ABI integers. + ValueConversion::IntSplit => { + let abi_ty = ty.half_width().expect("Invalid type for conversion"); + let lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + dfg.ins(pos).iconcat_lohi(lo, hi) + } + // Construct a `ty` by concatenating two halves of a vector. + ValueConversion::VectorSplit => { + let abi_ty = ty.half_vector().expect("Invalid type for conversion"); + let _lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let _hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + unimplemented!() + } + // Construct a `ty` by bit-casting from an integer type. + ValueConversion::IntBits => { + assert!(!ty.is_int()); + let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); + let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + dfg.ins(pos).bitcast(ty, arg) + } + // ABI argument is a sign-extended version of the value we want. + ValueConversion::Sext(abi_ty) => { + let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. + // We could insert an `assert_sreduce` which would fold with a following `sextend` of + // this value. + dfg.ins(pos).ireduce(ty, arg) + } + ValueConversion::Uext(abi_ty) => { + let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. + // We could insert an `assert_ureduce` which would fold with a following `uextend` of + // this value. + dfg.ins(pos).ireduce(ty, arg) + } + } } From 25677d1bd8b20ac2a10cb1fa7a90783cc7f84a28 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 7 Mar 2017 14:15:55 -0800 Subject: [PATCH 551/968] Add vsplit and vconcat instructions. Add support for two new type variable functions: half_vector() and double_vector(). Use these two instructions to break down unsupported SIMD types and build them up again. --- docs/langref.rst | 9 ++----- filetests/isa/riscv/abi.cton | 22 +++++++++++++++++ lib/cretonne/meta/base/instructions.py | 34 ++++++++++++++++++++++++++ lib/cretonne/meta/cdsl/typevar.py | 26 ++++++++++++++++++++ lib/cretonne/src/ir/instructions.rs | 8 ++++++ lib/cretonne/src/ir/types.rs | 2 ++ lib/cretonne/src/legalizer.rs | 6 ++--- 7 files changed, 97 insertions(+), 10 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 23cc225d7f..5fb42c44a2 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -668,14 +668,9 @@ allocation pass and beyond. Vector operations ----------------- +.. autoinst:: vsplit +.. autoinst:: vconcat .. autoinst:: vselect - -.. inst:: a = vbuild x, y, z, ... - - Vector build. - - Build a vector value from the provided lanes. - .. autoinst:: splat .. autoinst:: insertlane .. autoinst:: extractlane diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index e3bafb83e1..bb27ac20ae 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -21,6 +21,14 @@ function f(i32) { sig3 = signature(f64, f64, f64, f64, f64, f64, f64, i64) -> f64 ; check: sig3 = signature(f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] +; Splitting vectors. + sig4 = signature(i32x4) +; check: sig4 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) + +; Splitting vectors, then splitting ints. + sig5 = signature(i64x4) +; check: sig5 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) + ebb0(v0: i32): return_reg v0 } @@ -32,3 +40,17 @@ ebb0(v0: i64): v1 = iadd_imm v0, 1 return v0 } + +function vector_split_args(i64x4) -> i64 { +ebb0(v0: i64x4): +; check: $ebb0($(v0al=$VX): i32, $(v0ah=$VX): i32, $(v0bl=$VX): i32, $(v0bh=$VX): i32, $(v0cl=$VX): i32, $(v0ch=$VX): i32, $(v0dl=$VX): i32, $(v0dh=$VX): i32): +; check: $(v0a=$V) = iconcat_lohi $v0al, $v0ah +; check: $(v0b=$V) = iconcat_lohi $v0bl, $v0bh +; check: $(v0ab=$V) = vconcat $v0a, $v0b +; check: $(v0c=$V) = iconcat_lohi $v0cl, $v0ch +; check: $(v0d=$V) = iconcat_lohi $v0dl, $v0dh +; check: $(v0cd=$V) = vconcat $v0c, $v0d +; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd + v1 = iadd v0, v0 + return v0 +} diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 9cec7d9d58..09cd2c75c7 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -264,6 +264,40 @@ fill = Instruction( # Vector operations # +x = Operand('x', TxN, doc='Vector to split') +lo = Operand('lo', TxN.half_vector(), doc='Low-numbered lanes of `x`') +hi = Operand('hi', TxN.half_vector(), doc='High-numbered lanes of `x`') + +vsplit = Instruction( + 'vsplit', r""" + Split a vector into two halves. + + Split the vector `x` into two separate values, each containing half of + the lanes from ``x``. The result may be two scalars if ``x`` only had + two lanes. + """, + ins=x, outs=(lo, hi)) + +Any128 = TypeVar( + 'Any128', 'Any scalar or vector type with as most 128 lanes', + ints=True, floats=True, bools=True, scalars=True, simd=(1, 128)) +x = Operand('x', Any128, doc='Low-numbered lanes') +y = Operand('y', Any128, doc='High-numbered lanes') +a = Operand('a', Any128.double_vector(), doc='Concatenation of `x` and `y`') + +vconcat = Instruction( + 'vconcat', r""" + Vector concatenation. + + Return a vector formed by concatenating ``x`` and ``y``. The resulting + vector type has twice as many lanes as each of the inputs. The lanes of + ``x`` appear as the low-numbered lanes, and the lanes of ``y`` become + the high-numbered lanes of ``a``. + + It is possible to form a vector by concatenating two scalars. + """, + ins=(x, y), outs=a) + c = Operand('c', TxN.as_bool(), doc='Controlling vector') x = Operand('x', TxN, doc='Value to use where `c` is true') y = Operand('y', TxN, doc='Value to use where `c` is false') diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 580d367f51..908ebbd8cd 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -315,6 +315,8 @@ class TypeVar(object): ASBOOL = 'as_bool' HALFWIDTH = 'half_width' DOUBLEWIDTH = 'double_width' + HALFVECTOR = 'half_vector' + DOUBLEVECTOR = 'double_vector' @staticmethod def derived(base, derived_func): @@ -396,6 +398,30 @@ class TypeVar(object): return TypeVar.derived(self, self.DOUBLEWIDTH) + def half_vector(self): + # type: () -> TypeVar + """ + Return a derived type variable that has half the number of vector lanes + as this one, with the same lane type. + """ + if not self.is_derived: + ts = self.type_set + assert ts.min_lanes > 1, "Can't halve a scalar type" + + return TypeVar.derived(self, self.HALFVECTOR) + + def double_vector(self): + # type: () -> TypeVar + """ + Return a derived type variable that has twice the number of vector + lanes as this one, with the same lane type. + """ + if not self.is_derived: + ts = self.type_set + assert ts.max_lanes < 256, "Can't double 256 lanes." + + return TypeVar.derived(self, self.DOUBLEVECTOR) + def free_typevar(self): # type: () -> TypeVar """ diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index c4ac3b0c20..2a956ffc86 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -708,6 +708,12 @@ enum OperandConstraint { /// This operand is `ctrlType.double_width()`. DoubleWidth, + + /// This operand is `ctrlType.half_vector()`. + HalfVector, + + /// This operand is `ctrlType.double_vector()`. + DoubleVector, } impl OperandConstraint { @@ -725,6 +731,8 @@ impl OperandConstraint { AsBool => Some(ctrl_type.as_bool()), HalfWidth => Some(ctrl_type.half_width().expect("invalid type for half_width")), DoubleWidth => Some(ctrl_type.double_width().expect("invalid type for double_width")), + HalfVector => Some(ctrl_type.half_vector().expect("invalid type for half_vector")), + DoubleVector => Some(ctrl_type.by(2).expect("invalid type for double_vector")), } } } diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index 2391ca6072..550f25804b 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -217,6 +217,8 @@ impl Type { } /// Get a SIMD vector with half the number of lanes. + /// + /// There is no `double_vector()` method. Use `t.by(2)` instead. pub fn half_vector(self) -> Option { if self.is_scalar() { None diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 885a034084..9223a174b0 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -173,9 +173,9 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, // Construct a `ty` by concatenating two halves of a vector. ValueConversion::VectorSplit => { let abi_ty = ty.half_vector().expect("Invalid type for conversion"); - let _lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); - let _hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); - unimplemented!() + let lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + dfg.ins(pos).vconcat(lo, hi) } // Construct a `ty` by bit-casting from an integer type. ValueConversion::IntBits => { From 83d3a1020dc8bc332ee21a244c02dd46fe685f89 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 7 Mar 2017 15:07:00 -0800 Subject: [PATCH 552/968] Heed uext and sext annotations on RISC-V arguments. Translate the small integer arguments to i32 or i64 with the appropriate extend and ireduce instructions. --- filetests/isa/riscv/abi.cton | 7 +++++++ lib/cretonne/src/isa/riscv/abi.rs | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index bb27ac20ae..0282e94874 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -41,6 +41,13 @@ ebb0(v0: i64): return v0 } +function int_ext(i8, i8 sext, i8 uext) -> i8 { +ebb0(v1: i8, v2: i8, v3: i8): +; check: $ebb0($v1: i8, $(v2x=$VX): i32, $(v3x=$VX): i32): +; check: ireduce.i8 $v2x +; check: ireduce.i8 $v3x +} + function vector_split_args(i64x4) -> i64 { ebb0(v0: i64x4): ; check: $ebb0($(v0al=$VX): i32, $(v0ah=$VX): i32, $(v0bl=$VX): i32, $(v0bh=$VX): i32, $(v0cl=$VX): i32, $(v0ch=$VX): i32, $(v0dl=$VX): i32, $(v0dh=$VX): i32): diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 7813a59cef..9d78abc007 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -6,13 +6,14 @@ //! This doesn't support the soft-float ABI at the moment. use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; -use ir::{Signature, ArgumentType, ArgumentLoc}; +use ir::{Signature, Type, ArgumentType, ArgumentLoc, ArgumentExtension}; use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; struct Args { pointer_bits: u16, pointer_bytes: u32, + pointer_type: Type, regs: u32, offset: u32, } @@ -22,6 +23,7 @@ impl Args { Args { pointer_bits: bits, pointer_bytes: bits as u32 / 8, + pointer_type: Type::int(bits).unwrap(), regs: 0, offset: 0, } @@ -50,6 +52,19 @@ impl ArgAssigner for Args { return ArgAction::Convert(ValueConversion::IntSplit); } + // Small integers are extended to the size of a pointer register. + if ty.is_int() && ty.bits() < self.pointer_bits { + match arg.extension { + ArgumentExtension::None => {} + ArgumentExtension::Uext => { + return ArgAction::Convert(ValueConversion::Uext(self.pointer_type)) + } + ArgumentExtension::Sext => { + return ArgAction::Convert(ValueConversion::Sext(self.pointer_type)) + } + } + } + if self.regs < 8 { // Assign to a register. let reg = if ty.is_float() { From 3578fbc42868bb924ef4162a4b7947a59c2b32ea Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 7 Mar 2017 15:13:55 -0800 Subject: [PATCH 553/968] Implement From traits on ArgAction for convenience. --- lib/cretonne/src/abi.rs | 12 ++++++++++++ lib/cretonne/src/isa/riscv/abi.rs | 16 ++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs index 8d02de4aca..d011311e7c 100644 --- a/lib/cretonne/src/abi.rs +++ b/lib/cretonne/src/abi.rs @@ -23,6 +23,18 @@ pub enum ArgAction { Convert(ValueConversion), } +impl From for ArgAction { + fn from(x: ArgumentLoc) -> ArgAction { + ArgAction::Assign(x) + } +} + +impl From for ArgAction { + fn from(x: ValueConversion) -> ArgAction { + ArgAction::Convert(x) + } +} + /// Legalization action to be applied to a value that is being passed to or from a legalized ABI. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ValueConversion { diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 9d78abc007..fa158dceb5 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -41,7 +41,7 @@ impl ArgAssigner for Args { // Check for a legal type. // RISC-V doesn't have SIMD at all, so break all vectors down. if !ty.is_scalar() { - return ArgAction::Convert(ValueConversion::VectorSplit); + return ValueConversion::VectorSplit.into(); } // Large integers and booleans are broken down to fit in a register. @@ -49,19 +49,15 @@ impl ArgAssigner for Args { // Align registers and stack to a multiple of two pointers. self.regs = align(self.regs, 2); self.offset = align(self.offset, 2 * self.pointer_bytes); - return ArgAction::Convert(ValueConversion::IntSplit); + return ValueConversion::IntSplit.into(); } // Small integers are extended to the size of a pointer register. if ty.is_int() && ty.bits() < self.pointer_bits { match arg.extension { ArgumentExtension::None => {} - ArgumentExtension::Uext => { - return ArgAction::Convert(ValueConversion::Uext(self.pointer_type)) - } - ArgumentExtension::Sext => { - return ArgAction::Convert(ValueConversion::Sext(self.pointer_type)) - } + ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(), + ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(), } } @@ -73,12 +69,12 @@ impl ArgAssigner for Args { GPR.unit(10 + self.regs as usize) }; self.regs += 1; - ArgAction::Assign(ArgumentLoc::Reg(reg)) + ArgumentLoc::Reg(reg).into() } else { // Assign a stack location. let loc = ArgumentLoc::Stack(self.offset); self.offset += self.pointer_bytes; - ArgAction::Assign(loc) + loc.into() } } } From fd9d7fae7668dd768029785d971095f1bb14a7f0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 8 Mar 2017 12:14:27 -0800 Subject: [PATCH 554/968] Strip trailing white space from regex: directives. This was particularly confusing when reading a file with Windows line endings. The CR would become part of the regex. --- lib/filecheck/src/checker.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index 2474742fb1..3101865e71 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -71,7 +71,8 @@ impl Directive { var, rest))); } - Ok(Directive::Regex(var, rest[varlen + 1..].to_string())) + // Ignore trailing white space in the regex, including CR. + Ok(Directive::Regex(var, rest[varlen + 1..].trim_right().to_string())) } } @@ -410,10 +411,12 @@ mod tests { Ok(true)); assert_eq!(b.directive("[x86]sameln: $x $(y=[^]]*) there").map_err(e2s), Ok(true)); + // Windows line ending sneaking in. + assert_eq!(b.directive("regex: Y=foo\r").map_err(e2s), Ok(true)); let c = b.finish(); assert_eq!(c.to_string(), "#0 regex: X=more text\n#1 not: patt $(x) $(y) here\n#2 sameln: $(x) \ - $(y=[^]]*) there\n"); + $(y=[^]]*) there\n#3 regex: Y=foo\n"); } } From a081b09c94fe76208dfb0d59463a29742b657d02 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Fri, 3 Mar 2017 00:06:52 +0000 Subject: [PATCH 555/968] Added parsing of instruction encodings and result registers specifications. --- lib/reader/src/parser.rs | 145 +++++++++++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 13 deletions(-) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index bd6fd8fce8..f2061afec7 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -6,7 +6,7 @@ // ====--------------------------------------------------------------------------------------====// use std::str::FromStr; -use std::u32; +use std::{u16, u32}; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, @@ -17,7 +17,7 @@ use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, TernaryOverflowData, JumpData, BranchData, CallData, IndirectCallData, ReturnData, ReturnRegData}; -use cretonne::isa; +use cretonne::isa::{self, TargetIsa, Encoding}; use cretonne::settings; use testfile::{TestFile, Details, Comment}; use error::{Location, Error, Result}; @@ -40,11 +40,26 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { let mut parser = Parser::new(text); // Gather the preamble comments as 'Function'. parser.gather_comments(AnyEntity::Function); + + let commands = parser.parse_test_commands(); + let isa_spec = parser.parse_isa_specs()?; + let preamble_comments = parser.take_comments(); + + let functions = { + let mut unique_isa = None; + if let isaspec::IsaSpec::Some(ref isa_vec) = isa_spec { + if isa_vec.len() == 1 { + unique_isa = Some(&*isa_vec[0]); + } + } + parser.parse_function_list(unique_isa)? + }; + Ok(TestFile { - commands: parser.parse_test_commands(), - isa_spec: parser.parse_isa_specs()?, - preamble_comments: parser.take_comments(), - functions: parser.parse_function_list()?, + commands: commands, + isa_spec: isa_spec, + preamble_comments: preamble_comments, + functions: functions, }) } @@ -71,16 +86,35 @@ pub struct Parser<'a> { // // Many entities like values, stack slots, and function signatures are referenced in the `.cton` // file by number. We need to map these numbers to real references. -struct Context { +struct Context<'a> { function: Function, map: SourceMap, + + // Reference to the unique_isa for things like parsing ISA-specific instruction encoding + // information. This is only `Some` if exactly one set of `isa` directives were found in the + // prologue (it is valid to have directives for multiple different ISAs, but in that case we + // couldn't know which ISA the provided encodings are intended for) + unique_isa: Option<&'a TargetIsa>, } -impl Context { - fn new(f: Function) -> Context { +impl<'a> Context<'a> { + fn new(f: Function, unique_isa: Option<&'a TargetIsa>) -> Context<'a> { Context { function: f, map: SourceMap::new(), + unique_isa: unique_isa + } + } + + // Get the index of a recipe name if it exists. + fn find_recipe_index(&self, recipe_name: &str) -> Option { + if let Some(unique_isa) = self.unique_isa { + unique_isa.recipe_names() + .iter() + .position(|&name| name == recipe_name) + .map(|idx| idx as u16) + } else { + None } } @@ -454,6 +488,41 @@ impl<'a> Parser<'a> { } } + // Match and consume an identifier. + fn match_any_identifier(&mut self, err_msg: &str) -> Result<&'a str> { + if let Some(Token::Identifier(text)) = self.token() { + self.consume(); + Ok(text) + } else { + err!(self.loc, err_msg) + } + } + + // Match and consume a HexSequence that fits into a u16. + // This is used for instruction encodings. + fn match_hex16(&mut self, err_msg: &str) -> Result { + if let Some(Token::HexSequence(bits_str)) = self.token() { + self.consume(); + // The only error we anticipate from this parse is overflow, the lexer should + // already have ensured that the string doesn't contain invalid characters, and + // isn't empty or negative. + u16::from_str_radix(bits_str, 16) + .map_err(|_| self.error("the hex sequence given overflows the u16 type")) + } else { + err!(self.loc, err_msg) + } + } + + // Match and consume a name. + fn match_name(&mut self, err_msg: &str) -> Result<&'a str> { + if let Some(Token::Name(name)) = self.token() { + self.consume(); + Ok(name) + } else { + err!(self.loc, err_msg) + } + } + /// Parse a list of test commands. pub fn parse_test_commands(&mut self) -> Vec> { let mut list = Vec::new(); @@ -523,10 +592,11 @@ impl<'a> Parser<'a> { /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. - pub fn parse_function_list(&mut self) -> Result)>> { + pub fn parse_function_list(&mut self, unique_isa: Option<&TargetIsa>) + -> Result)>> { let mut list = Vec::new(); while self.token().is_some() { - list.push(self.parse_function()?); + list.push(self.parse_function(unique_isa)?); } Ok(list) } @@ -535,7 +605,7 @@ impl<'a> Parser<'a> { // // function ::= * function-spec "{" preamble function-body "}" // - fn parse_function(&mut self) -> Result<(Function, Details<'a>)> { + fn parse_function(&mut self, unique_isa: Option<&TargetIsa>) -> Result<(Function, Details<'a>)> { // Begin gathering comments. // Make sure we don't include any comments before the `function` keyword. self.token(); @@ -543,7 +613,7 @@ impl<'a> Parser<'a> { self.gather_comments(AnyEntity::Function); let (location, name, sig) = self.parse_function_spec()?; - let mut ctx = Context::new(Function::with_name_signature(name, sig)); + let mut ctx = Context::new(Function::with_name_signature(name, sig), unique_isa); // function ::= function-spec * "{" preamble function-body "}" self.match_token(Token::LBrace, "expected '{' before function body")?; @@ -894,6 +964,42 @@ impl<'a> Parser<'a> { ctx.map.def_value(vx, value, &vx_location) } + fn parse_instruction_encoding(&mut self, ctx: &Context) + -> Result<(Option, Option>)> { + let (mut encoding, mut result_registers) = (None, None); + + // encoding ::= "[" encoding_literal result_registers "]" + if self.optional(Token::LBracket) { + // encoding_literal ::= "-" | Identifier HexSequence + if !self.optional(Token::Minus) { + let recipe = self.match_any_identifier("expected instruction encoding or '-'")?; + let bits = self.match_hex16("expected a hex sequence")?; + + if let Some(recipe_index) = ctx.find_recipe_index(recipe) { + encoding = Some(Encoding::new(recipe_index, bits)); + } + } + + // result_registers ::= ("," ( "-" | names ) )? + // names ::= Name { "," Name } + if self.optional(Token::Comma) && !self.optional(Token::Minus) { + let mut result = Vec::new(); + + result.push(self.match_name("expected register")?); + while self.optional(Token::Comma) { + result.push(self.match_name("expected register")?); + } + + result_registers = Some(result); + } + + self.match_token(Token::RBracket, + "expected ']' to terminate instruction encoding")?; + } + + Ok((encoding, result_registers)) + } + // Parse an instruction, append it to `ebb`. // // instruction ::= [inst-results "="] Opcode(opc) ["." Type] ... @@ -903,6 +1009,8 @@ impl<'a> Parser<'a> { // Collect comments for the next instruction to be allocated. self.gather_comments(ctx.function.dfg.next_inst()); + let (encoding, result_registers) = self.parse_instruction_encoding(ctx)?; + // Result value numbers. let mut results = Vec::new(); @@ -921,6 +1029,13 @@ impl<'a> Parser<'a> { self.match_token(Token::Equal, "expected '=' before opcode")?; } + if let Some(ref result_registers) = result_registers { + if result_registers.len() != results.len() { + return err!(self.loc, + "must have same number of result registers as results"); + } + } + // instruction ::= [inst-results "="] * Opcode(opc) ["." Type] ... let opcode = if let Some(Token::Identifier(text)) = self.token() { match text.parse() { @@ -955,6 +1070,10 @@ impl<'a> Parser<'a> { ctx.function.layout.append_inst(inst, ebb); ctx.map.def_entity(inst.into(), &opcode_loc).expect("duplicate inst references created"); + if let Some(encoding) = encoding { + *ctx.function.encodings.ensure(inst) = encoding; + } + if results.len() != num_results { return err!(self.loc, "instruction produces {} result values, {} given", From 10f86baf63b1b6fdacc0c9f625cf3dbf0ae98ac0 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Fri, 3 Mar 2017 21:30:33 +0000 Subject: [PATCH 556/968] Added tests, some refactoring, fixed a parsing bug. --- filetests/parser/instruction_encoding.cton | 25 +++++++++++++ lib/reader/src/isaspec.rs | 12 +++++++ lib/reader/src/parser.rs | 41 +++++++++++----------- 3 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 filetests/parser/instruction_encoding.cton diff --git a/filetests/parser/instruction_encoding.cton b/filetests/parser/instruction_encoding.cton new file mode 100644 index 0000000000..49c1913bcf --- /dev/null +++ b/filetests/parser/instruction_encoding.cton @@ -0,0 +1,25 @@ +test cat + +isa riscv + +; regex: WS=[ \t]* + +function foo(i32, i32) { +ebb1(v0: i32, v1: i32): + [-,-] v2 = iadd v0, v1 + [-] trap + [R#1234, %x5, %x11] v6, v7 = iadd_cout v2, v0 + [Rshamt#beef, %x25] v8 = ishl_imm v6, 2 + v9 = iadd v8, v7 + [Iret#5] return v0, v8 +} +; sameln: function foo(i32, i32) { +; nextln: $ebb1($v0: i32, $v1: i32): +; nextln: [-]$WS $v2 = iadd $v0, $v1 +; nextln: [-]$WS trap +; nextln: [0#1234]$WS $v6, $v7 = iadd_cout $v2, $v0 +; TODO Add the full encoding information available: instruction recipe name and architectural registers if specified +; nextln: [2#beef]$WS $v8 = ishl_imm $v6, 2 +; nextln: [-]$WS $v9 = iadd $v8, $v7 +; nextln: [3#05]$WS return $v0, $v8 +; nextln: } \ No newline at end of file diff --git a/lib/reader/src/isaspec.rs b/lib/reader/src/isaspec.rs index 706c081e3a..b7884bc42c 100644 --- a/lib/reader/src/isaspec.rs +++ b/lib/reader/src/isaspec.rs @@ -22,6 +22,18 @@ pub enum IsaSpec { Some(Vec>), } +impl IsaSpec { + /// If the `IsaSpec` contains exactly 1 `TargetIsa` we return a reference to it + pub fn unique_isa(&self) -> Option<&TargetIsa> { + if let &IsaSpec::Some(ref isa_vec) = self { + if isa_vec.len() == 1 { + return Some(&*isa_vec[0]); + } + } + None + } +} + /// Parse an iterator of command line options and apply them to `config`. pub fn parse_options<'a, I>(iter: I, config: &mut Configurable, loc: &Location) -> Result<()> where I: Iterator diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f2061afec7..1c71f9f4f5 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -44,16 +44,7 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { let commands = parser.parse_test_commands(); let isa_spec = parser.parse_isa_specs()?; let preamble_comments = parser.take_comments(); - - let functions = { - let mut unique_isa = None; - if let isaspec::IsaSpec::Some(ref isa_vec) = isa_spec { - if isa_vec.len() == 1 { - unique_isa = Some(&*isa_vec[0]); - } - } - parser.parse_function_list(unique_isa)? - }; + let functions = parser.parse_function_list(isa_spec.unique_isa())?; Ok(TestFile { commands: commands, @@ -102,7 +93,7 @@ impl<'a> Context<'a> { Context { function: f, map: SourceMap::new(), - unique_isa: unique_isa + unique_isa: unique_isa, } } @@ -592,8 +583,9 @@ impl<'a> Parser<'a> { /// Parse a list of function definitions. /// /// This is the top-level parse function matching the whole contents of a file. - pub fn parse_function_list(&mut self, unique_isa: Option<&TargetIsa>) - -> Result)>> { + pub fn parse_function_list(&mut self, + unique_isa: Option<&TargetIsa>) + -> Result)>> { let mut list = Vec::new(); while self.token().is_some() { list.push(self.parse_function(unique_isa)?); @@ -605,7 +597,9 @@ impl<'a> Parser<'a> { // // function ::= * function-spec "{" preamble function-body "}" // - fn parse_function(&mut self, unique_isa: Option<&TargetIsa>) -> Result<(Function, Details<'a>)> { + fn parse_function(&mut self, + unique_isa: Option<&TargetIsa>) + -> Result<(Function, Details<'a>)> { // Begin gathering comments. // Make sure we don't include any comments before the `function` keyword. self.token(); @@ -916,6 +910,7 @@ impl<'a> Parser<'a> { while match self.token() { Some(Token::Value(_)) => true, Some(Token::Identifier(_)) => true, + Some(Token::LBracket) => true, _ => false, } { self.parse_instruction(ctx, ebb)?; @@ -964,8 +959,9 @@ impl<'a> Parser<'a> { ctx.map.def_value(vx, value, &vx_location) } - fn parse_instruction_encoding(&mut self, ctx: &Context) - -> Result<(Option, Option>)> { + fn parse_instruction_encoding(&mut self, + ctx: &Context) + -> Result<(Option, Option>)> { let (mut encoding, mut result_registers) = (None, None); // encoding ::= "[" encoding_literal result_registers "]" @@ -977,6 +973,11 @@ impl<'a> Parser<'a> { if let Some(recipe_index) = ctx.find_recipe_index(recipe) { encoding = Some(Encoding::new(recipe_index, bits)); + } else if ctx.unique_isa.is_some() { + return err!(self.loc, "invalid instruction recipe"); + } else { + return err!(self.loc, + "provided instruction encoding for unspecified ISA"); } } @@ -1525,7 +1526,7 @@ mod tests { ss3 = stack_slot 13 ss1 = stack_slot 1 }") - .parse_function() + .parse_function(None) .unwrap(); assert_eq!(func.name.to_string(), "foo"); let mut iter = func.stack_slots.keys(); @@ -1542,7 +1543,7 @@ mod tests { ss1 = stack_slot 13 ss1 = stack_slot 1 }") - .parse_function() + .parse_function(None) .unwrap_err() .to_string(), "3: duplicate stack slot: ss1"); @@ -1554,7 +1555,7 @@ mod tests { ebb0: ebb4(vx3: i32): }") - .parse_function() + .parse_function(None) .unwrap(); assert_eq!(func.name.to_string(), "ebbs"); @@ -1583,7 +1584,7 @@ mod tests { trap ; Instruction } ; Trailing. ; More trailing.") - .parse_function() + .parse_function(None) .unwrap(); assert_eq!(func.name.to_string(), "comment"); assert_eq!(comments.len(), 8); // no 'before' comment. From 7a45aeebebf1820564ccfdf615633d1e8800f6f2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 8 Mar 2017 12:50:43 -0800 Subject: [PATCH 557/968] Use a unique ISA in 'test cat' file tests. Add a Function::display() method which can include ISA-specific information when printing the function. If a test file has a unique ISA, use that in the `test cat` implementation. --- filetests/parser/instruction_encoding.cton | 8 ++++---- lib/cretonne/src/ir/function.rs | 16 +++++++++++++++- src/cat.rs | 2 +- src/filetest/runone.rs | 5 ++++- src/filetest/subtest.rs | 6 +++--- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/filetests/parser/instruction_encoding.cton b/filetests/parser/instruction_encoding.cton index 49c1913bcf..0e40d0b2f1 100644 --- a/filetests/parser/instruction_encoding.cton +++ b/filetests/parser/instruction_encoding.cton @@ -17,9 +17,9 @@ ebb1(v0: i32, v1: i32): ; nextln: $ebb1($v0: i32, $v1: i32): ; nextln: [-]$WS $v2 = iadd $v0, $v1 ; nextln: [-]$WS trap -; nextln: [0#1234]$WS $v6, $v7 = iadd_cout $v2, $v0 +; nextln: [R#1234]$WS $v6, $v7 = iadd_cout $v2, $v0 ; TODO Add the full encoding information available: instruction recipe name and architectural registers if specified -; nextln: [2#beef]$WS $v8 = ishl_imm $v6, 2 +; nextln: [Rshamt#beef]$WS $v8 = ishl_imm $v6, 2 ; nextln: [-]$WS $v9 = iadd $v8, $v7 -; nextln: [3#05]$WS return $v0, $v8 -; nextln: } \ No newline at end of file +; nextln: [Iret#05]$WS return $v0, $v8 +; nextln: } diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index b0115a70e3..1abf9c4b35 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -6,7 +6,7 @@ use std::fmt::{self, Display, Debug, Formatter}; use ir::{FunctionName, Signature, Value, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData, ValueLoc, DataFlowGraph, Layout}; -use isa::Encoding; +use isa::{TargetIsa, Encoding}; use entity_map::{EntityMap, PrimaryEntityData}; use write::write_function; @@ -64,6 +64,20 @@ impl Function { pub fn new() -> Function { Self::with_name_signature(FunctionName::default(), Signature::new()) } + + /// Return an object that can display this function with correct ISA-specific annotations. + pub fn display<'a, I: Into>>(&'a self, isa: I) -> DisplayFunction<'a> { + DisplayFunction(self, isa.into()) + } +} + +/// Wrapper type capable of displaying a `Function` with correct ISA annotations. +pub struct DisplayFunction<'a>(&'a Function, Option<&'a TargetIsa>); + +impl<'a> Display for DisplayFunction<'a> { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + write_function(fmt, self.0, self.1) + } } impl Display for Function { diff --git a/src/cat.rs b/src/cat.rs index 443c37dadc..fd49722ec0 100644 --- a/src/cat.rs +++ b/src/cat.rs @@ -61,6 +61,6 @@ impl SubTest for TestCat { } fn run(&self, func: Cow, context: &Context) -> STResult<()> { - subtest::run_filecheck(&func.to_string(), context) + subtest::run_filecheck(&func.display(context.isa).to_string(), context) } } diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index 06e4cfce6b..2067a245b9 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -90,7 +90,10 @@ fn test_tuples<'a>(tests: &'a [Box], } } } else { - out.push((&**test, no_isa_flags, None)); + // This test doesn't require an ISA, and we only want to run one instance of it. + // Still, give it an ISA ref if we happen to have a unique one. + // For example, `test cat` can use this to print encodings and register names. + out.push((&**test, no_isa_flags, isa_spec.unique_isa())); } } Ok(out) diff --git a/src/filetest/subtest.rs b/src/filetest/subtest.rs index 8ba3528a6c..170490fb3a 100644 --- a/src/filetest/subtest.rs +++ b/src/filetest/subtest.rs @@ -10,7 +10,7 @@ use filecheck::{self, CheckerBuilder, Checker, Value as FCValue}; pub type Result = result::Result; -/// Context for running a a test on a single function. +/// Context for running a test on a single function. pub struct Context<'a> { /// Comments from the preamble f the test file. These apply to all functions. pub preamble_comments: &'a [Comment<'a>], @@ -24,8 +24,8 @@ pub struct Context<'a> { /// ISA-independent flags for this test. pub flags: &'a Flags, - /// Target ISA to test against. Only present for sub-tests whose `needs_isa` method returned - /// true. + /// Target ISA to test against. Only guaranteed to be present for sub-tests whose `needs_isa` + /// method returned `true`. For other sub-tests, this is set if the test file has a unique ISA. pub isa: Option<&'a TargetIsa>, } From 58756e5d3463749ede0188e37cf0a29d2f2d06e5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 8 Mar 2017 14:48:50 -0800 Subject: [PATCH 558/968] Add is_call and is_return instruction attributes. --- lib/cretonne/meta/base/instructions.py | 10 ++++------ lib/cretonne/meta/cdsl/instructions.py | 18 +++++++++++++++--- lib/cretonne/meta/gen_instr.py | 26 ++++---------------------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 09cd2c75c7..a61f98cefe 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -111,7 +111,7 @@ x_return = Instruction( provided return values. The list of return values must match the function signature's return types. """, - ins=rvals, is_terminator=True) + ins=rvals, is_return=True, is_terminator=True) raddr = Operand('raddr', iAddr, doc='Return address') @@ -130,7 +130,7 @@ return_reg = Instruction( :inst:`return` will be legalized into this instruction on these architectures. """, - ins=(raddr, rvals), is_terminator=True) + ins=(raddr, rvals), is_return=True, is_terminator=True) FN = Operand( 'FN', @@ -145,8 +145,7 @@ call = Instruction( Call a function which has been declared in the preamble. The argument types must match the function's signature. """, - ins=(FN, args), - outs=rvals) + ins=(FN, args), outs=rvals, is_call=True) SIG = Operand('SIG', entities.sig_ref, doc='function signature') callee = Operand('callee', iAddr, doc='address of function to call') @@ -158,8 +157,7 @@ call_indirect = Instruction( Call the function pointed to by `callee` with the given arguments. The called function must match the specified signature. """, - ins=(SIG, callee, args), - outs=rvals) + ins=(SIG, callee, args), outs=rvals, is_call=True) # # Materializing constants. diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 4040e5338b..fcce1684fd 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -80,9 +80,22 @@ class Instruction(object): values or `variable_args`. :param is_terminator: This is a terminator instruction. :param is_branch: This is a branch instruction. + :param is_call: This is a call instruction. + :param is_return: This is a return instruction. :param can_trap: This instruction can trap. """ + # Boolean instruction attributes that can be passed as keyword arguments to + # the constructor. Map attribute name to doc comment for generated Rust + # code. + ATTRIBS = { + 'is_terminator': 'True for instructions that terminate the EBB.', + 'is_branch': 'True for all branch or jump instructions.', + 'is_call': 'Is this a call instruction?', + 'is_return': 'Is this a return instruction?', + 'can_trap': 'Can this instruction cause a trap?', + } + def __init__(self, name, doc, ins=(), outs=(), **kwargs): # type: (str, str, OpList, OpList, **Any) -> None # noqa self.name = name @@ -95,9 +108,8 @@ class Instruction(object): self.value_results = tuple( i for i, o in enumerate(self.outs) if o.is_value()) self._verify_polymorphic() - self.is_branch = 'is_branch' in kwargs - self.is_terminator = 'is_terminator' in kwargs - self.can_trap = 'can_trap' in kwargs + for attr in Instruction.ATTRIBS: + setattr(self, attr, not not kwargs.get(attr, False)) InstructionGroup.append(self) def __str__(self): diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 166967698e..353fa60551 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -288,31 +288,13 @@ def gen_opcodes(groups, fmt): fmt.line() with fmt.indented('impl Opcode {', '}'): - attrs = [ - { - 'name': 'is_branch', - 'comment': 'True for all branch instructions.' - }, - { - 'name': 'is_terminator', - 'comment': 'True for instructions that terminate EBB.' - }, - { - 'name': 'can_trap', - 'comment': 'True if instruction could trap.' - } - ] - - for attr in attrs: - if attr != attrs[0]: - fmt.line() - - fmt.doc_comment(attr['comment']) + for attr in sorted(Instruction.ATTRIBS.keys()): + fmt.doc_comment(Instruction.ATTRIBS[attr]) with fmt.indented('pub fn {}(self) -> bool {{' - .format(attr['name']), '}'): + .format(attr), '}'): with fmt.indented('match self {', '}'): for i in instrs: - if getattr(i, attr['name']): + if getattr(i, attr): fmt.format( 'Opcode::{} => true,', i.camel_name, i.name) From bc1901b766782176c9c2edede466de09387eee15 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 8 Mar 2017 19:43:24 -0800 Subject: [PATCH 559/968] Handle a half-full func.locations map. If func.locations has not been properly resized to have an entry for all values, we should just full in the default location for the missing values instead of crashing. --- lib/cretonne/src/write.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 40b625d6e0..b0ef3a03d3 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -186,7 +186,9 @@ fn write_instruction(w: &mut Write, if !func.locations.is_empty() { let regs = isa.register_info(); for r in func.dfg.inst_results(inst) { - write!(s, ",{}", func.locations[r].display(®s))? + write!(s, + ",{}", + func.locations.get(r).cloned().unwrap_or_default().display(®s))? } } write!(s, "]")?; From cfdecfdcbf738a7017aa543a7542265fc77231df Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 9 Mar 2017 02:45:20 +0000 Subject: [PATCH 560/968] We now parse and record a ValueLoc for each SSA value result of each instruction. Code currently not passing tests. --- filetests/parser/instruction_encoding.cton | 9 ++- lib/reader/src/parser.rs | 67 +++++++++++++++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/filetests/parser/instruction_encoding.cton b/filetests/parser/instruction_encoding.cton index 0e40d0b2f1..4971cba510 100644 --- a/filetests/parser/instruction_encoding.cton +++ b/filetests/parser/instruction_encoding.cton @@ -15,11 +15,10 @@ ebb1(v0: i32, v1: i32): } ; sameln: function foo(i32, i32) { ; nextln: $ebb1($v0: i32, $v1: i32): -; nextln: [-]$WS $v2 = iadd $v0, $v1 +; nextln: [-,-]$WS $v2 = iadd $v0, $v1 ; nextln: [-]$WS trap -; nextln: [R#1234]$WS $v6, $v7 = iadd_cout $v2, $v0 -; TODO Add the full encoding information available: instruction recipe name and architectural registers if specified -; nextln: [Rshamt#beef]$WS $v8 = ishl_imm $v6, 2 -; nextln: [-]$WS $v9 = iadd $v8, $v7 +; nextln: [R#1234,%x5,%x11]$WS $v6, $v7 = iadd_cout $v2, $v0 +; nextln: [Rshamt#beef,%x25]$WS $v8 = ishl_imm $v6, 2 +; nextln: [-,-]$WS $v9 = iadd $v8, $v7 ; nextln: [Iret#05]$WS return $v0, $v8 ; nextln: } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 1c71f9f4f5..7427139a4d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -10,7 +10,7 @@ use std::{u16, u32}; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, - FuncRef}; + FuncRef, ValueLoc}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; @@ -959,9 +959,32 @@ impl<'a> Parser<'a> { ctx.map.def_value(vx, value, &vx_location) } + fn parse_value_location(&mut self, ctx: &Context, isa: &TargetIsa) -> Result { + let reginfo = isa.register_info(); + match self.token() { + Some(Token::StackSlot(src_num)) => { + self.consume(); + if let Some(ss) = ctx.map.get_ss(src_num) { + Ok(ValueLoc::Stack(ss)) + } else { + err!(self.loc, + "attempted to use undefined stack slot ss{}", + src_num) + } + } + Some(Token::Name(name)) => { + self.consume(); + reginfo.parse_regunit(name) + .map(ValueLoc::Reg) + .ok_or(self.error("invalid register value location")) + } + _ => err!(self.loc, "invalid value location"), + } + } + fn parse_instruction_encoding(&mut self, ctx: &Context) - -> Result<(Option, Option>)> { + -> Result<(Option, Option>)> { let (mut encoding, mut result_registers) = (None, None); // encoding ::= "[" encoding_literal result_registers "]" @@ -984,11 +1007,13 @@ impl<'a> Parser<'a> { // result_registers ::= ("," ( "-" | names ) )? // names ::= Name { "," Name } if self.optional(Token::Comma) && !self.optional(Token::Minus) { + let isa = ctx.unique_isa + .ok_or(self.error("value locations specified without a unique isa"))?; let mut result = Vec::new(); - result.push(self.match_name("expected register")?); + result.push(self.parse_value_location(ctx, isa)?); while self.optional(Token::Comma) { - result.push(self.match_name("expected register")?); + result.push(self.parse_value_location(ctx, isa)?); } result_registers = Some(result); @@ -1010,7 +1035,7 @@ impl<'a> Parser<'a> { // Collect comments for the next instruction to be allocated. self.gather_comments(ctx.function.dfg.next_inst()); - let (encoding, result_registers) = self.parse_instruction_encoding(ctx)?; + let (encoding, result_locations) = self.parse_instruction_encoding(ctx)?; // Result value numbers. let mut results = Vec::new(); @@ -1030,13 +1055,6 @@ impl<'a> Parser<'a> { self.match_token(Token::Equal, "expected '=' before opcode")?; } - if let Some(ref result_registers) = result_registers { - if result_registers.len() != results.len() { - return err!(self.loc, - "must have same number of result registers as results"); - } - } - // instruction ::= [inst-results "="] * Opcode(opc) ["." Type] ... let opcode = if let Some(Token::Identifier(text)) = self.token() { match text.parse() { @@ -1086,8 +1104,29 @@ impl<'a> Parser<'a> { // Pass a reference to `ctx.values` instead of `ctx` itself since the `Values` iterator // holds a reference to `ctx.function`. self.add_values(&mut ctx.map, - results.into_iter(), - ctx.function.dfg.inst_results(inst)) + results.iter().map(|v| *v), + ctx.function.dfg.inst_results(inst))?; + + if let Some(ref result_locations) = result_locations { + if result_locations.len() != results.len() { + return err!(self.loc, + "must have same number of result locations as results"); + } else { + for (&src_value, &loc) in results.iter().zip(result_locations) { + // We are safe to unwrap here because all values should have been added to the + // map in the above call to self.add_values() + let value = ctx.map.get_value(src_value).unwrap(); + *ctx.function.locations.ensure(value) = loc; + } + } + } else { + for &src_value in results.iter() { + let value = ctx.map.get_value(src_value).unwrap(); + *ctx.function.locations.ensure(value) = ValueLoc::Unassigned; + } + } + + Ok(()) } // Type inference for polymorphic instructions. From 41e4ea91586520385110c6a4cc8d7686cc1e257c Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 9 Mar 2017 12:09:28 +0000 Subject: [PATCH 561/968] Some refactoring, relaxed error handling so we allow encoding specifiers even without a unique ISA. --- lib/reader/src/parser.rs | 84 +++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 7427139a4d..b83f8c0b38 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -504,16 +504,6 @@ impl<'a> Parser<'a> { } } - // Match and consume a name. - fn match_name(&mut self, err_msg: &str) -> Result<&'a str> { - if let Some(Token::Name(name)) = self.token() { - self.consume(); - Ok(name) - } else { - err!(self.loc, err_msg) - } - } - /// Parse a list of test commands. pub fn parse_test_commands(&mut self) -> Vec> { let mut list = Vec::new(); @@ -959,8 +949,7 @@ impl<'a> Parser<'a> { ctx.map.def_value(vx, value, &vx_location) } - fn parse_value_location(&mut self, ctx: &Context, isa: &TargetIsa) -> Result { - let reginfo = isa.register_info(); + fn parse_value_location(&mut self, ctx: &Context) -> Result { match self.token() { Some(Token::StackSlot(src_num)) => { self.consume(); @@ -974,9 +963,19 @@ impl<'a> Parser<'a> { } Some(Token::Name(name)) => { self.consume(); - reginfo.parse_regunit(name) - .map(ValueLoc::Reg) - .ok_or(self.error("invalid register value location")) + if let Some(isa) = ctx.unique_isa { + isa.register_info() + .parse_regunit(name) + .map(ValueLoc::Reg) + .ok_or(self.error("invalid register value location")) + } else { + // For convenience we ignore value locations when no unique ISA is specified + Ok(ValueLoc::Unassigned) + } + } + Some(Token::Minus) => { + self.consume(); + Ok(ValueLoc::Unassigned) } _ => err!(self.loc, "invalid value location"), } @@ -985,9 +984,9 @@ impl<'a> Parser<'a> { fn parse_instruction_encoding(&mut self, ctx: &Context) -> Result<(Option, Option>)> { - let (mut encoding, mut result_registers) = (None, None); + let (mut encoding, mut result_locations) = (None, None); - // encoding ::= "[" encoding_literal result_registers "]" + // encoding ::= "[" encoding_literal result_locations "]" if self.optional(Token::LBracket) { // encoding_literal ::= "-" | Identifier HexSequence if !self.optional(Token::Minus) { @@ -999,31 +998,29 @@ impl<'a> Parser<'a> { } else if ctx.unique_isa.is_some() { return err!(self.loc, "invalid instruction recipe"); } else { - return err!(self.loc, - "provided instruction encoding for unspecified ISA"); + // We allow encodings to be specified when there's no unique ISA purely + // for convenience, eg when copy-pasting code for a test. } } - // result_registers ::= ("," ( "-" | names ) )? + // result_locations ::= ("," ( "-" | names ) )? // names ::= Name { "," Name } - if self.optional(Token::Comma) && !self.optional(Token::Minus) { - let isa = ctx.unique_isa - .ok_or(self.error("value locations specified without a unique isa"))?; - let mut result = Vec::new(); + if self.optional(Token::Comma) { + let mut results = Vec::new(); - result.push(self.parse_value_location(ctx, isa)?); + results.push(self.parse_value_location(ctx)?); while self.optional(Token::Comma) { - result.push(self.parse_value_location(ctx, isa)?); + results.push(self.parse_value_location(ctx)?); } - result_registers = Some(result); + result_locations = Some(results); } self.match_token(Token::RBracket, "expected ']' to terminate instruction encoding")?; } - Ok((encoding, result_registers)) + Ok((encoding, result_locations)) } // Parse an instruction, append it to `ebb`. @@ -1100,29 +1097,26 @@ impl<'a> Parser<'a> { results.len()); } + if let Some(ref result_locations) = result_locations { + if results.len() != result_locations.len() { + return err!(self.loc, + "instruction produces {} result values, but {} locations were \ + specified", + results.len(), + result_locations.len()); + } + } + // Now map the source result values to the just created instruction results. // Pass a reference to `ctx.values` instead of `ctx` itself since the `Values` iterator // holds a reference to `ctx.function`. self.add_values(&mut ctx.map, - results.iter().map(|v| *v), + results.into_iter(), ctx.function.dfg.inst_results(inst))?; - if let Some(ref result_locations) = result_locations { - if result_locations.len() != results.len() { - return err!(self.loc, - "must have same number of result locations as results"); - } else { - for (&src_value, &loc) in results.iter().zip(result_locations) { - // We are safe to unwrap here because all values should have been added to the - // map in the above call to self.add_values() - let value = ctx.map.get_value(src_value).unwrap(); - *ctx.function.locations.ensure(value) = loc; - } - } - } else { - for &src_value in results.iter() { - let value = ctx.map.get_value(src_value).unwrap(); - *ctx.function.locations.ensure(value) = ValueLoc::Unassigned; + if let Some(result_locations) = result_locations { + for (value, loc) in ctx.function.dfg.inst_results(inst).zip(result_locations) { + *ctx.function.locations.ensure(value) = loc; } } From 3e87092ce8aae9e144e299dab2b7691f35b86426 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 10:05:55 -0800 Subject: [PATCH 562/968] Upgrade to Sphinx 1.5.3 Read the Docs is now using the latest version of Sphinx, so upgrade our recommended version too. As of Sphinx 1.4, index entries are 5-tuples instead of 4-tuples. Update the Cretonne Sphinx domain to generate the new 5-tuples. Since we're over this compatibility bump, there's no reason to recommend a specific Sphinx version, so just go back to 'current'. --- README.rst | 7 +++---- docs/cton_domain.py | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 4a46f9267c..8bd8dd13e6 100644 --- a/README.rst +++ b/README.rst @@ -65,11 +65,10 @@ Building the documentation To build the Cretonne documentation, you need the `Sphinx documentation generator `_:: - $ pip install sphinx==1.3.5 sphinx-autobuild + $ pip install sphinx>=1.4 sphinx-autobuild sphinx_rtd_theme $ cd cretonne/docs $ make html $ open _build/html/index.html -The specific Sphinx version is currently used by Read the Docs. Sphinx 1.4 has -been released, but produces lots of warnings about four-column indices. We'll -upgrade when Read the Docs does. +We don't support Sphinx versions before 1.4 since the format of index tuples +has changed. diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 2f56b5625e..23204f02e7 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -65,7 +65,8 @@ class CtonObject(ObjectDescription): indextext = self.get_index_text(name) if indextext: self.indexnode['entries'].append(('single', indextext, - targetname, '')) + targetname, '', None)) + # Type variables are indicated as %T. typevar = re.compile('(\%[A-Z])') @@ -113,6 +114,7 @@ class CtonType(CtonObject): def get_index_text(self, name): return name + ' (IL type)' + sep_equal = re.compile('\s*=\s*') sep_comma = re.compile('\s*,\s*') From 81f26cfbba2c184201ccd3eb74419a25cc0f42d8 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 9 Mar 2017 20:19:15 +0000 Subject: [PATCH 563/968] Added parsing of argument locations for functions and signatures. --- filetests/isa/riscv/abi.cton | 33 +++++++++++ lib/reader/src/parser.rs | 108 +++++++++++++++++++++++++++-------- 2 files changed, 117 insertions(+), 24 deletions(-) diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index 0282e94874..0b47fe6727 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -61,3 +61,36 @@ ebb0(v0: i64x4): v1 = iadd v0, v0 return v0 } + +function parse_encoding(i32 [%x5]) -> i32 [%x10] { +; check: function parse_encoding(i32 [%x5]) -> i32 [%x10] { + + sig0 = signature(i32 [%x10]) -> i32 [%x10] +; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + + sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] +; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + + sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] +; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + +; Arguments on stack where not necessary + sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] +; check: sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] + +; Stack argument before register argument + sig4 = signature(f32 [72], i32 [%x10]) +; check: sig4 = signature(f32 [72], i32 [%x10]) + +; Return value on stack + sig5 = signature() -> f32 [0] +; check: sig5 = signature() -> f32 [0] + +; function + signature + fn15 = function bar(i32 [%x10]) -> b1 [%x10] +; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] +; nextln: fn0 = sig6 bar + +ebb0(v0: i32): + return_reg v0 +} \ No newline at end of file diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index b83f8c0b38..6041039513 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -10,7 +10,7 @@ use std::{u16, u32}; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, - FuncRef, ValueLoc}; + FuncRef, ValueLoc, ArgumentLoc}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; @@ -445,6 +445,19 @@ impl<'a> Parser<'a> { } } + // Match and consume a u32 immediate. + // This is used for stack argument byte offsets. + fn match_uimm32(&mut self, err_msg: &str) -> Result { + if let Some(Token::Integer(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like an integer. + // Parse it as a u32 to check for overflow and other issues. + text.parse().map_err(|_| self.error("expected u32 decimal immediate")) + } else { + err!(self.loc, err_msg) + } + } + // Match and consume an Ieee32 immediate. fn match_ieee32(&mut self, err_msg: &str) -> Result { if let Some(Token::Float(text)) = self.token() { @@ -596,7 +609,7 @@ impl<'a> Parser<'a> { self.comments.clear(); self.gather_comments(AnyEntity::Function); - let (location, name, sig) = self.parse_function_spec()?; + let (location, name, sig) = self.parse_function_spec(unique_isa)?; let mut ctx = Context::new(Function::with_name_signature(name, sig), unique_isa); // function ::= function-spec * "{" preamble function-body "}" @@ -630,7 +643,9 @@ impl<'a> Parser<'a> { // // function-spec ::= * "function" name signature // - fn parse_function_spec(&mut self) -> Result<(Location, FunctionName, Signature)> { + fn parse_function_spec(&mut self, + unique_isa: Option<&TargetIsa>) + -> Result<(Location, FunctionName, Signature)> { self.match_identifier("function", "expected 'function'")?; let location = self.loc; @@ -638,7 +653,7 @@ impl<'a> Parser<'a> { let name = self.parse_function_name()?; // function-spec ::= "function" name * signature - let sig = self.parse_signature()?; + let sig = self.parse_signature(unique_isa)?; Ok((location, name, sig)) } @@ -661,17 +676,21 @@ impl<'a> Parser<'a> { // // signature ::= * "(" [arglist] ")" ["->" retlist] [call_conv] // - fn parse_signature(&mut self) -> Result { + fn parse_signature(&mut self, unique_isa: Option<&TargetIsa>) -> Result { let mut sig = Signature::new(); self.match_token(Token::LPar, "expected function signature: ( args... )")?; // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] if self.token() != Some(Token::RPar) { - sig.argument_types = self.parse_argument_list()?; + sig.argument_types = self.parse_argument_list(unique_isa)?; } self.match_token(Token::RPar, "expected ')' after function arguments")?; if self.optional(Token::Arrow) { - sig.return_types = self.parse_argument_list()?; + sig.return_types = self.parse_argument_list(unique_isa)?; + } + + if sig.argument_types.iter().all(|a| a.location.is_assigned()) { + sig.compute_argument_bytes(); } // TBD: calling convention. @@ -683,27 +702,27 @@ impl<'a> Parser<'a> { // // arglist ::= * arg { "," arg } // - fn parse_argument_list(&mut self) -> Result> { + fn parse_argument_list(&mut self, unique_isa: Option<&TargetIsa>) -> Result> { let mut list = Vec::new(); // arglist ::= * arg { "," arg } - list.push(self.parse_argument_type()?); + list.push(self.parse_argument_type(unique_isa)?); // arglist ::= arg * { "," arg } while self.optional(Token::Comma) { // arglist ::= arg { "," * arg } - list.push(self.parse_argument_type()?); + list.push(self.parse_argument_type(unique_isa)?); } Ok(list) } // Parse a single argument type with flags. - fn parse_argument_type(&mut self) -> Result { - // arg ::= * type { flag } + fn parse_argument_type(&mut self, unique_isa: Option<&TargetIsa>) -> Result { + // arg ::= * type { flag } [ argumentloc ] let mut arg = ArgumentType::new(self.match_type("expected argument type")?); - // arg ::= type * { flag } + // arg ::= type * { flag } [ argumentloc ] while let Some(Token::Identifier(s)) = self.token() { match s { "uext" => arg.extension = ArgumentExtension::Uext, @@ -714,9 +733,50 @@ impl<'a> Parser<'a> { self.consume(); } + // arg ::= type { flag } * [ argumentloc ] + arg.location = self.parse_argument_location(unique_isa)?; + Ok(arg) } + // Parse an argument location specifier; either a register or a byte offset into the stack. + fn parse_argument_location(&mut self, unique_isa: Option<&TargetIsa>) -> Result { + // argumentloc ::= '[' regname | uimm32 ']' + if self.optional(Token::LBracket) { + let result = match self.token() { + Some(Token::Name(name)) => { + self.consume(); + if let Some(isa) = unique_isa { + isa.register_info() + .parse_regunit(name) + .map(ArgumentLoc::Reg) + .ok_or(self.error("invalid register name")) + } else { + // We are unable to parse the register without a TargetISA, so we quietly + // ignore it. + Ok(ArgumentLoc::Unassigned) + } + } + Some(Token::Integer(_)) => { + let offset = self.match_uimm32("expected stack argument byte offset")?; + Ok(ArgumentLoc::Stack(offset)) + } + Some(Token::Minus) => { + self.consume(); + Ok(ArgumentLoc::Unassigned) + } + _ => err!(self.loc, "expected argument location"), + }; + + self.match_token(Token::RBracket, + "expected ']' to end argument location annotation")?; + + result + } else { + Ok(ArgumentLoc::Unassigned) + } + } + // Parse the function preamble. // // preamble ::= * { preamble-decl } @@ -736,7 +796,7 @@ impl<'a> Parser<'a> { } Some(Token::SigRef(..)) => { self.gather_comments(ctx.function.dfg.signatures.next_key()); - self.parse_signature_decl() + self.parse_signature_decl(ctx.unique_isa) .and_then(|(num, dat)| ctx.add_sig(num, dat, &self.loc)) } Some(Token::FuncRef(..)) => { @@ -781,11 +841,11 @@ impl<'a> Parser<'a> { // // signature-decl ::= SigRef(sigref) "=" "signature" signature // - fn parse_signature_decl(&mut self) -> Result<(u32, Signature)> { + fn parse_signature_decl(&mut self, unique_isa: Option<&TargetIsa>) -> Result<(u32, Signature)> { let number = self.match_sig("expected signature number: sig«n»")?; self.match_token(Token::Equal, "expected '=' in signature decl")?; self.match_identifier("signature", "expected 'signature'")?; - let data = self.parse_signature()?; + let data = self.parse_signature(unique_isa)?; Ok((number, data)) } @@ -805,7 +865,7 @@ impl<'a> Parser<'a> { let data = match self.token() { Some(Token::Identifier("function")) => { - let (loc, name, sig) = self.parse_function_spec()?; + let (loc, name, sig) = self.parse_function_spec(ctx.unique_isa)?; let sigref = ctx.function.dfg.signatures.push(sig); ctx.map.def_entity(sigref.into(), &loc).expect("duplicate SigRef entities created"); ExtFuncData { @@ -1523,33 +1583,33 @@ mod tests { #[test] fn argument_type() { let mut p = Parser::new("i32 sext"); - let arg = p.parse_argument_type().unwrap(); + let arg = p.parse_argument_type(None).unwrap(); assert_eq!(arg.value_type, types::I32); assert_eq!(arg.extension, ArgumentExtension::Sext); assert_eq!(arg.inreg, false); - let Error { location, message } = p.parse_argument_type().unwrap_err(); + let Error { location, message } = p.parse_argument_type(None).unwrap_err(); assert_eq!(location.line_number, 1); assert_eq!(message, "expected argument type"); } #[test] fn signature() { - let sig = Parser::new("()").parse_signature().unwrap(); + let sig = Parser::new("()").parse_signature(None).unwrap(); assert_eq!(sig.argument_types.len(), 0); assert_eq!(sig.return_types.len(), 0); let sig2 = Parser::new("(i8 inreg uext, f32, f64) -> i32 sext, f64") - .parse_signature() + .parse_signature(None) .unwrap(); assert_eq!(sig2.to_string(), "(i8 uext inreg, f32, f64) -> i32 sext, f64"); // `void` is not recognized as a type by the lexer. It should not appear in files. - assert_eq!(Parser::new("() -> void").parse_signature().unwrap_err().to_string(), + assert_eq!(Parser::new("() -> void").parse_signature(None).unwrap_err().to_string(), "1: expected argument type"); - assert_eq!(Parser::new("i8 -> i8").parse_signature().unwrap_err().to_string(), + assert_eq!(Parser::new("i8 -> i8").parse_signature(None).unwrap_err().to_string(), "1: expected function signature: ( args... )"); - assert_eq!(Parser::new("(i8 -> i8").parse_signature().unwrap_err().to_string(), + assert_eq!(Parser::new("(i8 -> i8").parse_signature(None).unwrap_err().to_string(), "1: expected ')' after function arguments"); } From 364b8e5f0aa0671e058fba74a265497770f3ca23 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 13:10:27 -0800 Subject: [PATCH 564/968] Use value lists for call arguments. Add a new kind of instruction format that keeps all of its value arguments in a value list. These value lists are all allocated out of the dfg.value_lists memory pool. Instruction formats with the value_list property set store *all* of their value arguments in a single value list. There is no distinction between fixed arguments and variable arguments. Change the Call instruction format to use the value list representation for its arguments. This change is only the beginning. The intent is to eliminate the boxed_storage instruction formats completely. Value lists use less memory, and when the transition is complete, InstructionData will have a trivial Drop implementation. --- lib/cretonne/meta/base/formats.py | 2 +- lib/cretonne/meta/cdsl/formats.py | 3 ++ lib/cretonne/meta/gen_instr.py | 53 +++++++++++++++++++++++++-- lib/cretonne/src/entity_list.rs | 6 +++ lib/cretonne/src/ir/builder.rs | 11 +++++- lib/cretonne/src/ir/dfg.rs | 10 +++-- lib/cretonne/src/ir/instructions.rs | 45 +++++++++++++---------- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/regalloc/coloring.rs | 3 +- lib/cretonne/src/regalloc/liveness.rs | 2 +- lib/cretonne/src/write.rs | 27 ++++++++++++-- lib/reader/src/parser.rs | 22 ++++++----- 12 files changed, 142 insertions(+), 44 deletions(-) diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 89704623fd..9b29c54775 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -47,7 +47,7 @@ Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS, boxed_storage=True) BranchTable = InstructionFormat(VALUE, jump_table) Call = InstructionFormat( - func_ref, VARIABLE_ARGS, multiple_results=True, boxed_storage=True) + func_ref, VARIABLE_ARGS, multiple_results=True, value_list=True) IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True, boxed_storage=True) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 974ca80541..422d84c296 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -34,6 +34,8 @@ class InstructionFormat(object): enums. :param multiple_results: Set to `True` if this instruction format allows more than one result to be produced. + :param value_list: Set to `True` if this instruction format uses a + `ValueList` member to store its value operands. :param boxed_storage: Set to `True` is this instruction format requires a `data: Box<...>` pointer to additional storage in its `InstructionData` variant. @@ -52,6 +54,7 @@ class InstructionFormat(object): # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa self.name = kwargs.get('name', None) # type: str self.multiple_results = kwargs.get('multiple_results', False) + self.has_value_list = kwargs.get('value_list', False) self.boxed_storage = kwargs.get('boxed_storage', False) self.members = list() # type: List[str] self.kinds = tuple(self._process_member_names(kinds)) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 353fa60551..b8afeb3eb7 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -58,17 +58,32 @@ def gen_arguments_method(fmt, is_mut): method = 'arguments' mut = '' rslice = 'ref_slice' + as_slice = 'as_slice' if is_mut: method += '_mut' mut = 'mut ' rslice += '_mut' + as_slice = 'as_mut_slice' with fmt.indented( - 'pub fn {f}(&{m}self) -> [&{m}[Value]; 2] {{' + 'pub fn {f}<\'a>(&\'a {m}self, pool: &\'a {m}ValueListPool) -> ' + '[&{m}[Value]; 2] {{' .format(f=method, m=mut), '}'): with fmt.indented('match *self {', '}'): for f in InstructionFormat.all_formats: n = 'InstructionData::' + f.name + + # Formats with a value list put all of their arguments in the + # list. We don't split them up, just return it all as variable + # arguments. (I expect the distinction to go away). + if f.has_value_list: + arg = ''.format(mut) + fmt.line( + '{} {{ ref {}args, .. }} => ' + '[ &{}[], args.{}(pool) ],' + .format(n, mut, mut, as_slice)) + continue + has_varargs = cdsl.operands.VARIABLE_ARGS in f.kinds # Formats with both fixed and variable arguments delegate to # the data struct. We need to work around borrow checker quirks @@ -472,7 +487,11 @@ def gen_format_constructor(iform, fmt): """ # Construct method arguments. - args = ['self', 'opcode: Opcode'] + if iform.has_value_list: + args = ['mut self'] + else: + args = ['self'] + args.append('opcode: Opcode') if iform.multiple_results: args.append('ctrl_typevar: Type') @@ -484,7 +503,10 @@ def gen_format_constructor(iform, fmt): # Normal operand arguments. for idx, kind in enumerate(iform.kinds): - args.append('op{}: {}'.format(idx, kind.rust_type)) + if kind is cdsl.operands.VARIABLE_ARGS and iform.has_value_list: + args.append('op{}: &[Value]'.format(idx, kind.rust_type)) + else: + args.append('op{}: {}'.format(idx, kind.rust_type)) proto = '{}({})'.format(iform.name, ', '.join(args)) proto += " -> (Inst, &'f mut DataFlowGraph)" @@ -492,6 +514,21 @@ def gen_format_constructor(iform, fmt): fmt.doc_comment(str(iform)) fmt.line('#[allow(non_snake_case)]') with fmt.indented('fn {} {{'.format(proto), '}'): + # Start by constructing a value list with *all* the arguments. + if iform.has_value_list: + fmt.line('let mut vlist = ValueList::default();') + with fmt.indented('{', '}'): + fmt.line( + 'let pool = ' + '&mut self.data_flow_graph_mut().value_lists;') + for idx, kind in enumerate(iform.kinds): + if kind is cdsl.operands.VALUE: + fmt.line('vlist.push(op{}, pool);'.format(idx)) + elif kind is cdsl.operands.VARIABLE_ARGS: + fmt.line( + 'vlist.extend(op{}.iter().cloned(), pool);' + .format(idx)) + # Generate the instruction data. with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): @@ -520,7 +557,10 @@ def gen_member_inits(iform, fmt): """ # Values first. - if len(iform.value_operands) == 1: + if iform.has_value_list: + # Value-list formats put *all* arguments in the list. + fmt.line('args: vlist,') + elif len(iform.value_operands) == 1: fmt.line('arg: op{},'.format(iform.value_operands[0])) elif len(iform.value_operands) > 1: fmt.line('args: [{}],'.format( @@ -528,6 +568,8 @@ def gen_member_inits(iform, fmt): # Immediates and entity references. for idx, member in enumerate(iform.members): + if iform.has_value_list and member == 'varargs': + continue if member: fmt.line('{}: op{},'.format(member, idx)) @@ -557,6 +599,9 @@ def gen_inst_builder(inst, fmt): t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name) tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type)) into_args.append(op.name) + elif (inst.format.has_value_list and + op.kind is cdsl.operands.VARIABLE_ARGS): + t = '&[Value]' else: t = op.kind.rust_type args.append('{}: {}'.format(op.name, t)) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index b2c75bd9ed..2b8ebb783e 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -54,6 +54,11 @@ use entity_map::EntityRef; /// /// All of the list methods that take a pool reference must be given the same pool reference every /// time they are called. Otherwise data structures will be corrupted. +/// +/// Entity lists can be cloned, but that operation should only be used as part of cloning the whole +/// function they belong to. *Cloning an entity list does not allocate new memory for the clone*. +/// It creates an alias of the same memory. +#[derive(Clone, Debug)] pub struct EntityList { index: u32, unused: PhantomData, @@ -70,6 +75,7 @@ impl Default for EntityList { } /// A memory pool for storing lists of `T`. +#[derive(Clone)] pub struct ListPool { // The main array containing the lists. data: Vec, diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 639b640c20..ebb8ae2350 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,7 +5,7 @@ use ir::{types, instructions}; use ir::{InstructionData, DataFlowGraph, Cursor}; -use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, SigRef, FuncRef}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, SigRef, FuncRef, ValueList}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::{IntCC, FloatCC}; @@ -21,6 +21,7 @@ pub trait InstBuilderBase<'f>: Sized { /// Get an immutable reference to the data flow graph that will hold the constructed /// instructions. fn data_flow_graph(&self) -> &DataFlowGraph; + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph; /// Insert a simple instruction and return a reference to it. /// @@ -76,6 +77,10 @@ impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { self.dfg } + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + self.dfg + } + fn simple_instruction(self, data: InstructionData) -> (Inst, &'fd mut DataFlowGraph) { let inst = self.dfg.make_inst(data); self.pos.insert_inst(inst); @@ -129,6 +134,10 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { self.dfg } + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + self.dfg + } + fn simple_instruction(self, data: InstructionData) -> (Inst, &'f mut DataFlowGraph) { // The replacement instruction cannot generate multiple results, so verify that the old // instruction's secondary results have been detached. diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 593d3a7907..07e7dbbdb5 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,6 +1,6 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef}; +use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueListPool}; use ir::entities::ExpandedValue; use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; @@ -24,7 +24,10 @@ pub struct DataFlowGraph { /// Data about all of the instructions in the function, including opcodes and operands. /// The instructions in this map are not in program order. That is tracked by `Layout`, along /// with the EBB containing each instruction. - insts: EntityMap, + pub insts: EntityMap, + + /// Memory pool of value lists referenced by instructions in `insts`. + pub value_lists: ValueListPool, /// Extended basic blocks in the function and their arguments. /// This map is not in program order. That is handled by `Layout`, and so is the sequence of @@ -56,6 +59,7 @@ impl DataFlowGraph { pub fn new() -> DataFlowGraph { DataFlowGraph { insts: EntityMap::new(), + value_lists: ValueListPool::new(), ebbs: EntityMap::new(), extended_values: Vec::new(), signatures: EntityMap::new(), @@ -429,7 +433,7 @@ impl DataFlowGraph { /// Get the call signature of a direct or indirect call instruction. /// Returns `None` if `inst` is not a call instruction. pub fn call_signature(&self, inst: Inst) -> Option { - match self.insts[inst].analyze_call() { + match self.insts[inst].analyze_call(&self.value_lists) { CallInfo::NotACall => None, CallInfo::Direct(f, _) => Some(self.ext_funcs[f].signature), CallInfo::Indirect(s, _) => Some(s), diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 2a956ffc86..2425b8814a 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -16,8 +16,17 @@ use ir::condcodes::*; use ir::types; use ir::DataFlowGraph; -use ref_slice::*; +use entity_list; use packed_option::PackedOption; +use ref_slice::{ref_slice, ref_slice_mut}; + +/// Some instructions use an external list of argument values because there is not enough space in +/// the 16-byte `InstructionData` struct. These value lists are stored in a memory pool in +/// `dfg.value_lists`. +pub type ValueList = entity_list::EntityList; + +/// Memory pool for holding value lists. See `ValueList`. +pub type ValueListPool = entity_list::ListPool; // Include code generated by `lib/cretonne/meta/gen_instr.py`. This file contains: // @@ -204,7 +213,8 @@ pub enum InstructionData { opcode: Opcode, ty: Type, second_result: PackedOption, - data: Box, + func_ref: FuncRef, + args: ValueList, }, IndirectCall { opcode: Opcode, @@ -244,6 +254,13 @@ impl VariableArgs { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + /// Convert this to a value list in `pool`. + pub fn into_value_list(self, pool: &mut ValueListPool) -> ValueList { + let mut vlist = ValueList::default(); + vlist.extend(self.0, pool); + vlist + } } // Coerce `VariableArgs` into a `&[Value]` slice. @@ -364,16 +381,6 @@ impl Display for BranchData { } } -/// Payload of a call instruction. -#[derive(Clone, Debug)] -pub struct CallData { - /// Callee function. - pub func_ref: FuncRef, - - /// Dynamically sized array containing call argument values. - pub varargs: VariableArgs, -} - /// Payload of an indirect call instruction. #[derive(Clone, Debug)] pub struct IndirectCallData { @@ -434,10 +441,10 @@ impl ReturnRegData { impl InstructionData { /// Execute a closure once for each argument to this instruction. /// See also the `arguments()` method. - pub fn each_arg(&self, mut func: F) + pub fn each_arg(&self, pool: &ValueListPool, mut func: F) where F: FnMut(Value) { - for part in &self.arguments() { + for part in &self.arguments(pool) { for &arg in part.iter() { func(arg); } @@ -446,10 +453,10 @@ impl InstructionData { /// Execute a closure with a mutable reference to each argument to this instruction. /// See also the `arguments_mut()` method. - pub fn each_arg_mut(&mut self, mut func: F) + pub fn each_arg_mut(&mut self, pool: &mut ValueListPool, mut func: F) where F: FnMut(&mut Value) { - for part in &mut self.arguments_mut() { + for part in &mut self.arguments_mut(pool) { for arg in part.iter_mut() { func(arg); } @@ -476,10 +483,10 @@ impl InstructionData { /// Return information about a call instruction. /// /// Any instruction that can call another function reveals its call signature here. - pub fn analyze_call<'a>(&'a self) -> CallInfo<'a> { + pub fn analyze_call<'a>(&'a self, pool: &'a ValueListPool) -> CallInfo<'a> { match self { - &InstructionData::Call { ref data, .. } => { - CallInfo::Direct(data.func_ref, &data.varargs) + &InstructionData::Call { func_ref, ref args, .. } => { + CallInfo::Direct(func_ref, &args.as_slice(pool)) } &InstructionData::IndirectCall { ref data, .. } => { CallInfo::Indirect(data.sig_ref, &data.varargs) diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index c14473e9f5..302978d81b 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -20,7 +20,7 @@ pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; -pub use ir::instructions::{Opcode, InstructionData, VariableArgs}; +pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; pub use ir::stackslot::StackSlotData; pub use ir::jumptable::JumpTableData; pub use ir::valueloc::{ValueLoc, ArgumentLoc}; diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 11018f664d..6549c4356f 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -299,7 +299,8 @@ impl<'a> Context<'a> { } ConstraintKind::Tied(arg_index) => { // This def must use the same register as a fixed instruction argument. - let loc = locations[dfg[inst].arguments()[0][arg_index as usize]]; + let arg = dfg[inst].arguments(&dfg.value_lists)[0][arg_index as usize]; + let loc = locations[arg]; *locations.ensure(lv.value) = loc; // Mark the reused register. It's not really clear if we support tied // stack operands. We could do that for some Intel read-modify-write diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 524b50e8ea..49db4a04cd 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -318,7 +318,7 @@ impl Liveness { let mut operand_constraints = recipe_constraints.get(recipe).map(|c| c.ins).unwrap_or(&[]).iter(); - func.dfg[inst].each_arg(|arg| { + func.dfg[inst].each_arg(&func.dfg.value_lists, |arg| { // Get the live range, create it as a dead range if necessary. let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints); diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index b0ef3a03d3..8cba9757f9 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -6,7 +6,7 @@ use ir::{Function, Ebb, Inst, Value, Type}; use isa::{TargetIsa, RegInfo}; -use std::fmt::{Result, Error, Write}; +use std::fmt::{self, Result, Error, Write}; use std::result; /// Write `func` to `w` as equivalent text. @@ -157,7 +157,7 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { // Write out any value aliases appearing in `inst`. fn write_value_aliases(w: &mut Write, func: &Function, inst: Inst, indent: usize) -> Result { - for &arg in func.dfg[inst].arguments().iter().flat_map(|x| x.iter()) { + for &arg in func.dfg[inst].arguments(&func.dfg.value_lists).iter().flat_map(|x| x.iter()) { let resolved = func.dfg.resolve_aliases(arg); if resolved != arg { writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)?; @@ -247,7 +247,12 @@ fn write_instruction(w: &mut Write, Jump { ref data, .. } => writeln!(w, " {}", data), Branch { ref data, .. } => writeln!(w, " {}", data), BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), - Call { ref data, .. } => writeln!(w, " {}({})", data.func_ref, data.varargs), + Call { func_ref, ref args, .. } => { + writeln!(w, + " {}({})", + func_ref, + DisplayValues(args.as_slice(&func.dfg.value_lists))) + } IndirectCall { ref data, .. } => { writeln!(w, " {}, {}({})", data.sig_ref, data.arg, data.varargs) } @@ -268,6 +273,22 @@ fn write_instruction(w: &mut Write, } } +/// Displayable slice of values. +struct DisplayValues<'a>(&'a [Value]); + +impl<'a> fmt::Display for DisplayValues<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result { + for (i, val) in self.0.iter().enumerate() { + if i == 0 { + write!(f, "{}", val)?; + } else { + write!(f, ", {}", val)?; + } + } + Ok(()) + } +} + #[cfg(test)] mod tests { use ir::{Function, FunctionName, StackSlotData}; diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 6041039513..c009270f54 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -15,8 +15,8 @@ use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, - TernaryOverflowData, JumpData, BranchData, CallData, - IndirectCallData, ReturnData, ReturnRegData}; + TernaryOverflowData, JumpData, BranchData, IndirectCallData, + ReturnData, ReturnRegData}; use cretonne::isa::{self, TargetIsa, Encoding}; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -167,7 +167,8 @@ impl<'a> Context<'a> { for ebb in self.function.layout.ebbs() { for inst in self.function.layout.ebb_insts(ebb) { let loc = inst.into(); - match self.function.dfg[inst] { + let value_lists = &mut self.function.dfg.value_lists; + match self.function.dfg.insts[inst] { InstructionData::Nullary { .. } | InstructionData::UnaryImm { .. } | InstructionData::UnaryIeee32 { .. } | @@ -210,8 +211,8 @@ impl<'a> Context<'a> { self.map.rewrite_values(&mut data.varargs, loc)?; } - InstructionData::Call { ref mut data, .. } => { - self.map.rewrite_values(&mut data.varargs, loc)?; + InstructionData::Call { ref mut args, .. } => { + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } InstructionData::IndirectCall { ref mut data, .. } => { @@ -1300,7 +1301,10 @@ impl<'a> Parser<'a> { // Parse the operands following the instruction opcode. // This depends on the format of the opcode. - fn parse_inst_operands(&mut self, ctx: &Context, opcode: Opcode) -> Result { + fn parse_inst_operands(&mut self, + ctx: &mut Context, + opcode: Opcode) + -> Result { Ok(match opcode.format() { InstructionFormat::Nullary => { InstructionData::Nullary { @@ -1506,10 +1510,8 @@ impl<'a> Parser<'a> { opcode: opcode, ty: VOID, second_result: None.into(), - data: Box::new(CallData { - func_ref: func_ref, - varargs: args, - }), + func_ref: func_ref, + args: args.into_value_list(&mut ctx.function.dfg.value_lists), } } InstructionFormat::IndirectCall => { From 1135a89af9d5978e194f3f156eb0cbf0222a58e0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 14:53:31 -0800 Subject: [PATCH 565/968] Convert the Branch and Jump instruction formats to value_list. The Branch format also stores its fixed argument in the value list. This requires the value pool to be passed to a few more functions. Note that this actually makes the Branch and Jump variants of InstructionData identical. The instruction format hashing does not yet understand that all value operands are stored in the value list. We'll fix that in a later patch. Also convert IndirectCall, noting that Call and IndirectCall remain separate instruction formats because they have different immediate fields. --- lib/cretonne/meta/base/formats.py | 6 +- lib/cretonne/meta/gen_instr.py | 9 +- lib/cretonne/src/cfg.rs | 12 +- lib/cretonne/src/dominator_tree.rs | 10 +- lib/cretonne/src/ir/instructions.rs | 109 +++--------------- .../src/regalloc/live_value_tracker.rs | 2 +- lib/cretonne/src/write.rs | 34 +++++- lib/reader/src/parser.rs | 45 +++----- src/print_cfg.rs | 2 +- 9 files changed, 88 insertions(+), 141 deletions(-) diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 9b29c54775..9f7a805940 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -42,15 +42,15 @@ ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) IntCompare = InstructionFormat(intcc, VALUE, VALUE) FloatCompare = InstructionFormat(floatcc, VALUE, VALUE) -Jump = InstructionFormat(ebb, VARIABLE_ARGS, boxed_storage=True) -Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS, boxed_storage=True) +Jump = InstructionFormat(ebb, VARIABLE_ARGS, value_list=True) +Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS, value_list=True) BranchTable = InstructionFormat(VALUE, jump_table) Call = InstructionFormat( func_ref, VARIABLE_ARGS, multiple_results=True, value_list=True) IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, - multiple_results=True, boxed_storage=True) + multiple_results=True, value_list=True) Return = InstructionFormat(VARIABLE_ARGS, boxed_storage=True) ReturnReg = InstructionFormat(VALUE, VARIABLE_ARGS, boxed_storage=True) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index b8afeb3eb7..376bfc0d56 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -206,12 +206,19 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( - 'pub fn typevar_operand(&self) -> Option {', '}'): + 'pub fn typevar_operand(&self, pool: &ValueListPool) -> ' + 'Option {', '}'): with fmt.indented('match *self {', '}'): for f in InstructionFormat.all_formats: n = 'InstructionData::' + f.name if f.typevar_operand is None: fmt.line(n + ' { .. } => None,') + elif f.has_value_list: + # We keep all arguments in a value list. + i = f.value_operands.index(f.typevar_operand) + fmt.line( + '{} {{ ref args, .. }} => ' + 'args.get({}, pool),'.format(n, i)) elif len(f.value_operands) == 1: # We have a single value operand called 'arg'. if f.boxed_storage: diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index 9b0f3f15e7..c3cf19efe4 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -75,7 +75,7 @@ impl ControlFlowGraph { for ebb in &func.layout { for inst in func.layout.ebb_insts(ebb) { - match func.dfg[inst].analyze_branch() { + match func.dfg[inst].analyze_branch(&func.dfg.value_lists) { BranchInfo::SingleDest(dest, _) => { self.add_edge((ebb, inst), dest); } @@ -148,7 +148,7 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { use super::*; - use ir::{Function, InstBuilder, Cursor, VariableArgs, types}; + use ir::{Function, InstBuilder, Cursor, types}; #[test] fn empty() { @@ -197,12 +197,12 @@ mod tests { let cur = &mut Cursor::new(&mut func.layout); cur.insert_ebb(ebb0); - br_ebb0_ebb2 = dfg.ins(cur).brnz(cond, ebb2, VariableArgs::new()); - jmp_ebb0_ebb1 = dfg.ins(cur).jump(ebb1, VariableArgs::new()); + br_ebb0_ebb2 = dfg.ins(cur).brnz(cond, ebb2, &[]); + jmp_ebb0_ebb1 = dfg.ins(cur).jump(ebb1, &[]); cur.insert_ebb(ebb1); - br_ebb1_ebb1 = dfg.ins(cur).brnz(cond, ebb1, VariableArgs::new()); - jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, VariableArgs::new()); + br_ebb1_ebb1 = dfg.ins(cur).brnz(cond, ebb1, &[]); + jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, &[]); cur.insert_ebb(ebb2); } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index fd2c6cfb50..8c90b46160 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -209,7 +209,7 @@ impl DominatorTree { #[cfg(test)] mod test { use super::*; - use ir::{Function, InstBuilder, Cursor, VariableArgs, types}; + use ir::{Function, InstBuilder, Cursor, types}; use cfg::ControlFlowGraph; #[test] @@ -238,14 +238,14 @@ mod test { let cur = &mut Cursor::new(&mut func.layout); cur.insert_ebb(ebb3); - jmp_ebb3_ebb1 = dfg.ins(cur).jump(ebb1, VariableArgs::new()); + jmp_ebb3_ebb1 = dfg.ins(cur).jump(ebb1, &[]); cur.insert_ebb(ebb1); - br_ebb1_ebb0 = dfg.ins(cur).brnz(cond, ebb0, VariableArgs::new()); - jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, VariableArgs::new()); + br_ebb1_ebb0 = dfg.ins(cur).brnz(cond, ebb0, &[]); + jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, &[]); cur.insert_ebb(ebb2); - dfg.ins(cur).jump(ebb0, VariableArgs::new()); + dfg.ins(cur).jump(ebb0, &[]); cur.insert_ebb(ebb0); } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 2425b8814a..d7393c9293 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -196,12 +196,14 @@ pub enum InstructionData { Jump { opcode: Opcode, ty: Type, - data: Box, + destination: Ebb, + args: ValueList, }, Branch { opcode: Opcode, ty: Type, - data: Box, + destination: Ebb, + args: ValueList, }, BranchTable { opcode: Opcode, @@ -220,7 +222,8 @@ pub enum InstructionData { opcode: Opcode, ty: Type, second_result: PackedOption, - data: Box, + sig_ref: SigRef, + args: ValueList, }, Return { opcode: Opcode, @@ -255,9 +258,10 @@ impl VariableArgs { self.0.is_empty() } - /// Convert this to a value list in `pool`. - pub fn into_value_list(self, pool: &mut ValueListPool) -> ValueList { + /// Convert this to a value list in `pool` with `fixed` prepended. + pub fn into_value_list(self, fixed: &[Value], pool: &mut ValueListPool) -> ValueList { let mut vlist = ValueList::default(); + vlist.extend(fixed.iter().cloned(), pool); vlist.extend(self.0, pool); vlist } @@ -327,85 +331,6 @@ impl Display for TernaryOverflowData { } } -/// Payload data for jump instructions. These need to carry lists of EBB arguments that won't fit -/// in the allowed `InstructionData` size. -#[derive(Clone, Debug)] -pub struct JumpData { - /// Jump destination EBB. - pub destination: Ebb, - /// Arguments passed to destination EBB. - pub varargs: VariableArgs, -} - -impl Display for JumpData { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.varargs.is_empty() { - write!(f, "{}", self.destination) - } else { - write!(f, "{}({})", self.destination, self.varargs) - } - } -} - -/// Payload data for branch instructions. These need to carry lists of EBB arguments that won't fit -/// in the allowed `InstructionData` size. -#[derive(Clone, Debug)] -pub struct BranchData { - /// Value argument controlling the branch. - pub arg: Value, - /// Branch destination EBB. - pub destination: Ebb, - /// Arguments passed to destination EBB. - pub varargs: VariableArgs, -} - -impl BranchData { - /// Get references to the arguments. - pub fn arguments(&self) -> [&[Value]; 2] { - [ref_slice(&self.arg), &self.varargs] - } - - /// Get mutable references to the arguments. - pub fn arguments_mut(&mut self) -> [&mut [Value]; 2] { - [ref_slice_mut(&mut self.arg), &mut self.varargs] - } -} - -impl Display for BranchData { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}, {}", self.arg, self.destination)?; - if !self.varargs.is_empty() { - write!(f, "({})", self.varargs)?; - } - Ok(()) - } -} - -/// Payload of an indirect call instruction. -#[derive(Clone, Debug)] -pub struct IndirectCallData { - /// Callee function. - pub arg: Value, - - /// Signature of the callee function. - pub sig_ref: SigRef, - - /// Dynamically sized array containing call argument values. - pub varargs: VariableArgs, -} - -impl IndirectCallData { - /// Get references to the arguments. - pub fn arguments(&self) -> [&[Value]; 2] { - [ref_slice(&self.arg), &self.varargs] - } - - /// Get mutable references to the arguments. - pub fn arguments_mut(&mut self) -> [&mut [Value]; 2] { - [ref_slice_mut(&mut self.arg), &mut self.varargs] - } -} - /// Payload of a return instruction. #[derive(Clone, Debug)] pub struct ReturnData { @@ -467,13 +392,13 @@ impl InstructionData { /// /// Any instruction that can transfer control to another EBB reveals its possible destinations /// here. - pub fn analyze_branch<'a>(&'a self) -> BranchInfo<'a> { + pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> { match self { - &InstructionData::Jump { ref data, .. } => { - BranchInfo::SingleDest(data.destination, &data.varargs) + &InstructionData::Jump { destination, ref args, .. } => { + BranchInfo::SingleDest(destination, &args.as_slice(pool)) } - &InstructionData::Branch { ref data, .. } => { - BranchInfo::SingleDest(data.destination, &data.varargs) + &InstructionData::Branch { destination, ref args, .. } => { + BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]) } &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), _ => BranchInfo::NotABranch, @@ -488,8 +413,8 @@ impl InstructionData { &InstructionData::Call { func_ref, ref args, .. } => { CallInfo::Direct(func_ref, &args.as_slice(pool)) } - &InstructionData::IndirectCall { ref data, .. } => { - CallInfo::Indirect(data.sig_ref, &data.varargs) + &InstructionData::IndirectCall { sig_ref, ref args, .. } => { + CallInfo::Indirect(sig_ref, &args.as_slice(pool)) } _ => CallInfo::NotACall, } @@ -507,7 +432,7 @@ impl InstructionData { } else if constraints.requires_typevar_operand() { // Not all instruction formats have a designated operand, but in that case // `requires_typevar_operand()` should never be true. - dfg.value_type(self.typevar_operand() + dfg.value_type(self.typevar_operand(&dfg.value_lists) .expect("Instruction format doesn't have a designated operand, bad opcode.")) } else { // For locality of reference, we prefer to get the controlling type variable from diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index bcbbb62fe1..c45da220e4 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -217,7 +217,7 @@ impl LiveValueTracker { -> (&[LiveValue], &[LiveValue]) { // Save a copy of the live values before any branches or jumps that could be somebody's // immediate dominator. - match dfg[inst].analyze_branch() { + match dfg[inst].analyze_branch(&dfg.value_lists) { BranchInfo::NotABranch => {} _ => self.save_idom_live_set(inst), } diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 8cba9757f9..5e8968c48b 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -225,6 +225,7 @@ fn write_instruction(w: &mut Write, } // Then the operands, depending on format. + let pool = &func.dfg.value_lists; use ir::instructions::InstructionData::*; match func.dfg[inst] { Nullary { .. } => writeln!(w, ""), @@ -244,8 +245,28 @@ fn write_instruction(w: &mut Write, ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), IntCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), FloatCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), - Jump { ref data, .. } => writeln!(w, " {}", data), - Branch { ref data, .. } => writeln!(w, " {}", data), + Jump { destination, ref args, .. } => { + if args.is_empty() { + writeln!(w, " {}", destination) + } else { + writeln!(w, + " {}({})", + destination, + DisplayValues(args.as_slice(pool))) + } + } + Branch { destination, ref args, .. } => { + let args = args.as_slice(pool); + if args.len() == 1 { + writeln!(w, " {}, {}", args[0], destination) + } else { + writeln!(w, + " {}, {}({})", + args[0], + destination, + DisplayValues(&args[1..])) + } + } BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), Call { func_ref, ref args, .. } => { writeln!(w, @@ -253,8 +274,13 @@ fn write_instruction(w: &mut Write, func_ref, DisplayValues(args.as_slice(&func.dfg.value_lists))) } - IndirectCall { ref data, .. } => { - writeln!(w, " {}, {}({})", data.sig_ref, data.arg, data.varargs) + IndirectCall { sig_ref, ref args, .. } => { + let args = args.as_slice(pool); + writeln!(w, + " {}, {}({})", + sig_ref, + args[0], + DisplayValues(&args[1..])) } Return { ref data, .. } => { if data.varargs.is_empty() { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index c009270f54..26fbe234a0 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -15,8 +15,7 @@ use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, - TernaryOverflowData, JumpData, BranchData, IndirectCallData, - ReturnData, ReturnRegData}; + TernaryOverflowData, ReturnData, ReturnRegData}; use cretonne::isa::{self, TargetIsa, Encoding}; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -200,24 +199,22 @@ impl<'a> Context<'a> { self.map.rewrite_values(&mut data.args, loc)?; } - InstructionData::Jump { ref mut data, .. } => { - self.map.rewrite_ebb(&mut data.destination, loc)?; - self.map.rewrite_values(&mut data.varargs, loc)?; + InstructionData::Jump { ref mut destination, ref mut args, .. } => { + self.map.rewrite_ebb(destination, loc)?; + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::Branch { ref mut data, .. } => { - self.map.rewrite_value(&mut data.arg, loc)?; - self.map.rewrite_ebb(&mut data.destination, loc)?; - self.map.rewrite_values(&mut data.varargs, loc)?; + InstructionData::Branch { ref mut destination, ref mut args, .. } => { + self.map.rewrite_ebb(destination, loc)?; + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } InstructionData::Call { ref mut args, .. } => { self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::IndirectCall { ref mut data, .. } => { - self.map.rewrite_value(&mut data.arg, loc)?; - self.map.rewrite_values(&mut data.varargs, loc)?; + InstructionData::IndirectCall { ref mut args, .. } => { + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } InstructionData::Return { ref mut data, .. } => { @@ -1209,7 +1206,7 @@ impl<'a> Parser<'a> { // TBD: If it is defined in another block, the type should have been specified // explicitly. It is unfortunate that the correctness of IL depends on the // layout of the blocks. - let ctrl_src_value = inst_data.typevar_operand() + let ctrl_src_value = inst_data.typevar_operand(&ctx.function.dfg.value_lists) .expect("Constraints <-> Format inconsistency"); ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { Some(v) => v, @@ -1429,10 +1426,8 @@ impl<'a> Parser<'a> { InstructionData::Jump { opcode: opcode, ty: VOID, - data: Box::new(JumpData { - destination: ebb_num, - varargs: args, - }), + destination: ebb_num, + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } InstructionFormat::Branch => { @@ -1443,11 +1438,8 @@ impl<'a> Parser<'a> { InstructionData::Branch { opcode: opcode, ty: VOID, - data: Box::new(BranchData { - arg: ctrl_arg, - destination: ebb_num, - varargs: args, - }), + destination: ebb_num, + args: args.into_value_list(&[ctrl_arg], &mut ctx.function.dfg.value_lists), } } InstructionFormat::InsertLane => { @@ -1511,7 +1503,7 @@ impl<'a> Parser<'a> { ty: VOID, second_result: None.into(), func_ref: func_ref, - args: args.into_value_list(&mut ctx.function.dfg.value_lists), + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } InstructionFormat::IndirectCall => { @@ -1526,11 +1518,8 @@ impl<'a> Parser<'a> { opcode: opcode, ty: VOID, second_result: None.into(), - data: Box::new(IndirectCallData { - sig_ref: sig_ref, - arg: callee, - varargs: args, - }), + sig_ref: sig_ref, + args: args.into_value_list(&[callee], &mut ctx.function.dfg.value_lists), } } InstructionFormat::Return => { diff --git a/src/print_cfg.rs b/src/print_cfg.rs index d8261dad10..206f5430c2 100644 --- a/src/print_cfg.rs +++ b/src/print_cfg.rs @@ -59,7 +59,7 @@ impl<'a> CFGPrinter<'a> { // Add all outgoing branch instructions to the label. for inst in self.func.layout.ebb_insts(ebb) { let idata = &self.func.dfg[inst]; - match idata.analyze_branch() { + match idata.analyze_branch(&self.func.dfg.value_lists) { BranchInfo::SingleDest(dest, _) => { write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)? } From 582a048089e13190405bfe0d2071ba65521827f4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 15:56:33 -0800 Subject: [PATCH 566/968] Convert return formats to value lists. With the Return and ReturnReg formats converted to using value lists for storing their arguments, thee are no remaining instruction formats with variable argument lists in boxed storage. The Return and ReturnReg formats are also going to be merged since they are identical now. --- lib/cretonne/meta/base/formats.py | 4 ++-- lib/cretonne/meta/cdsl/formats.py | 4 ++++ lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/instructions.rs | 32 ++--------------------------- lib/cretonne/src/write.rs | 18 ++++++++-------- lib/reader/src/parser.rs | 18 +++++++--------- 6 files changed, 25 insertions(+), 53 deletions(-) diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 9f7a805940..c72d5034f8 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -51,8 +51,8 @@ Call = InstructionFormat( IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True, value_list=True) -Return = InstructionFormat(VARIABLE_ARGS, boxed_storage=True) -ReturnReg = InstructionFormat(VALUE, VARIABLE_ARGS, boxed_storage=True) +Return = InstructionFormat(VARIABLE_ARGS, value_list=True) +ReturnReg = InstructionFormat(VALUE, VARIABLE_ARGS, value_list=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 422d84c296..98f6964e5c 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -63,6 +63,10 @@ class InstructionFormat(object): self.value_operands = tuple( i for i, k in enumerate(self.kinds) if k is VALUE) + # We require a value list for storage of variable arguments. + if VARIABLE_ARGS in self.kinds: + assert self.has_value_list, "Need a value list for variable args" + # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int if self.typevar_operand is not None: diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index ebb8ae2350..fa15587445 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,7 +5,7 @@ use ir::{types, instructions}; use ir::{InstructionData, DataFlowGraph, Cursor}; -use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, VariableArgs, SigRef, FuncRef, ValueList}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, ValueList}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; use ir::condcodes::{IntCC, FloatCC}; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index d7393c9293..a3cd4509da 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -228,12 +228,12 @@ pub enum InstructionData { Return { opcode: Opcode, ty: Type, - data: Box, + args: ValueList, }, ReturnReg { opcode: Opcode, ty: Type, - data: Box, + args: ValueList, }, } @@ -331,34 +331,6 @@ impl Display for TernaryOverflowData { } } -/// Payload of a return instruction. -#[derive(Clone, Debug)] -pub struct ReturnData { - /// Dynamically sized array containing return values. - pub varargs: VariableArgs, -} - -/// Payload of a return instruction. -#[derive(Clone, Debug)] -pub struct ReturnRegData { - /// Return address. - pub arg: Value, - /// Dynamically sized array containing return values. - pub varargs: VariableArgs, -} - -impl ReturnRegData { - /// Get references to the arguments. - pub fn arguments(&self) -> [&[Value]; 2] { - [ref_slice(&self.arg), &self.varargs] - } - - /// Get mutable references to the arguments. - pub fn arguments_mut(&mut self) -> [&mut [Value]; 2] { - [ref_slice_mut(&mut self.arg), &mut self.varargs] - } -} - /// Analyzing an instruction. /// /// Avoid large matches on instruction formats by using the methods defined here to examine diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 5e8968c48b..59c97d9283 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -282,19 +282,19 @@ fn write_instruction(w: &mut Write, args[0], DisplayValues(&args[1..])) } - Return { ref data, .. } => { - if data.varargs.is_empty() { + Return { ref args, .. } => { + if args.is_empty() { writeln!(w, "") } else { - writeln!(w, " {}", data.varargs) + writeln!(w, + " {}", + DisplayValues(args.as_slice(&func.dfg.value_lists))) } } - ReturnReg { ref data, .. } => { - if data.varargs.is_empty() { - writeln!(w, " {}", data.arg) - } else { - writeln!(w, " {}, {}", data.arg, data.varargs) - } + ReturnReg { ref args, .. } => { + writeln!(w, + " {}", + DisplayValues(args.as_slice(&func.dfg.value_lists))) } } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 26fbe234a0..72f43a043d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -15,7 +15,7 @@ use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, - TernaryOverflowData, ReturnData, ReturnRegData}; + TernaryOverflowData}; use cretonne::isa::{self, TargetIsa, Encoding}; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -217,13 +217,12 @@ impl<'a> Context<'a> { self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::Return { ref mut data, .. } => { - self.map.rewrite_values(&mut data.varargs, loc)?; + InstructionData::Return { ref mut args, .. } => { + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::ReturnReg { ref mut data, .. } => { - self.map.rewrite_value(&mut data.arg, loc)?; - self.map.rewrite_values(&mut data.varargs, loc)?; + InstructionData::ReturnReg { ref mut args, .. } => { + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } } } @@ -1527,7 +1526,7 @@ impl<'a> Parser<'a> { InstructionData::Return { opcode: opcode, ty: VOID, - data: Box::new(ReturnData { varargs: args }), + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } InstructionFormat::ReturnReg => { @@ -1540,10 +1539,7 @@ impl<'a> Parser<'a> { InstructionData::ReturnReg { opcode: opcode, ty: VOID, - data: Box::new(ReturnRegData { - arg: raddr, - varargs: args, - }), + args: args.into_value_list(&[raddr], &mut ctx.function.dfg.value_lists), } } InstructionFormat::BranchTable => { From cd06b176ac99036d222470ea197b1033df9c8780 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 19:12:00 -0800 Subject: [PATCH 567/968] Remove some has_value_list workarounds. Now that all variable_args formats use has_value_list, we can remove some workarounds that allowed the old boxed_storage form. --- lib/cretonne/meta/cdsl/operands.py | 2 +- lib/cretonne/meta/gen_instr.py | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index f7ec85eafa..277f2597b0 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -61,7 +61,7 @@ VARIABLE_ARGS = OperandKind( passed to an extended basic block, or a variable number of results returned from an instruction. """, - default_member='varargs') + rust_type='&[Value]') # Instances of immediate operand types are provided in the diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 376bfc0d56..b452daff2e 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -510,10 +510,7 @@ def gen_format_constructor(iform, fmt): # Normal operand arguments. for idx, kind in enumerate(iform.kinds): - if kind is cdsl.operands.VARIABLE_ARGS and iform.has_value_list: - args.append('op{}: &[Value]'.format(idx, kind.rust_type)) - else: - args.append('op{}: {}'.format(idx, kind.rust_type)) + args.append('op{}: {}'.format(idx, kind.rust_type)) proto = '{}({})'.format(iform.name, ', '.join(args)) proto += " -> (Inst, &'f mut DataFlowGraph)" @@ -575,8 +572,6 @@ def gen_member_inits(iform, fmt): # Immediates and entity references. for idx, member in enumerate(iform.members): - if iform.has_value_list and member == 'varargs': - continue if member: fmt.line('{}: op{},'.format(member, idx)) @@ -606,9 +601,6 @@ def gen_inst_builder(inst, fmt): t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name) tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type)) into_args.append(op.name) - elif (inst.format.has_value_list and - op.kind is cdsl.operands.VARIABLE_ARGS): - t = '&[Value]' else: t = op.kind.rust_type args.append('{}: {}'.format(op.name, t)) From 703762a67c527da07f90a8a567bbf29082f74428 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 21:03:52 -0800 Subject: [PATCH 568/968] Python InstructionFormat refactoring. Make some changes that will make it easier to get rid of the 'value_operands' and 'members' fields in the Python InstructionFormat class. This is necessary to be able to combine instruction formats that all use a value list representation, but with different fixed value operands. The goal is to eventually identify formats by a new signature: (multiple_results, imm_kinds, num_value_operands) Start by adding new fields: - imm_members and imm_kinds are lists describing the format operands, excluding any values and variable_args operands. - num_value_operands is the number of fixed value operands, or None in a has_value-list format. Use these new members in preference to the old ones where possible. --- lib/cretonne/meta/cdsl/formats.py | 26 ++++++++++++++++++++++---- lib/cretonne/meta/gen_instr.py | 30 +++++++----------------------- lib/cretonne/meta/gen_legalizer.py | 5 ++--- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 98f6964e5c..506f0d7322 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -57,16 +57,23 @@ class InstructionFormat(object): self.has_value_list = kwargs.get('value_list', False) self.boxed_storage = kwargs.get('boxed_storage', False) self.members = list() # type: List[str] + + # Struct member names for the immediate operands. All other instruction + # operands are values or variable argument lists. They are all handled + # specially. + self.imm_members = list() # type: List[str] + # Operand kinds for the immediate operands. + self.imm_kinds = list() # type: List[OperandKind] + # The number of value operands stored in the format, or `None` when + # `has_value_list` is set. + self.num_value_operands = 0 + self.kinds = tuple(self._process_member_names(kinds)) # Which of self.kinds are `value`? self.value_operands = tuple( i for i, k in enumerate(self.kinds) if k is VALUE) - # We require a value list for storage of variable arguments. - if VARIABLE_ARGS in self.kinds: - assert self.has_value_list, "Need a value list for variable args" - # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int if self.typevar_operand is not None: @@ -102,6 +109,17 @@ class InstructionFormat(object): k = arg else: member, k = arg + + # We define 'immediate' as not a value or variable arguments. + if k is VALUE: + self.num_value_operands += 1 + elif k is VARIABLE_ARGS: + # We require a value list for storage of variable arguments. + assert self.has_value_list, "Need a value list" + else: + self.imm_kinds.append(k) + self.imm_members.append(member) + self.members.append(member) yield k diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index b452daff2e..073891807f 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -84,20 +84,11 @@ def gen_arguments_method(fmt, is_mut): .format(n, mut, mut, as_slice)) continue - has_varargs = cdsl.operands.VARIABLE_ARGS in f.kinds - # Formats with both fixed and variable arguments delegate to - # the data struct. We need to work around borrow checker quirks - # when extracting two mutable references. - if has_varargs and len(f.value_operands) > 0: - fmt.line( - '{} {{ ref {}data, .. }} => data.{}(),' - .format(n, mut, method)) - continue # Fixed args. - if len(f.value_operands) == 0: + if f.num_value_operands == 0: arg = '&{}[]'.format(mut) capture = '' - elif len(f.value_operands) == 1: + elif f.num_value_operands == 1: if f.boxed_storage: capture = 'ref {}data, '.format(mut) arg = '{}(&{}data.arg)'.format(rslice, mut) @@ -111,16 +102,9 @@ def gen_arguments_method(fmt, is_mut): else: capture = 'ref {}args, '.format(mut) arg = 'args' - # Varargs. - if cdsl.operands.VARIABLE_ARGS in f.kinds: - varg = '&{}data.varargs'.format(mut) - capture = 'ref {}data, '.format(mut) - else: - varg = '&{}[]'.format(mut) - fmt.line( - '{} {{ {} .. }} => [{}, {}],' - .format(n, capture, arg, varg)) + '{} {{ {} .. }} => [{}, &{}[]],' + .format(n, capture, arg, mut)) def gen_instruction_data_impl(fmt): @@ -219,7 +203,7 @@ def gen_instruction_data_impl(fmt): fmt.line( '{} {{ ref args, .. }} => ' 'args.get({}, pool),'.format(n, i)) - elif len(f.value_operands) == 1: + elif f.num_value_operands == 1: # We have a single value operand called 'arg'. if f.boxed_storage: fmt.line( @@ -564,9 +548,9 @@ def gen_member_inits(iform, fmt): if iform.has_value_list: # Value-list formats put *all* arguments in the list. fmt.line('args: vlist,') - elif len(iform.value_operands) == 1: + elif iform.num_value_operands == 1: fmt.line('arg: op{},'.format(iform.value_operands[0])) - elif len(iform.value_operands) > 1: + elif iform.num_value_operands > 1: fmt.line('args: [{}],'.format( ', '.join('op{}'.format(i) for i in iform.value_operands))) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 983871ac40..3199b3741f 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -46,9 +46,8 @@ def unwrap_inst(iref, node, fmt): fmt.line('ref data,') else: # Fields are encoded directly. - for m in iform.members: - if m: - fmt.line('{},'.format(m)) + for m in iform.imm_members: + fmt.line('{},'.format(m)) if nvops == 1: fmt.line('arg,') elif nvops > 1: From f451cf42c8f81586211125d62b6209abc558ef51 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 9 Mar 2017 22:04:13 -0800 Subject: [PATCH 569/968] Simplify the arguments() return type. Now that variable arguments are always stored in a value list with the fixed arguments, we no longer need the arcane [&[Value]; 2] return type. Arguments are always stored contiguously, so just return a &[Value] slice. Also remove the each_arg() methods which were just trying to make it easier to work with the old slice pair. --- lib/cretonne/meta/gen_instr.py | 11 +++++------ lib/cretonne/src/ir/instructions.rs | 24 ------------------------ lib/cretonne/src/regalloc/coloring.rs | 2 +- lib/cretonne/src/regalloc/liveness.rs | 4 ++-- lib/cretonne/src/write.rs | 2 +- 5 files changed, 9 insertions(+), 34 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 073891807f..f5d5d07b74 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -67,7 +67,7 @@ def gen_arguments_method(fmt, is_mut): with fmt.indented( 'pub fn {f}<\'a>(&\'a {m}self, pool: &\'a {m}ValueListPool) -> ' - '[&{m}[Value]; 2] {{' + '&{m}[Value] {{' .format(f=method, m=mut), '}'): with fmt.indented('match *self {', '}'): for f in InstructionFormat.all_formats: @@ -79,9 +79,8 @@ def gen_arguments_method(fmt, is_mut): if f.has_value_list: arg = ''.format(mut) fmt.line( - '{} {{ ref {}args, .. }} => ' - '[ &{}[], args.{}(pool) ],' - .format(n, mut, mut, as_slice)) + '{} {{ ref {}args, .. }} => args.{}(pool),' + .format(n, mut, as_slice)) continue # Fixed args. @@ -103,8 +102,8 @@ def gen_arguments_method(fmt, is_mut): capture = 'ref {}args, '.format(mut) arg = 'args' fmt.line( - '{} {{ {} .. }} => [{}, &{}[]],' - .format(n, capture, arg, mut)) + '{} {{ {} .. }} => {},' + .format(n, capture, arg)) def gen_instruction_data_impl(fmt): diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index a3cd4509da..4cabfe6698 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -336,30 +336,6 @@ impl Display for TernaryOverflowData { /// Avoid large matches on instruction formats by using the methods defined here to examine /// instructions. impl InstructionData { - /// Execute a closure once for each argument to this instruction. - /// See also the `arguments()` method. - pub fn each_arg(&self, pool: &ValueListPool, mut func: F) - where F: FnMut(Value) - { - for part in &self.arguments(pool) { - for &arg in part.iter() { - func(arg); - } - } - } - - /// Execute a closure with a mutable reference to each argument to this instruction. - /// See also the `arguments_mut()` method. - pub fn each_arg_mut(&mut self, pool: &mut ValueListPool, mut func: F) - where F: FnMut(&mut Value) - { - for part in &mut self.arguments_mut(pool) { - for arg in part.iter_mut() { - func(arg); - } - } - } - /// Return information about the destination of a branch or jump instruction. /// /// Any instruction that can transfer control to another EBB reveals its possible destinations diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 6549c4356f..24f8c0c7e1 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -299,7 +299,7 @@ impl<'a> Context<'a> { } ConstraintKind::Tied(arg_index) => { // This def must use the same register as a fixed instruction argument. - let arg = dfg[inst].arguments(&dfg.value_lists)[0][arg_index as usize]; + let arg = dfg[inst].arguments(&dfg.value_lists)[arg_index as usize]; let loc = locations[arg]; *locations.ensure(lv.value) = loc; // Mark the reused register. It's not really clear if we support tied diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 49db4a04cd..582cbae48a 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -318,7 +318,7 @@ impl Liveness { let mut operand_constraints = recipe_constraints.get(recipe).map(|c| c.ins).unwrap_or(&[]).iter(); - func.dfg[inst].each_arg(&func.dfg.value_lists, |arg| { + for &arg in func.dfg[inst].arguments(&func.dfg.value_lists) { // Get the live range, create it as a dead range if necessary. let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints); @@ -333,7 +333,7 @@ impl Liveness { if let Some(constraint) = operand_constraints.next() { lr.affinity.merge(constraint, ®_info); } - }); + } } } } diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 59c97d9283..8f120f447a 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -157,7 +157,7 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { // Write out any value aliases appearing in `inst`. fn write_value_aliases(w: &mut Write, func: &Function, inst: Inst, indent: usize) -> Result { - for &arg in func.dfg[inst].arguments(&func.dfg.value_lists).iter().flat_map(|x| x.iter()) { + for &arg in func.dfg[inst].arguments(&func.dfg.value_lists) { let resolved = func.dfg.resolve_aliases(arg); if resolved != arg { writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)?; From f4d43101d08bb6e49cf43c088c66cae69125d04c Mon Sep 17 00:00:00 2001 From: Davide Italiano Date: Fri, 10 Mar 2017 09:04:41 -0800 Subject: [PATCH 570/968] Initial B-tree interface. --- lib/cretonne/src/btree.rs | 98 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 lib/cretonne/src/btree.rs diff --git a/lib/cretonne/src/btree.rs b/lib/cretonne/src/btree.rs new file mode 100644 index 0000000000..cd34c466b5 --- /dev/null +++ b/lib/cretonne/src/btree.rs @@ -0,0 +1,98 @@ +use std::marker::PhantomData; + +// A Node reference is a direct index to an element of the pool. +type NodeRef = u32; + +/// A B-tree data structure which nodes are allocated from a pool. +pub struct BTree { + index: NodeRef, + unused1: PhantomData, + unused2: PhantomData, +} + +/// An enum representing a B-tree node. +/// Keys and values are required to implement Default. +enum Node { + Inner { + size: u8, + keys: [K; 7], + nodes: [NodeRef; 8], + }, + Leaf { + size: u8, + keys: [K; 7], + values: [V; 7], + }, +} + +/// Memory pool for nodes. +struct NodePool { + // The array containing the nodes. + data: Vec>, + + // A free list + freelist: Vec, +} + +impl NodePool { + /// Create a new NodePool. + pub fn new() -> NodePool { + NodePool { + data: Vec::new(), + freelist: Vec::new(), + } + } + + /// Get a B-tree node. + pub fn get(&self, index: u32) -> Option<&Node> { + unimplemented!() + } +} + +impl BTree { + /// Search for `key` and return a `Cursor` that either points at `key` or the position where it would be inserted. + pub fn search(&mut self, key: K) -> Cursor { + unimplemented!() + } +} + +pub struct Cursor<'a, K: 'a, V: 'a> { + pool: &'a mut NodePool, + height: usize, + path: [(NodeRef, u8); 16], +} + +impl<'a, K: Default, V: Default> Cursor<'a, K, V> { + /// The key at the cursor position. Returns `None` when the cursor points off the end. + pub fn key(&self) -> Option { + unimplemented!() + } + + /// The value at the cursor position. Returns `None` when the cursor points off the end. + pub fn value(&self) -> Option<&V> { + unimplemented!() + } + + /// Move to the next element. + /// Returns `false` if that moves the cursor off the end. + pub fn next(&mut self) -> bool { + unimplemented!() + } + + /// Move to the previous element. + /// Returns `false` if this moves the cursor before the beginning. + pub fn prev(&mut self) -> bool { + unimplemented!() + } + + /// Insert a `(key, value)` pair at the cursor position. + /// It is an error to insert a key that would be out of order at this position. + pub fn insert(&mut self, key: K, value: V) { + unimplemented!() + } + + /// Remove the current element. + pub fn remove(&mut self) { + unimplemented!() + } +} From e217b04347c70dd2c3f8afc83341b81abce6f408 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 08:25:02 -0800 Subject: [PATCH 571/968] Clean up lifetimes a bit. No functional change. --- lib/cretonne/meta/gen_instr.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index f5d5d07b74..23f28efeb7 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -117,9 +117,9 @@ def gen_instruction_data_impl(fmt): - `pub fn opcode(&self) -> Opcode` - `pub fn first_type(&self) -> Type` - `pub fn second_result(&self) -> Option` - - `pub fn second_result_mut<'a>(&'a mut self) - -> Option<&'a mut PackedOption>` - - `pub fn arguments(&self) -> (&[Value], &[Value])` + - `pub fn second_result_mut(&mut self) -> Option<&mut PackedOption>` + - `pub fn arguments(&self, &pool) -> &[Value]` + - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]` """ # The `opcode` and `first_type` methods simply read the `opcode` and `ty` @@ -172,8 +172,8 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Mutable reference to second result value, if any.') with fmt.indented( - "pub fn second_result_mut<'a>(&'a mut self)" + - " -> Option<&'a mut PackedOption> {", '}'): + "pub fn second_result_mut(&mut self)" + + " -> Option<&mut PackedOption> {", '}'): with fmt.indented('match *self {', '}'): for f in InstructionFormat.all_formats: if f.multiple_results: From 2b209c791db1bbefddb53f6777b52794dab76e9f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 09:23:55 -0800 Subject: [PATCH 572/968] Add value_opnums and imm_opnums fields to Instruction. These two tuples contain operand indexes of the explicit value operands and immediate operands respectively. We can no longer use the instruction format value_operands field. --- lib/cretonne/meta/cdsl/instructions.py | 11 ++++++++++- lib/cretonne/meta/cdsl/operands.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index fcce1684fd..861e6f0883 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -104,9 +104,18 @@ class Instruction(object): self.ins = self._to_operand_tuple(ins) self.outs = self._to_operand_tuple(outs) self.format = InstructionFormat.lookup(self.ins, self.outs) - # Indexes into outs for value results. Others are `variable_args`. + + # Indexes into `self.outs` for value results. + # Other results are `variable_args`. self.value_results = tuple( i for i, o in enumerate(self.outs) if o.is_value()) + # Indexes into `self.ins` for value operands. + self.value_opnums = tuple( + i for i, o in enumerate(self.ins) if o.is_value()) + # Indexes into `self.ins` for non-value operands. + self.imm_opnums = tuple( + i for i, o in enumerate(self.ins) if o.is_immediate()) + self._verify_polymorphic() for attr in Instruction.ATTRIBS: setattr(self, attr, not not kwargs.get(attr, False)) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index 277f2597b0..dafa18de35 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -157,3 +157,14 @@ class Operand(object): Is this an SSA value operand? """ return self.kind is VALUE + + def is_immediate(self): + # type: () -> bool + """ + Is this an immediate operand? + + Note that this includes both `ImmediateKind` operands *and* entity + references. It is any operand that doesn't represent a value + dependency. + """ + return self.kind is not VALUE and self.kind is not VARIABLE_ARGS From be7ff71b151d92ff375503963e01cc0df5901b4e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 09:05:53 -0800 Subject: [PATCH 573/968] Change InstBuilder low-level format constructor signatures. The per-instruction format low-level constructors in InstBuilder should be independent of the relative ordering of value and immediate operands in order to prepare for the future instruction format merger. Reorder their arguments such that all the immediate operands are placed before the value operands. For instruction formats that use a value list representation, just take a single ValueList argument. The value lists are created by the individual instruction constructors. This means that the format constructor doesn't care how many of the instructions operands are 'fixed' and how many are 'variable' arguments. --- lib/cretonne/meta/cdsl/operands.py | 7 +++ lib/cretonne/meta/gen_instr.py | 94 +++++++++++++++++------------- 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index dafa18de35..49e3e588af 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -158,6 +158,13 @@ class Operand(object): """ return self.kind is VALUE + def is_varargs(self): + # type: () -> bool + """ + Is this a VARIABLE_ARGS operand? + """ + return self.kind is VARIABLE_ARGS + def is_immediate(self): # type: () -> bool """ diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 23f28efeb7..b9e46172d7 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -7,7 +7,6 @@ import constant_hash from unique_table import UniqueTable, UniqueSeqTable from cdsl import camel_case from cdsl.operands import ImmediateKind -import cdsl.types from cdsl.formats import InstructionFormat from cdsl.instructions import Instruction # noqa @@ -477,11 +476,7 @@ def gen_format_constructor(iform, fmt): """ # Construct method arguments. - if iform.has_value_list: - args = ['mut self'] - else: - args = ['self'] - args.append('opcode: Opcode') + args = ['self', 'opcode: Opcode'] if iform.multiple_results: args.append('ctrl_typevar: Type') @@ -491,9 +486,18 @@ def gen_format_constructor(iform, fmt): args.append('result_type: Type') result_type = 'result_type' - # Normal operand arguments. - for idx, kind in enumerate(iform.kinds): - args.append('op{}: {}'.format(idx, kind.rust_type)) + # Normal operand arguments. Start with the immediate operands. + for kind, name in zip(iform.imm_kinds, iform.imm_members): + args.append('{}: {}'.format(name, kind.rust_type)) + # Then the value operands. + if iform.has_value_list: + # Take all value arguments as a finished value list. The value lists + # are created by the individual instruction constructors. + args.append('args: ValueList') + else: + # Take a fixed number of value operands. + for i in range(iform.num_value_operands): + args.append('arg{}: Value'.format(i)) proto = '{}({})'.format(iform.name, ', '.join(args)) proto += " -> (Inst, &'f mut DataFlowGraph)" @@ -501,21 +505,6 @@ def gen_format_constructor(iform, fmt): fmt.doc_comment(str(iform)) fmt.line('#[allow(non_snake_case)]') with fmt.indented('fn {} {{'.format(proto), '}'): - # Start by constructing a value list with *all* the arguments. - if iform.has_value_list: - fmt.line('let mut vlist = ValueList::default();') - with fmt.indented('{', '}'): - fmt.line( - 'let pool = ' - '&mut self.data_flow_graph_mut().value_lists;') - for idx, kind in enumerate(iform.kinds): - if kind is cdsl.operands.VALUE: - fmt.line('vlist.push(op{}, pool);'.format(idx)) - elif kind is cdsl.operands.VARIABLE_ARGS: - fmt.line( - 'vlist.extend(op{}.iter().cloned(), pool);' - .format(idx)) - # Generate the instruction data. with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): @@ -543,20 +532,19 @@ def gen_member_inits(iform, fmt): Emit member initializers for an `iform` instruction. """ - # Values first. - if iform.has_value_list: - # Value-list formats put *all* arguments in the list. - fmt.line('args: vlist,') - elif iform.num_value_operands == 1: - fmt.line('arg: op{},'.format(iform.value_operands[0])) - elif iform.num_value_operands > 1: - fmt.line('args: [{}],'.format( - ', '.join('op{}'.format(i) for i in iform.value_operands))) + # Immediate operands. + # We have local variables with the same names as the members. + for member in iform.imm_members: + fmt.line('{}: {},'.format(member, member)) - # Immediates and entity references. - for idx, member in enumerate(iform.members): - if member: - fmt.line('{}: op{},'.format(member, idx)) + # Value operands. + if iform.has_value_list: + fmt.line('args: args,') + elif iform.num_value_operands == 1: + fmt.line('arg: arg0,') + elif iform.num_value_operands > 1: + args = ('arg{}'.format(i) for i in range(iform.num_value_operands)) + fmt.line('args: [{}],'.format(', '.join(args))) def gen_inst_builder(inst, fmt): @@ -570,7 +558,10 @@ def gen_inst_builder(inst, fmt): """ # Construct method arguments. - args = ['self'] + if inst.format.has_value_list: + args = ['mut self'] + else: + args = ['self'] # The controlling type variable will be inferred from the input values if # possible. Otherwise, it is the first method argument. @@ -645,7 +636,32 @@ def gen_inst_builder(inst, fmt): inst.outs[inst.value_results[0]] .typevar.singleton_type.rust_name()) - args.extend(op.name for op in inst.ins) + # Now add all of the immediate operands to the constructor arguments. + for opnum in inst.imm_opnums: + args.append(inst.ins[opnum].name) + + # Finally, the value operands. + if inst.format.has_value_list: + # We need to build a value list with all the arguments. + fmt.line('let mut vlist = ValueList::default();') + args.append('vlist') + with fmt.indented('{', '}'): + fmt.line( + 'let pool = ' + '&mut self.data_flow_graph_mut().value_lists;') + for op in inst.ins: + if op.is_value(): + fmt.line('vlist.push({}, pool);'.format(op.name)) + elif op.is_varargs(): + fmt.line( + 'vlist.extend({}.iter().cloned(), pool);' + .format(op.name)) + else: + # With no value list, we're guaranteed to just have a set of fixed + # value operands. + for opnum in inst.value_opnums: + args.append(inst.ins[opnum].name) + # Call to the format constructor, fcall = 'self.{}({})'.format(inst.format.name, ', '.join(args)) From c1fa8fbb61017091bf965fee5425832bddad584d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 09:56:37 -0800 Subject: [PATCH 574/968] Avoid using 'members' and 'value_operands' in the legalizer. Use the new Instruction fields instead. --- lib/cretonne/meta/gen_legalizer.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 3199b3741f..7c36700dc7 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -35,7 +35,7 @@ def unwrap_inst(iref, node, fmt): fmt.comment('Unwrap {}'.format(node)) expr = node.expr iform = expr.inst.format - nvops = len(iform.value_operands) + nvops = iform.num_value_operands # The tuple of locals we're extracting is `expr.args`. with fmt.indented( @@ -50,31 +50,31 @@ def unwrap_inst(iref, node, fmt): fmt.line('{},'.format(m)) if nvops == 1: fmt.line('arg,') - elif nvops > 1: + elif nvops != 0: fmt.line('args,') fmt.line('..') fmt.outdented_line('} = dfg[inst] {') # Generate the values for the tuple. outs = list() prefix = 'data.' if iform.boxed_storage else '' - for i, m in enumerate(iform.members): - if m: - outs.append(prefix + m) - else: - # This is a value operand. + for opnum, op in enumerate(expr.inst.ins): + if op.is_immediate(): + n = expr.inst.imm_opnums.index(opnum) + outs.append(prefix + iform.imm_members[n]) + elif op.is_value(): if nvops == 1: arg = prefix + 'arg' else: - arg = '{}args[{}]'.format( - prefix, iform.value_operands.index(i)) + n = expr.inst.value_opnums.index(opnum) + arg = '{}args[{}]'.format(prefix, n) outs.append('dfg.resolve_aliases({})'.format(arg)) fmt.line('({})'.format(', '.join(outs))) fmt.outdented_line('} else {') fmt.line('unreachable!("bad instruction format")') # Get the types of any variables where it is needed. - for i in iform.value_operands: - v = expr.args[i] + for opnum in expr.inst.value_opnums: + v = expr.args[opnum] if isinstance(v, Var) and v.has_free_typevar(): fmt.line('let typeof_{0} = dfg.value_type({0});'.format(v)) From 2c845ad65c5a83840d99eb39ff54a8c8628e678a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 10:04:30 -0800 Subject: [PATCH 575/968] Eliminate InstructionFormat.members. This field is no longer needed. We can use imm_members to get the names of immediate fields, and num_value_operands to access values. --- lib/cretonne/meta/cdsl/formats.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 506f0d7322..088461d1d2 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -56,7 +56,6 @@ class InstructionFormat(object): self.multiple_results = kwargs.get('multiple_results', False) self.has_value_list = kwargs.get('value_list', False) self.boxed_storage = kwargs.get('boxed_storage', False) - self.members = list() # type: List[str] # Struct member names for the immediate operands. All other instruction # operands are values or variable argument lists. They are all handled @@ -101,6 +100,8 @@ class InstructionFormat(object): pair. The member names correspond to members in the Rust `InstructionData` data structure. + Update the fields `num_value_operands`, `imm_kinds`, and `imm_members`. + Yields the operand kinds. """ for arg in kinds: @@ -120,25 +121,25 @@ class InstructionFormat(object): self.imm_kinds.append(k) self.imm_members.append(member) - self.members.append(member) yield k def __str__(self): # type: () -> str - args = ', '.join('{}: {}'.format(m, k) if m else str(k) - for m, k in zip(self.members, self.kinds)) - return '{}({})'.format(self.name, args) + args = ', '.join('{}: {}'.format(m, k) + for m, k in zip(self.imm_members, self.imm_kinds)) + return '{}({}, values={})'.format( + self.name, args, self.num_value_operands) def __getattr__(self, attr): # type: (str) -> FormatField """ - Make instruction format members available as attributes. + Make immediate instruction format members available as attributes. Each non-value format member becomes a corresponding `FormatField` attribute. """ try: - i = self.members.index(attr) + i = self.imm_members.index(attr) except ValueError: raise AttributeError( '{} is neither a {} member or a ' @@ -192,7 +193,7 @@ class FormatField(object): data type. :param format: Parent `InstructionFormat`. - :param operand: Operand number in parent. + :param operand: Immediate operand number in parent. :param name: Member name in `InstructionData` variant. """ From 405cc14522171a602ad324cee04e95ef71b37b04 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 10:38:38 -0800 Subject: [PATCH 576/968] Change index domain for typevar_operand. An instruction format is now seen as having two separate operand lists: immediates and values. Change InstructionFormat.typevar_operand to be a pure index into the value list. --- lib/cretonne/meta/cdsl/formats.py | 16 +++++++++------- lib/cretonne/meta/cdsl/instructions.py | 3 ++- lib/cretonne/meta/gen_instr.py | 12 ++++++------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 088461d1d2..4b7d512ff1 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -39,9 +39,10 @@ class InstructionFormat(object): :param boxed_storage: Set to `True` is this instruction format requires a `data: Box<...>` pointer to additional storage in its `InstructionData` variant. - :param typevar_operand: Index of the input operand that is used to infer - the controlling type variable. By default, this is the first `value` - operand. + :param typevar_operand: Index of the value input operand that is used to + infer the controlling type variable. By default, this is `0`, the first + `value` operand. The index is relative to the values only, ignoring + immediate operands. """ # Map (multiple_results, kind, kind, ...) -> InstructionFormat @@ -76,11 +77,12 @@ class InstructionFormat(object): # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int if self.typevar_operand is not None: - assert self.kinds[self.typevar_operand] is VALUE, \ - "typevar_operand must indicate a 'value' operand" - elif len(self.value_operands) > 0: + if not self.has_value_list: + assert self.typevar_operand < self.num_value_operands, \ + "typevar_operand must indicate a 'value' operand" + elif self.num_value_operands != 0: # Default to the first 'value' operand, if there is one. - self.typevar_operand = self.value_operands[0] + self.typevar_operand = 0 # Compute a signature for the global registry. sig = (self.multiple_results, self.kinds) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 861e6f0883..85581edde2 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -168,7 +168,8 @@ class Instruction(object): typevar_error = None if self.format.typevar_operand is not None: try: - tv = self.ins[self.format.typevar_operand].typevar + opnum = self.value_opnums[self.format.typevar_operand] + tv = self.ins[opnum].typevar if tv is tv.free_typevar(): self.other_typevars = self._verify_ctrl_typevar(tv) self.ctrl_typevar = tv diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index b9e46172d7..567cfa16f6 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -197,7 +197,7 @@ def gen_instruction_data_impl(fmt): fmt.line(n + ' { .. } => None,') elif f.has_value_list: # We keep all arguments in a value list. - i = f.value_operands.index(f.typevar_operand) + i = f.typevar_operand fmt.line( '{} {{ ref args, .. }} => ' 'args.get({}, pool),'.format(n, i)) @@ -211,9 +211,7 @@ def gen_instruction_data_impl(fmt): else: # We have multiple value operands and an array `args`. # Which `args` index to use? - # Map from index into f.kinds into f.value_operands - # index. - i = f.value_operands.index(f.typevar_operand) + i = f.typevar_operand if f.boxed_storage: fmt.line( n + @@ -280,9 +278,10 @@ def gen_opcodes(groups, fmt): # Document polymorphism. if i.is_polymorphic: if i.use_typevar_operand: + opnum = i.value_opnums[i.format.typevar_operand] fmt.doc_comment( 'Type inferred from {}.' - .format(i.ins[i.format.typevar_operand])) + .format(i.ins[opnum])) # Enum variant itself. if is_first_opcode: fmt.line(i.camel_name + ' = 1,') @@ -612,9 +611,10 @@ def gen_inst_builder(inst, fmt): args.append('types::VOID') elif inst.is_polymorphic: # Infer the controlling type variable from the input operands. + opnum = inst.value_opnums[inst.format.typevar_operand] fmt.line( 'let ctrl_typevar = self.data_flow_graph().value_type({});' - .format(inst.ins[inst.format.typevar_operand].name)) + .format(inst.ins[opnum].name)) if inst.format.multiple_results: # The format constructor will resolve the result types from the # type var. From c480f2264a3f7fbb89bcad9d516110960a17dd4f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 10:25:18 -0800 Subject: [PATCH 577/968] Eliminate InstructionFormat.value_operands and .kinds. Part of the refactoring of instruction formats. This list is now stored in the instruction itself as value_opnums. --- lib/cretonne/meta/cdsl/formats.py | 8 ++------ lib/cretonne/meta/cdsl/instructions.py | 8 ++++---- lib/cretonne/meta/cdsl/isa.py | 7 +++++-- lib/cretonne/meta/cdsl/xform.py | 2 +- lib/cretonne/meta/gen_instr.py | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 4b7d512ff1..aa7d384b7c 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -68,11 +68,7 @@ class InstructionFormat(object): # `has_value_list` is set. self.num_value_operands = 0 - self.kinds = tuple(self._process_member_names(kinds)) - - # Which of self.kinds are `value`? - self.value_operands = tuple( - i for i, k in enumerate(self.kinds) if k is VALUE) + sig_kinds = tuple(self._process_member_names(kinds)) # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int @@ -85,7 +81,7 @@ class InstructionFormat(object): self.typevar_operand = 0 # Compute a signature for the global registry. - sig = (self.multiple_results, self.kinds) + sig = (self.multiple_results, sig_kinds) if sig in InstructionFormat._registry: raise RuntimeError( "Format '{}' has the same signature as existing format '{}'" diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 85581edde2..30596fd1fb 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -154,7 +154,7 @@ class Instruction(object): variables. """ poly_ins = [ - i for i in self.format.value_operands + i for i in self.value_opnums if self.ins[i].typevar.free_typevar()] poly_outs = [ i for i, o in enumerate(self.outs) @@ -206,8 +206,8 @@ class Instruction(object): """ other_tvs = [] # Check value inputs. - for opidx in self.format.value_operands: - typ = self.ins[opidx].typevar + for opnum in self.value_opnums: + typ = self.ins[opnum].typevar tv = typ.free_typevar() # Non-polymorphic or derived form ctrl_typevar is OK. if tv is None or tv is ctrl_typevar: @@ -216,7 +216,7 @@ class Instruction(object): if typ is not tv: raise RuntimeError( "{}: type variable {} must be derived from {}" - .format(self.ins[opidx], typ.name, ctrl_typevar)) + .format(self.ins[opnum], typ.name, ctrl_typevar)) # Other free type variables can only be used once each. if tv in other_tvs: raise RuntimeError( diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 7e4c09e58d..ef667015ce 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -176,7 +176,8 @@ class EncRecipe(object): self.number = None # type: int self.ins = self._verify_constraints(ins) - assert len(self.ins) == len(format.value_operands) + if not format.has_value_list: + assert len(self.ins) == format.num_value_operands self.outs = self._verify_constraints(outs) if len(self.outs) > 1: assert format.multiple_results @@ -193,7 +194,9 @@ class EncRecipe(object): if isinstance(c, int): # An integer constraint is bound to a value operand. # Check that it is in range. - assert c >= 0 and c < len(self.format.value_operands) + assert c >= 0 + if not format.has_value_list: + assert c < self.format.num_value_operands else: assert isinstance(c, RegClass) or isinstance(c, Register) return seq diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index deca293e50..21c6103bcd 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -224,7 +224,7 @@ class XForm(object): ctrl_var = d.defs[inst.value_results[0]] # Reconcile arguments with the requirements of `inst`. - for opnum in inst.format.value_operands: + for opnum in inst.value_opnums: inst_tv = inst.ins[opnum].typevar v = d.expr.args[opnum] if isinstance(v, Var): diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 567cfa16f6..e6cd265118 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -411,9 +411,9 @@ def gen_type_constraints(fmt, instrs): for idx in i.value_results: constraints.append( get_constraint(i.outs[idx], ctrl_typevar, type_sets)) - for idx in i.format.value_operands: + for opnum in i.value_opnums: constraints.append( - get_constraint(i.ins[idx], ctrl_typevar, type_sets)) + get_constraint(i.ins[opnum], ctrl_typevar, type_sets)) offset = operand_seqs.add(constraints) fixed_results = len(i.value_results) # Can the controlling type variable be inferred from the designated From 60daf3e76b6e64a3fef742a5abb39eae3eb4e44a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 11:09:49 -0800 Subject: [PATCH 578/968] Separate immediate and value operands in the instruction format. Instruction formats are now identified by a signature that doesn't include the ordering of value operands relative to immediate operands. This means that the BinaryRev instruction format becomes redundant, so delete it. The isub_imm instruction was the only one using that format. Rename it to irsub_imm to make it clear what it does now that it is printed as 'irsub_imm v2, 45'. --- docs/langref.rst | 2 +- filetests/parser/tiny.cton | 2 ++ lib/cretonne/meta/base/formats.py | 1 - lib/cretonne/meta/base/instructions.py | 14 ++++----- lib/cretonne/meta/cdsl/formats.py | 41 +++++++++++++++++--------- lib/cretonne/src/ir/instructions.rs | 7 ----- lib/cretonne/src/write.rs | 1 - lib/reader/src/parser.rs | 12 -------- 8 files changed, 35 insertions(+), 45 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 5fb42c44a2..cff747752a 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -685,7 +685,7 @@ Integer operations .. autoinst:: iadd_cout .. autoinst:: iadd_carry .. autoinst:: isub -.. autoinst:: isub_imm +.. autoinst:: irsub_imm .. autoinst:: isub_bin .. autoinst:: isub_bout .. autoinst:: isub_borrow diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 588223c521..7158441ed2 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -55,12 +55,14 @@ ebb0(vx0: i32, vx1: i32): v0 = icmp eq, vx0, vx1 v1 = icmp ult, vx0, vx1 v2 = icmp sge, vx0, vx1 + v3 = irsub_imm vx1, 45 } ; sameln: function icmp(i32, i32) { ; nextln: ebb0(vx0: i32, vx1: i32): ; nextln: v0 = icmp eq, vx0, vx1 ; nextln: v1 = icmp ult, vx0, vx1 ; nextln: v2 = icmp sge, vx0, vx1 +; nextln: v3 = irsub_imm vx1, 45 ; nextln: } ; Floating condition codes. diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index c72d5034f8..7ed047c330 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -22,7 +22,6 @@ UnarySplit = InstructionFormat(VALUE, multiple_results=True) Binary = InstructionFormat(VALUE, VALUE) BinaryImm = InstructionFormat(VALUE, imm64) -BinaryImmRev = InstructionFormat(imm64, VALUE) # Generate result + overflow flag. BinaryOverflow = InstructionFormat(VALUE, VALUE, multiple_results=True) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index a61f98cefe..41b6ff6c05 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -507,22 +507,18 @@ srem_imm = Instruction( allowed. """, ins=(x, Y), outs=a) -# Swap x and y for isub_imm. -X = Operand('X', imm64) -y = Operand('y', iB) +irsub_imm = Instruction( + 'irsub_imm', """ + Immediate reverse wrapping subtraction: :math:`a := Y - x \pmod{2^B}`. -isub_imm = Instruction( - 'isub_imm', """ - Immediate wrapping subtraction: :math:`a := X - y \pmod{2^B}`. - - Also works as integer negation when :math:`X = 0`. Use :inst:`iadd_imm` + Also works as integer negation when :math:`Y = 0`. Use :inst:`iadd_imm` with a negative immediate operand for the reverse immediate subtraction. Polymorphic over all scalar integer types, but does not support vector types. """, - ins=(X, y), outs=a) + ins=(x, Y), outs=a) # # Integer arithmetic with carry and/or borrow. diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index aa7d384b7c..910afb6052 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -25,6 +25,14 @@ class InstructionFormat(object): produced. Some instructions, like `call`, may have a variable number of results. + The instruction format stores two separate lists of operands: Immediates + and values. Immediate operands (including entity references) are + represented as explicit members in the `InstructionData` variants. The + value operands are stored differently, depending on how many there are. + Beyond a certain point, instruction formats switch to an external value + list for storing value arguments. Value lists can hold an arbitrary number + of values. + All instruction formats must be predefined in the :py:mod:`cretonne.formats` module. @@ -45,8 +53,8 @@ class InstructionFormat(object): immediate operands. """ - # Map (multiple_results, kind, kind, ...) -> InstructionFormat - _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...]], InstructionFormat] # noqa + # Map (multiple_results, imm_kinds, num_value_operands) -> format + _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...], int, bool], InstructionFormat] # noqa # All existing formats. all_formats = list() # type: List[InstructionFormat] @@ -62,13 +70,11 @@ class InstructionFormat(object): # operands are values or variable argument lists. They are all handled # specially. self.imm_members = list() # type: List[str] - # Operand kinds for the immediate operands. - self.imm_kinds = list() # type: List[OperandKind] # The number of value operands stored in the format, or `None` when # `has_value_list` is set. self.num_value_operands = 0 - - sig_kinds = tuple(self._process_member_names(kinds)) + # Operand kinds for the immediate operands. + self.imm_kinds = tuple(self._process_member_names(kinds)) # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int @@ -81,7 +87,10 @@ class InstructionFormat(object): self.typevar_operand = 0 # Compute a signature for the global registry. - sig = (self.multiple_results, sig_kinds) + sig = ( + self.multiple_results, self.imm_kinds, + self.num_value_operands, + self.has_value_list) if sig in InstructionFormat._registry: raise RuntimeError( "Format '{}' has the same signature as existing format '{}'" @@ -98,9 +107,9 @@ class InstructionFormat(object): pair. The member names correspond to members in the Rust `InstructionData` data structure. - Update the fields `num_value_operands`, `imm_kinds`, and `imm_members`. + Update the fields `num_value_operands` and `imm_members`. - Yields the operand kinds. + Yields the immediate operand kinds. """ for arg in kinds: if isinstance(arg, OperandKind): @@ -116,16 +125,14 @@ class InstructionFormat(object): # We require a value list for storage of variable arguments. assert self.has_value_list, "Need a value list" else: - self.imm_kinds.append(k) self.imm_members.append(member) - - yield k + yield k def __str__(self): # type: () -> str args = ', '.join('{}: {}'.format(m, k) for m, k in zip(self.imm_members, self.imm_kinds)) - return '{}({}, values={})'.format( + return '{}(imms=({}), vals={})'.format( self.name, args, self.num_value_operands) def __getattr__(self, attr): @@ -162,7 +169,13 @@ class InstructionFormat(object): multiple_results = outs[0].kind == VARIABLE_ARGS else: multiple_results = len(outs) > 1 - sig = (multiple_results, tuple(op.kind for op in ins)) + + # Construct a signature. + imm_kinds = tuple(op.kind for op in ins if op.is_immediate()) + num_values = sum(1 for op in ins if op.is_value()) + has_varargs = (VARIABLE_ARGS in tuple(op.kind for op in ins)) + + sig = (multiple_results, imm_kinds, num_values, has_varargs) if sig not in InstructionFormat._registry: raise RuntimeError( "No instruction format matches ins = ({}){}".format( diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 4cabfe6698..09167907be 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -145,13 +145,6 @@ pub enum InstructionData { arg: Value, imm: Imm64, }, - // Same as `BinaryImm`, but the immediate is the left-hand-side operand. - BinaryImmRev { - opcode: Opcode, - ty: Type, - arg: Value, - imm: Imm64, - }, BinaryOverflow { opcode: Opcode, ty: Type, diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 8f120f447a..57d31d2e5c 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -237,7 +237,6 @@ fn write_instruction(w: &mut Write, UnarySplit { arg, .. } => writeln!(w, " {}", arg), Binary { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), - BinaryImmRev { imm, arg, .. } => writeln!(w, " {}, {}", imm, arg), BinaryOverflow { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), Ternary { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), TernaryOverflow { ref data, .. } => writeln!(w, " {}", data), diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 72f43a043d..1e5e194785 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -177,7 +177,6 @@ impl<'a> Context<'a> { InstructionData::Unary { ref mut arg, .. } | InstructionData::UnarySplit { ref mut arg, .. } | InstructionData::BinaryImm { ref mut arg, .. } | - InstructionData::BinaryImmRev { ref mut arg, .. } | InstructionData::ExtractLane { ref mut arg, .. } | InstructionData::BranchTable { ref mut arg, .. } => { self.map.rewrite_value(arg, loc)?; @@ -1368,17 +1367,6 @@ impl<'a> Parser<'a> { imm: rhs, } } - InstructionFormat::BinaryImmRev => { - let lhs = self.match_imm64("expected immediate integer first operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let rhs = self.match_value("expected SSA value second operand")?; - InstructionData::BinaryImmRev { - opcode: opcode, - ty: VOID, - imm: lhs, - arg: rhs, - } - } InstructionFormat::BinaryOverflow => { let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; From 6021da8e1c8ce9eea95bfe32954b62234695e1e7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 11:57:49 -0800 Subject: [PATCH 579/968] Remove the vconst instruction and the UnaryImmVector format. No instruction sets actually have single instructions for materializing vector constants. You always need to use a constant pool. Cretonne doesn't have constant pools yet, but it will in the future, and that is how vector constants should be represented. --- docs/langref.rst | 1 - lib/cretonne/meta/base/formats.py | 3 +-- lib/cretonne/meta/base/immediates.py | 6 ------ lib/cretonne/meta/base/instructions.py | 12 +----------- lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/immediates.rs | 6 ------ lib/cretonne/src/ir/instructions.rs | 24 +----------------------- lib/cretonne/src/write.rs | 1 - lib/reader/src/parser.rs | 6 +----- 9 files changed, 5 insertions(+), 56 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index cff747752a..b1262c0335 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -643,7 +643,6 @@ Constant materialization .. autoinst:: iconst .. autoinst:: f32const .. autoinst:: f64const -.. autoinst:: vconst Live range splitting -------------------- diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 7ed047c330..98547bbbd7 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -8,7 +8,7 @@ in this module. from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS -from .immediates import imm64, uimm8, ieee32, ieee64, immvector, intcc, floatcc +from .immediates import imm64, uimm8, ieee32, ieee64, intcc, floatcc from .entities import ebb, sig_ref, func_ref, jump_table Nullary = InstructionFormat() @@ -17,7 +17,6 @@ Unary = InstructionFormat(VALUE) UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) UnaryIeee64 = InstructionFormat(ieee64) -UnaryImmVector = InstructionFormat(immvector, boxed_storage=True) UnarySplit = InstructionFormat(VALUE, multiple_results=True) Binary = InstructionFormat(VALUE, VALUE) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 565dd26850..05a542b6d9 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -27,12 +27,6 @@ ieee32 = ImmediateKind('ieee32', 'A 32-bit immediate floating point number.') #: IEEE 754-2008 binary64 interchange format. ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') -#: A large SIMD vector constant. -immvector = ImmediateKind( - 'immvector', - 'An immediate SIMD vector.', - rust_type='ImmVector') - #: A condition code for comparing integer values. #: #: This enumerated operand kind is used for the :cton:inst:`icmp` instruction diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 41b6ff6c05..4f5febb0c4 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -9,7 +9,7 @@ from cdsl.operands import Operand, VARIABLE_ARGS from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 -from base.immediates import imm64, uimm8, ieee32, ieee64, immvector +from base.immediates import imm64, uimm8, ieee32, ieee64 from base.immediates import intcc, floatcc from base import entities import base.formats # noqa @@ -196,16 +196,6 @@ f64const = Instruction( """, ins=N, outs=a) -N = Operand('N', immvector) -a = Operand('a', TxN, doc='A constant vector value') -vconst = Instruction( - 'vconst', r""" - Vector constant (floating point or integer). - - Create a SIMD vector value where the lanes don't have to be identical. - """, - ins=N, outs=a) - # # Generics. # diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index fa15587445..b6d6a16b6b 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -6,7 +6,7 @@ use ir::{types, instructions}; use ir::{InstructionData, DataFlowGraph, Cursor}; use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, ValueList}; -use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64}; use ir::condcodes::{IntCC, FloatCC}; /// Base trait for instruction builders. diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 5673af1163..79229fc3a0 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -434,12 +434,6 @@ impl FromStr for Ieee64 { } } -/// Arbitrary vector immediate. -/// -/// This kind of immediate can represent any kind of SIMD vector constant. -/// The representation is simply the sequence of bytes that would be used to store the vector. -pub type ImmVector = Vec; - #[cfg(test)] mod tests { use super::*; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 09167907be..1292836d6d 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use std::ops::{Deref, DerefMut}; use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef}; -use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, ImmVector}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64}; use ir::condcodes::*; use ir::types; use ir::DataFlowGraph; @@ -123,11 +123,6 @@ pub enum InstructionData { ty: Type, imm: Ieee64, }, - UnaryImmVector { - opcode: Opcode, - ty: Type, - data: Box, - }, UnarySplit { opcode: Opcode, ty: Type, @@ -294,23 +289,6 @@ impl Default for VariableArgs { } } -/// Payload data for `vconst`. -#[derive(Clone, Debug)] -pub struct UnaryImmVectorData { - /// Raw vector data. - pub imm: ImmVector, -} - -impl Display for UnaryImmVectorData { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "#")?; - for b in &self.imm { - write!(f, "{:02x}", b)?; - } - Ok(()) - } -} - /// Payload data for ternary instructions with multiple results, such as `iadd_carry`. #[derive(Clone, Debug)] pub struct TernaryOverflowData { diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 57d31d2e5c..e6880de697 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -233,7 +233,6 @@ fn write_instruction(w: &mut Write, UnaryImm { imm, .. } => writeln!(w, " {}", imm), UnaryIeee32 { imm, .. } => writeln!(w, " {}", imm), UnaryIeee64 { imm, .. } => writeln!(w, " {}", imm), - UnaryImmVector { ref data, .. } => writeln!(w, " {}", data), UnarySplit { arg, .. } => writeln!(w, " {}", arg), Binary { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 1e5e194785..bd7d65c3ee 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -171,8 +171,7 @@ impl<'a> Context<'a> { InstructionData::Nullary { .. } | InstructionData::UnaryImm { .. } | InstructionData::UnaryIeee32 { .. } | - InstructionData::UnaryIeee64 { .. } | - InstructionData::UnaryImmVector { .. } => {} + InstructionData::UnaryIeee64 { .. } => {} InstructionData::Unary { ref mut arg, .. } | InstructionData::UnarySplit { ref mut arg, .. } | @@ -1335,9 +1334,6 @@ impl<'a> Parser<'a> { imm: self.match_ieee64("expected immediate 64-bit float operand")?, } } - InstructionFormat::UnaryImmVector => { - unimplemented!(); - } InstructionFormat::UnarySplit => { InstructionData::UnarySplit { opcode: opcode, From 519eb1934be15b8d5ca080acb2d34137bf21e916 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 12:17:12 -0800 Subject: [PATCH 580/968] Coalesce some formats into MultiAry. Allow some flexibility in the signature matching for instruction formats. In particular, look for a value list format as a second chance option. The Return, ReturnReg, and TernaryOverflow formats all fit the single MultiAry catch-all format for instructions without immediate operands. --- lib/cretonne/meta/base/formats.py | 9 +++-- lib/cretonne/meta/cdsl/formats.py | 28 +++++++++++---- lib/cretonne/meta/gen_legalizer.py | 6 ++-- lib/cretonne/meta/isa/riscv/recipes.py | 4 +-- lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/instructions.rs | 27 ++------------ lib/cretonne/src/write.rs | 24 +++++-------- lib/reader/src/parser.rs | 49 ++++---------------------- 8 files changed, 50 insertions(+), 99 deletions(-) diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 98547bbbd7..9d0cba38d9 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -30,9 +30,10 @@ BinaryOverflow = InstructionFormat(VALUE, VALUE, multiple_results=True) # The fma instruction has the same constraint on all inputs. Ternary = InstructionFormat(VALUE, VALUE, VALUE, typevar_operand=1) -# Carry in *and* carry out for `iadd_carry` and friends. -TernaryOverflow = InstructionFormat( - VALUE, VALUE, VALUE, multiple_results=True, boxed_storage=True) +# Catch-all for instructions with many outputs and inputs and no immediate +# operands. +MultiAry = InstructionFormat( + VARIABLE_ARGS, multiple_results=True, value_list=True) InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE) ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) @@ -49,8 +50,6 @@ Call = InstructionFormat( IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True, value_list=True) -Return = InstructionFormat(VARIABLE_ARGS, value_list=True) -ReturnReg = InstructionFormat(VALUE, VARIABLE_ARGS, value_list=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 910afb6052..b1784498ee 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -82,7 +82,7 @@ class InstructionFormat(object): if not self.has_value_list: assert self.typevar_operand < self.num_value_operands, \ "typevar_operand must indicate a 'value' operand" - elif self.num_value_operands != 0: + elif self.has_value_list or self.num_value_operands > 0: # Default to the first 'value' operand, if there is one. self.typevar_operand = 0 @@ -176,12 +176,26 @@ class InstructionFormat(object): has_varargs = (VARIABLE_ARGS in tuple(op.kind for op in ins)) sig = (multiple_results, imm_kinds, num_values, has_varargs) - if sig not in InstructionFormat._registry: - raise RuntimeError( - "No instruction format matches ins = ({}){}".format( - ", ".join(map(str, sig[1])), - "[multiple results]" if multiple_results else "")) - return InstructionFormat._registry[sig] + if sig in InstructionFormat._registry: + return InstructionFormat._registry[sig] + + # Try another value list format as an alternative. + sig = (True, imm_kinds, num_values, has_varargs) + if sig in InstructionFormat._registry: + return InstructionFormat._registry[sig] + + sig = (multiple_results, imm_kinds, 0, True) + if sig in InstructionFormat._registry: + return InstructionFormat._registry[sig] + + sig = (True, imm_kinds, 0, True) + if sig in InstructionFormat._registry: + return InstructionFormat._registry[sig] + + raise RuntimeError( + 'No instruction format matches multiple_results={},' + 'imms={}, vals={}, varargs={}'.format( + multiple_results, imm_kinds, num_values, has_varargs)) @staticmethod def extract_names(globs): diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 7c36700dc7..9da3e9a985 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -50,10 +50,12 @@ def unwrap_inst(iref, node, fmt): fmt.line('{},'.format(m)) if nvops == 1: fmt.line('arg,') - elif nvops != 0: - fmt.line('args,') + elif iform.has_value_list or nvops > 1: + fmt.line('ref args,') fmt.line('..') fmt.outdented_line('} = dfg[inst] {') + if iform.has_value_list: + fmt.line('let args = args.as_slice(&dfg.value_lists);') # Generate the values for the tuple. outs = list() prefix = 'data.' if iform.boxed_storage else '' diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index ea7e019eb4..0dce091713 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -11,7 +11,7 @@ instruction formats described in the reference: from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt -from base.formats import Binary, BinaryImm, ReturnReg +from base.formats import Binary, BinaryImm, MultiAry from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -86,4 +86,4 @@ I = EncRecipe( # I-type encoding for `jalr` as a return instruction. We won't use the # immediate offset. # The variable return values are not encoded. -Iret = EncRecipe('Iret', ReturnReg, ins=GPR, outs=()) +Iret = EncRecipe('Iret', MultiAry, ins=GPR, outs=()) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index b6d6a16b6b..8880351d9b 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -3,7 +3,7 @@ //! A `Builder` provides a convenient interface for inserting instructions into a Cretonne //! function. Many of its methods are generated from the meta language instruction definitions. -use ir::{types, instructions}; +use ir::types; use ir::{InstructionData, DataFlowGraph, Cursor}; use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, ValueList}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64}; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 1292836d6d..de34a708c9 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -151,11 +151,11 @@ pub enum InstructionData { ty: Type, args: [Value; 3], }, - TernaryOverflow { + MultiAry { opcode: Opcode, ty: Type, second_result: PackedOption, - data: Box, + args: ValueList, }, InsertLane { opcode: Opcode, @@ -213,16 +213,6 @@ pub enum InstructionData { sig_ref: SigRef, args: ValueList, }, - Return { - opcode: Opcode, - ty: Type, - args: ValueList, - }, - ReturnReg { - opcode: Opcode, - ty: Type, - args: ValueList, - }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to @@ -289,19 +279,6 @@ impl Default for VariableArgs { } } -/// Payload data for ternary instructions with multiple results, such as `iadd_carry`. -#[derive(Clone, Debug)] -pub struct TernaryOverflowData { - /// Value arguments. - pub args: [Value; 3], -} - -impl Display for TernaryOverflowData { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}, {}, {}", self.args[0], self.args[1], self.args[2]) - } -} - /// Analyzing an instruction. /// /// Avoid large matches on instruction formats by using the methods defined here to examine diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index e6880de697..1f9bfd4524 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -238,7 +238,15 @@ fn write_instruction(w: &mut Write, BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), BinaryOverflow { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), Ternary { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), - TernaryOverflow { ref data, .. } => writeln!(w, " {}", data), + MultiAry { ref args, .. } => { + if args.is_empty() { + writeln!(w, "") + } else { + writeln!(w, + " {}", + DisplayValues(args.as_slice(&func.dfg.value_lists))) + } + } InsertLane { lane, args, .. } => writeln!(w, " {}, {}, {}", args[0], lane, args[1]), ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), IntCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), @@ -280,20 +288,6 @@ fn write_instruction(w: &mut Write, args[0], DisplayValues(&args[1..])) } - Return { ref args, .. } => { - if args.is_empty() { - writeln!(w, "") - } else { - writeln!(w, - " {}", - DisplayValues(args.as_slice(&func.dfg.value_lists))) - } - } - ReturnReg { ref args, .. } => { - writeln!(w, - " {}", - DisplayValues(args.as_slice(&func.dfg.value_lists))) - } } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index bd7d65c3ee..c982a5be0c 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -14,8 +14,7 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; -use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs, - TernaryOverflowData}; +use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; use cretonne::isa::{self, TargetIsa, Encoding}; use cretonne::settings; use testfile::{TestFile, Details, Comment}; @@ -193,8 +192,8 @@ impl<'a> Context<'a> { self.map.rewrite_values(args, loc)?; } - InstructionData::TernaryOverflow { ref mut data, .. } => { - self.map.rewrite_values(&mut data.args, loc)?; + InstructionData::MultiAry { ref mut args, .. } => { + self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } InstructionData::Jump { ref mut destination, ref mut args, .. } => { @@ -214,14 +213,6 @@ impl<'a> Context<'a> { InstructionData::IndirectCall { ref mut args, .. } => { self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - - InstructionData::Return { ref mut args, .. } => { - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; - } - - InstructionData::ReturnReg { ref mut args, .. } => { - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; - } } } } @@ -1388,18 +1379,13 @@ impl<'a> Parser<'a> { args: [ctrl_arg, true_arg, false_arg], } } - InstructionFormat::TernaryOverflow => { - // Names here refer to the `iadd_carry` instruction. - let lhs = self.match_value("expected SSA value first operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let rhs = self.match_value("expected SSA value second operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let cin = self.match_value("expected SSA value third operand")?; - InstructionData::TernaryOverflow { + InstructionFormat::MultiAry => { + let args = self.parse_value_list()?; + InstructionData::MultiAry { opcode: opcode, ty: VOID, second_result: None.into(), - data: Box::new(TernaryOverflowData { args: [lhs, rhs, cin] }), + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } InstructionFormat::Jump => { @@ -1505,27 +1491,6 @@ impl<'a> Parser<'a> { args: args.into_value_list(&[callee], &mut ctx.function.dfg.value_lists), } } - InstructionFormat::Return => { - let args = self.parse_value_list()?; - InstructionData::Return { - opcode: opcode, - ty: VOID, - args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), - } - } - InstructionFormat::ReturnReg => { - let raddr = self.match_value("expected SSA value return address operand")?; - let args = if self.optional(Token::Comma) { - self.parse_value_list()? - } else { - VariableArgs::new() - }; - InstructionData::ReturnReg { - opcode: opcode, - ty: VOID, - args: args.into_value_list(&[raddr], &mut ctx.function.dfg.value_lists), - } - } InstructionFormat::BranchTable => { let arg = self.match_value("expected SSA value operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; From 1b6702ceba6fdca7c53078c139699a90a73aea68 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 10 Mar 2017 12:43:05 -0800 Subject: [PATCH 581/968] Remove the value_list and boxed_storage format flags. The value_list flag can be inferred from the presence of VARIABLE_ARGS in the operand list. The boxed_storage flag is obsolete. We don't need boxed storage anywhere no that we have value lists instead. --- lib/cretonne/meta/base/formats.py | 11 ++++---- lib/cretonne/meta/cdsl/formats.py | 17 +++--------- lib/cretonne/meta/gen_encoding.py | 15 ++++------ lib/cretonne/meta/gen_instr.py | 44 +++++++----------------------- lib/cretonne/meta/gen_legalizer.py | 25 +++++++---------- 5 files changed, 35 insertions(+), 77 deletions(-) diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 9d0cba38d9..5ae806b936 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -32,8 +32,7 @@ Ternary = InstructionFormat(VALUE, VALUE, VALUE, typevar_operand=1) # Catch-all for instructions with many outputs and inputs and no immediate # operands. -MultiAry = InstructionFormat( - VARIABLE_ARGS, multiple_results=True, value_list=True) +MultiAry = InstructionFormat(VARIABLE_ARGS, multiple_results=True) InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE) ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) @@ -41,15 +40,15 @@ ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) IntCompare = InstructionFormat(intcc, VALUE, VALUE) FloatCompare = InstructionFormat(floatcc, VALUE, VALUE) -Jump = InstructionFormat(ebb, VARIABLE_ARGS, value_list=True) -Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS, value_list=True) +Jump = InstructionFormat(ebb, VARIABLE_ARGS) +Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS) BranchTable = InstructionFormat(VALUE, jump_table) Call = InstructionFormat( - func_ref, VARIABLE_ARGS, multiple_results=True, value_list=True) + func_ref, VARIABLE_ARGS, multiple_results=True) IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, - multiple_results=True, value_list=True) + multiple_results=True) # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index b1784498ee..6233dbf7a6 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -42,11 +42,6 @@ class InstructionFormat(object): enums. :param multiple_results: Set to `True` if this instruction format allows more than one result to be produced. - :param value_list: Set to `True` if this instruction format uses a - `ValueList` member to store its value operands. - :param boxed_storage: Set to `True` is this instruction format requires a - `data: Box<...>` pointer to additional storage in its `InstructionData` - variant. :param typevar_operand: Index of the value input operand that is used to infer the controlling type variable. By default, this is `0`, the first `value` operand. The index is relative to the values only, ignoring @@ -63,8 +58,6 @@ class InstructionFormat(object): # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa self.name = kwargs.get('name', None) # type: str self.multiple_results = kwargs.get('multiple_results', False) - self.has_value_list = kwargs.get('value_list', False) - self.boxed_storage = kwargs.get('boxed_storage', False) # Struct member names for the immediate operands. All other instruction # operands are values or variable argument lists. They are all handled @@ -73,6 +66,8 @@ class InstructionFormat(object): # The number of value operands stored in the format, or `None` when # `has_value_list` is set. self.num_value_operands = 0 + # Does this format use a value list for storing value operands? + self.has_value_list = False # Operand kinds for the immediate operands. self.imm_kinds = tuple(self._process_member_names(kinds)) @@ -122,8 +117,7 @@ class InstructionFormat(object): if k is VALUE: self.num_value_operands += 1 elif k is VARIABLE_ARGS: - # We require a value list for storage of variable arguments. - assert self.has_value_list, "Need a value list" + self.has_value_list = True else: self.imm_members.append(member) yield k @@ -234,7 +228,4 @@ class FormatField(object): def rust_name(self): # type: () -> str - if self.format.boxed_storage: - return 'data.' + self.name - else: - return self.name + return self.name diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index c2849c1d6f..978519efaf 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -76,15 +76,12 @@ def emit_instp(instp, fmt): iform = instp.predicate_context() # Which fields do we need in the InstructionData pattern match? - if iform.boxed_storage: - fields = 'ref data' - else: - # Collect the leaf predicates - leafs = set() - instp.predicate_leafs(leafs) - # All the leafs are FieldPredicate instances. Here we just care about - # the field names. - fields = ', '.join(sorted(set(p.field.name for p in leafs))) + # Collect the leaf predicates. + leafs = set() + instp.predicate_leafs(leafs) + # All the leafs are FieldPredicate instances. Here we just care about + # the field names. + fields = ', '.join(sorted(set(p.field.name for p in leafs))) with fmt.indented('{} => {{'.format(instp.number), '}'): with fmt.indented( diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index e6cd265118..c05cb51078 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -87,19 +87,11 @@ def gen_arguments_method(fmt, is_mut): arg = '&{}[]'.format(mut) capture = '' elif f.num_value_operands == 1: - if f.boxed_storage: - capture = 'ref {}data, '.format(mut) - arg = '{}(&{}data.arg)'.format(rslice, mut) - else: - capture = 'ref {}arg, '.format(mut) - arg = '{}(arg)'.format(rslice) + capture = 'ref {}arg, '.format(mut) + arg = '{}(arg)'.format(rslice) else: - if f.boxed_storage: - capture = 'ref {}data, '.format(mut) - arg = '&{}data.args'.format(mut) - else: - capture = 'ref {}args, '.format(mut) - arg = 'args' + capture = 'ref {}args, '.format(mut) + arg = 'args' fmt.line( '{} {{ {} .. }} => {},' .format(n, capture, arg)) @@ -203,25 +195,15 @@ def gen_instruction_data_impl(fmt): 'args.get({}, pool),'.format(n, i)) elif f.num_value_operands == 1: # We have a single value operand called 'arg'. - if f.boxed_storage: - fmt.line( - n + ' { ref data, .. } => Some(data.arg),') - else: - fmt.line(n + ' { arg, .. } => Some(arg),') + fmt.line(n + ' { arg, .. } => Some(arg),') else: # We have multiple value operands and an array `args`. # Which `args` index to use? i = f.typevar_operand - if f.boxed_storage: - fmt.line( - n + - ' { ref data, .. } => ' + - ('Some(data.args[{}]),'.format(i))) - else: - fmt.line( - n + - ' {{ ref args, .. }} => Some(args[{}]),' - .format(i)) + fmt.line( + n + + ' {{ ref args, .. }} => Some(args[{}]),' + .format(i)) fmt.doc_comment( """ @@ -511,13 +493,7 @@ def gen_format_constructor(iform, fmt): fmt.line('ty: {},'.format(result_type)) if iform.multiple_results: fmt.line('second_result: None.into(),') - if iform.boxed_storage: - with fmt.indented( - 'data: Box::new(instructions::{}Data {{' - .format(iform.name), '}),'): - gen_member_inits(iform, fmt) - else: - gen_member_inits(iform, fmt) + gen_member_inits(iform, fmt) # Create result values if necessary. if iform.multiple_results: diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 9da3e9a985..85aa715996 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -41,34 +41,29 @@ def unwrap_inst(iref, node, fmt): with fmt.indented( 'let ({}) = if let InstructionData::{} {{' .format(', '.join(map(str, expr.args)), iform.name), '};'): - if iform.boxed_storage: - # This format indirects to a largish `data` struct. - fmt.line('ref data,') - else: - # Fields are encoded directly. - for m in iform.imm_members: - fmt.line('{},'.format(m)) - if nvops == 1: - fmt.line('arg,') - elif iform.has_value_list or nvops > 1: - fmt.line('ref args,') + # Fields are encoded directly. + for m in iform.imm_members: + fmt.line('{},'.format(m)) + if nvops == 1: + fmt.line('arg,') + elif iform.has_value_list or nvops > 1: + fmt.line('ref args,') fmt.line('..') fmt.outdented_line('} = dfg[inst] {') if iform.has_value_list: fmt.line('let args = args.as_slice(&dfg.value_lists);') # Generate the values for the tuple. outs = list() - prefix = 'data.' if iform.boxed_storage else '' for opnum, op in enumerate(expr.inst.ins): if op.is_immediate(): n = expr.inst.imm_opnums.index(opnum) - outs.append(prefix + iform.imm_members[n]) + outs.append(iform.imm_members[n]) elif op.is_value(): if nvops == 1: - arg = prefix + 'arg' + arg = 'arg' else: n = expr.inst.value_opnums.index(opnum) - arg = '{}args[{}]'.format(prefix, n) + arg = 'args[{}]'.format(n) outs.append('dfg.resolve_aliases({})'.format(arg)) fmt.line('({})'.format(', '.join(outs))) fmt.outdented_line('} else {') From 1ebf0fd81517c70618fc3f058e64f56589c39a63 Mon Sep 17 00:00:00 2001 From: Davide Italiano Date: Fri, 10 Mar 2017 11:52:11 -0800 Subject: [PATCH 582/968] [B-tree] Initial comment to describe the design choices. --- lib/cretonne/src/btree.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/cretonne/src/btree.rs b/lib/cretonne/src/btree.rs index cd34c466b5..b6732788f7 100644 --- a/lib/cretonne/src/btree.rs +++ b/lib/cretonne/src/btree.rs @@ -1,3 +1,16 @@ +//! Generic B-Tree implementation. +//! +//! This module defines a `Btree` type which provides similar functionality to +//! `BtreeMap`, but with some important differences in the implementation: +//! +//! 1. Memory is allocated from a `NodePool` instead of the global heap. +//! 2. The footprint of a BTree is only 4 bytes. +//! 3. A BTree doesn't implement `Drop`, leaving it to the pool to manage memory. +//! +//! The node pool is intended to be used as a LIFO allocator. After building up a larger data +//! structure with many list references, the whole thing can be discarded quickly by clearing the +//! pool. + use std::marker::PhantomData; // A Node reference is a direct index to an element of the pool. From 27e7945f63f437444d3ca35db464beac4bb59e08 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Sat, 11 Mar 2017 15:44:49 +0000 Subject: [PATCH 583/968] Define boolean conversion instructions. --- docs/langref.rst | 4 ++ lib/cretonne/meta/base/instructions.py | 61 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/docs/langref.rst b/docs/langref.rst index b1262c0335..9a08da062d 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -819,6 +819,10 @@ Conversion operations --------------------- .. autoinst:: bitcast +.. autoinst:: breduce +.. autoinst:: bextend +.. autoinst:: bint +.. autoinst:: bmask .. autoinst:: ireduce .. autoinst:: uextend .. autoinst:: sextend diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 4f5febb0c4..e0efafb1f5 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -1063,6 +1063,67 @@ bitcast = Instruction( """, ins=x, outs=a) +Bool = TypeVar( + 'Bool', + 'A scalar or vector boolean type', + bools=True, simd=True) +BoolTo = TypeVar( + 'BoolTo', + 'A smaller boolean type with the same number of lanes', + bools=True, simd=True) + +x = Operand('x', Bool) +a = Operand('a', BoolTo) + +breduce = Instruction( + 'breduce', r""" + Convert `x` to a smaller boolean type in the platform-defined way. + + The result type must have the same number of vector lanes as the input, + and each lane must not have more bits that the input lanes. If the + input and output types are the same, this is a no-op. + """, ins=x, outs=a) + +BoolTo = TypeVar( + 'BoolTo', + 'A larger boolean type with the same number of lanes', + bools=True, simd=True) + +x = Operand('x', Bool) +a = Operand('a', BoolTo) + +bextend = Instruction( + 'bextend', r""" + Convert `x` to a larger boolean type in the platform-defined way. + + The result type must have the same number of vector lanes as the input, + and each lane must not have fewer bits that the input lanes. If the + input and output types are the same, this is a no-op. + """, ins=x, outs=a) + +IntTo = TypeVar( + 'IntTo', 'An integer type with the same number of lanes', + ints=True, simd=True) + +x = Operand('x', Bool) +a = Operand('a', IntTo) + +bint = Instruction( + 'bint', r""" + Convert `x` to an integer. + + True maps to 1 and false maps to 0. The result type must have the same + number of vector lanes as the input. + """, ins=x, outs=a) + +bmask = Instruction( + 'bmask', r""" + Convert `x` to an integer mask. + + True maps to all 1s and false maps to all 0s. The result type must have + the same number of vector lanes as the input. + """, ins=x, outs=a) + Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) IntTo = TypeVar( 'IntTo', 'A smaller integer type with the same number of lanes', From 404a88f58197edf93fe3fe4c4c451200f4384207 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Sun, 12 Mar 2017 15:20:06 +0000 Subject: [PATCH 584/968] Added ControlFlowGraph::recompute_ebb for incremental CFG updates. --- lib/cretonne/src/cfg.rs | 124 ++++++++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 30 deletions(-) diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/cfg.rs index c3cf19efe4..f445d2c8f1 100644 --- a/lib/cretonne/src/cfg.rs +++ b/lib/cretonne/src/cfg.rs @@ -27,6 +27,7 @@ use ir::{Function, Inst, Ebb}; use ir::instructions::BranchInfo; use entity_map::{EntityMap, Keys}; use std::collections::HashSet; +use std::mem; /// A basic block denoted by its enclosing Ebb and last instruction. pub type BasicBlock = (Ebb, Inst); @@ -74,22 +75,49 @@ impl ControlFlowGraph { self.data.resize(func.dfg.num_ebbs()); for ebb in &func.layout { - for inst in func.layout.ebb_insts(ebb) { - match func.dfg[inst].analyze_branch(&func.dfg.value_lists) { - BranchInfo::SingleDest(dest, _) => { + self.compute_ebb(func, ebb); + } + } + + fn compute_ebb(&mut self, func: &Function, ebb: Ebb) { + for inst in func.layout.ebb_insts(ebb) { + match func.dfg[inst].analyze_branch(&func.dfg.value_lists) { + BranchInfo::SingleDest(dest, _) => { + self.add_edge((ebb, inst), dest); + } + BranchInfo::Table(jt) => { + for (_, dest) in func.jump_tables[jt].entries() { self.add_edge((ebb, inst), dest); } - BranchInfo::Table(jt) => { - for (_, dest) in func.jump_tables[jt].entries() { - self.add_edge((ebb, inst), dest); - } - } - BranchInfo::NotABranch => {} } + BranchInfo::NotABranch => {} } } } + fn invalidate_ebb_successors(&mut self, ebb: Ebb) { + // Temporarily take ownership because we need mutable access to self.data inside the loop. + // Unfortunately borrowck cannot see that our mut accesses to predecessors don't alias + // our iteration over successors. + let mut successors = mem::replace(&mut self.data[ebb].successors, Vec::new()); + for suc in successors.iter().cloned() { + self.data[suc].predecessors.retain(|&(e, _)| e != ebb); + } + successors.clear(); + self.data[ebb].successors = successors; + } + + /// Recompute the control flow graph of `ebb`. + /// + /// This is for use after modifying instructions within a specific EBB. It recomputes all edges + /// from `ebb` while leaving edges to `ebb` intact. Its functionality a subset of that of the + /// more expensive `compute`, and should be used when we know we don't need to recompute the CFG + /// from scratch, but rather that our changes have been restricted to specific EBBs. + pub fn recompute_ebb(&mut self, func: &Function, ebb: Ebb) { + self.invalidate_ebb_successors(ebb); + self.compute_ebb(func, ebb); + } + fn add_edge(&mut self, from: BasicBlock, to: Ebb) { self.data[from.0].successors.push(to); self.data[to].predecessors.push(from); @@ -207,32 +235,68 @@ mod tests { cur.insert_ebb(ebb2); } - let cfg = ControlFlowGraph::with_function(&func); + let mut cfg = ControlFlowGraph::with_function(&func); - let ebb0_predecessors = cfg.get_predecessors(ebb0); - let ebb1_predecessors = cfg.get_predecessors(ebb1); - let ebb2_predecessors = cfg.get_predecessors(ebb2); + { + let ebb0_predecessors = cfg.get_predecessors(ebb0); + let ebb1_predecessors = cfg.get_predecessors(ebb1); + let ebb2_predecessors = cfg.get_predecessors(ebb2); - let ebb0_successors = cfg.get_successors(ebb0); - let ebb1_successors = cfg.get_successors(ebb1); - let ebb2_successors = cfg.get_successors(ebb2); + let ebb0_successors = cfg.get_successors(ebb0); + let ebb1_successors = cfg.get_successors(ebb1); + let ebb2_successors = cfg.get_successors(ebb2); - assert_eq!(ebb0_predecessors.len(), 0); - assert_eq!(ebb1_predecessors.len(), 2); - assert_eq!(ebb2_predecessors.len(), 2); + assert_eq!(ebb0_predecessors.len(), 0); + assert_eq!(ebb1_predecessors.len(), 2); + assert_eq!(ebb2_predecessors.len(), 2); - assert_eq!(ebb1_predecessors.contains(&(ebb0, jmp_ebb0_ebb1)), true); - assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true); - assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), true); - assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true); + assert_eq!(ebb1_predecessors.contains(&(ebb0, jmp_ebb0_ebb1)), true); + assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true); + assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), true); + assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true); - assert_eq!(ebb0_successors.len(), 2); - assert_eq!(ebb1_successors.len(), 2); - assert_eq!(ebb2_successors.len(), 0); + assert_eq!(ebb0_successors.len(), 2); + assert_eq!(ebb1_successors.len(), 2); + assert_eq!(ebb2_successors.len(), 0); - assert_eq!(ebb0_successors.contains(&ebb1), true); - assert_eq!(ebb0_successors.contains(&ebb2), true); - assert_eq!(ebb1_successors.contains(&ebb1), true); - assert_eq!(ebb1_successors.contains(&ebb2), true); + assert_eq!(ebb0_successors.contains(&ebb1), true); + assert_eq!(ebb0_successors.contains(&ebb2), true); + assert_eq!(ebb1_successors.contains(&ebb1), true); + assert_eq!(ebb1_successors.contains(&ebb2), true); + } + + // Change some instructions and recompute ebb0 + func.dfg.replace(br_ebb0_ebb2).brnz(cond, ebb1, &[]); + func.dfg.replace(jmp_ebb0_ebb1).return_(&[]); + cfg.recompute_ebb(&mut func, ebb0); + let br_ebb0_ebb1 = br_ebb0_ebb2; + + { + let ebb0_predecessors = cfg.get_predecessors(ebb0); + let ebb1_predecessors = cfg.get_predecessors(ebb1); + let ebb2_predecessors = cfg.get_predecessors(ebb2); + + let ebb0_successors = cfg.get_successors(ebb0); + let ebb1_successors = cfg.get_successors(ebb1); + let ebb2_successors = cfg.get_successors(ebb2); + + assert_eq!(ebb0_predecessors.len(), 0); + assert_eq!(ebb1_predecessors.len(), 2); + assert_eq!(ebb2_predecessors.len(), 1); + + assert_eq!(ebb1_predecessors.contains(&(ebb0, br_ebb0_ebb1)), true); + assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true); + assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), false); + assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true); + + assert_eq!(ebb0_successors.len(), 1); + assert_eq!(ebb1_successors.len(), 2); + assert_eq!(ebb2_successors.len(), 0); + + assert_eq!(ebb0_successors.contains(&ebb1), true); + assert_eq!(ebb0_successors.contains(&ebb2), false); + assert_eq!(ebb1_successors.contains(&ebb1), true); + assert_eq!(ebb1_successors.contains(&ebb2), true); + } } } From 5f2e37e05c3ffc27dcf6cd14a65cd28046a57a2c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 13 Mar 2017 14:07:14 -0700 Subject: [PATCH 585/968] Add take_value_list and put_value_list methods. Any code that needs to manipulate a variable argument list on an instruction will need to remove the instruction's value list first, change the list, and then put it back on the instruction. This is required to avoid fighting the borrow checker over mutable locks on the DataFlowGraph and its value list pool. Add a generated InstructionData::take_value_list() method which lifts out and existing value list and returns it, levaing an empty list in its place, like Option::take() does it. Add a generated InstructionData::put_value_list() which puts it back, verifying that no existing value list is overwritten. --- lib/cretonne/meta/gen_instr.py | 41 +++++++++++++++++++++++++++++++++ lib/cretonne/src/entity_list.rs | 8 +++++++ 2 files changed, 49 insertions(+) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index c05cb51078..e6eaa82c32 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -111,6 +111,8 @@ def gen_instruction_data_impl(fmt): - `pub fn second_result_mut(&mut self) -> Option<&mut PackedOption>` - `pub fn arguments(&self, &pool) -> &[Value]` - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]` + - `pub fn take_value_list(&mut self) -> Option` + - `pub fn put_value_list(&mut self, args: ValueList>` """ # The `opcode` and `first_type` methods simply read the `opcode` and `ty` @@ -221,6 +223,45 @@ def gen_instruction_data_impl(fmt): """) gen_arguments_method(fmt, True) + fmt.doc_comment( + """ + Take out the value list with all the value arguments and return + it. + + This leaves the value list in the instruction empty. Use + `put_value_list` to put the value list back. + """) + with fmt.indented( + 'pub fn take_value_list(&mut self) -> Option {', + '}'): + with fmt.indented('match *self {', '}'): + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.has_value_list: + fmt.line( + n + ' { ref mut args, .. } => Some(args.take()),') + fmt.line('_ => None,') + + fmt.doc_comment( + """ + Put back a value list. + + After removing a value list with `take_value_list()`, use this + method to put it back. It is required that this instruction has + a format that accepts a value list, and that the existing value + list is empty. This avoids leaking list pool memory. + """) + with fmt.indented( + 'pub fn put_value_list(&mut self, vlist: ValueList) {', '}'): + with fmt.indented('let args = match *self {', '};'): + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.has_value_list: + fmt.line(n + ' { ref mut args, .. } => args,') + fmt.line('_ => panic!("No value list: {:?}", self),') + fmt.line('assert!(args.is_empty(), "Value list already in use");') + fmt.line('*args = vlist;') + def collect_instr_groups(isas): seen = set() diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 2b8ebb783e..64a0a4c830 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -47,6 +47,7 @@ //! reserved for the empty list which isn't allocated in the vector. use std::marker::PhantomData; +use std::mem; use entity_map::EntityRef; @@ -273,6 +274,13 @@ impl EntityList { self.index = 0; } + /// Take all elements from this list and return them as a new list. Leave this list empty. + /// + /// This is the equivalent of `Option::take()`. + pub fn take(&mut self) -> EntityList { + mem::replace(self, Default::default()) + } + /// Appends an element to the back of the list. pub fn push(&mut self, element: T, pool: &mut ListPool) { let idx = self.index as usize; From d9a0c0d05601b07c51a2623b591cc4ad7b20bff8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 13 Mar 2017 16:37:48 -0700 Subject: [PATCH 586/968] Add a grow_at() method to EntityList. This method opens up a hole in the middle of a list where new elements can be written. --- lib/cretonne/src/entity_list.rs | 86 +++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 64a0a4c830..423b0ed155 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -311,6 +311,41 @@ impl EntityList { } } + /// Grow list by adding `count` uninitialized elements at the end. + /// + /// Returns a mutable slice representing the whole list. + fn grow<'a>(&'a mut self, count: usize, pool: &'a mut ListPool) -> &'a mut [T] { + let idx = self.index as usize; + let new_len; + let block; + match pool.len_of(self) { + None => { + // This is an empty list. Allocate a block. + assert_eq!(idx, 0, "Invalid pool"); + if count == 0 { + return &mut []; + } + new_len = count; + block = pool.alloc(sclass_for_length(new_len)); + self.index = (block + 1) as u32; + } + Some(len) => { + // Do we need to reallocate? + let sclass = sclass_for_length(len); + new_len = len + count; + let new_sclass = sclass_for_length(new_len); + if new_sclass != sclass { + block = pool.realloc(idx - 1, sclass, new_sclass, len + 1); + self.index = (block + 1) as u32; + } else { + block = idx - 1; + } + } + } + pool.data[block] = T::new(new_len); + &mut pool.data[block + 1..block + 1 + new_len] + } + /// Appends multiple elements to the back of the list. pub fn extend(&mut self, elements: I, pool: &mut ListPool) where I: IntoIterator @@ -371,6 +406,20 @@ impl EntityList { // Finally adjust the length. pool.data[block] = T::new(len - 1); } + + /// Grow the list by inserting `count` elements at `index`. + /// + /// The new elements are not initialized, they will contain whatever happened to be in memory. + /// Since the memory comes from the pool, this will be either zero entity references or + /// whatever where in a previously deallocated list. + pub fn grow_at(&mut self, index: usize, count: usize, pool: &mut ListPool) { + let mut data = self.grow(count, pool); + + // Copy elements after `index` up. + for i in (index + count..data.len()).rev() { + data[i] = data[i - count]; + } + } } #[cfg(test)] @@ -530,4 +579,41 @@ mod tests { assert_eq!(list.as_slice(pool), &[]); assert!(list.is_empty()); } + + #[test] + fn growing() { + let pool = &mut ListPool::::new(); + let mut list = EntityList::::default(); + + let i1 = Inst::new(1); + let i2 = Inst::new(2); + let i3 = Inst::new(3); + let i4 = Inst::new(4); + + // This is not supposed to change the list. + list.grow_at(0, 0, pool); + assert_eq!(list.len(pool), 0); + assert!(list.is_empty()); + + list.grow_at(0, 2, pool); + assert_eq!(list.len(pool), 2); + + list.as_mut_slice(pool).copy_from_slice(&[i2, i3]); + + list.grow_at(1, 0, pool); + assert_eq!(list.as_slice(pool), &[i2, i3]); + + list.grow_at(1, 1, pool); + list.as_mut_slice(pool)[1] = i1; + assert_eq!(list.as_slice(pool), &[i2, i1, i3]); + + // Append nothing at the end. + list.grow_at(3, 0, pool); + assert_eq!(list.as_slice(pool), &[i2, i1, i3]); + + // Append something at the end. + list.grow_at(3, 1, pool); + list.as_mut_slice(pool)[3] = i4; + assert_eq!(list.as_slice(pool), &[i2, i1, i3, i4]); + } } From 849f3f3e9bba85612db6bd4f256511678139c50e Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Mon, 13 Mar 2017 21:51:16 +0000 Subject: [PATCH 587/968] Expanded instruction integrity checking in the verifier, now verifying result types and entity references. --- lib/cretonne/src/entity_list.rs | 6 ++ lib/cretonne/src/ir/dfg.rs | 13 +++ lib/cretonne/src/verifier.rs | 136 +++++++++++++++++++++++++++++++- 3 files changed, 152 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 423b0ed155..1a05464c21 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -233,6 +233,12 @@ impl EntityList { pool.len_of(self).unwrap_or(0) } + /// Returns `true` if the list is valid + pub fn is_valid(&self, pool: &ListPool) -> bool { + // We consider an empty list to be valid + self.is_empty() || pool.len_of(self) != None + } + /// Get the list as a slice. pub fn as_slice<'a>(&'a self, pool: &'a ListPool) -> &'a [T] { let idx = self.index as usize; diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 07e7dbbdb5..8183a04cc5 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -82,6 +82,11 @@ impl DataFlowGraph { pub fn num_ebbs(&self) -> usize { self.ebbs.len() } + + /// Returns `true` if the given ebb reference is valid. + pub fn ebb_is_valid(&self, ebb: Ebb) -> bool { + self.ebbs.is_valid(ebb) + } } /// Handling values. @@ -95,6 +100,14 @@ impl DataFlowGraph { vref } + /// Check if a value reference is valid. + pub fn value_is_valid(&self, v: Value) -> bool { + match v.expand() { + ExpandedValue::Direct(inst) => self.insts.is_valid(inst), + ExpandedValue::Table(index) => index < self.extended_values.len(), + } + } + /// Get the type of a value. pub fn value_type(&self, v: Value) -> Type { use ir::entities::ExpandedValue::*; diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 2610475759..90bf2dda90 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -12,17 +12,18 @@ //! Instruction integrity //! //! - The instruction format must match the opcode. -//! TODO: //! - All result values must be created for multi-valued instructions. //! - Instructions with no results must have a VOID `first_type()`. //! - All referenced entities must exist. (Values, EBBs, stack slots, ...) //! //! SSA form //! +//! TODO: //! - Values must be defined by an instruction that exists and that is inserted in //! an EBB, or be an argument of an existing EBB. //! - Values used by an instruction must dominate the instruction. -//! Control flow graph and dominator tree integrity: +//! +//! Control flow graph and dominator tree integrity: //! //! - All predecessors in the CFG must be branches to the EBB. //! - All branches to an EBB must be present in the CFG. @@ -52,7 +53,7 @@ //! - Swizzle and shuffle instructions take a variable number of lane arguments. The number //! of arguments must match the destination type, and the lane indexes must be in range. -use ir::{Function, ValueDef, Ebb, Inst}; +use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value}; use ir::instructions::InstructionFormat; use ir::entities::AnyEntity; use std::fmt::{self, Display, Formatter}; @@ -147,15 +148,144 @@ impl<'a> Verifier<'a> { fn instruction_integrity(&self, inst: Inst) -> Result<()> { let inst_data = &self.func.dfg[inst]; + let dfg = &self.func.dfg; // The instruction format matches the opcode if inst_data.opcode().format() != InstructionFormat::from(inst_data) { return err!(inst, "instruction opcode doesn't match instruction format"); } + let fixed_results = inst_data.opcode().constraints().fixed_results(); + // var_results is 0 if we aren't a call instruction + let var_results = dfg.call_signature(inst) + .map(|sig| dfg.signatures[sig].return_types.len()) + .unwrap_or(0); + let total_results = fixed_results + var_results; + + if total_results == 0 { + // Instructions with no results have a NULL `first_type()` + let ret_type = inst_data.first_type(); + if ret_type != types::VOID { + return err!(inst, + "instruction expected to have NULL return value, found {}", + ret_type); + } + } else { + // All result values for multi-valued instructions are created + let got_results = dfg.inst_results(inst).count(); + if got_results != total_results { + return err!(inst, + "expected {} result values, found {}", + total_results, + got_results); + } + } + + self.verify_entity_references(inst) + } + + fn verify_entity_references(&self, inst: Inst) -> Result<()> { + use ir::instructions::InstructionData::*; + + for &arg in self.func.dfg[inst].arguments(&self.func.dfg.value_lists) { + self.verify_value(inst, arg)?; + } + + for res in self.func.dfg.inst_results(inst) { + self.verify_value(inst, res)?; + } + + match &self.func.dfg[inst] { + &MultiAry { ref args, .. } => { + self.verify_value_list(inst, args)?; + } + &Jump { destination, ref args, .. } => { + self.verify_ebb(inst, destination)?; + self.verify_value_list(inst, args)?; + } + &Branch { destination, ref args, .. } => { + self.verify_ebb(inst, destination)?; + self.verify_value_list(inst, args)?; + } + &BranchTable { table, .. } => { + self.verify_jump_table(inst, table)?; + } + &Call { func_ref, ref args, .. } => { + self.verify_func_ref(inst, func_ref)?; + self.verify_value_list(inst, args)?; + } + &IndirectCall { sig_ref, ref args, .. } => { + self.verify_sig_ref(inst, sig_ref)?; + self.verify_value_list(inst, args)?; + } + // Exhaustive list so we can't forget to add new formats + &Nullary { .. } | + &Unary { .. } | + &UnaryImm { .. } | + &UnaryIeee32 { .. } | + &UnaryIeee64 { .. } | + &UnarySplit { .. } | + &Binary { .. } | + &BinaryImm { .. } | + &BinaryOverflow { .. } | + &Ternary { .. } | + &InsertLane { .. } | + &ExtractLane { .. } | + &IntCompare { .. } | + &FloatCompare { .. } => {} + } + Ok(()) } + fn verify_ebb(&self, inst: Inst, e: Ebb) -> Result<()> { + if !self.func.dfg.ebb_is_valid(e) { + err!(inst, "invalid ebb reference {}", e) + } else { + Ok(()) + } + } + + fn verify_sig_ref(&self, inst: Inst, s: SigRef) -> Result<()> { + if !self.func.dfg.signatures.is_valid(s) { + err!(inst, "invalid signature reference {}", s) + } else { + Ok(()) + } + } + + fn verify_func_ref(&self, inst: Inst, f: FuncRef) -> Result<()> { + if !self.func.dfg.ext_funcs.is_valid(f) { + err!(inst, "invalid function reference {}", f) + } else { + Ok(()) + } + } + + fn verify_value_list(&self, inst: Inst, l: &ValueList) -> Result<()> { + if !l.is_valid(&self.func.dfg.value_lists) { + err!(inst, "invalid value list reference {:?}", l) + } else { + Ok(()) + } + } + + fn verify_jump_table(&self, inst: Inst, j: JumpTable) -> Result<()> { + if !self.func.jump_tables.is_valid(j) { + err!(inst, "invalid jump table reference {}", j) + } else { + Ok(()) + } + } + + fn verify_value(&self, inst: Inst, v: Value) -> Result<()> { + if !self.func.dfg.value_is_valid(v) { + err!(inst, "invalid value reference {}", v) + } else { + Ok(()) + } + } + pub fn run(&self) -> Result<()> { for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { From 32709a56ca8ef31bc15c311f0d56976b782fe26c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Mar 2017 10:48:05 -0700 Subject: [PATCH 588/968] Upgrade to rustfmt 0.8.0. Lots of changes this time. Worked around what looks like a rustfmt bug in parse_inst_operands where a large match was nested inside Ok(). --- lib/cretonne/build.rs | 3 +- lib/cretonne/src/dominator_tree.rs | 10 +- lib/cretonne/src/ir/condcodes.rs | 52 +++---- lib/cretonne/src/ir/dfg.rs | 67 ++++----- lib/cretonne/src/ir/extfunc.rs | 8 +- lib/cretonne/src/ir/immediates.rs | 20 +-- lib/cretonne/src/ir/jumptable.rs | 10 +- lib/cretonne/src/ir/layout.rs | 13 +- lib/cretonne/src/ir/types.rs | 14 +- lib/cretonne/src/isa/arm32/mod.rs | 22 +-- lib/cretonne/src/isa/arm64/mod.rs | 20 +-- lib/cretonne/src/isa/intel/mod.rs | 22 +-- lib/cretonne/src/isa/registers.rs | 13 +- lib/cretonne/src/isa/riscv/mod.rs | 22 +-- lib/cretonne/src/partition_slice.rs | 5 +- lib/cretonne/src/regalloc/allocatable_set.rs | 28 ++-- lib/cretonne/src/regalloc/coloring.rs | 5 +- .../src/regalloc/live_value_tracker.rs | 24 ++-- lib/cretonne/src/regalloc/liveness.rs | 6 +- lib/cretonne/src/regalloc/liverange.rs | 18 ++- lib/cretonne/src/verifier.rs | 21 +-- lib/cretonne/src/write.rs | 6 +- lib/filecheck/src/checker.rs | 19 +-- lib/filecheck/src/explain.rs | 56 ++++---- lib/filecheck/src/pattern.rs | 18 +-- lib/reader/src/lexer.rs | 89 ++++++------ lib/reader/src/parser.rs | 135 +++++++++++------- lib/reader/src/sourcemap.rs | 40 +++--- src/cton-util.rs | 6 +- src/filetest/concurrent.rs | 16 +-- src/filetest/domtree.rs | 5 +- src/filetest/runner.rs | 24 ++-- src/filetest/runone.rs | 5 +- src/filetest/subtest.rs | 8 +- src/rsfilecheck.rs | 3 +- test-all.sh | 2 +- tests/cfg_traversal.rs | 4 +- 37 files changed, 462 insertions(+), 377 deletions(-) diff --git a/lib/cretonne/build.rs b/lib/cretonne/build.rs index 2067a37afd..4766a032e0 100644 --- a/lib/cretonne/build.rs +++ b/lib/cretonne/build.rs @@ -26,7 +26,8 @@ fn main() { // Make sure we rebuild is this build script changes. // I guess that won't happen if you have non-UTF8 bytes in your path names. // The `build.py` script prints out its own dependencies. - println!("cargo:rerun-if-changed={}", crate_dir.join("build.rs").to_string_lossy()); + println!("cargo:rerun-if-changed={}", + crate_dir.join("build.rs").to_string_lossy()); // Scripts are in `$crate_dir/meta`. let meta_dir = crate_dir.join("meta"); diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 8c90b46160..17bd3445ab 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -191,12 +191,14 @@ impl DominatorTree { // Get an iterator with just the reachable predecessors to `ebb`. // Note that during the first pass, `is_reachable` returns false for blocks that haven't // been visited yet. - let mut reachable_preds = - cfg.get_predecessors(ebb).iter().cloned().filter(|&(ebb, _)| self.is_reachable(ebb)); + let mut reachable_preds = cfg.get_predecessors(ebb) + .iter() + .cloned() + .filter(|&(ebb, _)| self.is_reachable(ebb)); // The RPO must visit at least one predecessor before this node. - let mut idom = reachable_preds.next() - .expect("EBB node must have one reachable predecessor"); + let mut idom = + reachable_preds.next().expect("EBB node must have one reachable predecessor"); for pred in reachable_preds { idom = self.common_dominator(idom, pred, layout); diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs index dfa6b054f5..5117e39d9d 100644 --- a/lib/cretonne/src/ir/condcodes.rs +++ b/lib/cretonne/src/ir/condcodes.rs @@ -89,17 +89,17 @@ impl Display for IntCC { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use self::IntCC::*; f.write_str(match self { - &Equal => "eq", - &NotEqual => "ne", - &SignedGreaterThan => "sgt", - &SignedGreaterThanOrEqual => "sge", - &SignedLessThan => "slt", - &SignedLessThanOrEqual => "sle", - &UnsignedGreaterThan => "ugt", - &UnsignedGreaterThanOrEqual => "uge", - &UnsignedLessThan => "ult", - &UnsignedLessThanOrEqual => "ule", - }) + &Equal => "eq", + &NotEqual => "ne", + &SignedGreaterThan => "sgt", + &SignedGreaterThanOrEqual => "sge", + &SignedLessThan => "slt", + &SignedLessThanOrEqual => "sle", + &UnsignedGreaterThan => "ugt", + &UnsignedGreaterThanOrEqual => "uge", + &UnsignedLessThan => "ult", + &UnsignedLessThanOrEqual => "ule", + }) } } @@ -220,21 +220,21 @@ impl Display for FloatCC { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use self::FloatCC::*; f.write_str(match self { - &Ordered => "ord", - &Unordered => "uno", - &Equal => "eq", - &NotEqual => "ne", - &OrderedNotEqual => "one", - &UnorderedOrEqual => "ueq", - &LessThan => "lt", - &LessThanOrEqual => "le", - &GreaterThan => "gt", - &GreaterThanOrEqual => "ge", - &UnorderedOrLessThan => "ult", - &UnorderedOrLessThanOrEqual => "ule", - &UnorderedOrGreaterThan => "ugt", - &UnorderedOrGreaterThanOrEqual => "uge", - }) + &Ordered => "ord", + &Unordered => "uno", + &Equal => "eq", + &NotEqual => "ne", + &OrderedNotEqual => "one", + &UnorderedOrEqual => "ueq", + &LessThan => "lt", + &LessThanOrEqual => "le", + &GreaterThan => "gt", + &GreaterThanOrEqual => "ge", + &UnorderedOrLessThan => "ult", + &UnorderedOrLessThanOrEqual => "ule", + &UnorderedOrGreaterThan => "ugt", + &UnorderedOrGreaterThanOrEqual => "uge", + }) } } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 8183a04cc5..f5d48ad81f 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -180,19 +180,20 @@ impl DataFlowGraph { for _ in 0..self.insts.len() { v = self.resolve_aliases(match v.expand() { - Direct(inst) => { - match self[inst] { - InstructionData::Unary { opcode, arg, .. } => { - match opcode { - Opcode::Copy | Opcode::Spill | Opcode::Fill => arg, - _ => return v, - } - } - _ => return v, - } - } - _ => return v, - }); + Direct(inst) => { + match self[inst] { + InstructionData::Unary { opcode, arg, .. } => { + match opcode { + Opcode::Copy | Opcode::Spill | + Opcode::Fill => arg, + _ => return v, + } + } + _ => return v, + } + } + _ => return v, + }); } panic!("Copy loop detected for {}", value); } @@ -361,11 +362,11 @@ impl DataFlowGraph { for res_idx in (0..var_results).rev() { if let Some(ty) = first_type { head = Some(self.make_value(ValueData::Inst { - ty: ty, - num: (total_results - rev_num) as u16, - inst: inst, - next: head.into(), - })); + ty: ty, + num: (total_results - rev_num) as u16, + inst: inst, + next: head.into(), + })); rev_num += 1; } first_type = Some(self.signatures[sig].return_types[res_idx].value_type); @@ -376,11 +377,11 @@ impl DataFlowGraph { for res_idx in (0..fixed_results).rev() { if let Some(ty) = first_type { head = Some(self.make_value(ValueData::Inst { - ty: ty, - num: (total_results - rev_num) as u16, - inst: inst, - next: head.into(), - })); + ty: ty, + num: (total_results - rev_num) as u16, + inst: inst, + next: head.into(), + })); rev_num += 1; } first_type = Some(constraints.result_type(res_idx, ctrl_typevar)); @@ -474,11 +475,11 @@ impl DataFlowGraph { // Not a fixed result, try to extract a return type from the call signature. self.call_signature(inst).and_then(|sigref| { - self.signatures[sigref] - .return_types - .get(result_idx - fixed_results) - .map(|&arg| arg.value_type) - }) + self.signatures[sigref] + .return_types + .get(result_idx - fixed_results) + .map(|&arg| arg.value_type) + }) } } @@ -523,11 +524,11 @@ impl DataFlowGraph { /// Append an argument with type `ty` to `ebb`. pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { let val = self.make_value(ValueData::Arg { - ty: ty, - ebb: ebb, - num: 0, - next: None.into(), - }); + ty: ty, + ebb: ebb, + num: 0, + next: None.into(), + }); self.put_ebb_arg(ebb, val); val } diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 9f81d11442..bc293d1169 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -50,9 +50,11 @@ impl Signature { let bytes = self.argument_types .iter() .filter_map(|arg| match arg.location { - ArgumentLoc::Stack(offset) => Some(offset + arg.value_type.bits() as u32 / 8), - _ => None, - }) + ArgumentLoc::Stack(offset) => { + Some(offset + arg.value_type.bits() as u32 / 8) + } + _ => None, + }) .fold(0, cmp::max); self.argument_bytes = Some(bytes); } diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 79229fc3a0..84f5978429 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -252,20 +252,20 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { if s2.starts_with("NaN:0x") { // Quiet NaN with payload. return match u64::from_str_radix(&s2[6..], 16) { - Ok(payload) if payload < quiet_bit => { - Ok(sign_bit | max_e_bits | quiet_bit | payload) - } - _ => Err("Invalid NaN payload"), - }; + Ok(payload) if payload < quiet_bit => { + Ok(sign_bit | max_e_bits | quiet_bit | payload) + } + _ => Err("Invalid NaN payload"), + }; } if s2.starts_with("sNaN:0x") { // Signaling NaN with payload. return match u64::from_str_radix(&s2[7..], 16) { - Ok(payload) if 0 < payload && payload < quiet_bit => { - Ok(sign_bit | max_e_bits | payload) - } - _ => Err("Invalid sNaN payload"), - }; + Ok(payload) if 0 < payload && payload < quiet_bit => { + Ok(sign_bit | max_e_bits | payload) + } + _ => Err("Invalid sNaN payload"), + }; } return Err("Float must be hexadecimal"); diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index 29da4456bc..69a44ef8fb 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -66,7 +66,10 @@ impl JumpTableData { /// /// This returns an iterator that skips any empty slots in the table. pub fn entries<'a>(&'a self) -> Entries { - Entries(self.table.iter().cloned().enumerate()) + Entries(self.table + .iter() + .cloned() + .enumerate()) } /// Access the whole table as a mutable slice. @@ -101,7 +104,10 @@ impl Display for JumpTableData { Some(first) => write!(fmt, "jump_table {}", first)?, } - for dest in self.table.iter().skip(1).map(|e| e.expand()) { + for dest in self.table + .iter() + .skip(1) + .map(|e| e.expand()) { match dest { None => write!(fmt, ", 0")?, Some(ebb) => write!(fmt, ", {}", ebb)?, diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index d30f336780..83edbea76f 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -125,10 +125,7 @@ impl Layout { /// Get the last sequence number in `ebb`. fn last_ebb_seq(&self, ebb: Ebb) -> SequenceNumber { // Get the seq of the last instruction if it exists, otherwise use the EBB header seq. - self.ebbs[ebb] - .last_inst - .map(|inst| self.insts[inst].seq) - .unwrap_or(self.ebbs[ebb].seq) + self.ebbs[ebb].last_inst.map(|inst| self.insts[inst].seq).unwrap_or(self.ebbs[ebb].seq) } /// Assign a valid sequence number to `ebb` such that the numbers are still monotonic. This may @@ -439,8 +436,8 @@ impl Layout { /// Insert `inst` before the instruction `before` in the same EBB. pub fn insert_inst(&mut self, inst: Inst, before: Inst) { assert_eq!(self.inst_ebb(inst), None); - let ebb = self.inst_ebb(before) - .expect("Instruction before insertion point not in the layout"); + let ebb = + self.inst_ebb(before).expect("Instruction before insertion point not in the layout"); let after = self.insts[before].prev; { let inst_node = self.insts.ensure(inst); @@ -488,8 +485,8 @@ impl Layout { /// i4 /// ``` pub fn split_ebb(&mut self, new_ebb: Ebb, before: Inst) { - let old_ebb = self.inst_ebb(before) - .expect("The `before` instruction must be in the layout"); + let old_ebb = + self.inst_ebb(before).expect("The `before` instruction must be in the layout"); assert!(!self.is_ebb_inserted(new_ebb)); // Insert new_ebb after old_ebb. diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index 550f25804b..1321636add 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -348,7 +348,12 @@ mod tests { assert_eq!(big.bits(), 64 * 256); assert_eq!(big.half_vector().unwrap().to_string(), "f64x128"); - assert_eq!(B1.by(2).unwrap().half_vector().unwrap().to_string(), "b1"); + assert_eq!(B1.by(2) + .unwrap() + .half_vector() + .unwrap() + .to_string(), + "b1"); assert_eq!(I32.half_vector(), None); assert_eq!(VOID.half_vector(), None); @@ -378,7 +383,12 @@ mod tests { assert_eq!(B1.by(8).unwrap().to_string(), "b1x8"); assert_eq!(B8.by(1).unwrap().to_string(), "b8"); assert_eq!(B16.by(256).unwrap().to_string(), "b16x256"); - assert_eq!(B32.by(4).unwrap().by(2).unwrap().to_string(), "b32x8"); + assert_eq!(B32.by(4) + .unwrap() + .by(2) + .unwrap() + .to_string(), + "b32x8"); assert_eq!(B64.by(8).unwrap().to_string(), "b64x8"); assert_eq!(I8.by(64).unwrap().to_string(), "i8x64"); assert_eq!(F64.by(2).unwrap().to_string(), "f64x2"); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 9af02883e3..7cc6a93358 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -34,10 +34,10 @@ fn isa_constructor(shared_flags: shared_settings::Flags, &enc_tables::LEVEL1_A32[..] }; Box::new(Isa { - isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, - cpumode: level1, - }) + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + cpumode: level1, + }) } impl TargetIsa for Isa { @@ -58,13 +58,13 @@ impl TargetIsa for Isa { inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) } fn recipe_names(&self) -> &'static [&'static str] { diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 50f54524a9..5f253c39c2 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -28,9 +28,9 @@ fn isa_constructor(shared_flags: shared_settings::Flags, builder: &shared_settings::Builder) -> Box { Box::new(Isa { - isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, - }) + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + }) } impl TargetIsa for Isa { @@ -51,13 +51,13 @@ impl TargetIsa for Isa { inst.opcode(), &enc_tables::LEVEL1_A64[..], &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) } fn recipe_names(&self) -> &'static [&'static str] { diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index e5c99521e3..4a5ea8201a 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -34,10 +34,10 @@ fn isa_constructor(shared_flags: shared_settings::Flags, &enc_tables::LEVEL1_I32[..] }; Box::new(Isa { - isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, - cpumode: level1, - }) + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + cpumode: level1, + }) } impl TargetIsa for Isa { @@ -58,13 +58,13 @@ impl TargetIsa for Isa { inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) } fn recipe_names(&self) -> &'static [&'static str] { diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index c51413eb20..864b950f63 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -67,10 +67,10 @@ impl RegBank { } } .and_then(|offset| if offset < self.units { - Some(offset + self.first_unit) - } else { - None - }) + Some(offset + self.first_unit) + } else { + None + }) } /// Write `regunit` to `w`, assuming that it belongs to this bank. @@ -199,7 +199,10 @@ impl RegInfo { /// Try to parse a regunit name. The name is not expected to begin with `%`. pub fn parse_regunit(&self, name: &str) -> Option { - self.banks.iter().filter_map(|b| b.parse_regunit(name)).next() + self.banks + .iter() + .filter_map(|b| b.parse_regunit(name)) + .next() } /// Make a temporary object that can display a register unit. diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 220f062278..ce04fe70b6 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -35,10 +35,10 @@ fn isa_constructor(shared_flags: shared_settings::Flags, &enc_tables::LEVEL1_RV32[..] }; Box::new(Isa { - isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, - cpumode: level1, - }) + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags: shared_flags, + cpumode: level1, + }) } impl TargetIsa for Isa { @@ -59,13 +59,13 @@ impl TargetIsa for Isa { inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + .and_then(|enclist_offset| { + general_encoding(enclist_offset, + &enc_tables::ENCLISTS[..], + |instp| enc_tables::check_instp(inst, instp), + |isap| self.isa_flags.numbered_predicate(isap as usize)) + .ok_or(Legalize::Expand) + }) } fn recipe_names(&self) -> &'static [&'static str] { diff --git a/lib/cretonne/src/partition_slice.rs b/lib/cretonne/src/partition_slice.rs index 9626b5fd37..0986613040 100644 --- a/lib/cretonne/src/partition_slice.rs +++ b/lib/cretonne/src/partition_slice.rs @@ -35,7 +35,10 @@ mod tests { fn check(x: &[u32], want: &[u32]) { assert_eq!(x.len(), want.len()); - let want_count = want.iter().cloned().filter(|&x| x % 10 == 0).count(); + let want_count = want.iter() + .cloned() + .filter(|&x| x % 10 == 0) + .count(); let mut v = Vec::new(); v.extend(x.iter().cloned()); let count = partition_slice(&mut v[..], |&x| x % 10 == 0); diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 40ef3c3636..29a7e26964 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -118,21 +118,21 @@ mod tests { // Register classes for testing. const GPR: RegClass = &RegClassData { - name: "GPR", - index: 0, - width: 1, - first: 28, - subclasses: 0, - mask: [0xf0000000, 0x0000000f, 0], - }; + name: "GPR", + index: 0, + width: 1, + first: 28, + subclasses: 0, + mask: [0xf0000000, 0x0000000f, 0], + }; const DPR: RegClass = &RegClassData { - name: "DPR", - index: 0, - width: 2, - first: 28, - subclasses: 0, - mask: [0x50000000, 0x0000000a, 0], - }; + name: "DPR", + index: 0, + width: 2, + first: 28, + subclasses: 0, + mask: [0x50000000, 0x0000000a, 0], + }; #[test] fn put_and_take() { diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 24f8c0c7e1..19800600b4 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -200,7 +200,10 @@ impl<'a> Context<'a> { for lv in liveins { let value = lv.value; - let affinity = self.liveness.get(value).expect("No live range for live-in").affinity; + let affinity = self.liveness + .get(value) + .expect("No live range for live-in") + .affinity; if let Affinity::Reg(rc_index) = affinity { let regclass = self.reginfo.rc(rc_index); match func.locations[value] { diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index c45da220e4..19ed62e8fb 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -69,10 +69,10 @@ impl LiveValueVec { /// Add a new live value to `values`. fn push(&mut self, value: Value, endpoint: Inst, affinity: Affinity) { self.values.push(LiveValue { - value: value, - endpoint: endpoint, - affinity: affinity, - }); + value: value, + endpoint: endpoint, + affinity: affinity, + }); } /// Remove all elements. @@ -167,8 +167,7 @@ impl LiveValueTracker { self.idom_sets.get(&idom).expect("No stored live set for dominator"); // Get just the values that are live-in to `ebb`. for &value in idom_live_list.as_slice(&self.idom_pool) { - let lr = liveness.get(value) - .expect("Immediate dominator value has no live range"); + let lr = liveness.get(value).expect("Immediate dominator value has no live range"); // Check if this value is live-in here. if let Some(endpoint) = lr.livein_local_end(ebb, program_order) { @@ -260,13 +259,16 @@ impl LiveValueTracker { /// Save the current set of live values so it is associated with `idom`. fn save_idom_live_set(&mut self, idom: Inst) { - let values = self.live.values.iter().map(|lv| lv.value); + let values = self.live + .values + .iter() + .map(|lv| lv.value); let pool = &mut self.idom_pool; // If there already is a set saved for `idom`, just keep it. self.idom_sets.entry(idom).or_insert_with(|| { - let mut list = ValueList::default(); - list.extend(values, pool); - list - }); + let mut list = ValueList::default(); + list.extend(values, pool); + list + }); } } diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 582cbae48a..989d3bb8b7 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -315,8 +315,10 @@ impl Liveness { let recipe = func.encodings[inst].recipe(); // Iterator of constraints, one per value operand. // TODO: Should we fail here if the instruction doesn't have a valid encoding? - let mut operand_constraints = - recipe_constraints.get(recipe).map(|c| c.ins).unwrap_or(&[]).iter(); + let mut operand_constraints = recipe_constraints.get(recipe) + .map(|c| c.ins) + .unwrap_or(&[]) + .iter(); for &arg in func.dfg[inst].arguments(&func.dfg.value_lists) { // Get the live range, create it as a dead range if necessary. diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index a2d7a8f32e..116ccce4aa 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -221,16 +221,14 @@ impl LiveRange { /// Return `Ok(n)` if `liveins[n]` already contains `ebb`. /// Otherwise, return `Err(n)` with the index where such an interval should be inserted. fn find_ebb_interval(&self, ebb: Ebb, order: &PO) -> Result { - self.liveins - .binary_search_by(|intv| order.cmp(intv.begin, ebb)) - .or_else(|n| { - // The interval at `n-1` may cover `ebb`. - if n > 0 && order.cmp(self.liveins[n - 1].end, ebb) == Ordering::Greater { - Ok(n - 1) - } else { - Err(n) - } - }) + self.liveins.binary_search_by(|intv| order.cmp(intv.begin, ebb)).or_else(|n| { + // The interval at `n-1` may cover `ebb`. + if n > 0 && order.cmp(self.liveins[n - 1].end, ebb) == Ordering::Greater { + Ok(n - 1) + } else { + Err(n) + } + }) } /// Extend the local interval for `ebb` so it reaches `to` which must belong to `ebb`. diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 90bf2dda90..f63db1919f 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -157,9 +157,8 @@ impl<'a> Verifier<'a> { let fixed_results = inst_data.opcode().constraints().fixed_results(); // var_results is 0 if we aren't a call instruction - let var_results = dfg.call_signature(inst) - .map(|sig| dfg.signatures[sig].return_types.len()) - .unwrap_or(0); + let var_results = + dfg.call_signature(inst).map(|sig| dfg.signatures[sig].return_types.len()).unwrap_or(0); let total_results = fixed_results + var_results; if total_results == 0 { @@ -247,7 +246,10 @@ impl<'a> Verifier<'a> { } fn verify_sig_ref(&self, inst: Inst, s: SigRef) -> Result<()> { - if !self.func.dfg.signatures.is_valid(s) { + if !self.func + .dfg + .signatures + .is_valid(s) { err!(inst, "invalid signature reference {}", s) } else { Ok(()) @@ -255,7 +257,10 @@ impl<'a> Verifier<'a> { } fn verify_func_ref(&self, inst: Inst, f: FuncRef) -> Result<()> { - if !self.func.dfg.ext_funcs.is_valid(f) { + if !self.func + .dfg + .ext_funcs + .is_valid(f) { err!(inst, "invalid function reference {}", f) } else { Ok(()) @@ -330,9 +335,9 @@ mod tests { let ebb0 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); let nullary_with_bad_opcode = func.dfg.make_inst(InstructionData::Nullary { - opcode: Opcode::Jump, - ty: types::VOID, - }); + opcode: Opcode::Jump, + ty: types::VOID, + }); func.layout.append_inst(nullary_with_bad_opcode, ebb0); let verifier = Verifier::new(&func); assert_err_with_msg!(verifier.run(), "instruction format"); diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 1f9bfd4524..081da35617 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -188,7 +188,11 @@ fn write_instruction(w: &mut Write, for r in func.dfg.inst_results(inst) { write!(s, ",{}", - func.locations.get(r).cloned().unwrap_or_default().display(®s))? + func.locations + .get(r) + .cloned() + .unwrap_or_default() + .display(®s))? } } write!(s, "]")?; diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index 3101865e71..c854f3b6b8 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -270,7 +270,10 @@ impl<'a> State<'a> { // Get the offset following the match that defined `var`, or 0 if var is an environment // variable or unknown. fn def_offset(&self, var: &str) -> usize { - self.vars.get(var).map(|&VarDef { offset, .. }| offset).unwrap_or(0) + self.vars + .get(var) + .map(|&VarDef { offset, .. }| offset) + .unwrap_or(0) } // Get the offset of the beginning of the next line after `pos`. @@ -344,13 +347,13 @@ impl<'a> State<'a> { }) }; Ok(if let Some((b, e)) = matched_range { - let r = (range.0 + b, range.0 + e); - self.recorder.matched_check(rx.as_str(), r); - Some(r) - } else { - self.recorder.missed_check(rx.as_str(), range); - None - }) + let r = (range.0 + b, range.0 + e); + self.recorder.matched_check(rx.as_str(), r); + Some(r) + } else { + self.recorder.missed_check(rx.as_str(), range); + None + }) } } diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs index 75fe6f9286..04a6cd233e 100644 --- a/lib/filecheck/src/explain.rs +++ b/lib/filecheck/src/explain.rs @@ -148,49 +148,49 @@ impl<'a> Recorder for Explainer<'a> { fn matched_check(&mut self, regex: &str, matched: MatchRange) { self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: false, - regex: regex.to_owned(), - range: matched, - }); + directive: self.directive, + is_match: true, + is_not: false, + regex: regex.to_owned(), + range: matched, + }); } fn matched_not(&mut self, regex: &str, matched: MatchRange) { self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: true, - regex: regex.to_owned(), - range: matched, - }); + directive: self.directive, + is_match: true, + is_not: true, + regex: regex.to_owned(), + range: matched, + }); } fn missed_check(&mut self, regex: &str, searched: MatchRange) { self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: false, - regex: regex.to_owned(), - range: searched, - }); + directive: self.directive, + is_match: false, + is_not: false, + regex: regex.to_owned(), + range: searched, + }); } fn missed_not(&mut self, regex: &str, searched: MatchRange) { self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: true, - regex: regex.to_owned(), - range: searched, - }); + directive: self.directive, + is_match: false, + is_not: true, + regex: regex.to_owned(), + range: searched, + }); } fn defined_var(&mut self, varname: &str, value: &str) { self.vardefs.push(VarDef { - directive: self.directive, - varname: varname.to_owned(), - value: value.to_owned(), - }); + directive: self.directive, + varname: varname.to_owned(), + value: value.to_owned(), + }); } } diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs index 9f54a80cf6..67ec359c4a 100644 --- a/lib/filecheck/src/pattern.rs +++ b/lib/filecheck/src/pattern.rs @@ -112,7 +112,7 @@ impl Pattern { // All remaining possibilities start with `$(`. if s.len() < 2 || !s.starts_with("$(") { return Err(Error::Syntax("pattern syntax error, use $$ to match a single $" - .to_string())); + .to_string())); } // Match the variable name, allowing for an empty varname in `$()`, or `$(=...)`. @@ -164,14 +164,14 @@ impl Pattern { } let refname = s[refname_begin..refname_end].to_string(); return if let Some(defidx) = def { - Ok((Part::DefVar { - def: defidx, - var: refname, - }, - refname_end + 1)) - } else { - Err(Error::Syntax(format!("expected variable name in $(=${})", refname))) - }; + Ok((Part::DefVar { + def: defidx, + var: refname, + }, + refname_end + 1)) + } else { + Err(Error::Syntax(format!("expected variable name in $(=${})", refname))) + }; } // Last case: `$(var=...)` where `...` is a regular expression, possibly containing matched diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index caf32ed252..e1e4cc31c4 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -55,9 +55,9 @@ pub struct LocatedToken<'a> { /// Wrap up a `Token` with the given location. fn token<'a>(token: Token<'a>, loc: Location) -> Result, LocatedError> { Ok(LocatedToken { - token: token, - location: loc, - }) + token: token, + location: loc, + }) } /// An error from the lexical analysis. @@ -76,15 +76,20 @@ pub struct LocatedError { /// Wrap up an `Error` with the given location. fn error<'a>(error: Error, loc: Location) -> Result, LocatedError> { Err(LocatedError { - error: error, - location: loc, - }) + error: error, + location: loc, + }) } /// Get the number of decimal digits at the end of `s`. fn trailing_digits(s: &str) -> usize { // It's faster to iterate backwards over bytes, and we're only counting ASCII digits. - s.as_bytes().iter().rev().cloned().take_while(|&b| b'0' <= b && b <= b'9').count() + s.as_bytes() + .iter() + .rev() + .cloned() + .take_while(|&b| b'0' <= b && b <= b'9') + .count() } /// Pre-parse a supposed entity name by splitting it into two parts: A head of lowercase ASCII @@ -284,9 +289,9 @@ impl<'a> Lexer<'a> { // Look for numbered well-known entities like ebb15, v45, ... token(split_entity_name(text) .and_then(|(prefix, number)| { - Self::numbered_entity(prefix, number) + Self::numbered_entity(prefix, number) .or_else(|| Self::value_type(text, prefix, number)) - }) + }) .unwrap_or(Token::Identifier(text)), loc) } @@ -378,39 +383,39 @@ impl<'a> Lexer<'a> { loop { let loc = self.loc(); return match self.lookahead { - None => None, - Some(';') => Some(self.scan_comment()), - Some('(') => Some(self.scan_char(Token::LPar)), - Some(')') => Some(self.scan_char(Token::RPar)), - Some('{') => Some(self.scan_char(Token::LBrace)), - Some('}') => Some(self.scan_char(Token::RBrace)), - Some('[') => Some(self.scan_char(Token::LBracket)), - Some(']') => Some(self.scan_char(Token::RBracket)), - Some(',') => Some(self.scan_char(Token::Comma)), - Some('.') => Some(self.scan_char(Token::Dot)), - Some(':') => Some(self.scan_char(Token::Colon)), - Some('=') => Some(self.scan_char(Token::Equal)), - Some('-') => { - if self.looking_at("->") { - Some(self.scan_chars(2, Token::Arrow)) - } else { - Some(self.scan_number()) - } - } - Some(ch) if ch.is_digit(10) => Some(self.scan_number()), - Some(ch) if ch.is_alphabetic() => Some(self.scan_word()), - Some('%') => Some(self.scan_name()), - Some('#') => Some(self.scan_hex_sequence()), - Some(ch) if ch.is_whitespace() => { - self.next_ch(); - continue; - } - _ => { - // Skip invalid char, return error. - self.next_ch(); - Some(error(Error::InvalidChar, loc)) - } - }; + None => None, + Some(';') => Some(self.scan_comment()), + Some('(') => Some(self.scan_char(Token::LPar)), + Some(')') => Some(self.scan_char(Token::RPar)), + Some('{') => Some(self.scan_char(Token::LBrace)), + Some('}') => Some(self.scan_char(Token::RBrace)), + Some('[') => Some(self.scan_char(Token::LBracket)), + Some(']') => Some(self.scan_char(Token::RBracket)), + Some(',') => Some(self.scan_char(Token::Comma)), + Some('.') => Some(self.scan_char(Token::Dot)), + Some(':') => Some(self.scan_char(Token::Colon)), + Some('=') => Some(self.scan_char(Token::Equal)), + Some('-') => { + if self.looking_at("->") { + Some(self.scan_chars(2, Token::Arrow)) + } else { + Some(self.scan_number()) + } + } + Some(ch) if ch.is_digit(10) => Some(self.scan_number()), + Some(ch) if ch.is_alphabetic() => Some(self.scan_word()), + Some('%') => Some(self.scan_name()), + Some('#') => Some(self.scan_hex_sequence()), + Some(ch) if ch.is_whitespace() => { + self.next_ch(); + continue; + } + _ => { + // Skip invalid char, return error. + self.next_ch(); + Some(error(Error::InvalidChar, loc)) + } + }; } } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index c982a5be0c..44b72afc4a 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -28,7 +28,12 @@ use sourcemap::{SourceMap, MutableSourceMap}; /// /// Any test commands or ISA declarations are ignored. pub fn parse_functions(text: &str) -> Result> { - parse_test(text).map(|file| file.functions.into_iter().map(|(func, _)| func).collect()) + parse_test(text).map(|file| { + file.functions + .into_iter() + .map(|(func, _)| func) + .collect() + }) } /// Parse the entire `text` as a test case file. @@ -45,11 +50,11 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { let functions = parser.parse_function_list(isa_spec.unique_isa())?; Ok(TestFile { - commands: commands, - isa_spec: isa_spec, - preamble_comments: preamble_comments, - functions: functions, - }) + commands: commands, + isa_spec: isa_spec, + preamble_comments: preamble_comments, + functions: functions, + }) } pub struct Parser<'a> { @@ -114,7 +119,12 @@ impl<'a> Context<'a> { // Allocate a new signature and add a mapping number -> SigRef. fn add_sig(&mut self, number: u32, data: Signature, loc: &Location) -> Result<()> { - self.map.def_sig(number, self.function.dfg.signatures.push(data), loc) + self.map.def_sig(number, + self.function + .dfg + .signatures + .push(data), + loc) } // Resolve a reference to a signature. @@ -127,7 +137,12 @@ impl<'a> Context<'a> { // Allocate a new external function and add a mapping number -> FuncRef. fn add_fn(&mut self, number: u32, data: ExtFuncData, loc: &Location) -> Result<()> { - self.map.def_fn(number, self.function.dfg.ext_funcs.push(data), loc) + self.map.def_fn(number, + self.function + .dfg + .ext_funcs + .push(data), + loc) } // Resolve a reference to a function. @@ -269,9 +284,9 @@ impl<'a> Parser<'a> { // Gather comments, associate them with `comment_entity`. if let Some(entity) = self.comment_entity { self.comments.push(Comment { - entity: entity, - text: text, - }); + entity: entity, + text: text, + }); } } _ => self.lookahead = Some(token), @@ -777,23 +792,35 @@ impl<'a> Parser<'a> { match self.token() { Some(Token::StackSlot(..)) => { self.gather_comments(ctx.function.stack_slots.next_key()); - self.parse_stack_slot_decl() - .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) + self.parse_stack_slot_decl().and_then(|(num, dat)| { + ctx.add_ss(num, dat, &self.loc) + }) } Some(Token::SigRef(..)) => { - self.gather_comments(ctx.function.dfg.signatures.next_key()); - self.parse_signature_decl(ctx.unique_isa) - .and_then(|(num, dat)| ctx.add_sig(num, dat, &self.loc)) + self.gather_comments(ctx.function + .dfg + .signatures + .next_key()); + self.parse_signature_decl(ctx.unique_isa).and_then(|(num, dat)| { + ctx.add_sig(num, + dat, + &self.loc) + }) } Some(Token::FuncRef(..)) => { - self.gather_comments(ctx.function.dfg.ext_funcs.next_key()); - self.parse_function_decl(ctx) - .and_then(|(num, dat)| ctx.add_fn(num, dat, &self.loc)) + self.gather_comments(ctx.function + .dfg + .ext_funcs + .next_key()); + self.parse_function_decl(ctx).and_then(|(num, dat)| { + ctx.add_fn(num, dat, &self.loc) + }) } Some(Token::JumpTable(..)) => { self.gather_comments(ctx.function.jump_tables.next_key()); - self.parse_jump_table_decl() - .and_then(|(num, dat)| ctx.add_jt(num, dat, &self.loc)) + self.parse_jump_table_decl().and_then(|(num, dat)| { + ctx.add_jt(num, dat, &self.loc) + }) } // More to come.. _ => return Ok(()), @@ -852,7 +879,10 @@ impl<'a> Parser<'a> { let data = match self.token() { Some(Token::Identifier("function")) => { let (loc, name, sig) = self.parse_function_spec(ctx.unique_isa)?; - let sigref = ctx.function.dfg.signatures.push(sig); + let sigref = ctx.function + .dfg + .signatures + .push(sig); ctx.map.def_entity(sigref.into(), &loc).expect("duplicate SigRef entities created"); ExtFuncData { name: name, @@ -944,11 +974,11 @@ impl<'a> Parser<'a> { // extended-basic-block ::= ebb-header * { instruction } while match self.token() { - Some(Token::Value(_)) => true, - Some(Token::Identifier(_)) => true, - Some(Token::LBracket) => true, - _ => false, - } { + Some(Token::Value(_)) => true, + Some(Token::Identifier(_)) => true, + Some(Token::LBracket) => true, + _ => false, + } { self.parse_instruction(ctx, ebb)?; } @@ -1161,7 +1191,10 @@ impl<'a> Parser<'a> { ctx.function.dfg.inst_results(inst))?; if let Some(result_locations) = result_locations { - for (value, loc) in ctx.function.dfg.inst_results(inst).zip(result_locations) { + for (value, loc) in ctx.function + .dfg + .inst_results(inst) + .zip(result_locations) { *ctx.function.locations.ensure(value) = loc; } } @@ -1197,13 +1230,13 @@ impl<'a> Parser<'a> { let ctrl_src_value = inst_data.typevar_operand(&ctx.function.dfg.value_lists) .expect("Constraints <-> Format inconsistency"); ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { - Some(v) => v, - None => { - return err!(self.loc, - "cannot determine type of operand {}", - ctrl_src_value); - } - }) + Some(v) => v, + None => { + return err!(self.loc, + "cannot determine type of operand {}", + ctrl_src_value); + } + }) } else if constraints.is_polymorphic() { // This opcode does not support type inference, so the explicit type variable // is required. @@ -1290,7 +1323,7 @@ impl<'a> Parser<'a> { ctx: &mut Context, opcode: Opcode) -> Result { - Ok(match opcode.format() { + let idata = match opcode.format() { InstructionFormat::Nullary => { InstructionData::Nullary { opcode: opcode, @@ -1502,7 +1535,8 @@ impl<'a> Parser<'a> { table: table, } } - }) + }; + Ok(idata) } } @@ -1555,8 +1589,8 @@ mod tests { ss3 = stack_slot 13 ss1 = stack_slot 1 }") - .parse_function(None) - .unwrap(); + .parse_function(None) + .unwrap(); assert_eq!(func.name.to_string(), "foo"); let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); @@ -1572,9 +1606,9 @@ mod tests { ss1 = stack_slot 13 ss1 = stack_slot 1 }") - .parse_function(None) - .unwrap_err() - .to_string(), + .parse_function(None) + .unwrap_err() + .to_string(), "3: duplicate stack slot: ss1"); } @@ -1584,8 +1618,8 @@ mod tests { ebb0: ebb4(vx3: i32): }") - .parse_function(None) - .unwrap(); + .parse_function(None) + .unwrap(); assert_eq!(func.name.to_string(), "ebbs"); let mut ebbs = func.layout.ebbs(); @@ -1602,8 +1636,7 @@ mod tests { #[test] fn comments() { - let (func, Details { comments, .. }) = - Parser::new("; before + let (func, Details { comments, .. }) = Parser::new("; before function comment() { ; decl ss10 = stack_slot 13 ; stackslot. ; Still stackslot. @@ -1645,7 +1678,7 @@ mod tests { set enable_float=false ; still preamble function comment() {}") - .unwrap(); + .unwrap(); assert_eq!(tf.commands.len(), 2); assert_eq!(tf.commands[0].command, "cfg"); assert_eq!(tf.commands[1].command, "verify"); @@ -1664,18 +1697,18 @@ mod tests { fn isa_spec() { assert!(parse_test("isa function foo() {}") - .is_err()); + .is_err()); assert!(parse_test("isa riscv set enable_float=false function foo() {}") - .is_err()); + .is_err()); match parse_test("set enable_float=false isa riscv function foo() {}") - .unwrap() - .isa_spec { + .unwrap() + .isa_spec { IsaSpec::None(_) => panic!("Expected some ISA"), IsaSpec::Some(v) => { assert_eq!(v.len(), 1); diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index dda6420220..cbea4b02a2 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -63,23 +63,27 @@ impl SourceMap { /// Returns the entity reference corresponding to `name`, if it exists. pub fn lookup_str(&self, name: &str) -> Option { split_entity_name(name).and_then(|(ent, num)| match ent { - "v" => { - Value::direct_with_number(num) - .and_then(|v| self.get_value(v)) - .map(AnyEntity::Value) - } - "vx" => { - Value::table_with_number(num) - .and_then(|v| self.get_value(v)) - .map(AnyEntity::Value) - } - "ebb" => Ebb::with_number(num).and_then(|e| self.get_ebb(e)).map(AnyEntity::Ebb), - "ss" => self.get_ss(num).map(AnyEntity::StackSlot), - "sig" => self.get_sig(num).map(AnyEntity::SigRef), - "fn" => self.get_fn(num).map(AnyEntity::FuncRef), - "jt" => self.get_jt(num).map(AnyEntity::JumpTable), - _ => None, - }) + "v" => { + Value::direct_with_number(num) + .and_then(|v| self.get_value(v)) + .map(AnyEntity::Value) + } + "vx" => { + Value::table_with_number(num) + .and_then(|v| self.get_value(v)) + .map(AnyEntity::Value) + } + "ebb" => { + Ebb::with_number(num) + .and_then(|e| self.get_ebb(e)) + .map(AnyEntity::Ebb) + } + "ss" => self.get_ss(num).map(AnyEntity::StackSlot), + "sig" => self.get_sig(num).map(AnyEntity::SigRef), + "fn" => self.get_fn(num).map(AnyEntity::FuncRef), + "jt" => self.get_jt(num).map(AnyEntity::JumpTable), + _ => None, + }) } /// Get the source location where an entity was defined. @@ -229,7 +233,7 @@ mod tests { ebb0(v4: i32, vx7: i32): v10 = iadd v4, vx7 }") - .unwrap(); + .unwrap(); let map = &tf.functions[0].1.map; assert_eq!(map.lookup_str("v0"), None); diff --git a/src/cton-util.rs b/src/cton-util.rs index 35a5c8a564..89fee3c5a5 100644 --- a/src/cton-util.rs +++ b/src/cton-util.rs @@ -51,11 +51,7 @@ pub type CommandResult = Result<(), String>; fn cton_util() -> CommandResult { // Parse comand line arguments. let args: Args = Docopt::new(USAGE) - .and_then(|d| { - d.help(true) - .version(Some(format!("Cretonne {}", VERSION))) - .decode() - }) + .and_then(|d| d.help(true).version(Some(format!("Cretonne {}", VERSION))).decode()) .unwrap_or_else(|e| e.exit()); // Find the sub-command to execute. diff --git a/src/filetest/concurrent.rs b/src/filetest/concurrent.rs index e0afc8bead..aa0fba8f1d 100644 --- a/src/filetest/concurrent.rs +++ b/src/filetest/concurrent.rs @@ -97,8 +97,8 @@ fn heartbeat_thread(replies: Sender) -> thread::JoinHandle<()> { thread::Builder::new() .name("heartbeat".to_string()) .spawn(move || while replies.send(Reply::Tick).is_ok() { - thread::sleep(Duration::from_secs(1)); - }) + thread::sleep(Duration::from_secs(1)); + }) .unwrap() } @@ -120,9 +120,9 @@ fn worker_thread(thread_num: usize, // Tell them we're starting this job. // The receiver should always be present for this as long as we have jobs. replies.send(Reply::Starting { - jobid: jobid, - thread_num: thread_num, - }) + jobid: jobid, + thread_num: thread_num, + }) .unwrap(); let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| { @@ -138,9 +138,9 @@ fn worker_thread(thread_num: usize, }); replies.send(Reply::Done { - jobid: jobid, - result: result, - }) + jobid: jobid, + result: result, + }) .unwrap(); } }) diff --git a/src/filetest/domtree.rs b/src/filetest/domtree.rs index efa4ee857b..84b63177ec 100644 --- a/src/filetest/domtree.rs +++ b/src/filetest/domtree.rs @@ -89,7 +89,10 @@ impl SubTest for TestDomtree { // Now we know that everything in `expected` is consistent with `domtree`. // All other EBB's should be either unreachable or the entry block. - for ebb in func.layout.ebbs().skip(1).filter(|ebb| !expected.contains_key(&ebb)) { + for ebb in func.layout + .ebbs() + .skip(1) + .filter(|ebb| !expected.contains_key(&ebb)) { if let Some(got_inst) = domtree.idom(ebb) { return Err(format!("mismatching idoms for renumbered {}:\n\ want: unrechable, got: {}", diff --git a/src/filetest/runner.rs b/src/filetest/runner.rs index 8794de0004..91331a0299 100644 --- a/src/filetest/runner.rs +++ b/src/filetest/runner.rs @@ -105,9 +105,9 @@ impl TestRunner { /// Any problems reading `file` as a test case file will be reported as a test failure. pub fn push_test>(&mut self, file: P) { self.tests.push(QueueEntry { - path: file.into(), - state: State::New, - }); + path: file.into(), + state: State::New, + }); } /// Begin running tests concurrently. @@ -277,9 +277,9 @@ impl TestRunner { let mut times = self.tests .iter() .filter_map(|entry| match *entry { - QueueEntry { state: State::Done(Ok(dur)), .. } => Some(dur), - _ => None, - }) + QueueEntry { state: State::Done(Ok(dur)), .. } => Some(dur), + _ => None, + }) .collect::>(); // Get me some real data, kid. @@ -303,12 +303,12 @@ impl TestRunner { return; } - for t in self.tests - .iter() - .filter(|entry| match **entry { - QueueEntry { state: State::Done(Ok(dur)), .. } => dur > cut, - _ => false, - }) { + for t in self.tests.iter().filter(|entry| match **entry { + QueueEntry { state: State::Done(Ok(dur)), .. } => { + dur > cut + } + _ => false, + }) { println!("slow: {}", t) } diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index 2067a245b9..8e707d4ff5 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -25,7 +25,10 @@ pub fn run(path: &Path) -> TestResult { } // Parse the test commands. - let mut tests = testfile.commands.iter().map(new_subtest).collect::>>()?; + let mut tests = testfile.commands + .iter() + .map(new_subtest) + .collect::>>()?; // Flags to use for those tests that don't need an ISA. // This is the cumulative effect of all the `set` commands in the file. diff --git a/src/filetest/subtest.rs b/src/filetest/subtest.rs index 170490fb3a..ede013ef9f 100644 --- a/src/filetest/subtest.rs +++ b/src/filetest/subtest.rs @@ -66,7 +66,10 @@ pub trait SubTest { /// match 'inst10'. impl<'a> filecheck::VariableMap for Context<'a> { fn lookup(&self, varname: &str) -> Option { - self.details.map.lookup_str(varname).map(|e| FCValue::Regex(format!(r"\b{}\b", e).into())) + self.details + .map + .lookup_str(varname) + .map(|e| FCValue::Regex(format!(r"\b{}\b", e).into())) } } @@ -77,8 +80,7 @@ pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { Ok(()) } else { // Filecheck mismatch. Emit an explanation as output. - let (_, explain) = checker.explain(&text, context) - .map_err(|e| format!("explain: {}", e))?; + let (_, explain) = checker.explain(&text, context).map_err(|e| format!("explain: {}", e))?; Err(format!("filecheck failed:\n{}{}", checker, explain)) } } diff --git a/src/rsfilecheck.rs b/src/rsfilecheck.rs index 9794e10813..734c067a36 100644 --- a/src/rsfilecheck.rs +++ b/src/rsfilecheck.rs @@ -21,8 +21,7 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { io::stdin().read_to_string(&mut buffer).map_err(|e| format!("stdin: {}", e))?; if verbose { - let (success, explain) = checker.explain(&buffer, NO_VARIABLES) - .map_err(|e| e.to_string())?; + let (success, explain) = checker.explain(&buffer, NO_VARIABLES).map_err(|e| e.to_string())?; print!("{}", explain); if success { println!("OK"); diff --git a/test-all.sh b/test-all.sh index aebec1a872..6e2ceb2de1 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.7.1" +RUSTFMT_VERSION="0.8.0" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index 453e0f83b7..ff5cf6020c 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -9,9 +9,7 @@ use self::cretonne::entity_map::EntityMap; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &parse_functions(function_source).unwrap()[0]; let cfg = ControlFlowGraph::with_function(&func); - let ebbs = ebb_order.iter() - .map(|n| Ebb::with_number(*n).unwrap()) - .collect::>(); + let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()).collect::>(); let mut postorder_ebbs = cfg.postorder_ebbs(); let mut postorder_map = EntityMap::with_capacity(postorder_ebbs.len()); From ce1ad378630ecc095d3c5c6f03d73956b5293dab Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sun, 8 Jan 2017 18:58:12 -0800 Subject: [PATCH 589/968] Parse instruction results separately from instructions --- lib/reader/src/parser.rs | 61 ++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 44b72afc4a..7c310cbf1d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -979,7 +979,26 @@ impl<'a> Parser<'a> { Some(Token::LBracket) => true, _ => false, } { - self.parse_instruction(ctx, ebb)?; + let (encoding, result_locations) = self.parse_instruction_encoding(ctx)?; + + // We need to parse instruction results here because they are shared + // between the parsing of value aliases and the parsing of instructions. + // + // inst-results ::= Value(v) { "," Value(vx) } + let results = self.parse_inst_results()?; + + match self.token() { + Some(Token::Arrow) => { + self.consume(); + self.parse_value_alias(results, ctx)?; + } + Some(Token::Equal) => { + self.consume(); + self.parse_instruction(results, encoding, result_locations, ctx, ebb)?; + } + _ if results.len() != 0 => return err!(self.loc, "expected -> or ="), + _ => self.parse_instruction(results, encoding, result_locations, ctx, ebb)?, + } } Ok(()) @@ -1099,17 +1118,11 @@ impl<'a> Parser<'a> { Ok((encoding, result_locations)) } - // Parse an instruction, append it to `ebb`. + // Parse instruction results and return them. // - // instruction ::= [inst-results "="] Opcode(opc) ["." Type] ... // inst-results ::= Value(v) { "," Value(vx) } // - fn parse_instruction(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { - // Collect comments for the next instruction to be allocated. - self.gather_comments(ctx.function.dfg.next_inst()); - - let (encoding, result_locations) = self.parse_instruction_encoding(ctx)?; - + fn parse_inst_results(&mut self) -> Result> { // Result value numbers. let mut results = Vec::new(); @@ -1124,10 +1137,36 @@ impl<'a> Parser<'a> { // inst-results ::= Value(v) { "," * Value(vx) } results.push(self.match_value("expected result value")?); } - - self.match_token(Token::Equal, "expected '=' before opcode")?; } + Ok(results) + } + + // Parse a value alias, and append it to `ebb`. + // + // value_alias ::= [inst-results] "->" Value(vx) + // + fn parse_value_alias(&mut self, results: Vec, ctx: &mut Context) -> Result<()> { + if results.len() != 1 { + return err!(self.loc, "wrong number of aliases"); + } + Ok(()) + } + + // Parse an instruction, append it to `ebb`. + // + // instruction ::= [inst-results "="] Opcode(opc) ["." Type] ... + // + fn parse_instruction(&mut self, + results: Vec, + encoding: Option, + result_locations: Option>, + ctx: &mut Context, + ebb: Ebb) + -> Result<()> { + // Collect comments for the next instruction to be allocated. + self.gather_comments(ctx.function.dfg.next_inst()); + // instruction ::= [inst-results "="] * Opcode(opc) ["." Type] ... let opcode = if let Some(Token::Identifier(text)) = self.token() { match text.parse() { From a18ad5a306f221ee894eb81ddeba600a11ac228e Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 13 Mar 2017 21:29:22 -0700 Subject: [PATCH 590/968] Create alias HashMap in parser context --- lib/reader/src/parser.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 7c310cbf1d..858049efc8 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -5,6 +5,7 @@ // // ====--------------------------------------------------------------------------------------====// +use std::collections::HashMap; use std::str::FromStr; use std::{u16, u32}; use std::mem; @@ -83,6 +84,8 @@ pub struct Parser<'a> { struct Context<'a> { function: Function, map: SourceMap, + // Store aliases until the values can be reliably looked up. + aliases: HashMap, // Reference to the unique_isa for things like parsing ISA-specific instruction encoding // information. This is only `Some` if exactly one set of `isa` directives were found in the @@ -96,6 +99,7 @@ impl<'a> Context<'a> { Context { function: f, map: SourceMap::new(), + aliases: HashMap::new(), unique_isa: unique_isa, } } @@ -173,6 +177,13 @@ impl<'a> Context<'a> { self.map.def_ebb(src_ebb, ebb, loc).and(Ok(ebb)) } + fn add_alias(&mut self, src: Value, dest: Value, loc: Location) -> Result<()> { + match self.aliases.insert(src, (dest, loc)) { + Some((v, _)) if v != dest => err!(loc, "duplicate alias: {} -> {}", src, dest), + _ => Ok(()), + } + } + // The parser creates all instructions with Ebb and Value references using the source file // numbering. These references need to be rewritten after parsing is complete since forward // references are allowed. @@ -1150,7 +1161,8 @@ impl<'a> Parser<'a> { if results.len() != 1 { return err!(self.loc, "wrong number of aliases"); } - Ok(()) + let dest = self.match_value("expected value alias")?; + ctx.add_alias(results[0], dest, self.loc) } // Parse an instruction, append it to `ebb`. From e1d17b2acf4d275521b360e0d8509bd6e97a7927 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 17 Jan 2017 01:17:29 -0800 Subject: [PATCH 591/968] Make value aliases during references rewriting --- lib/cretonne/src/ir/dfg.rs | 13 +++++++++++++ lib/reader/src/parser.rs | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index f5d48ad81f..c944582031 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -234,6 +234,19 @@ impl DataFlowGraph { panic!("Cannot change direct value {} into an alias", dest); } } + + /// Create a new value alias. + /// + /// Note that this function should only be called by the parser. + pub fn make_value_alias(&mut self, src: Value) -> Value { + let ty = self.value_type(src); + + let data = ValueData::Alias { + ty: ty, + original: src, + }; + self.make_value(data) + } } /// Where did a value come from? diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 858049efc8..21de8bcda7 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -188,6 +188,24 @@ impl<'a> Context<'a> { // numbering. These references need to be rewritten after parsing is complete since forward // references are allowed. fn rewrite_references(&mut self) -> Result<()> { + for (&source_from, &(source_to, source_loc)) in &self.aliases { + let ir_to = match self.map.get_value(source_to) { + Some(v) => v, + None => { + return err!(source_loc, + "IR destination value alias not found for {}", + source_to); + } + }; + let dest_loc = self.map + .location(AnyEntity::from(ir_to)) + .expect(&*format!("Error in looking up location of IR destination value alias \ + for {}", + ir_to)); + let ir_from = self.function.dfg.make_value_alias(ir_to); + self.map.def_value(source_from, ir_from, &dest_loc)?; + } + for ebb in self.function.layout.ebbs() { for inst in self.function.layout.ebb_insts(ebb) { let loc = inst.into(); From c6f8bfff0b2c475631a608b691eaf308f7d8a3c5 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 14 Mar 2017 10:25:30 -0700 Subject: [PATCH 592/968] Allow type inference to go through value aliasing --- lib/reader/src/parser.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 21de8bcda7..9f29e54090 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1299,13 +1299,20 @@ impl<'a> Parser<'a> { let ctrl_src_value = inst_data.typevar_operand(&ctx.function.dfg.value_lists) .expect("Constraints <-> Format inconsistency"); ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { - Some(v) => v, - None => { - return err!(self.loc, - "cannot determine type of operand {}", - ctrl_src_value); - } - }) + Some(v) => v, + None => { + if let Some(v) = ctx.aliases + .get(&ctrl_src_value) + .and_then(|&(aliased, _)| ctx.map.get_value(aliased)) + { + v + } else { + return err!(self.loc, + "cannot determine type of operand {}", + ctrl_src_value); + } + } + }) } else if constraints.is_polymorphic() { // This opcode does not support type inference, so the explicit type variable // is required. From c190884bca8d9f2fd3818a71c4efc29257a845c7 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 14 Mar 2017 11:17:20 -0700 Subject: [PATCH 593/968] Add unit test for value aliasing --- lib/reader/src/parser.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 9f29e54090..414dd291e2 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1638,6 +1638,25 @@ mod tests { assert_eq!(message, "expected argument type"); } + #[test] + fn aliases() { + let (func, details) = Parser::new("function qux() { + ebb0: + v4 = iconst.i8 6 + vx3 -> v4 + v1 = iadd_imm vx3, 17 + }") + .parse_function(None) + .unwrap(); + assert_eq!(func.name.to_string(), "qux"); + let v4 = details.map.lookup_str("v4").unwrap(); + assert_eq!(v4.to_string(), "v0"); + let vx3 = details.map.lookup_str("vx3").unwrap(); + assert_eq!(vx3.to_string(), "vx0"); + let aliased_to = func.dfg.resolve_aliases(Value::table_with_number(0).unwrap()); + assert_eq!(aliased_to.to_string(), "v0"); + } + #[test] fn signature() { let sig = Parser::new("()").parse_signature(None).unwrap(); From b4b913cc543aa601bf6b62bd423eda06e84c5fe8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Mar 2017 13:17:21 -0700 Subject: [PATCH 594/968] Add OpcodeConstraints::fixed_value_arguments() Now that some instruction formats put all of their value arguments in a value list, we need to know how many value are fixed and how many are variable_args. CC @angusholder who may need this information in the verifier. --- lib/cretonne/meta/gen_instr.py | 9 ++++++--- lib/cretonne/src/ir/instructions.rs | 30 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index e6eaa82c32..02677bfdbd 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -439,6 +439,7 @@ def gen_type_constraints(fmt, instrs): get_constraint(i.ins[opnum], ctrl_typevar, type_sets)) offset = operand_seqs.add(constraints) fixed_results = len(i.value_results) + fixed_values = len(i.value_opnums) # Can the controlling type variable be inferred from the designated # operand? use_typevar_operand = i.is_polymorphic and i.use_typevar_operand @@ -449,10 +450,10 @@ def gen_type_constraints(fmt, instrs): # result? requires_typevar_operand = use_typevar_operand and not use_result fmt.comment( - ('{}: fixed_results={}, use_typevar_operand={}, ' + - 'requires_typevar_operand={}') + '{}: fixed_results={}, use_typevar_operand={}, ' + 'requires_typevar_operand={}, fixed_values={}' .format(i.camel_name, fixed_results, use_typevar_operand, - requires_typevar_operand)) + requires_typevar_operand, fixed_values)) fmt.comment('Constraints={}'.format(constraints)) if i.is_polymorphic: fmt.comment( @@ -464,6 +465,8 @@ def gen_type_constraints(fmt, instrs): flags |= 8 if requires_typevar_operand: flags |= 0x10 + assert fixed_values < 8, "Bit field encoding too tight" + flags |= fixed_values << 5 with fmt.indented('OpcodeConstraints {', '},'): fmt.line('flags: {:#04x},'.format(flags)) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index de34a708c9..7e970f73b8 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -387,6 +387,9 @@ pub struct OpcodeConstraints { /// Bit 4: /// This opcode is polymorphic and the controlling type variable does *not* appear as the /// first result type. + /// + /// Bits 5-7: + /// Number of fixed value arguments. The minimum required number of value operands. flags: u8, /// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. @@ -423,6 +426,17 @@ impl OpcodeConstraints { (self.flags & 0x7) as usize } + /// Get the number of *fixed* input values required by this opcode. + /// + /// This does not include `variable_args` arguments on call and branch instructions. + /// + /// The number of fixed input values is usually implied by the instruction format, but + /// instruction formats that use a `ValueList` put both fixed and variable arguments in the + /// list. This method returns the *minimum* number of values required in the value list. + pub fn fixed_value_arguments(self) -> usize { + ((self.flags >> 5) & 0x7) as usize + } + /// Get the offset into `TYPE_SETS` for the controlling type variable. /// Returns `None` if the instruction is not polymorphic. fn typeset_offset(self) -> Option { @@ -609,6 +623,22 @@ mod tests { assert_eq!(mem::size_of::(), 16); } + #[test] + fn constraints() { + let a = Opcode::Iadd.constraints(); + assert!(a.use_typevar_operand()); + assert_eq!(a.fixed_results(), 1); + assert_eq!(a.fixed_value_arguments(), 2); + + let c = Opcode::Call.constraints(); + assert_eq!(c.fixed_results(), 0); + assert_eq!(c.fixed_value_arguments(), 0); + + let i = Opcode::CallIndirect.constraints(); + assert_eq!(i.fixed_results(), 0); + assert_eq!(i.fixed_value_arguments(), 1); + } + #[test] fn value_set() { use ir::types::*; From d86854b286b442e008a7bbe62a43f637dd797655 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 14 Mar 2017 13:31:01 -0700 Subject: [PATCH 595/968] Fix logic bug in requires_typevar_operand. The Python code computing this property had a bug. The property has only been used for optimizing the ctrl_typevar() method so far. --- lib/cretonne/meta/gen_instr.py | 2 +- lib/cretonne/src/ir/instructions.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 02677bfdbd..f3c36a716f 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -445,7 +445,7 @@ def gen_type_constraints(fmt, instrs): use_typevar_operand = i.is_polymorphic and i.use_typevar_operand # Can the controlling type variable be inferred from the result? use_result = (fixed_results > 0 and - i.outs[i.value_results[0]].typevar != ctrl_typevar) + i.outs[i.value_results[0]].typevar == ctrl_typevar) # Are we required to use the designated operand instead of the # result? requires_typevar_operand = use_typevar_operand and not use_result diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 7e970f73b8..c51afcc118 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -627,9 +627,16 @@ mod tests { fn constraints() { let a = Opcode::Iadd.constraints(); assert!(a.use_typevar_operand()); + assert!(!a.requires_typevar_operand()); assert_eq!(a.fixed_results(), 1); assert_eq!(a.fixed_value_arguments(), 2); + let b = Opcode::Bitcast.constraints(); + assert!(!b.use_typevar_operand()); + assert!(!b.requires_typevar_operand()); + assert_eq!(b.fixed_results(), 1); + assert_eq!(b.fixed_value_arguments(), 1); + let c = Opcode::Call.constraints(); assert_eq!(c.fixed_results(), 0); assert_eq!(c.fixed_value_arguments(), 0); @@ -637,6 +644,12 @@ mod tests { let i = Opcode::CallIndirect.constraints(); assert_eq!(i.fixed_results(), 0); assert_eq!(i.fixed_value_arguments(), 1); + + let cmp = Opcode::Icmp.constraints(); + assert!(cmp.use_typevar_operand()); + assert!(cmp.requires_typevar_operand()); + assert_eq!(cmp.fixed_results(), 1); + assert_eq!(cmp.fixed_value_arguments(), 2); } #[test] From 210530da9c31f3014bf492dbe775ad7fa302a8b2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Mar 2017 11:04:38 -0700 Subject: [PATCH 596/968] Add a primitive debug tracing facility. When the CRETONNE_DBG environment variable is set, send debug messages to a file named cretonne.dbg.*. The trace facility is only enabled when debug assertions are on. --- .gitignore | 1 + lib/cretonne/src/dbg.rs | 100 +++++++++++++++++++++++++++++++++++++ lib/cretonne/src/lib.rs | 3 ++ src/cton-util.rs | 2 +- src/filetest/concurrent.rs | 4 ++ src/filetest/runone.rs | 2 + 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 lib/cretonne/src/dbg.rs diff --git a/.gitignore b/.gitignore index 765772864e..5f3b8f2899 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ tags target Cargo.lock .*.rustfmt +cretonne.dbg* diff --git a/lib/cretonne/src/dbg.rs b/lib/cretonne/src/dbg.rs new file mode 100644 index 0000000000..06723fa018 --- /dev/null +++ b/lib/cretonne/src/dbg.rs @@ -0,0 +1,100 @@ +//! Debug tracing macros. +//! +//! This module defines the `dbg!` macro which works like `println!` except it writes to the +//! Cretonne tracing output file if enabled. +//! +//! Tracing can be enabled by setting the `CRETONNE_DBG` environment variable to something +/// other than `0`. +/// +/// The output will appear in files named `cretonne.dbg.*`, where the suffix is named after the +/// thread doing the logging. + +use std::ascii::AsciiExt; +use std::cell::RefCell; +use std::env; +use std::ffi::OsStr; +use std::fs::File; +use std::io::{Write, BufWriter}; +use std::sync::atomic; +use std::thread; + +static STATE: atomic::AtomicIsize = atomic::ATOMIC_ISIZE_INIT; + +/// Is debug tracing enabled? +/// +/// Debug tracing can be enabled by setting the `CRETONNE_DBG` environment variable to something +/// other than `0`. +/// +/// This inline function turns into a constant `false` when debug assertions are disabled. +#[inline] +pub fn enabled() -> bool { + if cfg!(debug_assertions) { + match STATE.load(atomic::Ordering::Relaxed) { + 0 => initialize(), + s => s > 0, + } + } else { + false + } +} + +/// Initialize `STATE` from the environment variable. +fn initialize() -> bool { + let enable = match env::var_os("CRETONNE_DBG") { + Some(s) => s != OsStr::new("0"), + None => false, + }; + + if enable { + STATE.store(1, atomic::Ordering::Relaxed); + } else { + STATE.store(-1, atomic::Ordering::Relaxed); + } + + enable +} + +thread_local! { + static WRITER : RefCell> = RefCell::new(open_file()); +} + +/// Execute a closure with mutable access to the tracing file writer. +/// +/// This is for use by the `dbg!` macro. +pub fn with_writer(f: F) -> R + where F: FnOnce(&mut Write) -> R +{ + WRITER.with(|rc| f(&mut *rc.borrow_mut())) +} + +/// Open the tracing file for the current thread. +fn open_file() -> BufWriter { + let file = match thread::current().name() { + None => File::create("cretonne.dbg"), + Some(name) => { + let mut path = "cretonne.dbg.".to_owned(); + for ch in name.chars() { + if ch.is_ascii() && ch.is_alphanumeric() { + path.push(ch); + } + } + File::create(path) + } + } + .expect("Can't open tracing file"); + BufWriter::new(file) +} + +/// Write a line to the debug trace file if tracing is enabled. +/// +/// Arguments are the same as for `printf!`. +#[macro_export] +macro_rules! dbg { + ($($arg:tt)+) => { + if $crate::dbg::enabled() { + // Drop the error result so we don't get compiler errors for ignoring it. + // What are you going to do, log the error? + $crate::dbg::with_writer(|w| { writeln!(w, $($arg)+).ok(); }) + } + } +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 2839ecfc1f..733506a605 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -21,6 +21,9 @@ pub mod settings; pub mod sparse_map; pub mod verifier; +#[macro_use] +pub mod dbg; + mod abi; mod constant_hash; mod context; diff --git a/src/cton-util.rs b/src/cton-util.rs index 89fee3c5a5..681ab5b8bc 100644 --- a/src/cton-util.rs +++ b/src/cton-util.rs @@ -1,4 +1,4 @@ - +#[macro_use(dbg)] extern crate cretonne; extern crate cton_reader; extern crate docopt; diff --git a/src/filetest/concurrent.rs b/src/filetest/concurrent.rs index aa0fba8f1d..effe454d38 100644 --- a/src/filetest/concurrent.rs +++ b/src/filetest/concurrent.rs @@ -137,6 +137,10 @@ fn worker_thread(thread_num: usize, } }); + if let &Err(ref msg) = &result { + dbg!("FAIL: {}", msg); + } + replies.send(Reply::Done { jobid: jobid, result: result, diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index 8e707d4ff5..ed4cae1ee7 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -17,6 +17,7 @@ use filetest::subtest::{SubTest, Context, Result}; /// /// If running this test causes a panic, it will propagate as normal. pub fn run(path: &Path) -> TestResult { + dbg!("---\nFile: {}", path.to_string_lossy()); let started = time::Instant::now(); let buffer = read_to_string(path).map_err(|e| e.to_string())?; let testfile = parse_test(&buffer).map_err(|e| e.to_string())?; @@ -108,6 +109,7 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), -> Result<()> { let (test, flags, isa) = tuple; let name = format!("{}({})", test.name(), func.name); + dbg!("Test: {} {}", name, isa.map(TargetIsa::name).unwrap_or("-")); context.flags = flags; context.isa = isa; From aa400d46ecde03ea1b4a5d65531ab178599e3ba1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Mar 2017 13:40:58 -0700 Subject: [PATCH 597/968] Add DataFlowGraph::display_inst(). This method returns an object that can display an instruction in the standard textual format, but without any encoding annotations. --- lib/cretonne/src/ir/dfg.rs | 37 ++++++++++++++++ lib/cretonne/src/write.rs | 86 +++++++++++++++++++------------------- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index c944582031..699f2a2f1a 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -8,7 +8,9 @@ use entity_map::{EntityMap, PrimaryEntityData}; use ir::builder::{InsertBuilder, ReplaceBuilder}; use ir::layout::Cursor; use packed_option::PackedOption; +use write::write_operands; +use std::fmt; use std::ops::{Index, IndexMut}; use std::u16; @@ -337,6 +339,11 @@ impl DataFlowGraph { self.insts.next_key() } + /// Returns an object that displays `inst`. + pub fn display_inst(&self, inst: Inst) -> DisplayInst { + DisplayInst(self, inst) + } + /// Create result values for an instruction that produces multiple results. /// /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If @@ -649,6 +656,34 @@ impl EbbData { } } +/// Object that can display an instruction. +pub struct DisplayInst<'a>(&'a DataFlowGraph, Inst); + +impl<'a> fmt::Display for DisplayInst<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let dfg = self.0; + let inst = &dfg[self.1]; + + let mut results = dfg.inst_results(self.1); + if let Some(first) = results.next() { + write!(f, "{}", first)?; + for v in results { + write!(f, ", {}", v)?; + } + write!(f, " = ")?; + } + + + let typevar = inst.ctrl_typevar(dfg); + if typevar.is_void() { + write!(f, "{}", inst.opcode())?; + } else { + write!(f, "{}.{}", inst.opcode(), typevar)?; + } + write_operands(f, dfg, self.1) + } +} + #[cfg(test)] mod tests { use super::*; @@ -665,6 +700,7 @@ mod tests { }; let inst = dfg.make_inst(idata); assert_eq!(inst.to_string(), "inst0"); + assert_eq!(dfg.display_inst(inst).to_string(), "v0 = iconst.i32"); // Immutable reference resolution. { @@ -692,6 +728,7 @@ mod tests { ty: types::VOID, }; let inst = dfg.make_inst(idata); + assert_eq!(dfg.display_inst(inst).to_string(), "trap"); // Result iterator should be empty. let mut res = dfg.inst_results(inst); diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 081da35617..dfa7688f20 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -4,7 +4,7 @@ //! equivalent textual representation. This textual representation can be read back by the //! `cretonne-reader` crate. -use ir::{Function, Ebb, Inst, Value, Type}; +use ir::{Function, DataFlowGraph, Ebb, Inst, Value, Type}; use isa::{TargetIsa, RegInfo}; use std::fmt::{self, Result, Error, Write}; use std::result; @@ -228,69 +228,69 @@ fn write_instruction(w: &mut Write, None => write!(w, "{}", opcode)?, } - // Then the operands, depending on format. - let pool = &func.dfg.value_lists; + write_operands(w, &func.dfg, inst)?; + writeln!(w, "") +} + +/// Write the operands of `inst` to `w` with a prepended space. +pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result { + let pool = &dfg.value_lists; use ir::instructions::InstructionData::*; - match func.dfg[inst] { - Nullary { .. } => writeln!(w, ""), - Unary { arg, .. } => writeln!(w, " {}", arg), - UnaryImm { imm, .. } => writeln!(w, " {}", imm), - UnaryIeee32 { imm, .. } => writeln!(w, " {}", imm), - UnaryIeee64 { imm, .. } => writeln!(w, " {}", imm), - UnarySplit { arg, .. } => writeln!(w, " {}", arg), - Binary { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), - BinaryImm { arg, imm, .. } => writeln!(w, " {}, {}", arg, imm), - BinaryOverflow { args, .. } => writeln!(w, " {}, {}", args[0], args[1]), - Ternary { args, .. } => writeln!(w, " {}, {}, {}", args[0], args[1], args[2]), + match dfg[inst] { + Nullary { .. } => write!(w, ""), + Unary { arg, .. } => write!(w, " {}", arg), + UnaryImm { imm, .. } => write!(w, " {}", imm), + UnaryIeee32 { imm, .. } => write!(w, " {}", imm), + UnaryIeee64 { imm, .. } => write!(w, " {}", imm), + UnarySplit { arg, .. } => write!(w, " {}", arg), + Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), + BinaryImm { arg, imm, .. } => write!(w, " {}, {}", arg, imm), + BinaryOverflow { args, .. } => write!(w, " {}, {}", args[0], args[1]), + Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), MultiAry { ref args, .. } => { if args.is_empty() { - writeln!(w, "") + write!(w, "") } else { - writeln!(w, - " {}", - DisplayValues(args.as_slice(&func.dfg.value_lists))) + write!(w, " {}", DisplayValues(args.as_slice(pool))) } } - InsertLane { lane, args, .. } => writeln!(w, " {}, {}, {}", args[0], lane, args[1]), - ExtractLane { lane, arg, .. } => writeln!(w, " {}, {}", arg, lane), - IntCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), - FloatCompare { cond, args, .. } => writeln!(w, " {}, {}, {}", cond, args[0], args[1]), + InsertLane { lane, args, .. } => write!(w, " {}, {}, {}", args[0], lane, args[1]), + ExtractLane { lane, arg, .. } => write!(w, " {}, {}", arg, lane), + IntCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), + FloatCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), Jump { destination, ref args, .. } => { if args.is_empty() { - writeln!(w, " {}", destination) + write!(w, " {}", destination) } else { - writeln!(w, - " {}({})", - destination, - DisplayValues(args.as_slice(pool))) + write!(w, + " {}({})", + destination, + DisplayValues(args.as_slice(pool))) } } Branch { destination, ref args, .. } => { let args = args.as_slice(pool); if args.len() == 1 { - writeln!(w, " {}, {}", args[0], destination) + write!(w, " {}, {}", args[0], destination) } else { - writeln!(w, - " {}, {}({})", - args[0], - destination, - DisplayValues(&args[1..])) + write!(w, + " {}, {}({})", + args[0], + destination, + DisplayValues(&args[1..])) } } - BranchTable { arg, table, .. } => writeln!(w, " {}, {}", arg, table), + BranchTable { arg, table, .. } => write!(w, " {}, {}", arg, table), Call { func_ref, ref args, .. } => { - writeln!(w, - " {}({})", - func_ref, - DisplayValues(args.as_slice(&func.dfg.value_lists))) + write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool))) } IndirectCall { sig_ref, ref args, .. } => { let args = args.as_slice(pool); - writeln!(w, - " {}, {}({})", - sig_ref, - args[0], - DisplayValues(&args[1..])) + write!(w, + " {}, {}({})", + sig_ref, + args[0], + DisplayValues(&args[1..])) } } } From 28153c97eaf9f32c163aac646ee256009f78db95 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 8 Mar 2017 17:33:20 -0800 Subject: [PATCH 598/968] Legalize ABI arguments to call and return instructions. The type signatures of functions can change when they are legalized for a specific ABI. This means that all call and return instructions need to be rewritten to use the correct arguments. - Fix arguments to call instructions. - Fix arguments to return instructions. TBD: - Fix return values from call instructions. --- filetests/isa/riscv/abi.cton | 98 +++++++------ lib/cretonne/src/legalizer.rs | 249 +++++++++++++++++++++++++++++++++- 2 files changed, 302 insertions(+), 45 deletions(-) diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index 0b47fe6727..50af69638f 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -7,27 +7,26 @@ isa riscv function f(i32) { sig0 = signature(i32) -> i32 - -; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] sig1 = signature(i64) -> b1 -; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + ; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] -; The i64 argument must go in an even-odd register pair. + ; The i64 argument must go in an even-odd register pair. sig2 = signature(f32, i64) -> f64 -; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + ; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] -; Spilling into the stack args. + ; Spilling into the stack args. sig3 = signature(f64, f64, f64, f64, f64, f64, f64, i64) -> f64 -; check: sig3 = signature(f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] + ; check: sig3 = signature(f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] -; Splitting vectors. + ; Splitting vectors. sig4 = signature(i32x4) -; check: sig4 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) + ; check: sig4 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) -; Splitting vectors, then splitting ints. + ; Splitting vectors, then splitting ints. sig5 = signature(i64x4) -; check: sig5 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) + ; check: sig5 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) ebb0(v0: i32): return_reg v0 @@ -35,62 +34,75 @@ ebb0(v0: i32): function int_split_args(i64) -> i64 { ebb0(v0: i64): -; check: $ebb0($(v0l=$VX): i32, $(v0h=$VX): i32): -; check: iconcat_lohi $v0l, $v0h + ; check: $ebb0($(v0l=$VX): i32, $(v0h=$VX): i32): + ; check: iconcat_lohi $v0l, $v0h v1 = iadd_imm v0, 1 - return v0 + ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 + ; check: return $v1l, $v1h + return v1 } -function int_ext(i8, i8 sext, i8 uext) -> i8 { +function int_ext(i8, i8 sext, i8 uext) -> i8 uext { ebb0(v1: i8, v2: i8, v3: i8): -; check: $ebb0($v1: i8, $(v2x=$VX): i32, $(v3x=$VX): i32): -; check: ireduce.i8 $v2x -; check: ireduce.i8 $v3x + ; check: $ebb0($v1: i8, $(v2x=$VX): i32, $(v3x=$VX): i32): + ; check: ireduce.i8 $v2x + ; check: ireduce.i8 $v3x + ; check: $(v1x=$V) = uextend.i32 $v1 + ; check: return $v1x + return v1 } -function vector_split_args(i64x4) -> i64 { +function vector_split_args(i64x4) -> i64x4 { ebb0(v0: i64x4): -; check: $ebb0($(v0al=$VX): i32, $(v0ah=$VX): i32, $(v0bl=$VX): i32, $(v0bh=$VX): i32, $(v0cl=$VX): i32, $(v0ch=$VX): i32, $(v0dl=$VX): i32, $(v0dh=$VX): i32): -; check: $(v0a=$V) = iconcat_lohi $v0al, $v0ah -; check: $(v0b=$V) = iconcat_lohi $v0bl, $v0bh -; check: $(v0ab=$V) = vconcat $v0a, $v0b -; check: $(v0c=$V) = iconcat_lohi $v0cl, $v0ch -; check: $(v0d=$V) = iconcat_lohi $v0dl, $v0dh -; check: $(v0cd=$V) = vconcat $v0c, $v0d -; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd + ; check: $ebb0($(v0al=$VX): i32, $(v0ah=$VX): i32, $(v0bl=$VX): i32, $(v0bh=$VX): i32, $(v0cl=$VX): i32, $(v0ch=$VX): i32, $(v0dl=$VX): i32, $(v0dh=$VX): i32): + ; check: $(v0a=$V) = iconcat_lohi $v0al, $v0ah + ; check: $(v0b=$V) = iconcat_lohi $v0bl, $v0bh + ; check: $(v0ab=$V) = vconcat $v0a, $v0b + ; check: $(v0c=$V) = iconcat_lohi $v0cl, $v0ch + ; check: $(v0d=$V) = iconcat_lohi $v0dl, $v0dh + ; check: $(v0cd=$V) = vconcat $v0c, $v0d + ; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd v1 = iadd v0, v0 - return v0 + ; check: $(v1ab=$V), $(v1cd=$VX) = vsplit + ; check: $(v1a=$V), $(v1b=$VX) = vsplit $v1ab + ; check: $(v1al=$V), $(v1ah=$VX) = isplit_lohi $v1a + ; check: $(v1bl=$V), $(v1bh=$VX) = isplit_lohi $v1b + ; check: $(v1c=$V), $(v1d=$VX) = vsplit $v1cd + ; check: $(v1cl=$V), $(v1ch=$VX) = isplit_lohi $v1c + ; check: $(v1dl=$V), $(v1dh=$VX) = isplit_lohi $v1d + ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh + return v1 } function parse_encoding(i32 [%x5]) -> i32 [%x10] { -; check: function parse_encoding(i32 [%x5]) -> i32 [%x10] { + ; check: function parse_encoding(i32 [%x5]) -> i32 [%x10] { sig0 = signature(i32 [%x10]) -> i32 [%x10] -; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] -; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + ; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] -; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + ; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] -; Arguments on stack where not necessary + ; Arguments on stack where not necessary sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] -; check: sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] + ; check: sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] -; Stack argument before register argument + ; Stack argument before register argument sig4 = signature(f32 [72], i32 [%x10]) -; check: sig4 = signature(f32 [72], i32 [%x10]) + ; check: sig4 = signature(f32 [72], i32 [%x10]) -; Return value on stack + ; Return value on stack sig5 = signature() -> f32 [0] -; check: sig5 = signature() -> f32 [0] + ; check: sig5 = signature() -> f32 [0] -; function + signature + ; function + signature fn15 = function bar(i32 [%x10]) -> b1 [%x10] -; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] -; nextln: fn0 = sig6 bar + ; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] + ; nextln: fn0 = sig6 bar ebb0(v0: i32): - return_reg v0 -} \ No newline at end of file + return v0 +} diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 9223a174b0..e22cb6c95e 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -14,9 +14,10 @@ //! from the encoding recipes, and solved later by the register allocator. use abi::{legalize_abi_value, ValueConversion}; -use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder, Ebb, Type, Value, - ArgumentType}; +use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, Inst, InstBuilder, Ebb, Type, + Value, Signature, SigRef, ArgumentType}; use ir::condcodes::IntCC; +use ir::instructions::CallInfo; use isa::{TargetIsa, Legalize}; /// Legalize `func` for `isa`. @@ -36,6 +37,21 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { let mut prev_pos = pos.position(); while let Some(inst) = pos.next_inst() { + let opcode = func.dfg[inst].opcode(); + + // Check for ABI boundaries that need to be converted to the legalized signature. + if opcode.is_call() && handle_call_abi(&mut func.dfg, &mut pos) { + // Go back and legalize the inserted argument conversion instructions. + pos.set_position(prev_pos); + continue; + } + + if opcode.is_return() && handle_return_abi(&mut func.dfg, &mut pos, &func.signature) { + // Go back and legalize the inserted return value conversion instructions. + pos.set_position(prev_pos); + continue; + } + match isa.encode(&func.dfg, &func.dfg[inst]) { Ok(encoding) => *func.encodings.ensure(inst) = encoding, Err(action) => { @@ -201,3 +217,232 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, } } } + +/// Convert `value` to match an ABI signature by inserting instructions at `pos`. +/// +/// This may require expanding the value to multiple ABI arguments. The conversion process is +/// recursive and controlled by the `put_arg` closure. When a candidate argument value is presented +/// to the closure, it will perform one of two actions: +/// +/// 1. If the suggested argument has an acceptable value type, consume it by adding it to the list +/// of arguments and return `None`. +/// 2. If the suggested argument doesn't have the right value type, don't change anything, but +/// return the `ArgumentType` that is needed. +/// +fn convert_to_abi(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + value: Value, + put_arg: &mut PutArg) + where PutArg: FnMut(&mut DataFlowGraph, Value) -> Option +{ + // Start by invoking the closure to either terminate the recursion or get the argument type + // we're trying to match. + let arg_type = match put_arg(dfg, value) { + None => return, + Some(t) => t, + }; + + let ty = dfg.value_type(value); + match legalize_abi_value(ty, &arg_type) { + ValueConversion::IntSplit => { + let (lo, hi) = dfg.ins(pos).isplit_lohi(value); + convert_to_abi(dfg, pos, lo, put_arg); + convert_to_abi(dfg, pos, hi, put_arg); + } + ValueConversion::VectorSplit => { + let (lo, hi) = dfg.ins(pos).vsplit(value); + convert_to_abi(dfg, pos, lo, put_arg); + convert_to_abi(dfg, pos, hi, put_arg); + } + ValueConversion::IntBits => { + assert!(!ty.is_int()); + let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); + let arg = dfg.ins(pos).bitcast(abi_ty, value); + convert_to_abi(dfg, pos, arg, put_arg); + } + ValueConversion::Sext(abi_ty) => { + let arg = dfg.ins(pos).sextend(abi_ty, value); + convert_to_abi(dfg, pos, arg, put_arg); + } + ValueConversion::Uext(abi_ty) => { + let arg = dfg.ins(pos).uextend(abi_ty, value); + convert_to_abi(dfg, pos, arg, put_arg); + } + } +} + +/// Check if a sequence of arguments match a desired sequence of argument types. +fn check_arg_types(dfg: &DataFlowGraph, args: Args, types: &[ArgumentType]) -> bool + where Args: IntoIterator +{ + let mut n = 0; + for arg in args { + match types.get(n) { + Some(&ArgumentType { value_type, .. }) => { + if dfg.value_type(arg) != value_type { + return false; + } + } + None => return false, + } + n += 1 + } + + // Also verify that the number of arguments matches. + n == types.len() +} + +/// Check if the arguments of the call `inst` match the signature. +/// +/// Returns `None` if the signature matches and no changes are needed, or `Some(sig_ref)` if the +/// signature doesn't match. +fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Option { + // Extract the signature and argument values. + let (sig_ref, args) = match dfg[inst].analyze_call(&dfg.value_lists) { + CallInfo::Direct(func, args) => (dfg.ext_funcs[func].signature, args), + CallInfo::Indirect(sig_ref, args) => (sig_ref, args), + CallInfo::NotACall => panic!("Expected call, got {:?}", dfg[inst]), + }; + let sig = &dfg.signatures[sig_ref]; + + if check_arg_types(dfg, args.iter().cloned(), &sig.argument_types[..]) && + check_arg_types(dfg, dfg.inst_results(inst), &sig.return_types[..]) { + // All types check out. + None + } else { + // Call types need fixing. + Some(sig_ref) + } +} + +/// Insert ABI conversion code for the arguments to the call or return instruction at `pos`. +/// +/// - `abi_args` is the number of arguments that the ABI signature requires. +/// - `get_abi_type` is a closure that can provide the desired `ArgumentType` for a given ABI +/// argument number in `0..abi_args`. +/// +fn legalize_inst_arguments(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + abi_args: usize, + mut get_abi_type: ArgType) + where ArgType: FnMut(&DataFlowGraph, usize) -> ArgumentType +{ + let inst = pos.current_inst().expect("Cursor must point to a call instruction"); + + // Lift the value list out of the call instruction so we modify it. + let mut vlist = dfg[inst].take_value_list().expect("Call must have a value list"); + + // The value list contains all arguments to the instruction, including the callee on an + // indirect call which isn't part of the call arguments that must match the ABI signature. + // Figure out how many fixed values are at the front of the list. We won't touch those. + let fixed_values = dfg[inst].opcode().constraints().fixed_value_arguments(); + let have_args = vlist.len(&dfg.value_lists) - fixed_values; + + // Grow the value list to the right size and shift all the existing arguments to the right. + // This lets us write the new argument values into the list without overwriting the old + // arguments. + // + // Before: + // + // <--> fixed_values + // <-----------> have_args + // [FFFFOOOOOOOOOOOOO] + // + // After grow_at(): + // + // <--> fixed_values + // <-----------> have_args + // <------------------> abi_args + // [FFFF-------OOOOOOOOOOOOO] + // ^ + // old_arg_offset + // + // After writing the new arguments: + // + // <--> fixed_values + // <------------------> abi_args + // [FFFFNNNNNNNNNNNNNNNNNNNN] + // + vlist.grow_at(fixed_values, abi_args - have_args, &mut dfg.value_lists); + let old_arg_offset = fixed_values + abi_args - have_args; + + let mut abi_arg = 0; + for old_arg in 0..have_args { + let old_value = vlist.get(old_arg_offset + old_arg, &dfg.value_lists).unwrap(); + convert_to_abi(dfg, + pos, + old_value, + &mut |dfg, arg| { + let abi_type = get_abi_type(dfg, abi_arg); + if dfg.value_type(arg) == abi_type.value_type { + // This is the argument type we need. + vlist.as_mut_slice(&mut dfg.value_lists)[fixed_values + abi_arg] = arg; + abi_arg += 1; + None + } else { + // Nope, `arg` needs to be converted. + Some(abi_type) + } + }); + } + + // Put the modified value list back. + dfg[inst].put_value_list(vlist); +} + +/// Insert ABI conversion code before and after the call instruction at `pos`. +/// +/// Instructions inserted before the call will compute the appropriate ABI values for the +/// callee's new ABI-legalized signature. The function call arguments are rewritten in place to +/// match the new signature. +/// +/// Instructions will be inserted after the call to convert returned ABI values back to the +/// original return values. The call's result values will be adapted to match the new signature. +/// +/// Returns `true` if any instructions were inserted. +fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { + let inst = pos.current_inst().expect("Cursor must point to a call instruction"); + + // Start by checking if the argument types already match the signature. + let sig_ref = match check_call_signature(dfg, inst) { + None => return false, + Some(s) => s, + }; + + // OK, we need to fix the call arguments to match the ABI signature. + let abi_args = dfg.signatures[sig_ref].argument_types.len(); + legalize_inst_arguments(dfg, + pos, + abi_args, + |dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]); + + // TODO: Convert return values. + + // Yes, we changed stuff. + true +} + +/// Insert ABI conversion code before and after the call instruction at `pos`. +/// +/// Return `true` if any instructions were inserted. +fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signature) -> bool { + let inst = pos.current_inst().expect("Cursor must point to a return instruction"); + + // Check if the returned types already match the signature. + let fixed_values = dfg[inst].opcode().constraints().fixed_value_arguments(); + if check_arg_types(dfg, + dfg[inst] + .arguments(&dfg.value_lists) + .iter() + .skip(fixed_values) + .cloned(), + &sig.return_types[..]) { + return false; + } + + let abi_args = sig.return_types.len(); + legalize_inst_arguments(dfg, pos, abi_args, |_, abi_arg| sig.return_types[abi_arg]); + + // Yes, we changed stuff. + true +} From 765c866971926a78d917d3fadcb79b9f21913761 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Mar 2017 15:21:57 -0700 Subject: [PATCH 599/968] Rename take_ebb_args to detach_ebb_args() This better matches the detach_secondary_results cousin. Also rename the converse put_ebb_arg -> attach_ebb_arg. --- lib/cretonne/src/ir/dfg.rs | 18 +++++++++--------- lib/cretonne/src/legalizer.rs | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 699f2a2f1a..1b1ff418e8 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -549,7 +549,7 @@ impl DataFlowGraph { num: 0, next: None.into(), }); - self.put_ebb_arg(ebb, val); + self.attach_ebb_arg(ebb, val); val } @@ -578,9 +578,9 @@ impl DataFlowGraph { /// processing the list of arguments. /// /// This is a quite low-level operation. Sensible things to do with the detached EBB arguments - /// is to put them back on the same EBB with `put_ebb_arg()` or change them into aliases + /// is to put them back on the same EBB with `attach_ebb_arg()` or change them into aliases /// with `change_to_alias()`. - pub fn take_ebb_args(&mut self, ebb: Ebb) -> Option { + pub fn detach_ebb_args(&mut self, ebb: Ebb) -> Option { let first = self.ebbs[ebb].first_arg.into(); self.ebbs[ebb].first_arg = None.into(); self.ebbs[ebb].last_arg = None.into(); @@ -591,10 +591,10 @@ impl DataFlowGraph { /// /// The appended value should already be an EBB argument belonging to `ebb`, but it can't be /// attached. In practice, this means that it should be one of the values returned from - /// `take_ebb_args()`. + /// `detach_ebb_args()`. /// /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. - pub fn put_ebb_arg(&mut self, ebb: Ebb, arg: Value) { + pub fn attach_ebb_arg(&mut self, ebb: Ebb, arg: Value) { let arg_num = match self.ebbs[ebb].last_arg.map(|v| v.expand()) { // If last_argument is `None`, we're adding the first EBB argument. None => { @@ -743,7 +743,7 @@ mod tests { assert_eq!(ebb.to_string(), "ebb0"); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb).next(), None); - assert_eq!(dfg.take_ebb_args(ebb), None); + assert_eq!(dfg.detach_ebb_args(ebb), None); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb).next(), None); @@ -771,16 +771,16 @@ mod tests { assert_eq!(dfg.value_type(arg2), types::I16); // Swap the two EBB arguments. - let take1 = dfg.take_ebb_args(ebb).unwrap(); + let take1 = dfg.detach_ebb_args(ebb).unwrap(); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb).next(), None); let take2 = dfg.next_ebb_arg(take1).unwrap(); assert_eq!(take1, arg1); assert_eq!(take2, arg2); assert_eq!(dfg.next_ebb_arg(take2), None); - dfg.put_ebb_arg(ebb, take2); + dfg.attach_ebb_arg(ebb, take2); let arg3 = dfg.append_ebb_arg(ebb, types::I32); - dfg.put_ebb_arg(ebb, take1); + dfg.attach_ebb_arg(ebb, take1); assert_eq!(dfg.ebb_args(ebb).collect::>(), [take2, arg3, take1]); } diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index e22cb6c95e..d6894321fe 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -133,7 +133,7 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { // Process the EBB arguments one at a time, possibly replacing one argument with multiple new // ones. We do this by detaching the entry EBB arguments first. - let mut next_arg = func.dfg.take_ebb_args(entry); + let mut next_arg = func.dfg.detach_ebb_args(entry); while let Some(arg) = next_arg { // Get the next argument before we mutate `arg`. next_arg = func.dfg.next_ebb_arg(arg); @@ -142,7 +142,7 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { if arg_type == abi_types[abi_arg].value_type { // No value translation is necessary, this argument matches the ABI type. // Just use the original EBB argument value. This is the most common case. - func.dfg.put_ebb_arg(entry, arg); + func.dfg.attach_ebb_arg(entry, arg); abi_arg += 1; } else { // Compute the value we want for `arg` from the legalized ABI arguments. From 89f45a5c824c4d08bd66f68baf7f670ca8448bed Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Mar 2017 15:38:47 -0700 Subject: [PATCH 600/968] Don't return a Values iterator from detach_secondary_results(). Instead, just return the first of the detached values, and provide a next_secondary_result() method for traversing the list. This is equivalent to how detach_ebb_args() works, and it allows the data flow graph to be modified while traversing the list of results. --- lib/cretonne/meta/gen_legalizer.py | 15 +++++++++++---- lib/cretonne/src/ir/dfg.rs | 31 +++++++++++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 85aa715996..95d16d3663 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -90,10 +90,17 @@ def unwrap_inst(iref, node, fmt): for d in node.defs[1:]: fmt.line('let src_{};'.format(d)) with fmt.indented('{', '}'): - fmt.line('let mut vals = dfg.detach_secondary_results(inst);') - for d in node.defs[1:]: - fmt.line('src_{} = vals.next().unwrap();'.format(d)) - fmt.line('assert_eq!(vals.next(), None);') + fmt.line( + 'src_{} = dfg.detach_secondary_results(inst).unwrap();' + .format(node.defs[1])) + for i in range(2, len(node.defs)): + fmt.line( + 'src_{} = dfg.next_secondary_result(src_{})' + '.unwrap();' + .format(node.defs[i], node.defs[i - 1])) + fmt.line( + 'assert_eq!(dfg.next_secondary_result(src_{}), None);' + .format(node.defs[-1])) for d in node.defs[1:]: if d.has_free_typevar(): fmt.line( diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 1b1ff418e8..5a70c92608 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -430,19 +430,29 @@ impl DataFlowGraph { ReplaceBuilder::new(self, inst) } - /// Detach secondary instruction results, and return them as an iterator. + /// Detach secondary instruction results, and return the first of them. /// - /// If `inst` produces two or more results, detach these secondary result values from `inst`, - /// and return an iterator that will enumerate them. The first result value cannot be detached. + /// If `inst` produces two or more results, detach these secondary result values from `inst`. + /// The first result value cannot be detached. + /// The full list of secondary results can be traversed with `next_secondary_result()`. /// /// Use this method to detach secondary values before using `replace(inst)` to provide an /// alternate instruction for computing the primary result value. - pub fn detach_secondary_results(&mut self, inst: Inst) -> Values { - let second_result = self[inst].second_result_mut().and_then(|r| r.take()); - Values { - dfg: self, - cur: second_result, + pub fn detach_secondary_results(&mut self, inst: Inst) -> Option { + self[inst].second_result_mut().and_then(|r| r.take()) + } + + /// Get the next secondary result after `value`. + /// + /// Use this function to traverse the full list of instruction results returned from + /// `detach_secondary_results()`. + pub fn next_secondary_result(&self, value: Value) -> Option { + if let ExpandedValue::Table(index) = value.expand() { + if let ValueData::Inst { next, .. } = self.extended_values[index] { + return next.into(); + } } + panic!("{} is not a secondary result value", value); } /// Get the first result of an instruction. @@ -811,9 +821,8 @@ mod tests { // Detach the 'c' value from `iadd`. { - let mut vals = dfg.detach_secondary_results(iadd); - assert_eq!(vals.next(), Some(c)); - assert_eq!(vals.next(), None); + assert_eq!(dfg.detach_secondary_results(iadd), Some(c)); + assert_eq!(dfg.next_secondary_result(c), None); } // Replace `iadd_cout` with a normal `iadd` and an `icmp`. From 578fec90cd122d9626f507619a586490bdb7ae90 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 15 Mar 2017 17:04:58 -0700 Subject: [PATCH 601/968] Add DataFlowGraph::redefine_first_value() This makes it possible to compute the first result of an instruction in a different way without overwriting the original instruction with replace(). --- lib/cretonne/src/ir/dfg.rs | 49 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 5a70c92608..a1eb864af2 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -5,7 +5,7 @@ use ir::entities::ExpandedValue; use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; -use ir::builder::{InsertBuilder, ReplaceBuilder}; +use ir::builder::{InsertBuilder, ReplaceBuilder, InstBuilder}; use ir::layout::Cursor; use packed_option::PackedOption; use write::write_operands; @@ -455,6 +455,41 @@ impl DataFlowGraph { panic!("{} is not a secondary result value", value); } + /// Move the instruction at `pos` to a new `Inst` reference so its first result can be + /// redefined without overwriting the original instruction. + /// + /// The first result value of an instruction is intrinsically tied to the `Inst` reference, so + /// it is not possible to detach the value and attach it to something else. This function + /// copies the instruction pointed to by `pos` to a new `Inst` reference, making the original + /// `Inst` reference available to be redefined with `dfg.replace(inst)` above. + /// + /// Before: + /// + /// inst1: v1, vx2 = foo <-- pos + /// + /// After: + /// + /// inst7: v7, vx2 = foo + /// inst1: v1 = copy v7 <-- pos + /// + /// Returns the new `Inst` reference where the original instruction has been moved. + pub fn redefine_first_value(&mut self, pos: &mut Cursor) -> Inst { + let orig = pos.current_inst().expect("Cursor must point at an instruction"); + let data = self[orig].clone(); + // After cloning, any secondary values are attached to both copies. Don't do that, we only + // want them on the new clone. + self.detach_secondary_results(orig); + let new = self.make_inst(data); + pos.insert_inst(new); + // Replace the original instruction with a copy of the new value. + // This is likely to be immediately overwritten by something else, but this way we avoid + // leaving the DFG in a state with multiple references to secondary results and value + // lists. It also means that this method doesn't change the semantics of the program. + let new_value = self.first_result(new); + self.replace(orig).copy(new_value); + new + } + /// Get the first result of an instruction. /// /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. @@ -819,9 +854,19 @@ mod tests { _ => panic!(), }; + // Redefine the first value out of `iadd_cout`. + assert_eq!(pos.prev_inst(), Some(iadd)); + let new_iadd = dfg.redefine_first_value(pos); + let new_s = dfg.first_result(new_iadd); + assert_eq!(dfg[iadd].opcode(), Opcode::Copy); + assert_eq!(dfg.inst_results(iadd).collect::>(), [s]); + assert_eq!(dfg.inst_results(new_iadd).collect::>(), [new_s, c]); + assert_eq!(dfg.resolve_copies(s), new_s); + pos.next_inst(); + // Detach the 'c' value from `iadd`. { - assert_eq!(dfg.detach_secondary_results(iadd), Some(c)); + assert_eq!(dfg.detach_secondary_results(new_iadd), Some(c)); assert_eq!(dfg.next_secondary_result(c), None); } From e4a83c8063fa4699a5fbfd66f7b2cc40073cbb2f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Mar 2017 10:05:56 -0700 Subject: [PATCH 602/968] Break out differently purposed tests from abi.cton. - abi.cton is for testing the actual RISC-V ABI. - legalize-abi.cton is for testing the legalizer around ABI boundaries. - parse-encoding.cton is for testing the parser's handling of RISC-V encoding and register annotations. --- filetests/isa/riscv/abi.cton | 75 ------------------------- filetests/isa/riscv/legalize-abi.cton | 47 ++++++++++++++++ filetests/isa/riscv/parse-encoding.cton | 36 ++++++++++++ 3 files changed, 83 insertions(+), 75 deletions(-) create mode 100644 filetests/isa/riscv/legalize-abi.cton create mode 100644 filetests/isa/riscv/parse-encoding.cton diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index 50af69638f..d75813e220 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -31,78 +31,3 @@ function f(i32) { ebb0(v0: i32): return_reg v0 } - -function int_split_args(i64) -> i64 { -ebb0(v0: i64): - ; check: $ebb0($(v0l=$VX): i32, $(v0h=$VX): i32): - ; check: iconcat_lohi $v0l, $v0h - v1 = iadd_imm v0, 1 - ; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi $v1 - ; check: return $v1l, $v1h - return v1 -} - -function int_ext(i8, i8 sext, i8 uext) -> i8 uext { -ebb0(v1: i8, v2: i8, v3: i8): - ; check: $ebb0($v1: i8, $(v2x=$VX): i32, $(v3x=$VX): i32): - ; check: ireduce.i8 $v2x - ; check: ireduce.i8 $v3x - ; check: $(v1x=$V) = uextend.i32 $v1 - ; check: return $v1x - return v1 -} - -function vector_split_args(i64x4) -> i64x4 { -ebb0(v0: i64x4): - ; check: $ebb0($(v0al=$VX): i32, $(v0ah=$VX): i32, $(v0bl=$VX): i32, $(v0bh=$VX): i32, $(v0cl=$VX): i32, $(v0ch=$VX): i32, $(v0dl=$VX): i32, $(v0dh=$VX): i32): - ; check: $(v0a=$V) = iconcat_lohi $v0al, $v0ah - ; check: $(v0b=$V) = iconcat_lohi $v0bl, $v0bh - ; check: $(v0ab=$V) = vconcat $v0a, $v0b - ; check: $(v0c=$V) = iconcat_lohi $v0cl, $v0ch - ; check: $(v0d=$V) = iconcat_lohi $v0dl, $v0dh - ; check: $(v0cd=$V) = vconcat $v0c, $v0d - ; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd - v1 = iadd v0, v0 - ; check: $(v1ab=$V), $(v1cd=$VX) = vsplit - ; check: $(v1a=$V), $(v1b=$VX) = vsplit $v1ab - ; check: $(v1al=$V), $(v1ah=$VX) = isplit_lohi $v1a - ; check: $(v1bl=$V), $(v1bh=$VX) = isplit_lohi $v1b - ; check: $(v1c=$V), $(v1d=$VX) = vsplit $v1cd - ; check: $(v1cl=$V), $(v1ch=$VX) = isplit_lohi $v1c - ; check: $(v1dl=$V), $(v1dh=$VX) = isplit_lohi $v1d - ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh - return v1 -} - -function parse_encoding(i32 [%x5]) -> i32 [%x10] { - ; check: function parse_encoding(i32 [%x5]) -> i32 [%x10] { - - sig0 = signature(i32 [%x10]) -> i32 [%x10] - ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] - - sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] - ; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] - - sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] - ; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] - - ; Arguments on stack where not necessary - sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] - ; check: sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] - - ; Stack argument before register argument - sig4 = signature(f32 [72], i32 [%x10]) - ; check: sig4 = signature(f32 [72], i32 [%x10]) - - ; Return value on stack - sig5 = signature() -> f32 [0] - ; check: sig5 = signature() -> f32 [0] - - ; function + signature - fn15 = function bar(i32 [%x10]) -> b1 [%x10] - ; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] - ; nextln: fn0 = sig6 bar - -ebb0(v0: i32): - return v0 -} diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton new file mode 100644 index 0000000000..bf93596128 --- /dev/null +++ b/filetests/isa/riscv/legalize-abi.cton @@ -0,0 +1,47 @@ +; Test legalizer's handling of ABI boundaries. +test legalizer +isa riscv + +; regex: V=vx?\d+ + +function int_split_args(i64) -> i64 { +ebb0(v0: i64): + ; check: $ebb0($(v0l=$V): i32, $(v0h=$V): i32): + ; check: iconcat_lohi $v0l, $v0h + v1 = iadd_imm v0, 1 + ; check: $(v1l=$V), $(v1h=$V) = isplit_lohi $v1 + ; check: return $v1l, $v1h + return v1 +} + +function int_ext(i8, i8 sext, i8 uext) -> i8 uext { +ebb0(v1: i8, v2: i8, v3: i8): + ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32): + ; check: ireduce.i8 $v2x + ; check: ireduce.i8 $v3x + ; check: $(v1x=$V) = uextend.i32 $v1 + ; check: return $v1x + return v1 +} + +function vector_split_args(i64x4) -> i64x4 { +ebb0(v0: i64x4): + ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32): + ; check: $(v0a=$V) = iconcat_lohi $v0al, $v0ah + ; check: $(v0b=$V) = iconcat_lohi $v0bl, $v0bh + ; check: $(v0ab=$V) = vconcat $v0a, $v0b + ; check: $(v0c=$V) = iconcat_lohi $v0cl, $v0ch + ; check: $(v0d=$V) = iconcat_lohi $v0dl, $v0dh + ; check: $(v0cd=$V) = vconcat $v0c, $v0d + ; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd + v1 = iadd v0, v0 + ; check: $(v1ab=$V), $(v1cd=$V) = vsplit + ; check: $(v1a=$V), $(v1b=$V) = vsplit $v1ab + ; check: $(v1al=$V), $(v1ah=$V) = isplit_lohi $v1a + ; check: $(v1bl=$V), $(v1bh=$V) = isplit_lohi $v1b + ; check: $(v1c=$V), $(v1d=$V) = vsplit $v1cd + ; check: $(v1cl=$V), $(v1ch=$V) = isplit_lohi $v1c + ; check: $(v1dl=$V), $(v1dh=$V) = isplit_lohi $v1d + ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh + return v1 +} diff --git a/filetests/isa/riscv/parse-encoding.cton b/filetests/isa/riscv/parse-encoding.cton new file mode 100644 index 0000000000..d3cc6eee4b --- /dev/null +++ b/filetests/isa/riscv/parse-encoding.cton @@ -0,0 +1,36 @@ +; Test the parser's support for encoding annotations. +test legalizer +isa riscv + +function parse_encoding(i32 [%x5]) -> i32 [%x10] { + ; check: function parse_encoding(i32 [%x5]) -> i32 [%x10] { + + sig0 = signature(i32 [%x10]) -> i32 [%x10] + ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + + sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + ; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + + sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + ; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + + ; Arguments on stack where not necessary + sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] + ; check: sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] + + ; Stack argument before register argument + sig4 = signature(f32 [72], i32 [%x10]) + ; check: sig4 = signature(f32 [72], i32 [%x10]) + + ; Return value on stack + sig5 = signature() -> f32 [0] + ; check: sig5 = signature() -> f32 [0] + + ; function + signature + fn15 = function bar(i32 [%x10]) -> b1 [%x10] + ; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] + ; nextln: fn0 = sig6 bar + +ebb0(v0: i32): + return v0 +} From a36160aa20d9561ac2533787afaa5e031d6b5849 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 16 Mar 2017 14:42:41 -0700 Subject: [PATCH 603/968] Use a closure to control the convert_from_abi() function. This will be used for converting function return types soon, so generalize it a bit. --- lib/cretonne/src/legalizer.rs | 87 +++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index d6894321fe..6814e3fe2a 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -146,12 +146,16 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { abi_arg += 1; } else { // Compute the value we want for `arg` from the legalized ABI arguments. - let converted = convert_from_abi(&mut func.dfg, - &mut pos, - entry, - &mut abi_arg, - abi_types, - arg_type); + let mut get_arg = |dfg: &mut DataFlowGraph, ty| { + let abi_type = abi_types[abi_arg]; + if ty == abi_type.value_type { + abi_arg += 1; + Ok(dfg.append_ebb_arg(entry, ty)) + } else { + Err(abi_type) + } + }; + let converted = convert_from_abi(&mut func.dfg, &mut pos, arg_type, &mut get_arg); // The old `arg` is no longer an attached EBB argument, but there are probably still // uses of the value. Make it an alias to the converted value. func.dfg.change_to_alias(arg, converted); @@ -159,57 +163,63 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { } } -/// Compute original value of type `ty` from the legalized ABI arguments beginning at `abi_arg`. +/// Compute original value of type `ty` from the legalized ABI arguments. /// -/// Update `abi_arg` to reflect the ABI arguments consumed and return the computed value. -fn convert_from_abi(dfg: &mut DataFlowGraph, - pos: &mut Cursor, - entry: Ebb, - abi_arg: &mut usize, - abi_types: &[ArgumentType], - ty: Type) - -> Value { +/// The conversion is recursive, controlled by the `get_arg` closure which is called to retrieve an +/// ABI argument. It returns: +/// +/// - `Ok(arg)` if the requested type matches the next ABI argument. +/// - `Err(arg_type)` if further conversions are needed from the ABI argument `arg_type`. +/// +fn convert_from_abi(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + ty: Type, + get_arg: &mut GetArg) + -> Value + where GetArg: FnMut(&mut DataFlowGraph, Type) -> Result +{ // Terminate the recursion when we get the desired type. - if ty == abi_types[*abi_arg].value_type { - return dfg.append_ebb_arg(entry, ty); - } + let arg_type = match get_arg(dfg, ty) { + Ok(v) => return v, + Err(t) => t, + }; - // Reconstruct how `ty` was legalized into the argument at `abi_arg`. - let conversion = legalize_abi_value(ty, &abi_types[*abi_arg]); + // Reconstruct how `ty` was legalized into the `arg_type` argument. + let conversion = legalize_abi_value(ty, &arg_type); // The conversion describes value to ABI argument. We implement the reverse conversion here. match conversion { // Construct a `ty` by concatenating two ABI integers. ValueConversion::IntSplit => { let abi_ty = ty.half_width().expect("Invalid type for conversion"); - let lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); - let hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let lo = convert_from_abi(dfg, pos, abi_ty, get_arg); + let hi = convert_from_abi(dfg, pos, abi_ty, get_arg); dfg.ins(pos).iconcat_lohi(lo, hi) } // Construct a `ty` by concatenating two halves of a vector. ValueConversion::VectorSplit => { let abi_ty = ty.half_vector().expect("Invalid type for conversion"); - let lo = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); - let hi = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let lo = convert_from_abi(dfg, pos, abi_ty, get_arg); + let hi = convert_from_abi(dfg, pos, abi_ty, get_arg); dfg.ins(pos).vconcat(lo, hi) } // Construct a `ty` by bit-casting from an integer type. ValueConversion::IntBits => { assert!(!ty.is_int()); let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); - let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let arg = convert_from_abi(dfg, pos, abi_ty, get_arg); dfg.ins(pos).bitcast(ty, arg) } // ABI argument is a sign-extended version of the value we want. ValueConversion::Sext(abi_ty) => { - let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let arg = convert_from_abi(dfg, pos, abi_ty, get_arg); // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. // We could insert an `assert_sreduce` which would fold with a following `sextend` of // this value. dfg.ins(pos).ireduce(ty, arg) } ValueConversion::Uext(abi_ty) => { - let arg = convert_from_abi(dfg, pos, entry, abi_arg, abi_types, abi_ty); + let arg = convert_from_abi(dfg, pos, abi_ty, get_arg); // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. // We could insert an `assert_ureduce` which would fold with a following `uextend` of // this value. @@ -225,21 +235,21 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, /// to the closure, it will perform one of two actions: /// /// 1. If the suggested argument has an acceptable value type, consume it by adding it to the list -/// of arguments and return `None`. +/// of arguments and return `Ok(())`. /// 2. If the suggested argument doesn't have the right value type, don't change anything, but -/// return the `ArgumentType` that is needed. +/// return the `Err(ArgumentType)` that is needed. /// fn convert_to_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value, put_arg: &mut PutArg) - where PutArg: FnMut(&mut DataFlowGraph, Value) -> Option + where PutArg: FnMut(&mut DataFlowGraph, Value) -> Result<(), ArgumentType> { // Start by invoking the closure to either terminate the recursion or get the argument type // we're trying to match. let arg_type = match put_arg(dfg, value) { - None => return, - Some(t) => t, + Ok(_) => return, + Err(t) => t, }; let ty = dfg.value_type(value); @@ -369,21 +379,18 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, let mut abi_arg = 0; for old_arg in 0..have_args { let old_value = vlist.get(old_arg_offset + old_arg, &dfg.value_lists).unwrap(); - convert_to_abi(dfg, - pos, - old_value, - &mut |dfg, arg| { + let mut put_arg = |dfg: &mut DataFlowGraph, arg| { let abi_type = get_abi_type(dfg, abi_arg); if dfg.value_type(arg) == abi_type.value_type { // This is the argument type we need. vlist.as_mut_slice(&mut dfg.value_lists)[fixed_values + abi_arg] = arg; abi_arg += 1; - None + Ok(()) } else { - // Nope, `arg` needs to be converted. - Some(abi_type) + Err(abi_type) } - }); + }; + convert_to_abi(dfg, pos, old_value, &mut put_arg); } // Put the modified value list back. From 6549065491e6114375ade004543154833c9d746d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 16 Mar 2017 16:54:26 -0700 Subject: [PATCH 604/968] Add attach_secondary_result and append_secondary_result. These low-level functions allow us to build up a list of instruction results incrementally. They are equivalent to the existing attach_ebb_arg and append_ebb_arg. --- lib/cretonne/src/ir/dfg.rs | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index a1eb864af2..8977cc6751 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -455,6 +455,63 @@ impl DataFlowGraph { panic!("{} is not a secondary result value", value); } + /// Attach an existing value as a secondary result after `last_res` which must be the last + /// result of an instruction. + /// + /// This is a very low-level operation. Usually, instruction results with the correct types are + /// created automatically. The `res` value must be a secondary instruction result detached from + /// somewhere else. + pub fn attach_secondary_result(&mut self, last_res: Value, res: Value) { + let (res_inst, res_num) = match last_res.expand() { + ExpandedValue::Direct(inst) => { + // We're adding the second value to `inst`. + let next = self[inst].second_result_mut().expect("bad inst format"); + assert!(next.is_none(), "last_res is not the last result"); + *next = res.into(); + (inst, 1) + } + ExpandedValue::Table(idx) => { + if let ValueData::Inst { num, inst, ref mut next, .. } = self.extended_values[idx] { + assert!(next.is_none(), "last_res is not the last result"); + *next = res.into(); + assert!(num < u16::MAX, "Too many arguments to EBB"); + (inst, num + 1) + } else { + panic!("last_res is not an instruction result"); + } + } + }; + + // Now update `res` itself. + if let ExpandedValue::Table(idx) = res.expand() { + if let ValueData::Inst { ref mut num, ref mut inst, ref mut next, .. } = + self.extended_values[idx] { + *num = res_num; + *inst = res_inst; + *next = None.into(); + return; + } + } + panic!("{} must be a result", res); + } + + /// Append a new instruction result value after `last_res`. + /// + /// The `last_res` value must be the last value on an instruction. + pub fn append_secondary_result(&mut self, last_res: Value, ty: Type) -> Value { + // The only member that matters is `ty`. The rest is filled in by + // `attach_secondary_result`. + use entity_map::EntityRef; + let res = self.make_value(ValueData::Inst { + ty: ty, + inst: Inst::new(0), + num: 0, + next: None.into(), + }); + self.attach_secondary_result(last_res, res); + res + } + /// Move the instruction at `pos` to a new `Inst` reference so its first result can be /// redefined without overwriting the original instruction. /// From a8a7cebe8c7796870a07ec9e4c960b4ed8d5e004 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 16 Mar 2017 17:50:49 -0700 Subject: [PATCH 605/968] Legalize return values from call instructions. Like the entry block arguments, the return values from a call instruction need to be converted back from their ABI representation. Add tests of call instruction legalization. --- filetests/isa/riscv/legalize-abi.cton | 60 ++++++++++++++++++ lib/cretonne/src/legalizer.rs | 89 ++++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index bf93596128..eada8ebda9 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -14,6 +14,50 @@ ebb0(v0: i64): return v1 } +function split_call_arg(i32) { + fn1 = function foo(i64) + fn2 = function foo(i32, i64) +ebb0(v0: i32): + v1 = uextend.i64 v0 + call fn1(v1) + ; check: $(v1l=$V), $(v1h=$V) = isplit_lohi $v1 + ; check: call $fn1($v1l, $v1h) + call fn2(v0, v1) + ; check: call $fn2($v0, $V, $V) + return +} + +function split_ret_val() { + fn1 = function foo() -> i64 +ebb0: + v1 = call fn1() + ; check: $ebb0: + ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() + ; check: $(v1new=$V) = iconcat_lohi $v1l, $v1h + ; check: $v1 = copy $v1new + jump ebb1(v1) + ; check: jump $ebb1($v1) + +ebb1(v10: i64): + jump ebb1(v10) +} + +; First return value is fine, second one is expanded. +function split_ret_val2() { + fn1 = function foo() -> i32, i64 +ebb0: + v1, v2 = call fn1() + ; check: $ebb0: + ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() + ; check: $(v2new=$V) = iconcat_lohi $v2l, $v2h + ; check: $v2 -> $v2new + jump ebb1(v1, v2) + ; check: jump $ebb1($v1, $v2) + +ebb1(v9: i32, v10: i64): + jump ebb1(v9, v10) +} + function int_ext(i8, i8 sext, i8 uext) -> i8 uext { ebb0(v1: i8, v2: i8, v3: i8): ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32): @@ -24,6 +68,22 @@ ebb0(v1: i8, v2: i8, v3: i8): return v1 } +; Function produces single return value, still need to copy. +function ext_ret_val() { + fn1 = function foo() -> i8 sext +ebb0: + v1 = call fn1() + ; check: $ebb0: + ; nextln: $(rv=$V) = call $fn1() + ; check: $(v1new=$V) = ireduce.i8 $rv + ; check: $v1 = copy $v1new + jump ebb1(v1) + ; check: jump $ebb1($v1) + +ebb1(v10: i8): + jump ebb1(v10) +} + function vector_split_args(i64x4) -> i64x4 { ebb0(v0: i64x4): ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32): diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 6814e3fe2a..49651491fd 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -163,6 +163,89 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { } } +/// Legalize the results returned from a call instruction to match the ABI signature. +/// +/// The cursor `pos` points to a call instruction with at least one return value. The cursor will +/// be left pointing after the instructions inserted to convert the return values. +/// +/// This function is very similar to the `legalize_entry_arguments` function above. +fn legalize_inst_results(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + mut get_abi_type: ResType) + where ResType: FnMut(&DataFlowGraph, usize) -> ArgumentType +{ + let mut call = pos.current_inst().expect("Cursor must point to a call instruction"); + + // We theoretically allow for call instructions that return a number of fixed results before + // the call return values. In practice, it doesn't happen. + let fixed_results = dfg[call].opcode().constraints().fixed_results(); + assert_eq!(fixed_results, 0, "Fixed results on calls not supported"); + + let mut next_res = dfg.detach_secondary_results(call); + // The currently last result on the call instruction. + let mut last_res = dfg.first_result(call); + let mut abi_res = 0; + + // The first result requires special handling. + let first_ty = dfg.value_type(last_res); + if first_ty != get_abi_type(dfg, abi_res).value_type { + // Move the call out of the way, so we can redefine the first result. + let copy = call; + call = dfg.redefine_first_value(pos); + last_res = dfg.first_result(call); + // Set up a closure that can attach new results to `call`. + let mut get_res = |dfg: &mut DataFlowGraph, ty| { + let abi_type = get_abi_type(dfg, abi_res); + if ty == abi_type.value_type { + // Don't append the first result - it's not detachable. + if fixed_results + abi_res == 0 { + *dfg[call].first_type_mut() = ty; + debug_assert_eq!(last_res, dfg.first_result(call)); + } else { + last_res = dfg.append_secondary_result(last_res, ty); + } + abi_res += 1; + Ok(last_res) + } else { + Err(abi_type) + } + }; + + let v = convert_from_abi(dfg, pos, first_ty, &mut get_res); + dfg.replace(copy).copy(v); + } + + // Point immediately after the call and any instructions dealing with the first result. + pos.next_inst(); + + // Now do the secondary results. + while let Some(res) = next_res { + next_res = dfg.next_secondary_result(res); + + let res_type = dfg.value_type(res); + if res_type == get_abi_type(dfg, abi_res).value_type { + // No value translation is necessary, this result matches the ABI type. + dfg.attach_secondary_result(last_res, res); + last_res = res; + abi_res += 1; + } else { + let mut get_res = |dfg: &mut DataFlowGraph, ty| { + let abi_type = get_abi_type(dfg, abi_res); + if ty == abi_type.value_type { + last_res = dfg.append_secondary_result(last_res, ty); + abi_res += 1; + Ok(last_res) + } else { + Err(abi_type) + } + }; + let v = convert_from_abi(dfg, pos, res_type, &mut get_res); + // The old `res` is no longer an attached result. + dfg.change_to_alias(res, v); + } + } +} + /// Compute original value of type `ty` from the legalized ABI arguments. /// /// The conversion is recursive, controlled by the `get_arg` closure which is called to retrieve an @@ -423,7 +506,11 @@ fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { abi_args, |dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]); - // TODO: Convert return values. + if !dfg.signatures[sig_ref].return_types.is_empty() { + legalize_inst_results(dfg, + pos, + |dfg, abi_res| dfg.signatures[sig_ref].return_types[abi_res]); + } // Yes, we changed stuff. true From e83cccb6fc956c59871284da3bd6544aeebcee8b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 17 Mar 2017 12:34:03 -0700 Subject: [PATCH 606/968] Fix a bug in analyze_call(). The call arguments on call_indirect should not include the fixed callee argument. Add legalizer assertions to verify that signatures are actually valid after legalization. If not, we would get infinite legalizer loops. --- filetests/isa/riscv/legalize-abi.cton | 16 ++++++++ lib/cretonne/src/ir/instructions.rs | 2 +- lib/cretonne/src/legalizer.rs | 57 ++++++++++++++++++--------- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index eada8ebda9..c092c3f2f4 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -105,3 +105,19 @@ ebb0(v0: i64x4): ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh return v1 } + +function indirect(i32) { + sig1 = signature() +ebb0(v0: i32): + call_indirect sig1, v0() + return +} + +; The first argument to call_indirect doesn't get altered. +function indirect_arg(i32, f32x2) { + sig1 = signature(f32x2) +ebb0(v0: i32, v1: f32x2): + call_indirect sig1, v0(v1) + ; check: call_indirect $sig1, $v0($V, $V) + return +} diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index c51afcc118..58801ce85f 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -310,7 +310,7 @@ impl InstructionData { CallInfo::Direct(func_ref, &args.as_slice(pool)) } &InstructionData::IndirectCall { sig_ref, ref args, .. } => { - CallInfo::Indirect(sig_ref, &args.as_slice(pool)) + CallInfo::Indirect(sig_ref, &args.as_slice(pool)[1..]) } _ => CallInfo::NotACall, } diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 49651491fd..88bf0c9ac4 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -169,9 +169,12 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { /// be left pointing after the instructions inserted to convert the return values. /// /// This function is very similar to the `legalize_entry_arguments` function above. +/// +/// Returns the possibly new instruction representing the call. fn legalize_inst_results(dfg: &mut DataFlowGraph, pos: &mut Cursor, mut get_abi_type: ResType) + -> Inst where ResType: FnMut(&DataFlowGraph, usize) -> ArgumentType { let mut call = pos.current_inst().expect("Cursor must point to a call instruction"); @@ -244,6 +247,8 @@ fn legalize_inst_results(dfg: &mut DataFlowGraph, dfg.change_to_alias(res, v); } } + + call } /// Compute original value of type `ty` from the legalized ABI arguments. @@ -387,9 +392,9 @@ fn check_arg_types(dfg: &DataFlowGraph, args: Args, types: &[ArgumentType] /// Check if the arguments of the call `inst` match the signature. /// -/// Returns `None` if the signature matches and no changes are needed, or `Some(sig_ref)` if the +/// Returns `Ok(())` if the signature matches and no changes are needed, or `Err(sig_ref)` if the /// signature doesn't match. -fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Option { +fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Result<(), SigRef> { // Extract the signature and argument values. let (sig_ref, args) = match dfg[inst].analyze_call(&dfg.value_lists) { CallInfo::Direct(func, args) => (dfg.ext_funcs[func].signature, args), @@ -401,13 +406,25 @@ fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Option { if check_arg_types(dfg, args.iter().cloned(), &sig.argument_types[..]) && check_arg_types(dfg, dfg.inst_results(inst), &sig.return_types[..]) { // All types check out. - None + Ok(()) } else { // Call types need fixing. - Some(sig_ref) + Err(sig_ref) } } +/// Check if the arguments of the return `inst` match the signature. +fn check_return_signature(dfg: &DataFlowGraph, inst: Inst, sig: &Signature) -> bool { + let fixed_values = dfg[inst].opcode().constraints().fixed_value_arguments(); + check_arg_types(dfg, + dfg[inst] + .arguments(&dfg.value_lists) + .iter() + .skip(fixed_values) + .cloned(), + &sig.return_types) +} + /// Insert ABI conversion code for the arguments to the call or return instruction at `pos`. /// /// - `abi_args` is the number of arguments that the ABI signature requires. @@ -491,12 +508,12 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, /// /// Returns `true` if any instructions were inserted. fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { - let inst = pos.current_inst().expect("Cursor must point to a call instruction"); + let mut inst = pos.current_inst().expect("Cursor must point to a call instruction"); // Start by checking if the argument types already match the signature. let sig_ref = match check_call_signature(dfg, inst) { - None => return false, - Some(s) => s, + Ok(_) => return false, + Err(s) => s, }; // OK, we need to fix the call arguments to match the ABI signature. @@ -507,11 +524,17 @@ fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { |dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]); if !dfg.signatures[sig_ref].return_types.is_empty() { - legalize_inst_results(dfg, - pos, - |dfg, abi_res| dfg.signatures[sig_ref].return_types[abi_res]); + inst = legalize_inst_results(dfg, + pos, + |dfg, abi_res| dfg.signatures[sig_ref].return_types[abi_res]); } + debug_assert!(check_call_signature(dfg, inst).is_ok(), + "Signature still wrong: {}, {}{}", + dfg.display_inst(inst), + sig_ref, + dfg.signatures[sig_ref]); + // Yes, we changed stuff. true } @@ -523,20 +546,18 @@ fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signature) let inst = pos.current_inst().expect("Cursor must point to a return instruction"); // Check if the returned types already match the signature. - let fixed_values = dfg[inst].opcode().constraints().fixed_value_arguments(); - if check_arg_types(dfg, - dfg[inst] - .arguments(&dfg.value_lists) - .iter() - .skip(fixed_values) - .cloned(), - &sig.return_types[..]) { + if check_return_signature(dfg, inst, sig) { return false; } let abi_args = sig.return_types.len(); legalize_inst_arguments(dfg, pos, abi_args, |_, abi_arg| sig.return_types[abi_arg]); + debug_assert!(check_return_signature(dfg, inst, sig), + "Signature still wrong: {}, sig{}", + dfg.display_inst(inst), + sig); + // Yes, we changed stuff. true } From 298cf2ed2110473a4ac89c9ba29b09919383eec4 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Tue, 14 Mar 2017 20:13:44 +0000 Subject: [PATCH 607/968] Verify that values have a valid reference to either an instruction inserted in an EBB, or an EBB inserted in the layout. --- lib/cretonne/src/verifier.rs | 45 ++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index f63db1919f..ddddb9c509 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -18,9 +18,9 @@ //! //! SSA form //! -//! TODO: //! - Values must be defined by an instruction that exists and that is inserted in //! an EBB, or be an argument of an existing EBB. +//! TODO: //! - Values used by an instruction must dominate the instruction. //! //! Control flow graph and dominator tree integrity: @@ -166,7 +166,7 @@ impl<'a> Verifier<'a> { let ret_type = inst_data.first_type(); if ret_type != types::VOID { return err!(inst, - "instruction expected to have NULL return value, found {}", + "instruction with no results expects NULL return type, found {}", ret_type); } } else { @@ -283,12 +283,43 @@ impl<'a> Verifier<'a> { } } - fn verify_value(&self, inst: Inst, v: Value) -> Result<()> { - if !self.func.dfg.value_is_valid(v) { - err!(inst, "invalid value reference {}", v) - } else { - Ok(()) + fn verify_value(&self, loc_inst: Inst, v: Value) -> Result<()> { + let dfg = &self.func.dfg; + if !dfg.value_is_valid(v) { + return err!(loc_inst, "invalid value reference {}", v); } + + // SSA form + match dfg.value_def(v) { + // Value is defined by an instruction that exists and is inserted in an EBB. + ValueDef::Res(def_inst, _) => { + if !dfg.insts.is_valid(def_inst) { + return err!(loc_inst, + "{} is defined by invalid instruction {}", + v, + def_inst); + } + if self.func.layout.inst_ebb(def_inst) == None { + return err!(loc_inst, + "{} is defined by {} which has no EBB", + v, + def_inst); + } + } + // Value is defined by an existing EBB which is inserted in the layout. + ValueDef::Arg(ebb, _) => { + if !dfg.ebb_is_valid(ebb) { + return err!(loc_inst, "{} is defined by invalid EBB {}", v, ebb); + } + if !self.func.layout.is_ebb_inserted(ebb) { + return err!(loc_inst, + "{} is defined by {} which is not in the layout", + v, + ebb); + } + } + } + Ok(()) } pub fn run(&self) -> Result<()> { From c596ea1ac1d94e98fc86cfeb46d980eb84001311 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Mon, 20 Mar 2017 10:02:06 +0000 Subject: [PATCH 608/968] Verify that values are defined by an EBB/instruction that dominates the instruction that uses them. --- lib/cretonne/src/dominator_tree.rs | 16 ++++++++++++++-- lib/cretonne/src/verifier.rs | 28 ++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 17bd3445ab..a426fedbea 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -68,8 +68,20 @@ impl DominatorTree { /// is unreachable. /// /// An instruction is considered to dominate itself. - pub fn dominates(&self, a: Inst, mut b: Inst, layout: &Layout) -> bool { + pub fn dominates(&self, a: Inst, b: Inst, layout: &Layout) -> bool { let ebb_a = layout.inst_ebb(a).expect("Instruction not in layout."); + self.ebb_dominates(ebb_a, b, layout) && layout.cmp(a, b) != Ordering::Greater + } + + /// Returns `true` if `ebb_a` dominates `b`. + /// + /// This means that every control-flow path from the function entry to `b` must go through + /// `ebb_a`. + /// + /// Dominance is ill defined for unreachable blocks. This function can always determine + /// dominance for instructions in the same EBB, but otherwise returns `false` if either block + /// is unreachable. + pub fn ebb_dominates(&self, ebb_a: Ebb, mut b: Inst, layout: &Layout) -> bool { let mut ebb_b = layout.inst_ebb(b).expect("Instruction not in layout."); let rpo_a = self.nodes[ebb_a].rpo_number; @@ -80,7 +92,7 @@ impl DominatorTree { ebb_b = layout.inst_ebb(b).expect("Dominator got removed."); } - ebb_a == ebb_b && layout.cmp(a, b) != Ordering::Greater + ebb_a == ebb_b } /// Compute the common dominator of two basic blocks. diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index ddddb9c509..d4715805ea 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -20,11 +20,11 @@ //! //! - Values must be defined by an instruction that exists and that is inserted in //! an EBB, or be an argument of an existing EBB. -//! TODO: //! - Values used by an instruction must dominate the instruction. //! //! Control flow graph and dominator tree integrity: //! +//! TODO: //! - All predecessors in the CFG must be branches to the EBB. //! - All branches to an EBB must be present in the CFG. //! - A recomputed dominator tree is identical to the existing one. @@ -56,6 +56,8 @@ use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value}; use ir::instructions::InstructionFormat; use ir::entities::AnyEntity; +use cfg::ControlFlowGraph; +use dominator_tree::DominatorTree; use std::fmt::{self, Display, Formatter}; use std::result; @@ -101,11 +103,19 @@ pub fn verify_function(func: &Function) -> Result<()> { struct Verifier<'a> { func: &'a Function, + cfg: ControlFlowGraph, + domtree: DominatorTree, } impl<'a> Verifier<'a> { pub fn new(func: &'a Function) -> Verifier { - Verifier { func: func } + let cfg = ControlFlowGraph::with_function(func); + let domtree = DominatorTree::with_function(func, &cfg); + Verifier { + func: func, + cfg: cfg, + domtree: domtree, + } } fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<()> { @@ -291,32 +301,42 @@ impl<'a> Verifier<'a> { // SSA form match dfg.value_def(v) { - // Value is defined by an instruction that exists and is inserted in an EBB. ValueDef::Res(def_inst, _) => { + // Value is defined by an instruction that exists. if !dfg.insts.is_valid(def_inst) { return err!(loc_inst, "{} is defined by invalid instruction {}", v, def_inst); } + // Defining instruction is inserted in an EBB. if self.func.layout.inst_ebb(def_inst) == None { return err!(loc_inst, "{} is defined by {} which has no EBB", v, def_inst); } + // Defining instruction dominates the instruction that uses the value. + if !self.domtree.dominates(def_inst, loc_inst, &self.func.layout) { + return err!(loc_inst, "uses value from non-dominating {}", def_inst); + } } - // Value is defined by an existing EBB which is inserted in the layout. ValueDef::Arg(ebb, _) => { + // Value is defined by an existing EBB. if !dfg.ebb_is_valid(ebb) { return err!(loc_inst, "{} is defined by invalid EBB {}", v, ebb); } + // Defining EBB is inserted in the layout if !self.func.layout.is_ebb_inserted(ebb) { return err!(loc_inst, "{} is defined by {} which is not in the layout", v, ebb); } + // The defining EBB dominates the instruction using this value. + if !self.domtree.ebb_dominates(ebb, loc_inst, &self.func.layout) { + return err!(loc_inst, "uses value arg from non-dominating {}", ebb); + } } } Ok(()) From bb0246c8c1496dcda6441aceb058104b844fdfb9 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Mon, 20 Mar 2017 11:07:50 +0000 Subject: [PATCH 609/968] Verify that all predecessors to an EBB are valid branches, and have the EBB recorded as a successor. --- lib/cretonne/src/ir/jumptable.rs | 5 ++++ lib/cretonne/src/verifier.rs | 40 ++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index 69a44ef8fb..e7f98a329d 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -72,6 +72,11 @@ impl JumpTableData { .enumerate()) } + /// Checks if any of the entries branch to `ebb`. + pub fn branches_to(&self, ebb: Ebb) -> bool { + self.table.iter().any(|target_ebb| target_ebb.expand() == Some(ebb)) + } + /// Access the whole table as a mutable slice. pub fn as_mut_slice(&mut self) -> &mut [PackedOption] { self.table.as_mut_slice() diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index d4715805ea..dbb57a5b3e 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -24,9 +24,9 @@ //! //! Control flow graph and dominator tree integrity: //! -//! TODO: //! - All predecessors in the CFG must be branches to the EBB. //! - All branches to an EBB must be present in the CFG. +//! TODO: //! - A recomputed dominator tree is identical to the existing one. //! //! Type checking @@ -54,7 +54,7 @@ //! of arguments must match the destination type, and the lane indexes must be in range. use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value}; -use ir::instructions::InstructionFormat; +use ir::instructions::{InstructionFormat, BranchInfo}; use ir::entities::AnyEntity; use cfg::ControlFlowGraph; use dominator_tree::DominatorTree; @@ -342,12 +342,48 @@ impl<'a> Verifier<'a> { Ok(()) } + fn cfg_integrity(&self, ebb: Ebb) -> Result<()> { + for &(pred_ebb, pred_inst) in self.cfg.get_predecessors(ebb) { + // All predecessors in the CFG must be branches to the EBB + match self.func.dfg[pred_inst].analyze_branch(&self.func.dfg.value_lists) { + BranchInfo::SingleDest(target_ebb, _) => { + if target_ebb != ebb { + return err!(ebb, + "has predecessor {} in {} which does not branch here", + pred_inst, + pred_ebb); + } + } + BranchInfo::Table(jt) => { + if !self.func.jump_tables[jt].branches_to(ebb) { + return err!(ebb, + "has predecessor {} using {} in {} which never branches here", + pred_inst, + jt, + pred_ebb); + } + } + BranchInfo::NotABranch => { + return err!(ebb, "has predecessor {} which is not a branch", pred_inst); + } + } + // All EBBs branching to `ebb` have it recorded as a successor in the CFG. + if !self.cfg.get_successors(pred_ebb).contains(&ebb) { + return err!(ebb, + "predecessor {} does not have this EBB recorded as a successor", + pred_ebb); + } + } + Ok(()) + } + pub fn run(&self) -> Result<()> { for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { self.ebb_integrity(ebb, inst)?; self.instruction_integrity(inst)?; } + self.cfg_integrity(ebb)?; } Ok(()) } From 90d68e0435204810bd5cb0680cf7b48010d41f0c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 20 Mar 2017 14:47:09 -0700 Subject: [PATCH 610/968] Add OpcodeConstraints::value_argument_constraint(). As discussed in #3. Once we know the controlling type variable of a polymorphic instruction, the types of input operands are either bound to known types, or they can vary freely. --- lib/cretonne/src/ir/instructions.rs | 68 +++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 58801ce85f..fe76791211 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -397,7 +397,7 @@ pub struct OpcodeConstraints { /// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first /// `fixed_results()` entries describe the result constraints, then follows constraints for the - /// fixed `Value` input operands. The number of `Value` inputs is determined by the instruction + /// fixed `Value` input operands. (`fixed_value_arguments()` of them). /// format. constraint_offset: u16, } @@ -457,9 +457,24 @@ impl OpcodeConstraints { /// `ctrl_type`. pub fn result_type(self, n: usize, ctrl_type: Type) -> Type { assert!(n < self.fixed_results(), "Invalid result index"); - OPERAND_CONSTRAINTS[self.constraint_offset() + n] - .resolve(ctrl_type) - .expect("Result constraints can't be free") + if let ResolvedConstraint::Bound(t) = + OPERAND_CONSTRAINTS[self.constraint_offset() + n].resolve(ctrl_type) { + t + } else { + panic!("Result constraints can't be free"); + } + } + + /// Get the value type of input value number `n`, having resolved the controlling type variable + /// to `ctrl_type`. + /// + /// Unlike results, it is possible for some input values to vary freely within a specific + /// `ValueTypeSet`. This is represented with the `ArgumentConstraint::Free` variant. + pub fn value_argument_constraint(self, n: usize, ctrl_type: Type) -> ResolvedConstraint { + assert!(n < self.fixed_value_arguments(), + "Invalid value argument index"); + let offset = self.constraint_offset() + self.fixed_results(); + OPERAND_CONSTRAINTS[offset + n].resolve(ctrl_type) } /// Get the typeset of allowed types for the controlling type variable in a polymorphic @@ -475,7 +490,7 @@ impl OpcodeConstraints { } /// A value type set describes the permitted set of types for a type variable. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ValueTypeSet { min_lanes: u8, max_lanes: u8, @@ -561,24 +576,31 @@ enum OperandConstraint { impl OperandConstraint { /// Resolve this operand constraint into a concrete value type, given the value of the /// controlling type variable. - /// Returns `None` if this is a free operand which is independent of the controlling type - /// variable. - pub fn resolve(&self, ctrl_type: Type) -> Option { + pub fn resolve(&self, ctrl_type: Type) -> ResolvedConstraint { use self::OperandConstraint::*; + use self::ResolvedConstraint::Bound; match *self { - Concrete(t) => Some(t), - Free(_) => None, - Same => Some(ctrl_type), - LaneOf => Some(ctrl_type.lane_type()), - AsBool => Some(ctrl_type.as_bool()), - HalfWidth => Some(ctrl_type.half_width().expect("invalid type for half_width")), - DoubleWidth => Some(ctrl_type.double_width().expect("invalid type for double_width")), - HalfVector => Some(ctrl_type.half_vector().expect("invalid type for half_vector")), - DoubleVector => Some(ctrl_type.by(2).expect("invalid type for double_vector")), + Concrete(t) => Bound(t), + Free(vts) => ResolvedConstraint::Free(TYPE_SETS[vts as usize]), + Same => Bound(ctrl_type), + LaneOf => Bound(ctrl_type.lane_type()), + AsBool => Bound(ctrl_type.as_bool()), + HalfWidth => Bound(ctrl_type.half_width().expect("invalid type for half_width")), + DoubleWidth => Bound(ctrl_type.double_width().expect("invalid type for double_width")), + HalfVector => Bound(ctrl_type.half_vector().expect("invalid type for half_vector")), + DoubleVector => Bound(ctrl_type.by(2).expect("invalid type for double_vector")), } } } +/// The type constraint on a value argument once the controlling type variable is known. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ResolvedConstraint { + /// The operand is bound to a known type. + Bound(Type), + /// The operand type can vary freely within the given set. + Free(ValueTypeSet), +} #[cfg(test)] mod tests { @@ -630,12 +652,24 @@ mod tests { assert!(!a.requires_typevar_operand()); assert_eq!(a.fixed_results(), 1); assert_eq!(a.fixed_value_arguments(), 2); + assert_eq!(a.result_type(0, types::I32), types::I32); + assert_eq!(a.result_type(0, types::I8), types::I8); + assert_eq!(a.value_argument_constraint(0, types::I32), + ResolvedConstraint::Bound(types::I32)); + assert_eq!(a.value_argument_constraint(1, types::I32), + ResolvedConstraint::Bound(types::I32)); let b = Opcode::Bitcast.constraints(); assert!(!b.use_typevar_operand()); assert!(!b.requires_typevar_operand()); assert_eq!(b.fixed_results(), 1); assert_eq!(b.fixed_value_arguments(), 1); + assert_eq!(b.result_type(0, types::I32), types::I32); + assert_eq!(b.result_type(0, types::I8), types::I8); + match b.value_argument_constraint(0, types::I32) { + ResolvedConstraint::Free(vts) => assert!(vts.contains(types::F32)), + _ => panic!("Unexpected constraint from value_argument_constraint"), + } let c = Opcode::Call.constraints(); assert_eq!(c.fixed_results(), 0); From 159486c70735a87bbc4297dc5350b47ffe314c0d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 20 Mar 2017 15:14:51 -0700 Subject: [PATCH 611/968] Move ABI boundary legalization into a sub-module. Keep things organized. --- .../{legalizer.rs => legalizer/boundary.rs} | 112 ++++-------------- lib/cretonne/src/legalizer/mod.rs | 96 +++++++++++++++ 2 files changed, 116 insertions(+), 92 deletions(-) rename lib/cretonne/src/{legalizer.rs => legalizer/boundary.rs} (79%) create mode 100644 lib/cretonne/src/legalizer/mod.rs diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer/boundary.rs similarity index 79% rename from lib/cretonne/src/legalizer.rs rename to lib/cretonne/src/legalizer/boundary.rs index 88bf0c9ac4..1dc638a086 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -1,106 +1,34 @@ -//! Legalize instructions. +//! Legalize ABI boundaries. //! -//! A legal instruction is one that can be mapped directly to a machine code instruction for the -//! target ISA. The `legalize_function()` function takes as input any function and transforms it -//! into an equivalent function using only legal instructions. +//! This legalizer sub-module contains code for dealing with ABI boundaries: //! -//! The characteristics of legal instructions depend on the target ISA, so any given instruction -//! can be legal for one ISA and illegal for another. +//! - Function arguments passed to the entry block. +//! - Function arguments passed to call instructions. +//! - Return values from call instructions. +//! - Return values passed to return instructions. //! -//! Besides transforming instructions, the legalizer also fills out the `function.encodings` map -//! which provides a legal encoding recipe for every instruction. +//! The ABI boundary legalization happens in two phases: //! -//! The legalizer does not deal with register allocation constraints. These constraints are derived -//! from the encoding recipes, and solved later by the register allocator. +//! 1. The `legalize_signatures` function rewrites all the preamble signatures with ABI information +//! and possibly new argument types. It also rewrites the entry block arguments to match. +//! 2. The `handle_call_abi` and `handle_return_abi` functions rewrite call and return instructions +//! to match the new ABI signatures. +//! +//! Between the two phases, preamble signatures and call/return arguments don't match. This +//! intermediate state doesn't type check. use abi::{legalize_abi_value, ValueConversion}; -use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, Inst, InstBuilder, Ebb, Type, - Value, Signature, SigRef, ArgumentType}; -use ir::condcodes::IntCC; +use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef, + ArgumentType}; use ir::instructions::CallInfo; -use isa::{TargetIsa, Legalize}; - -/// Legalize `func` for `isa`. -/// -/// - Transform any instructions that don't have a legal representation in `isa`. -/// - Fill out `func.encodings`. -/// -pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { - legalize_signatures(func, isa); - - // TODO: This is very simplified and incomplete. - func.encodings.resize(func.dfg.num_insts()); - let mut pos = Cursor::new(&mut func.layout); - while let Some(_ebb) = pos.next_ebb() { - // Keep track of the cursor position before the instruction being processed, so we can - // double back when replacing instructions. - let mut prev_pos = pos.position(); - - while let Some(inst) = pos.next_inst() { - let opcode = func.dfg[inst].opcode(); - - // Check for ABI boundaries that need to be converted to the legalized signature. - if opcode.is_call() && handle_call_abi(&mut func.dfg, &mut pos) { - // Go back and legalize the inserted argument conversion instructions. - pos.set_position(prev_pos); - continue; - } - - if opcode.is_return() && handle_return_abi(&mut func.dfg, &mut pos, &func.signature) { - // Go back and legalize the inserted return value conversion instructions. - pos.set_position(prev_pos); - continue; - } - - match isa.encode(&func.dfg, &func.dfg[inst]) { - Ok(encoding) => *func.encodings.ensure(inst) = encoding, - Err(action) => { - // We should transform the instruction into legal equivalents. - // Possible strategies are: - // 1. Legalize::Expand: Expand instruction into sequence of legal instructions. - // Possibly iteratively. () - // 2. Legalize::Narrow: Split the controlling type variable into high and low - // parts. This applies both to SIMD vector types which can be halved and to - // integer types such as `i64` used on a 32-bit ISA. (). - // 3. TODO: Promote the controlling type variable to a larger type. This - // typically means expressing `i8` and `i16` arithmetic in terms if `i32` - // operations on RISC targets. (It may or may not be beneficial to promote - // small vector types versus splitting them.) - // 4. TODO: Convert to library calls. For example, floating point operations on - // an ISA with no IEEE 754 support. - let changed = match action { - Legalize::Expand => expand(&mut pos, &mut func.dfg), - Legalize::Narrow => narrow(&mut pos, &mut func.dfg), - }; - // If the current instruction was replaced, we need to double back and revisit - // the expanded sequence. This is both to assign encodings and possible to - // expand further. - // There's a risk of infinite looping here if the legalization patterns are - // unsound. Should we attempt to detect that? - if changed { - pos.set_position(prev_pos); - } - } - } - - // Remember this position in case we need to double back. - prev_pos = pos.position(); - } - } -} - -// Include legalization patterns that were generated by `gen_legalizer.py` from the `XForms` in -// `meta/cretonne/legalize.py`. -// -// Concretely, this defines private functions `narrow()`, and `expand()`. -include!(concat!(env!("OUT_DIR"), "/legalizer.rs")); +use isa::TargetIsa; /// Legalize all the function signatures in `func`. /// /// This changes all signatures to be ABI-compliant with full `ArgumentLoc` annotations. It doesn't /// change the entry block arguments, calls, or return instructions, so this can leave the function /// in a state with type discrepancies. -fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { +pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { isa.legalize_signature(&mut func.signature); for sig in func.dfg.signatures.keys() { isa.legalize_signature(&mut func.dfg.signatures[sig]); @@ -507,7 +435,7 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, /// original return values. The call's result values will be adapted to match the new signature. /// /// Returns `true` if any instructions were inserted. -fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { +pub fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { let mut inst = pos.current_inst().expect("Cursor must point to a call instruction"); // Start by checking if the argument types already match the signature. @@ -542,7 +470,7 @@ fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { /// Insert ABI conversion code before and after the call instruction at `pos`. /// /// Return `true` if any instructions were inserted. -fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signature) -> bool { +pub fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signature) -> bool { let inst = pos.current_inst().expect("Cursor must point to a return instruction"); // Check if the returned types already match the signature. diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs new file mode 100644 index 0000000000..a315cf069f --- /dev/null +++ b/lib/cretonne/src/legalizer/mod.rs @@ -0,0 +1,96 @@ +//! Legalize instructions. +//! +//! A legal instruction is one that can be mapped directly to a machine code instruction for the +//! target ISA. The `legalize_function()` function takes as input any function and transforms it +//! into an equivalent function using only legal instructions. +//! +//! The characteristics of legal instructions depend on the target ISA, so any given instruction +//! can be legal for one ISA and illegal for another. +//! +//! Besides transforming instructions, the legalizer also fills out the `function.encodings` map +//! which provides a legal encoding recipe for every instruction. +//! +//! The legalizer does not deal with register allocation constraints. These constraints are derived +//! from the encoding recipes, and solved later by the register allocator. + +use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; +use ir::condcodes::IntCC; +use isa::{TargetIsa, Legalize}; + +mod boundary; + +/// Legalize `func` for `isa`. +/// +/// - Transform any instructions that don't have a legal representation in `isa`. +/// - Fill out `func.encodings`. +/// +pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { + boundary::legalize_signatures(func, isa); + + // TODO: This is very simplified and incomplete. + func.encodings.resize(func.dfg.num_insts()); + let mut pos = Cursor::new(&mut func.layout); + while let Some(_ebb) = pos.next_ebb() { + // Keep track of the cursor position before the instruction being processed, so we can + // double back when replacing instructions. + let mut prev_pos = pos.position(); + + while let Some(inst) = pos.next_inst() { + let opcode = func.dfg[inst].opcode(); + + // Check for ABI boundaries that need to be converted to the legalized signature. + if opcode.is_call() && boundary::handle_call_abi(&mut func.dfg, &mut pos) { + // Go back and legalize the inserted argument conversion instructions. + pos.set_position(prev_pos); + continue; + } + + if opcode.is_return() && + boundary::handle_return_abi(&mut func.dfg, &mut pos, &func.signature) { + // Go back and legalize the inserted return value conversion instructions. + pos.set_position(prev_pos); + continue; + } + + match isa.encode(&func.dfg, &func.dfg[inst]) { + Ok(encoding) => *func.encodings.ensure(inst) = encoding, + Err(action) => { + // We should transform the instruction into legal equivalents. + // Possible strategies are: + // 1. Legalize::Expand: Expand instruction into sequence of legal instructions. + // Possibly iteratively. () + // 2. Legalize::Narrow: Split the controlling type variable into high and low + // parts. This applies both to SIMD vector types which can be halved and to + // integer types such as `i64` used on a 32-bit ISA. (). + // 3. TODO: Promote the controlling type variable to a larger type. This + // typically means expressing `i8` and `i16` arithmetic in terms if `i32` + // operations on RISC targets. (It may or may not be beneficial to promote + // small vector types versus splitting them.) + // 4. TODO: Convert to library calls. For example, floating point operations on + // an ISA with no IEEE 754 support. + let changed = match action { + Legalize::Expand => expand(&mut pos, &mut func.dfg), + Legalize::Narrow => narrow(&mut pos, &mut func.dfg), + }; + // If the current instruction was replaced, we need to double back and revisit + // the expanded sequence. This is both to assign encodings and possible to + // expand further. + // There's a risk of infinite looping here if the legalization patterns are + // unsound. Should we attempt to detect that? + if changed { + pos.set_position(prev_pos); + } + } + } + + // Remember this position in case we need to double back. + prev_pos = pos.position(); + } + } +} + +// Include legalization patterns that were generated by `gen_legalizer.py` from the `XForms` in +// `meta/cretonne/legalize.py`. +// +// Concretely, this defines private functions `narrow()`, and `expand()`. +include!(concat!(env!("OUT_DIR"), "/legalizer.rs")); From a44a4d2718af4f513eed2114d45278b607ad377a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Mar 2017 13:08:17 -0700 Subject: [PATCH 612/968] Strip the _lohi suffix from the isplit instructions. For symmetry with the vector splitting instructions, we now have: isplit iconcat vsplit vconcat No functional change. --- docs/langref.rst | 4 ++-- filetests/isa/riscv/legalize-abi.cton | 26 +++++++++++++------------- filetests/isa/riscv/legalize-i64.cton | 24 ++++++++++++------------ lib/cretonne/meta/base/instructions.py | 8 ++++---- lib/cretonne/meta/base/legalize.py | 20 ++++++++++---------- lib/cretonne/src/abi.rs | 4 ++-- lib/cretonne/src/legalizer/boundary.rs | 4 ++-- 7 files changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 9a08da062d..19e17490a2 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -839,8 +839,8 @@ Legalization operations These instructions are used as helpers when legalizing types and operations for the target ISA. -.. autoinst:: isplit_lohi -.. autoinst:: iconcat_lohi +.. autoinst:: isplit +.. autoinst:: iconcat Base instruction group ====================== diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index c092c3f2f4..926a3c2820 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -7,9 +7,9 @@ isa riscv function int_split_args(i64) -> i64 { ebb0(v0: i64): ; check: $ebb0($(v0l=$V): i32, $(v0h=$V): i32): - ; check: iconcat_lohi $v0l, $v0h + ; check: iconcat $v0l, $v0h v1 = iadd_imm v0, 1 - ; check: $(v1l=$V), $(v1h=$V) = isplit_lohi $v1 + ; check: $(v1l=$V), $(v1h=$V) = isplit $v1 ; check: return $v1l, $v1h return v1 } @@ -20,7 +20,7 @@ function split_call_arg(i32) { ebb0(v0: i32): v1 = uextend.i64 v0 call fn1(v1) - ; check: $(v1l=$V), $(v1h=$V) = isplit_lohi $v1 + ; check: $(v1l=$V), $(v1h=$V) = isplit $v1 ; check: call $fn1($v1l, $v1h) call fn2(v0, v1) ; check: call $fn2($v0, $V, $V) @@ -33,7 +33,7 @@ ebb0: v1 = call fn1() ; check: $ebb0: ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() - ; check: $(v1new=$V) = iconcat_lohi $v1l, $v1h + ; check: $(v1new=$V) = iconcat $v1l, $v1h ; check: $v1 = copy $v1new jump ebb1(v1) ; check: jump $ebb1($v1) @@ -49,7 +49,7 @@ ebb0: v1, v2 = call fn1() ; check: $ebb0: ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() - ; check: $(v2new=$V) = iconcat_lohi $v2l, $v2h + ; check: $(v2new=$V) = iconcat $v2l, $v2h ; check: $v2 -> $v2new jump ebb1(v1, v2) ; check: jump $ebb1($v1, $v2) @@ -87,21 +87,21 @@ ebb1(v10: i8): function vector_split_args(i64x4) -> i64x4 { ebb0(v0: i64x4): ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32): - ; check: $(v0a=$V) = iconcat_lohi $v0al, $v0ah - ; check: $(v0b=$V) = iconcat_lohi $v0bl, $v0bh + ; check: $(v0a=$V) = iconcat $v0al, $v0ah + ; check: $(v0b=$V) = iconcat $v0bl, $v0bh ; check: $(v0ab=$V) = vconcat $v0a, $v0b - ; check: $(v0c=$V) = iconcat_lohi $v0cl, $v0ch - ; check: $(v0d=$V) = iconcat_lohi $v0dl, $v0dh + ; check: $(v0c=$V) = iconcat $v0cl, $v0ch + ; check: $(v0d=$V) = iconcat $v0dl, $v0dh ; check: $(v0cd=$V) = vconcat $v0c, $v0d ; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd v1 = iadd v0, v0 ; check: $(v1ab=$V), $(v1cd=$V) = vsplit ; check: $(v1a=$V), $(v1b=$V) = vsplit $v1ab - ; check: $(v1al=$V), $(v1ah=$V) = isplit_lohi $v1a - ; check: $(v1bl=$V), $(v1bh=$V) = isplit_lohi $v1b + ; check: $(v1al=$V), $(v1ah=$V) = isplit $v1a + ; check: $(v1bl=$V), $(v1bh=$V) = isplit $v1b ; check: $(v1c=$V), $(v1d=$V) = vsplit $v1cd - ; check: $(v1cl=$V), $(v1ch=$V) = isplit_lohi $v1c - ; check: $(v1dl=$V), $(v1dh=$V) = isplit_lohi $v1d + ; check: $(v1cl=$V), $(v1ch=$V) = isplit $v1c + ; check: $(v1dl=$V), $(v1dh=$V) = isplit $v1d ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh return v1 } diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index fb8d412d8c..1d446f8a47 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -10,39 +10,39 @@ ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi +; check: $(v1l=$V), $(v1h=$VX) = isplit +; check: $(v2l=$V), $(v2h=$VX) = isplit ; check: [R#ec ; sameln: $(v3l=$V) = band $v1l, $v2l ; check: [R#ec ; sameln: $(v3h=$V) = band $v1h, $v2h -; check: $v3 = iconcat_lohi $v3l, $v3h +; check: $v3 = iconcat $v3l, $v3h function bitwise_or(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi +; check: $(v1l=$V), $(v1h=$VX) = isplit +; check: $(v2l=$V), $(v2h=$VX) = isplit ; check: [R#cc ; sameln: $(v3l=$V) = bor $v1l, $v2l ; check: [R#cc ; sameln: $(v3h=$V) = bor $v1h, $v2h -; check: $v3 = iconcat_lohi $v3l, $v3h +; check: $v3 = iconcat $v3l, $v3h function bitwise_xor(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi +; check: $(v1l=$V), $(v1h=$VX) = isplit +; check: $(v2l=$V), $(v2h=$VX) = isplit ; check: [R#8c ; sameln: $(v3l=$V) = bxor $v1l, $v2l ; check: [R#8c ; sameln: $(v3h=$V) = bxor $v1h, $v2h -; check: $v3 = iconcat_lohi $v3l, $v3h +; check: $v3 = iconcat $v3l, $v3h function arith_add(i64, i64) -> i64 { ; Legalizing iadd.i64 requires two steps: @@ -52,8 +52,8 @@ ebb0(v1: i64, v2: i64): v3 = iadd v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit_lohi -; check: $(v2l=$V), $(v2h=$VX) = isplit_lohi +; check: $(v1l=$V), $(v1h=$VX) = isplit +; check: $(v2l=$V), $(v2h=$VX) = isplit ; check: [R#0c ; sameln: $(v3l=$V) = iadd $v1l, $v2l ; check: $(c=$V) = icmp ult, $v3l, $v1l @@ -62,4 +62,4 @@ ebb0(v1: i64, v2: i64): ; TODO: This doesn't typecheck. We need to convert the b1 result to i32. ; check: [R#0c ; sameln: $(v3h=$V) = iadd $v3h1, $c -; check: $v3 = iconcat_lohi $v3l, $v3h +; check: $v3 = iconcat $v3l, $v3h diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index e0efafb1f5..66dc0e9090 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -1286,8 +1286,8 @@ lo = Operand( hi = Operand( 'hi', WideInt.half_width(), 'The high bits of `x`') -isplit_lohi = Instruction( - 'isplit_lohi', r""" +isplit = Instruction( + 'isplit', r""" Split a scalar integer into low and high parts. Returns the low half of `x` and the high half of `x` as two independent @@ -1305,8 +1305,8 @@ a = Operand( 'a', NarrowInt.double_width(), doc='The concatenation of `lo` and `hi`') -iconcat_lohi = Instruction( - 'iconcat_lohi', r""" +iconcat = Instruction( + 'iconcat', r""" Concatenate low and high bits to form a larger integer type. """, ins=(lo, hi), outs=a) diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index b2f8fc4f6d..9b0def9ae9 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -9,7 +9,7 @@ instructions that are legal. from __future__ import absolute_import from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm from .instructions import isub, isub_bin, isub_bout, isub_borrow -from .instructions import band, bor, bxor, isplit_lohi, iconcat_lohi +from .instructions import band, bor, bxor, isplit, iconcat from .instructions import icmp, iconst from cdsl.ast import Var from cdsl.xform import Rtl, XFormGroup @@ -54,32 +54,32 @@ ah = Var('ah') narrow.legalize( a << iadd(x, y), Rtl( - (xl, xh) << isplit_lohi(x), - (yl, yh) << isplit_lohi(y), + (xl, xh) << isplit(x), + (yl, yh) << isplit(y), (al, c) << iadd_cout(xl, yl), ah << iadd_cin(xh, yh, c), - a << iconcat_lohi(al, ah) + a << iconcat(al, ah) )) narrow.legalize( a << isub(x, y), Rtl( - (xl, xh) << isplit_lohi(x), - (yl, yh) << isplit_lohi(y), + (xl, xh) << isplit(x), + (yl, yh) << isplit(y), (al, b) << isub_bout(xl, yl), ah << isub_bin(xh, yh, b), - a << iconcat_lohi(al, ah) + a << iconcat(al, ah) )) for bitop in [band, bor, bxor]: narrow.legalize( a << bitop(x, y), Rtl( - (xl, xh) << isplit_lohi(x), - (yl, yh) << isplit_lohi(y), + (xl, xh) << isplit(x), + (yl, yh) << isplit(y), al << bitop(xl, yl), ah << bitop(xh, yh), - a << iconcat_lohi(al, ah) + a << iconcat(al, ah) )) # Expand integer operations with carry for RISC architectures that don't have diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs index d011311e7c..14a034b8fb 100644 --- a/lib/cretonne/src/abi.rs +++ b/lib/cretonne/src/abi.rs @@ -38,10 +38,10 @@ impl From for ArgAction { /// Legalization action to be applied to a value that is being passed to or from a legalized ABI. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ValueConversion { - /// Split an integer types into low and high parts, using `isplit_lohi`. + /// Split an integer types into low and high parts, using `isplit`. IntSplit, - /// Split a vector type into halves with identical lane types. + /// Split a vector type into halves with identical lane types, using `vsplit`. VectorSplit, /// Bit-cast to an integer type of the same size. diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 1dc638a086..3c98009aed 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -210,7 +210,7 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, let abi_ty = ty.half_width().expect("Invalid type for conversion"); let lo = convert_from_abi(dfg, pos, abi_ty, get_arg); let hi = convert_from_abi(dfg, pos, abi_ty, get_arg); - dfg.ins(pos).iconcat_lohi(lo, hi) + dfg.ins(pos).iconcat(lo, hi) } // Construct a `ty` by concatenating two halves of a vector. ValueConversion::VectorSplit => { @@ -271,7 +271,7 @@ fn convert_to_abi(dfg: &mut DataFlowGraph, let ty = dfg.value_type(value); match legalize_abi_value(ty, &arg_type) { ValueConversion::IntSplit => { - let (lo, hi) = dfg.ins(pos).isplit_lohi(value); + let (lo, hi) = dfg.ins(pos).isplit(value); convert_to_abi(dfg, pos, lo, put_arg); convert_to_abi(dfg, pos, hi, put_arg); } From 22334bcb54bcce89abc2d4161c7c5279504869d6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Mar 2017 13:25:08 -0700 Subject: [PATCH 613/968] Avoid generating value split instructions. The legalizer often splits values into parts with the vsplit and isplit_lohi instructions. Avoid doing that for values that are already defined by the corresponding concatenation instructions. This reduces the number of instructions created during legalization, and it simplifies later optimizations. A number of dead concatenation instructions are left behind. They can be trivially cleaned up by a dead code elimination pass. --- filetests/isa/riscv/legalize-i64.cton | 19 ++-- lib/cretonne/meta/gen_legalizer.py | 71 ++++++++++----- lib/cretonne/src/legalizer/boundary.rs | 5 +- lib/cretonne/src/legalizer/mod.rs | 1 + lib/cretonne/src/legalizer/split.rs | 120 +++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 35 deletions(-) create mode 100644 lib/cretonne/src/legalizer/split.rs diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index 1d446f8a47..dfa78447af 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -2,47 +2,46 @@ test legalizer isa riscv supports_m=1 -; regex: V=v\d+ -; regex: VX=vx\d+ +; regex: V=vx?\d+ function bitwise_and(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit -; check: $(v2l=$V), $(v2h=$VX) = isplit +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): ; check: [R#ec ; sameln: $(v3l=$V) = band $v1l, $v2l ; check: [R#ec ; sameln: $(v3h=$V) = band $v1h, $v2h ; check: $v3 = iconcat $v3l, $v3h +; check: return $v3l, $v3h function bitwise_or(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit -; check: $(v2l=$V), $(v2h=$VX) = isplit +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): ; check: [R#cc ; sameln: $(v3l=$V) = bor $v1l, $v2l ; check: [R#cc ; sameln: $(v3h=$V) = bor $v1h, $v2h ; check: $v3 = iconcat $v3l, $v3h +; check: return $v3l, $v3h function bitwise_xor(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit -; check: $(v2l=$V), $(v2h=$VX) = isplit +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): ; check: [R#8c ; sameln: $(v3l=$V) = bxor $v1l, $v2l ; check: [R#8c ; sameln: $(v3h=$V) = bxor $v1h, $v2h ; check: $v3 = iconcat $v3l, $v3h +; check: return $v3l, $v3h function arith_add(i64, i64) -> i64 { ; Legalizing iadd.i64 requires two steps: @@ -52,8 +51,7 @@ ebb0(v1: i64, v2: i64): v3 = iadd v1, v2 return v3 } -; check: $(v1l=$V), $(v1h=$VX) = isplit -; check: $(v2l=$V), $(v2h=$VX) = isplit +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): ; check: [R#0c ; sameln: $(v3l=$V) = iadd $v1l, $v2l ; check: $(c=$V) = icmp ult, $v3l, $v1l @@ -63,3 +61,4 @@ ebb0(v1: i64, v2: i64): ; check: [R#0c ; sameln: $(v3h=$V) = iadd $v3h1, $c ; check: $v3 = iconcat $v3l, $v3h +; check: return $v3l, $v3h diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 95d16d3663..10e9641df7 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -9,7 +9,7 @@ the input instruction. """ from __future__ import absolute_import from srcgen import Formatter -from base import legalize +from base import legalize, instructions from cdsl.ast import Var try: @@ -117,36 +117,61 @@ def wrap_tup(seq): return '({})'.format(', '.join(tup)) +def is_value_split(node): + # type: (Def) -> bool + """ + Determine if `node` represents one of the value splitting instructions: + `isplit` or `vsplit. These instructions are lowered specially by the + `legalize::split` module. + """ + if len(node.defs) != 2: + return False + return node.expr.inst in (instructions.isplit, instructions.vsplit) + + def emit_dst_inst(node, fmt): # type: (Def, Formatter) -> None exact_replace = False replaced_inst = None # type: str fixup_first_result = False - if len(node.defs) == 0: - # This node doesn't define any values, so just insert the new - # instruction. - builder = 'dfg.ins(pos)' - else: - src_def0 = node.defs[0].src_def - if src_def0 and node.defs[0] == src_def0.defs[0]: - # The primary result is replacing the primary result of the src - # pattern. - # Replace the whole instruction. - builder = 'let {} = dfg.replace(inst)'.format(wrap_tup(node.defs)) - replaced_inst = 'inst' - # Secondary values weren't replaced if this is an exact replacement - # for all the src results. - exact_replace = (node.defs == src_def0.defs) - else: - # Insert a new instruction since its primary def doesn't match the - # src. - builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) - fixup_first_result = node.defs[0].is_output() - fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs))) + if is_value_split(node): + # Split instructions are not emitted with the builder, but by calling + # special functions in the `legalizer::split` module. These functions + # will eliminate concat-split patterns. + fmt.line( + 'let {} = split::{}(dfg, pos, {});' + .format( + wrap_tup(node.defs), + node.expr.inst.snake_name(), + node.expr.args[0])) + else: + if len(node.defs) == 0: + # This node doesn't define any values, so just insert the new + # instruction. + builder = 'dfg.ins(pos)' + else: + src_def0 = node.defs[0].src_def + if src_def0 and node.defs[0] == src_def0.defs[0]: + # The primary result is replacing the primary result of the + # source pattern. + # Replace the whole instruction. + builder = 'let {} = dfg.replace(inst)'.format( + wrap_tup(node.defs)) + replaced_inst = 'inst' + # Secondary values weren't replaced if this is an exact + # replacement for all the source results. + exact_replace = (node.defs == src_def0.defs) + else: + # Insert a new instruction since its primary def doesn't match + # the source. + builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) + fixup_first_result = node.defs[0].is_output() + + fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs))) # If we just replaced an instruction, we need to bump the cursor so - # following instructions are inserted *after* the replaced insruction. + # following instructions are inserted *after* the replaced instruction. if replaced_inst: with fmt.indented( 'if pos.current_inst() == Some({}) {{' diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 3c98009aed..a53526232a 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -22,6 +22,7 @@ use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, S ArgumentType}; use ir::instructions::CallInfo; use isa::TargetIsa; +use legalizer::split::{isplit, vsplit}; /// Legalize all the function signatures in `func`. /// @@ -271,12 +272,12 @@ fn convert_to_abi(dfg: &mut DataFlowGraph, let ty = dfg.value_type(value); match legalize_abi_value(ty, &arg_type) { ValueConversion::IntSplit => { - let (lo, hi) = dfg.ins(pos).isplit(value); + let (lo, hi) = isplit(dfg, pos, value); convert_to_abi(dfg, pos, lo, put_arg); convert_to_abi(dfg, pos, hi, put_arg); } ValueConversion::VectorSplit => { - let (lo, hi) = dfg.ins(pos).vsplit(value); + let (lo, hi) = vsplit(dfg, pos, value); convert_to_abi(dfg, pos, lo, put_arg); convert_to_abi(dfg, pos, hi, put_arg); } diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index a315cf069f..56e3e10dd6 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -18,6 +18,7 @@ use ir::condcodes::IntCC; use isa::{TargetIsa, Legalize}; mod boundary; +mod split; /// Legalize `func` for `isa`. /// diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs new file mode 100644 index 0000000000..b8323a59fb --- /dev/null +++ b/lib/cretonne/src/legalizer/split.rs @@ -0,0 +1,120 @@ +//! Value splitting. +//! +//! Some value types are too large to fit in registers, so they need to be split into smaller parts +//! that the ISA can operate on. There's two dimensions of splitting, represented by two +//! complementary instruction pairs: +//! +//! - `isplit` and `iconcat` for splitting integer types into smaller integers. +//! - `vsplit` and `vconcat` for splitting vector types into smaller vector types with the same +//! lane types. +//! +//! There is no floating point splitting. If an ISA doesn't support `f64` values, they probably +//! have to be bit-cast to `i64` and possibly split into two `i32` values that fit in registers. +//! This breakdown is handled by the ABI lowering. +//! +//! When legalizing a single instruction, it is wrapped in splits and concatenations: +//! +//!```cton +//! v1 = bxor.i64 v2, v3 +//! ``` +//! +//! becomes: +//! +//!```cton +//! v20, v21 = isplit v2 +//! v30, v31 = isplit v3 +//! v10 = bxor.i32 v20, v30 +//! v11 = bxor.i32 v21, v31 +//! v1 = iconcat v10, v11 +//! ``` +//! +//! This local expansion approach still leaves the original `i64` values in the code as operands on +//! the `split` and `concat` instructions. It also creates a lot of redundant code to clean up as +//! values are constantly split and concatenated. +//! +//! # Optimized splitting +//! +//! We can eliminate a lot of the splitting code quite easily. Whenever we need to split a value, +//! first check if the value is defined by the corresponding concatenation. If so, then just use +//! the two concatenation inputs directly: +//! +//! ```cton +//! v4 = iadd_imm.i64 v1, 1 +//! ``` +//! +//! becomes, using the expanded code from above: +//! +//! ```cton +//! v40, v5 = iadd_imm_cout.i32 v10, 1 +//! v6 = bint.i32 +//! v41 = iadd.i32 v11, v6 +//! v4 = iconcat v40, v41 +//! ``` +//! +//! This means that the `iconcat` instructions defining `v1` and `v4` end up with no uses, so they +//! can be trivially deleted by a dead code elimination pass. +//! +//! # EBB arguments +//! +//! If all instructions that produce an `i64` value are legalized as above, we will eventually end +//! up with no `i64` values anywhere, except for EBB arguments. We can work around this by +//! iteratively splitting EBB arguments too. That should leave us with no illegal value types +//! anywhere. +//! +//! It is possible to have circular dependencies of EBB arguments that are never used by any real +//! instructions. These loops will remain in the program. + +use ir::{DataFlowGraph, Cursor, Value, Opcode, ValueDef, InstructionData, InstBuilder}; + +/// Split `value` into two values using the `isplit` semantics. Do this by reusing existing values +/// if possible. +pub fn isplit(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) { + split_value(dfg, pos, value, Opcode::Iconcat) +} + +/// Split `value` into halves using the `vsplit` semantics. Do this by reusing existing values if +/// possible. +pub fn vsplit(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) { + split_value(dfg, pos, value, Opcode::Vconcat) +} + +/// Split a single value using the integer or vector semantics given by the `concat` opcode. +/// +/// If the value is defined by a `concat` instruction, just reuse the operand values of that +/// instruction. +/// +/// Return the two new values representing the parts of `value`. +fn split_value(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + value: Value, + concat: Opcode) + -> (Value, Value) { + let value = dfg.resolve_copies(value); + let mut reuse = None; + + match dfg.value_def(value) { + ValueDef::Res(inst, num) => { + // This is an instruction result. See if the value was created by a `concat` + // instruction. + if let InstructionData::Binary { opcode, args, .. } = dfg[inst] { + assert_eq!(num, 0); + if opcode == concat { + reuse = Some((args[0], args[1])); + } + } + } + ValueDef::Arg(_ebb, _num) => {} + } + + // Did the code above succeed in finding values we can reuse? + if let Some(pair) = reuse { + pair + } else { + // No, we'll just have to insert the requested split instruction at `pos`. + match concat { + Opcode::Iconcat => dfg.ins(pos).isplit(value), + Opcode::Vconcat => dfg.ins(pos).vsplit(value), + _ => panic!("Unhandled concat opcode: {}", concat), + } + } +} From f84e218a932bf76220d7cc0a10fba2375af212ed Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Mar 2017 15:33:23 -0700 Subject: [PATCH 614/968] Rename the 'cfg' module to 'flowgraph'. The 'cfg' name was easy to confuse with 'configuration'. --- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/dominator_tree.rs | 8 ++++---- lib/cretonne/src/{cfg.rs => flowgraph.rs} | 0 lib/cretonne/src/lib.rs | 2 +- lib/cretonne/src/regalloc/context.rs | 4 ++-- lib/cretonne/src/regalloc/liveness.rs | 4 ++-- lib/cretonne/src/verifier.rs | 8 ++++---- src/filetest/domtree.rs | 8 ++++---- src/print_cfg.rs | 6 +++--- tests/cfg_traversal.rs | 6 +++--- 10 files changed, 24 insertions(+), 24 deletions(-) rename lib/cretonne/src/{cfg.rs => flowgraph.rs} (100%) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index ce1e8b50ff..a4fc155c41 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -9,8 +9,8 @@ //! contexts concurrently. Typically, you would have one context per compilation thread and only a //! single ISA instance. -use cfg::ControlFlowGraph; use dominator_tree::DominatorTree; +use flowgraph::ControlFlowGraph; use ir::Function; use isa::TargetIsa; use legalize_function; diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index a426fedbea..b014e85ec1 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -1,8 +1,8 @@ //! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. -use cfg::{ControlFlowGraph, BasicBlock}; -use ir::{Ebb, Inst, Function, Layout, ProgramOrder}; use entity_map::EntityMap; +use flowgraph::{ControlFlowGraph, BasicBlock}; +use ir::{Ebb, Inst, Function, Layout, ProgramOrder}; use packed_option::PackedOption; use std::cmp::Ordering; @@ -222,9 +222,9 @@ impl DominatorTree { #[cfg(test)] mod test { - use super::*; + use flowgraph::ControlFlowGraph; use ir::{Function, InstBuilder, Cursor, types}; - use cfg::ControlFlowGraph; + use super::*; #[test] fn empty() { diff --git a/lib/cretonne/src/cfg.rs b/lib/cretonne/src/flowgraph.rs similarity index 100% rename from lib/cretonne/src/cfg.rs rename to lib/cretonne/src/flowgraph.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 733506a605..079b9a4559 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -10,7 +10,7 @@ pub use write::write_function; /// Version number of the cretonne crate. pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub mod cfg; +pub mod flowgraph; pub mod dominator_tree; pub mod entity_list; pub mod entity_map; diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 75863fbc63..195c480372 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -5,12 +5,12 @@ //! avoids allocating data structures independently for each function begin compiled. use dominator_tree::DominatorTree; +use flowgraph::ControlFlowGraph; use ir::Function; +use isa::TargetIsa; use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; -use isa::TargetIsa; -use cfg::ControlFlowGraph; /// Persistent memory allocations for register allocation. pub struct Context { diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 989d3bb8b7..0e384017c2 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -175,12 +175,12 @@ //! //! There is some room for improvement. -use cfg::ControlFlowGraph; +use flowgraph::ControlFlowGraph; use ir::dfg::ValueDef; use ir::{Function, Value, Inst, Ebb}; use isa::{TargetIsa, RecipeConstraints}; -use regalloc::liverange::LiveRange; use regalloc::affinity::Affinity; +use regalloc::liverange::LiveRange; use sparse_map::SparseMap; /// A set of live ranges, indexed by value number. diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index dbb57a5b3e..0e59b14e2e 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -53,11 +53,11 @@ //! - Swizzle and shuffle instructions take a variable number of lane arguments. The number //! of arguments must match the destination type, and the lane indexes must be in range. -use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value}; -use ir::instructions::{InstructionFormat, BranchInfo}; -use ir::entities::AnyEntity; -use cfg::ControlFlowGraph; use dominator_tree::DominatorTree; +use flowgraph::ControlFlowGraph; +use ir::entities::AnyEntity; +use ir::instructions::{InstructionFormat, BranchInfo}; +use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value}; use std::fmt::{self, Display, Formatter}; use std::result; diff --git a/src/filetest/domtree.rs b/src/filetest/domtree.rs index 84b63177ec..29bde6d13f 100644 --- a/src/filetest/domtree.rs +++ b/src/filetest/domtree.rs @@ -11,14 +11,14 @@ //! We verify that the dominator tree annotations are complete and correct. //! -use std::collections::HashMap; -use std::borrow::{Borrow, Cow}; +use cretonne::dominator_tree::DominatorTree; +use cretonne::flowgraph::ControlFlowGraph; use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; -use cretonne::cfg::ControlFlowGraph; -use cretonne::dominator_tree::DominatorTree; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result}; +use std::borrow::{Borrow, Cow}; +use std::collections::HashMap; use utils::match_directive; struct TestDomtree; diff --git a/src/print_cfg.rs b/src/print_cfg.rs index 206f5430c2..d5b87d806b 100644 --- a/src/print_cfg.rs +++ b/src/print_cfg.rs @@ -7,12 +7,12 @@ use std::borrow::Cow; use std::fmt::{Result, Write, Display, Formatter}; use CommandResult; -use utils::read_to_string; -use filetest::subtest::{self, SubTest, Context, Result as STResult}; +use cretonne::flowgraph::ControlFlowGraph; use cretonne::ir::Function; -use cretonne::cfg::ControlFlowGraph; use cretonne::ir::instructions::BranchInfo; use cton_reader::{parse_functions, TestCommand}; +use filetest::subtest::{self, SubTest, Context, Result as STResult}; +use utils::read_to_string; pub fn run(files: Vec) -> CommandResult { for (i, f) in files.into_iter().enumerate() { diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index ff5cf6020c..f7fb119463 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -1,10 +1,10 @@ extern crate cretonne; extern crate cton_reader; -use self::cton_reader::parse_functions; -use self::cretonne::ir::Ebb; -use self::cretonne::cfg::ControlFlowGraph; use self::cretonne::entity_map::EntityMap; +use self::cretonne::flowgraph::ControlFlowGraph; +use self::cretonne::ir::Ebb; +use self::cton_reader::parse_functions; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &parse_functions(function_source).unwrap()[0]; From e941a7db5f1c934b0c1f3f12d3c23fa5e2e737fd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Mar 2017 15:48:08 -0700 Subject: [PATCH 615/968] Add a ControlFlowGraph argument to legalize_function. Legalizing some instructions may require modifications to the control flow graph, and some operations need to use the CFG analysis. The CFG reference is threaded through all the legalization functions to reach the generated expansion functions as well as the legalizer::split module where it will be used first. --- lib/cretonne/meta/gen_legalizer.py | 9 +++--- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/legalizer/boundary.rs | 38 +++++++++++++++++--------- lib/cretonne/src/legalizer/mod.rs | 11 ++++---- lib/cretonne/src/legalizer/split.rs | 13 +++++++-- src/filetest/legalizer.rs | 4 ++- src/filetest/regalloc.rs | 3 +- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 10e9641df7..9131b5d19e 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -140,7 +140,7 @@ def emit_dst_inst(node, fmt): # special functions in the `legalizer::split` module. These functions # will eliminate concat-split patterns. fmt.line( - 'let {} = split::{}(dfg, pos, {});' + 'let {} = split::{}(dfg, cfg, pos, {});' .format( wrap_tup(node.defs), node.expr.inst.snake_name(), @@ -220,9 +220,10 @@ def gen_xform_group(xgrp, fmt): fmt.doc_comment("Legalize the instruction pointed to by `pos`.") fmt.line('#[allow(unused_variables,unused_assignments)]') with fmt.indented( - 'fn ' + xgrp.name + - '(pos: &mut Cursor, dfg: &mut DataFlowGraph) -> bool {', - '}'): + 'fn {}(dfg: &mut DataFlowGraph, ' + 'cfg: &mut ControlFlowGraph, pos: &mut Cursor) -> ' + 'bool {{'.format(xgrp.name), '}'): + # Gen the instruction to be legalized. The cursor we're passed must be # pointing at an instruction. fmt.line('let inst = pos.current_inst().expect("need instruction");') diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index a4fc155c41..9ce9d4912e 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -47,7 +47,7 @@ impl Context { /// Run the legalizer for `isa` on the function. pub fn legalize(&mut self, isa: &TargetIsa) { - legalize_function(&mut self.func, isa); + legalize_function(&mut self.func, &mut self.cfg, isa); } /// Recompute the control flow graph and dominator tree. diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index a53526232a..893e9097b7 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -18,6 +18,7 @@ //! intermediate state doesn't type check. use abi::{legalize_abi_value, ValueConversion}; +use flowgraph::ControlFlowGraph; use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef, ArgumentType}; use ir::instructions::CallInfo; @@ -257,6 +258,7 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, /// return the `Err(ArgumentType)` that is needed. /// fn convert_to_abi(dfg: &mut DataFlowGraph, + cfg: &ControlFlowGraph, pos: &mut Cursor, value: Value, put_arg: &mut PutArg) @@ -272,28 +274,28 @@ fn convert_to_abi(dfg: &mut DataFlowGraph, let ty = dfg.value_type(value); match legalize_abi_value(ty, &arg_type) { ValueConversion::IntSplit => { - let (lo, hi) = isplit(dfg, pos, value); - convert_to_abi(dfg, pos, lo, put_arg); - convert_to_abi(dfg, pos, hi, put_arg); + let (lo, hi) = isplit(dfg, cfg, pos, value); + convert_to_abi(dfg, cfg, pos, lo, put_arg); + convert_to_abi(dfg, cfg, pos, hi, put_arg); } ValueConversion::VectorSplit => { - let (lo, hi) = vsplit(dfg, pos, value); - convert_to_abi(dfg, pos, lo, put_arg); - convert_to_abi(dfg, pos, hi, put_arg); + let (lo, hi) = vsplit(dfg, cfg, pos, value); + convert_to_abi(dfg, cfg, pos, lo, put_arg); + convert_to_abi(dfg, cfg, pos, hi, put_arg); } ValueConversion::IntBits => { assert!(!ty.is_int()); let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); let arg = dfg.ins(pos).bitcast(abi_ty, value); - convert_to_abi(dfg, pos, arg, put_arg); + convert_to_abi(dfg, cfg, pos, arg, put_arg); } ValueConversion::Sext(abi_ty) => { let arg = dfg.ins(pos).sextend(abi_ty, value); - convert_to_abi(dfg, pos, arg, put_arg); + convert_to_abi(dfg, cfg, pos, arg, put_arg); } ValueConversion::Uext(abi_ty) => { let arg = dfg.ins(pos).uextend(abi_ty, value); - convert_to_abi(dfg, pos, arg, put_arg); + convert_to_abi(dfg, cfg, pos, arg, put_arg); } } } @@ -361,6 +363,7 @@ fn check_return_signature(dfg: &DataFlowGraph, inst: Inst, sig: &Signature) -> b /// argument number in `0..abi_args`. /// fn legalize_inst_arguments(dfg: &mut DataFlowGraph, + cfg: &ControlFlowGraph, pos: &mut Cursor, abi_args: usize, mut get_abi_type: ArgType) @@ -419,7 +422,7 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, Err(abi_type) } }; - convert_to_abi(dfg, pos, old_value, &mut put_arg); + convert_to_abi(dfg, cfg, pos, old_value, &mut put_arg); } // Put the modified value list back. @@ -436,7 +439,7 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, /// original return values. The call's result values will be adapted to match the new signature. /// /// Returns `true` if any instructions were inserted. -pub fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { +pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mut Cursor) -> bool { let mut inst = pos.current_inst().expect("Cursor must point to a call instruction"); // Start by checking if the argument types already match the signature. @@ -448,6 +451,7 @@ pub fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { // OK, we need to fix the call arguments to match the ABI signature. let abi_args = dfg.signatures[sig_ref].argument_types.len(); legalize_inst_arguments(dfg, + cfg, pos, abi_args, |dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]); @@ -471,7 +475,11 @@ pub fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { /// Insert ABI conversion code before and after the call instruction at `pos`. /// /// Return `true` if any instructions were inserted. -pub fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signature) -> bool { +pub fn handle_return_abi(dfg: &mut DataFlowGraph, + cfg: &ControlFlowGraph, + pos: &mut Cursor, + sig: &Signature) + -> bool { let inst = pos.current_inst().expect("Cursor must point to a return instruction"); // Check if the returned types already match the signature. @@ -480,7 +488,11 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signat } let abi_args = sig.return_types.len(); - legalize_inst_arguments(dfg, pos, abi_args, |_, abi_arg| sig.return_types[abi_arg]); + legalize_inst_arguments(dfg, + cfg, + pos, + abi_args, + |_, abi_arg| sig.return_types[abi_arg]); debug_assert!(check_return_signature(dfg, inst, sig), "Signature still wrong: {}, sig{}", diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 56e3e10dd6..deb0bbc6b1 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -13,6 +13,7 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. +use flowgraph::ControlFlowGraph; use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; use ir::condcodes::IntCC; use isa::{TargetIsa, Legalize}; @@ -25,7 +26,7 @@ mod split; /// - Transform any instructions that don't have a legal representation in `isa`. /// - Fill out `func.encodings`. /// -pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { +pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: &TargetIsa) { boundary::legalize_signatures(func, isa); // TODO: This is very simplified and incomplete. @@ -40,14 +41,14 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { let opcode = func.dfg[inst].opcode(); // Check for ABI boundaries that need to be converted to the legalized signature. - if opcode.is_call() && boundary::handle_call_abi(&mut func.dfg, &mut pos) { + if opcode.is_call() && boundary::handle_call_abi(&mut func.dfg, cfg, &mut pos) { // Go back and legalize the inserted argument conversion instructions. pos.set_position(prev_pos); continue; } if opcode.is_return() && - boundary::handle_return_abi(&mut func.dfg, &mut pos, &func.signature) { + boundary::handle_return_abi(&mut func.dfg, cfg, &mut pos, &func.signature) { // Go back and legalize the inserted return value conversion instructions. pos.set_position(prev_pos); continue; @@ -70,8 +71,8 @@ pub fn legalize_function(func: &mut Function, isa: &TargetIsa) { // 4. TODO: Convert to library calls. For example, floating point operations on // an ISA with no IEEE 754 support. let changed = match action { - Legalize::Expand => expand(&mut pos, &mut func.dfg), - Legalize::Narrow => narrow(&mut pos, &mut func.dfg), + Legalize::Expand => expand(&mut func.dfg, cfg, &mut pos), + Legalize::Narrow => narrow(&mut func.dfg, cfg, &mut pos), }; // If the current instruction was replaced, we need to double back and revisit // the expanded sequence. This is both to assign encodings and possible to diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index b8323a59fb..7b36c4997e 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -64,17 +64,26 @@ //! It is possible to have circular dependencies of EBB arguments that are never used by any real //! instructions. These loops will remain in the program. +use flowgraph::ControlFlowGraph; use ir::{DataFlowGraph, Cursor, Value, Opcode, ValueDef, InstructionData, InstBuilder}; /// Split `value` into two values using the `isplit` semantics. Do this by reusing existing values /// if possible. -pub fn isplit(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) { +pub fn isplit(dfg: &mut DataFlowGraph, + _cfg: &ControlFlowGraph, + pos: &mut Cursor, + value: Value) + -> (Value, Value) { split_value(dfg, pos, value, Opcode::Iconcat) } /// Split `value` into halves using the `vsplit` semantics. Do this by reusing existing values if /// possible. -pub fn vsplit(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) { +pub fn vsplit(dfg: &mut DataFlowGraph, + _cfg: &ControlFlowGraph, + pos: &mut Cursor, + value: Value) + -> (Value, Value) { split_value(dfg, pos, value, Opcode::Vconcat) } diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index b7c3345436..25247a5aba 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -5,6 +5,7 @@ use std::borrow::Cow; use cretonne::{legalize_function, write_function}; +use cretonne::flowgraph::ControlFlowGraph; use cretonne::ir::Function; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; @@ -36,7 +37,8 @@ impl SubTest for TestLegalizer { fn run(&self, func: Cow, context: &Context) -> Result<()> { let mut func = func.into_owned(); let isa = context.isa.expect("legalizer needs an ISA"); - legalize_function(&mut func, isa); + let mut cfg = ControlFlowGraph::with_function(&func); + legalize_function(&mut func, &mut cfg, isa); let mut text = String::new(); write_function(&mut text, &func, Some(isa)).map_err(|e| e.to_string())?; diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index 90796c1b85..f64d7bb830 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -42,10 +42,9 @@ impl SubTest for TestRegalloc { let mut comp_ctx = cretonne::Context::new(); comp_ctx.func = func.into_owned(); + comp_ctx.flowgraph(); // TODO: Should we have an option to skip legalization? comp_ctx.legalize(isa); - - comp_ctx.flowgraph(); comp_ctx.regalloc(isa); let mut text = String::new(); From 2ebbc78f74b50e5526afc58609bfd4c1ff597b0e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 22 Mar 2017 10:36:42 -0700 Subject: [PATCH 616/968] Add a DataFlowGraph::replace_ebb_arg() method. This is needed for rewriting EBB arguments with legalized types. --- lib/cretonne/src/ir/dfg.rs | 93 +++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 8977cc6751..e86b87de32 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -261,7 +261,7 @@ pub enum ValueDef { } // Internal table storage for extended values. -#[derive(Clone)] +#[derive(Clone, Debug)] enum ValueData { // Value is defined by an instruction, but it is not the first result. Inst { @@ -663,6 +663,67 @@ impl DataFlowGraph { } } + /// Replace an EBB argument with a new value of type `ty`. + /// + /// The `old_value` must be an attached EBB argument. It is removed from its place in the list + /// of arguments and replaced by a new value of type `new_type`. The new value gets the same + /// position in the list, and other arguments are not disturbed. + /// + /// The old value is left detached, so it should probably be changed into something else. + /// + /// Returns the new value. + pub fn replace_ebb_arg(&mut self, old_arg: Value, new_type: Type) -> Value { + let old_data = if let ExpandedValue::Table(index) = old_arg.expand() { + self.extended_values[index].clone() + } else { + panic!("old_arg: {} must be an EBB argument", old_arg); + }; + + // Create new value identical to the old one except for the type. + let (ebb, num, new_arg) = if let ValueData::Arg { num, ebb, next, .. } = old_data { + (ebb, + num, + self.make_value(ValueData::Arg { + ty: new_type, + num: num, + ebb: ebb, + next: next, + })) + } else { + panic!("old_arg: {} must be an EBB argument: {:?}", + old_arg, + old_data); + }; + + // Now fix up the linked lists. + if self.ebbs[ebb].last_arg.expand() == Some(old_arg) { + self.ebbs[ebb].last_arg = new_arg.into(); + } + + if self.ebbs[ebb].first_arg.expand() == Some(old_arg) { + assert_eq!(num, 0); + self.ebbs[ebb].first_arg = new_arg.into(); + } else { + // We need to find the num-1 argument value and change its next link. + let mut arg = self.ebbs[ebb].first_arg.expect("EBB has no arguments"); + for _ in 1..num { + arg = self.next_ebb_arg(arg).expect("Too few EBB arguments"); + } + if let ExpandedValue::Table(index) = arg.expand() { + if let ValueData::Arg { ref mut next, .. } = self.extended_values[index] { + assert_eq!(next.expand(), Some(old_arg)); + *next = new_arg.into(); + } else { + panic!("{} is not an EBB argument", arg); + } + } else { + panic!("{} is not an EBB argument", arg); + } + } + + new_arg + } + /// Given a value that is known to be an EBB argument, return the next EBB argument. pub fn next_ebb_arg(&self, arg: Value) -> Option { if let ExpandedValue::Table(index) = arg.expand() { @@ -886,6 +947,36 @@ mod tests { assert_eq!(dfg.ebb_args(ebb).collect::>(), [take2, arg3, take1]); } + #[test] + fn replace_ebb_arguments() { + let mut dfg = DataFlowGraph::new(); + + let ebb = dfg.make_ebb(); + let arg1 = dfg.append_ebb_arg(ebb, types::F32); + + let new1 = dfg.replace_ebb_arg(arg1, types::I64); + assert_eq!(dfg.value_type(arg1), types::F32); + assert_eq!(dfg.value_type(new1), types::I64); + assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1]); + + dfg.attach_ebb_arg(ebb, arg1); + assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, arg1]); + + let new2 = dfg.replace_ebb_arg(arg1, types::I8); + assert_eq!(dfg.value_type(arg1), types::F32); + assert_eq!(dfg.value_type(new2), types::I8); + assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, new2]); + + dfg.attach_ebb_arg(ebb, arg1); + assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, new2, arg1]); + + let new3 = dfg.replace_ebb_arg(new2, types::I16); + assert_eq!(dfg.value_type(new1), types::I64); + assert_eq!(dfg.value_type(new2), types::I8); + assert_eq!(dfg.value_type(new3), types::I16); + assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, new3, arg1]); + } + #[test] fn aliases() { use ir::InstBuilder; From 19db81f6a81fc9a4f960b4be928afc2e05d61548 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 21 Mar 2017 14:38:42 -0700 Subject: [PATCH 617/968] Iteratively split EBB arguments. When the legalizer splits a value into halves, it would previously stop if the value was an EBB argument. With this change, we also split EBB arguments and iteratively split arguments on branches to the EBB. The iterative splitting stops when we hit the entry block arguments or an instruction that isn't one of the concatenation instructions. --- filetests/isa/riscv/split-args.cton | 40 +++++++ lib/cretonne/meta/base/instructions.py | 2 +- lib/cretonne/src/ir/layout.rs | 3 +- lib/cretonne/src/legalizer/split.rs | 152 +++++++++++++++++++++++-- 4 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 filetests/isa/riscv/split-args.cton diff --git a/filetests/isa/riscv/split-args.cton b/filetests/isa/riscv/split-args.cton new file mode 100644 index 0000000000..1cecb85c10 --- /dev/null +++ b/filetests/isa/riscv/split-args.cton @@ -0,0 +1,40 @@ +; Test the legalization of EBB arguments that are split. +test legalizer +isa riscv + +; regex: V=vx?\d+ + +function simple(i64, i64) -> i64 { +ebb0(v1: i64, v2: i64): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): + jump ebb1(v1) + ; check: jump $ebb1($v1l, $v1h) + +ebb1(v3: i64): +; check: $ebb1($(v3l=$V): i32, $(v3h=$V): i32): + v4 = band v3, v2 + ; check: $(v4l=$V) = band $v3l, $v2l + ; check: $(v4h=$V) = band $v3h, $v2h + return v4 + ; check: return $v4l, $v4h +} + +function multi(i64) -> i64 { +ebb1(v1: i64): +; check: $ebb1($(v1l=$V): i32, $(v1h=$V): i32): + jump ebb2(v1, v1) + ; check: jump $ebb2($v1l, $v1l, $v1h, $v1h) + +ebb2(v2: i64, v3: i64): +; check: $ebb2($(v2l=$V): i32, $(v3l=$V): i32, $(v2h=$V): i32, $(v3h=$V): i32): + jump ebb3(v2) + ; check: jump $ebb3($v2l, $v2h) + +ebb3(v4: i64): +; check: $ebb3($(v4l=$V): i32, $(v4h=$V): i32): + v5 = band v4, v3 + ; check: $(v5l=$V) = band $v4l, $v3l + ; check: $(v5h=$V) = band $v4h, $v3h + return v5 + ; check: return $v5l, $v5h +} diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 66dc0e9090..ade9b540bd 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -44,7 +44,7 @@ jump = Instruction( EBB arguments. The number and types of arguments must match the destination EBB. """, - ins=(EBB, args), is_terminator=True) + ins=(EBB, args), is_branch=True, is_terminator=True) brz = Instruction( 'brz', r""" diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 83edbea76f..c34d6a0aba 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -588,7 +588,8 @@ impl<'f> DoubleEndedIterator for Insts<'f> { /// When new instructions are added, the cursor can either append them to an EBB or insert them /// before the current instruction. pub struct Cursor<'f> { - layout: &'f mut Layout, + /// Borrowed function layout. Public so it can be re-borrowed from this cursor. + pub layout: &'f mut Layout, pos: CursorPosition, } diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index 7b36c4997e..d58c7ed285 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -65,26 +65,103 @@ //! instructions. These loops will remain in the program. use flowgraph::ControlFlowGraph; -use ir::{DataFlowGraph, Cursor, Value, Opcode, ValueDef, InstructionData, InstBuilder}; +use ir::{DataFlowGraph, Ebb, Cursor, Value, Type, Opcode, ValueDef, InstructionData, InstBuilder}; +use std::iter; /// Split `value` into two values using the `isplit` semantics. Do this by reusing existing values /// if possible. pub fn isplit(dfg: &mut DataFlowGraph, - _cfg: &ControlFlowGraph, + cfg: &ControlFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) { - split_value(dfg, pos, value, Opcode::Iconcat) + split_any(dfg, cfg, pos, value, Opcode::Iconcat) } /// Split `value` into halves using the `vsplit` semantics. Do this by reusing existing values if /// possible. pub fn vsplit(dfg: &mut DataFlowGraph, - _cfg: &ControlFlowGraph, + cfg: &ControlFlowGraph, pos: &mut Cursor, value: Value) -> (Value, Value) { - split_value(dfg, pos, value, Opcode::Vconcat) + split_any(dfg, cfg, pos, value, Opcode::Vconcat) +} + +/// After splitting an EBB argument, we need to go back and fix up all of the predecessor +/// instructions. This is potentially a recursive operation, but we don't implement it recursively +/// since that could use up too muck stack. +/// +/// Instead, the repairs are deferred and placed on a work list in stack form. +struct Repair { + concat: Opcode, + // The argument type after splitting. + split_type: Type, + // The destination EBB whose arguments have been split. + ebb: Ebb, + // Number of the original EBB argument which has been replaced by the low part. + num: usize, + // Number of the new EBB argument which represents the high part after the split. + hi_num: usize, +} + +/// Generic version of `isplit` and `vsplit` controlled by the `concat` opcode. +fn split_any(dfg: &mut DataFlowGraph, + cfg: &ControlFlowGraph, + pos: &mut Cursor, + value: Value, + concat: Opcode) + -> (Value, Value) { + let saved_pos = pos.position(); + let mut repairs = Vec::new(); + let result = split_value(dfg, pos, value, concat, &mut repairs); + + // We have split the value requested, and now we may need to fix some EBB predecessors. + while let Some(repair) = repairs.pop() { + for &(_, inst) in cfg.get_predecessors(repair.ebb) { + let branch_opc = dfg[inst].opcode(); + assert!(branch_opc.is_branch(), + "Predecessor not a branch: {}", + dfg.display_inst(inst)); + let fixed_args = branch_opc.constraints().fixed_value_arguments(); + let mut args = dfg[inst].take_value_list().expect("Branches must have value lists."); + let num_args = args.len(&dfg.value_lists); + // Get the old value passed to the EBB argument we're repairing. + let old_arg = args.get(fixed_args + repair.num, &dfg.value_lists) + .expect("Too few branch arguments"); + + // It's possible that the CFG's predecessor list has duplicates. Detect them here. + if dfg.value_type(old_arg) == repair.split_type { + dfg[inst].put_value_list(args); + continue; + } + + // Split the old argument, possibly causing more repairs to be scheduled. + pos.goto_inst(inst); + let (lo, hi) = split_value(dfg, pos, old_arg, repair.concat, &mut repairs); + + // The `lo` part replaces the original argument. + *args.get_mut(fixed_args + repair.num, &mut dfg.value_lists).unwrap() = lo; + + // The `hi` part goes at the end. Since multiple repairs may have been scheduled to the + // same EBB, there could be multiple arguments missing. + if num_args > fixed_args + repair.hi_num { + *args.get_mut(fixed_args + repair.hi_num, &mut dfg.value_lists).unwrap() = hi; + } else { + // We need to append one or more arguments. If we're adding more than one argument, + // there must be pending repairs on the stack that will fill in the correct values + // instead of `hi`. + args.extend(iter::repeat(hi).take(1 + fixed_args + repair.hi_num - num_args), + &mut dfg.value_lists); + } + + // Put the value list back after manipulating it. + dfg[inst].put_value_list(args); + } + } + + pos.set_position(saved_pos); + result } /// Split a single value using the integer or vector semantics given by the `concat` opcode. @@ -96,7 +173,8 @@ pub fn vsplit(dfg: &mut DataFlowGraph, fn split_value(dfg: &mut DataFlowGraph, pos: &mut Cursor, value: Value, - concat: Opcode) + concat: Opcode, + repairs: &mut Vec) -> (Value, Value) { let value = dfg.resolve_copies(value); let mut reuse = None; @@ -112,14 +190,56 @@ fn split_value(dfg: &mut DataFlowGraph, } } } - ValueDef::Arg(_ebb, _num) => {} + ValueDef::Arg(ebb, num) => { + // This is an EBB argument. We can split the argument value unless this is the entry + // block. + if pos.layout.entry_block() != Some(ebb) { + // We are going to replace the argument at `num` with two new arguments. + // Determine the new value types. + let ty = dfg.value_type(value); + let split_type = match concat { + Opcode::Iconcat => ty.half_width().expect("Invalid type for isplit"), + Opcode::Vconcat => ty.half_vector().expect("Invalid type for vsplit"), + _ => panic!("Unhandled concat opcode: {}", concat), + }; + + // Since the `repairs` stack potentially contains other argument numbers for `ebb`, + // avoid shifting and renumbering EBB arguments. It could invalidate other + // `repairs` entries. + // + // Replace the original `value` with the low part, and append the high part at the + // end of the argument list. + let lo = dfg.replace_ebb_arg(value, split_type); + let hi_num = dfg.num_ebb_args(ebb); + let hi = dfg.append_ebb_arg(ebb, split_type); + reuse = Some((lo, hi)); + + + // Now the original value is dangling. Insert a concatenation instruction that can + // compute it from the two new arguments. This also serves as a record of what we + // did so a future call to this function doesn't have to redo the work. + // + // Note that it is safe to move `pos` here since `reuse` was set above, so we don't + // need to insert a split instruction before returning. + pos.goto_top(ebb); + pos.next_inst(); + let concat_inst = dfg.ins(pos).Binary(concat, ty, lo, hi).0; + let concat_val = dfg.first_result(concat_inst); + dfg.change_to_alias(value, concat_val); + + // Finally, splitting the EBB argument is not enough. We also have to repair all + // of the predecessor instructions that branch here. + add_repair(concat, split_type, ebb, num, hi_num, repairs); + } + } } // Did the code above succeed in finding values we can reuse? if let Some(pair) = reuse { pair } else { - // No, we'll just have to insert the requested split instruction at `pos`. + // No, we'll just have to insert the requested split instruction at `pos`. Note that `pos` + // has not been moved by the EBB argument code above when `reuse` is `None`. match concat { Opcode::Iconcat => dfg.ins(pos).isplit(value), Opcode::Vconcat => dfg.ins(pos).vsplit(value), @@ -127,3 +247,19 @@ fn split_value(dfg: &mut DataFlowGraph, } } } + +// Add a repair entry to the work list. +fn add_repair(concat: Opcode, + split_type: Type, + ebb: Ebb, + num: usize, + hi_num: usize, + repairs: &mut Vec) { + repairs.push(Repair { + concat: concat, + split_type: split_type, + ebb: ebb, + num: num, + hi_num: hi_num, + }); +} From 098a48e3324f6c652f9cdecdea29a91d3a9e83b2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 22 Mar 2017 13:17:25 -0700 Subject: [PATCH 618/968] Fix weird indentation. --- lib/cretonne/src/ir/dfg.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index e86b87de32..ae5e10f6ab 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -178,24 +178,24 @@ impl DataFlowGraph { /// copy/spill/fill instructions. pub fn resolve_copies(&self, value: Value) -> Value { use ir::entities::ExpandedValue::Direct; - let mut v = self.resolve_aliases(value); + let mut v = value; for _ in 0..self.insts.len() { - v = self.resolve_aliases(match v.expand() { - Direct(inst) => { - match self[inst] { - InstructionData::Unary { opcode, arg, .. } => { - match opcode { - Opcode::Copy | Opcode::Spill | - Opcode::Fill => arg, - _ => return v, - } - } - _ => return v, - } - } - _ => return v, - }); + v = self.resolve_aliases(v); + v = match v.expand() { + Direct(inst) => { + match self[inst] { + InstructionData::Unary { opcode, arg, .. } => { + match opcode { + Opcode::Copy | Opcode::Spill | Opcode::Fill => arg, + _ => return v, + } + } + _ => return v, + } + } + _ => return v, + }; } panic!("Copy loop detected for {}", value); } From 07740b5e3cc14428ec3cd4be8c3ba662665bf19c Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 22 Mar 2017 06:02:07 +0800 Subject: [PATCH 619/968] Limit type inference for controlling type variables in write.rs --- filetests/parser/branch.cton | 4 ++-- lib/cretonne/src/write.rs | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/filetests/parser/branch.cton b/filetests/parser/branch.cton index 46612973af..1aa0ca185a 100644 --- a/filetests/parser/branch.cton +++ b/filetests/parser/branch.cton @@ -62,7 +62,7 @@ ebb1: ; nextln: brz vx0, ebb1 ; nextln: ; nextln: ebb1: -; nextln: brnz vx0, ebb1 +; nextln: brnz.i32 vx0, ebb1 ; nextln: } function twoargs(i32, f32) { @@ -77,7 +77,7 @@ ebb1(vx2: i32, vx3: f32): ; nextln: brz vx0, ebb1(vx0, vx1) ; nextln: ; nextln: ebb1(vx2: i32, vx3: f32): -; nextln: brnz vx0, ebb0(vx2, vx3) +; nextln: brnz.i32 vx0, ebb0(vx2, vx3) ; nextln: } function jumptable(i32) { diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index dfa7688f20..5ff48ec91c 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -4,7 +4,7 @@ //! equivalent textual representation. This textual representation can be read back by the //! `cretonne-reader` crate. -use ir::{Function, DataFlowGraph, Ebb, Inst, Value, Type}; +use ir::{Function, DataFlowGraph, Ebb, Inst, Value, ValueDef, Type}; use isa::{TargetIsa, RegInfo}; use std::fmt::{self, Result, Error, Write}; use std::result; @@ -132,7 +132,8 @@ pub fn write_ebb(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, ebb: E // if it can't be trivially inferred. // fn type_suffix(func: &Function, inst: Inst) -> Option { - let constraints = func.dfg[inst].opcode().constraints(); + let inst_data = &func.dfg[inst]; + let constraints = inst_data.opcode().constraints(); if !constraints.is_polymorphic() { return None; @@ -140,16 +141,18 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { // If the controlling type variable can be inferred from the type of the designated value input // operand, we don't need the type suffix. - // TODO: Should we include the suffix when the input value is defined in another block? The - // parser needs to know the type of the value, so it must be defined in a block that lexically - // comes before this one. if constraints.use_typevar_operand() { - return None; + let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap(); + let def_ebb = match func.dfg.value_def(ctrl_var) { + ValueDef::Res(instr, _) => func.layout.inst_ebb(instr), + ValueDef::Arg(ebb, _) => Some(ebb), + }; + if def_ebb.is_some() && def_ebb == func.layout.inst_ebb(inst) { + return None; + } } - // This polymorphic instruction doesn't support basic type inference. - // The controlling type variable is required to be the type of the first result. - let rtype = func.dfg.value_type(func.dfg.first_result(inst)); + let rtype = inst_data.ctrl_typevar(&func.dfg); assert!(!rtype.is_void(), "Polymorphic instruction must produce a result"); Some(rtype) From 12b2af3f8a4a60a6fccbd2fe2a6fbc0962e07131 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 22 Mar 2017 15:26:06 -0700 Subject: [PATCH 620/968] Simplify branch arguments after splitting EBB arguments. The EBB argument splitting may generate concat-split dependencies when it repairs branch arguments in EBBs that have not yet been fully legalized. Add a branch argument simplification step that can resolve these dependency chains. This means that all split and concatenation instructions will be dead after legalization for types that have no legal instructions using them. --- filetests/isa/riscv/legalize-abi.cton | 10 +++-- filetests/isa/riscv/split-args.cton | 15 +++++++ lib/cretonne/src/legalizer/mod.rs | 4 ++ lib/cretonne/src/legalizer/split.rs | 62 ++++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 5 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 926a3c2820..06fcfd5f6c 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -36,7 +36,8 @@ ebb0: ; check: $(v1new=$V) = iconcat $v1l, $v1h ; check: $v1 = copy $v1new jump ebb1(v1) - ; check: jump $ebb1($v1) + ; The v1 copy gets resolved by split::simplify_branch_arguments(). + ; check: jump $ebb1($v1new) ebb1(v10: i64): jump ebb1(v10) @@ -50,9 +51,9 @@ ebb0: ; check: $ebb0: ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() ; check: $(v2new=$V) = iconcat $v2l, $v2h - ; check: $v2 -> $v2new jump ebb1(v1, v2) - ; check: jump $ebb1($v1, $v2) + ; The v2 -> v2new alias is resolved by split::simplify_branch_arguments(). + ; check: jump $ebb1($v1, $v2new) ebb1(v9: i32, v10: i64): jump ebb1(v9, v10) @@ -78,7 +79,8 @@ ebb0: ; check: $(v1new=$V) = ireduce.i8 $rv ; check: $v1 = copy $v1new jump ebb1(v1) - ; check: jump $ebb1($v1) + ; The v1 copy gets resolved by split::simplify_branch_arguments(). + ; check: jump $ebb1($v1new) ebb1(v10: i8): jump ebb1(v10) diff --git a/filetests/isa/riscv/split-args.cton b/filetests/isa/riscv/split-args.cton index 1cecb85c10..8c69378f42 100644 --- a/filetests/isa/riscv/split-args.cton +++ b/filetests/isa/riscv/split-args.cton @@ -38,3 +38,18 @@ ebb3(v4: i64): return v5 ; check: return $v5l, $v5h } + +function loop(i64, i64) -> i64 { +ebb0(v1: i64, v2: i64): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): + jump ebb1(v1) + ; check: jump $ebb1($v1l, $v1h) + +ebb1(v3: i64): +; check: $ebb1($(v3l=$V): i32, $(v3h=$V): i32): + v4 = band v3, v2 + ; check: $(v4l=$V) = band $v3l, $v2l + ; check: $(v4h=$V) = band $v3h, $v2h + jump ebb1(v4) + ; check: jump $ebb1($v4l, $v4h) +} diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index deb0bbc6b1..b9dc493527 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -54,6 +54,10 @@ pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: & continue; } + if opcode.is_branch() { + split::simplify_branch_arguments(&mut func.dfg, inst); + } + match isa.encode(&func.dfg, &func.dfg[inst]) { Ok(encoding) => *func.encodings.ensure(inst) = encoding, Err(action) => { diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index d58c7ed285..43a2c4d385 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -65,7 +65,8 @@ //! instructions. These loops will remain in the program. use flowgraph::ControlFlowGraph; -use ir::{DataFlowGraph, Ebb, Cursor, Value, Type, Opcode, ValueDef, InstructionData, InstBuilder}; +use ir::{DataFlowGraph, Ebb, Inst, Cursor, Value, Type, Opcode, ValueDef, InstructionData, + InstBuilder}; use std::iter; /// Split `value` into two values using the `isplit` semantics. Do this by reusing existing values @@ -263,3 +264,62 @@ fn add_repair(concat: Opcode, hi_num: hi_num, }); } + +/// Strip concat-split chains. Return a simpler way of computing the same value. +/// +/// Given this input: +/// +/// ```cton +/// v10 = iconcat v1, v2 +/// v11, v12 = isplit v10 +/// ``` +/// +/// This function resolves `v11` to `v1` and `v12` to `v2`. +fn resolve_splits(dfg: &DataFlowGraph, value: Value) -> Value { + let value = dfg.resolve_copies(value); + + // Deconstruct a split instruction. + let split_res; + let concat_opc; + let split_arg; + if let ValueDef::Res(inst, num) = dfg.value_def(value) { + split_res = num; + concat_opc = match dfg[inst].opcode() { + Opcode::Isplit => Opcode::Iconcat, + Opcode::Vsplit => Opcode::Vconcat, + _ => return value, + }; + split_arg = dfg[inst].arguments(&dfg.value_lists)[0]; + } else { + return value; + } + + // See if split_arg is defined by a concatenation instruction. + if let ValueDef::Res(inst, _) = dfg.value_def(split_arg) { + if dfg[inst].opcode() == concat_opc { + return dfg[inst].arguments(&dfg.value_lists)[split_res]; + } + } + + value +} + +/// Simplify the arguments to a branch *after* the instructions leading up to the branch have been +/// legalized. +/// +/// The branch argument repairs performed by `split_any()` above may be performed on branches that +/// have not yet been legalized. The repaired arguments can be defined by actual split +/// instructions in that case. +/// +/// After legalizing the instructions computing the value that was split, it is likely that we can +/// avoid depending on the split instruction. Its input probably comes from a concatenation. +pub fn simplify_branch_arguments(dfg: &mut DataFlowGraph, branch: Inst) { + let mut new_args = Vec::new(); + + for &arg in dfg[branch].arguments(&dfg.value_lists) { + let new_arg = resolve_splits(dfg, arg); + new_args.push(new_arg); + } + + dfg.insts[branch].arguments_mut(&mut dfg.value_lists).copy_from_slice(&new_args); +} From cd52b671e6f81a85bb797f357a10115919d3cd95 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 23 Mar 2017 09:42:36 -0700 Subject: [PATCH 621/968] Remove spurious shell redirections from install commands. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8bd8dd13e6..6f343fb258 100644 --- a/README.rst +++ b/README.rst @@ -65,7 +65,7 @@ Building the documentation To build the Cretonne documentation, you need the `Sphinx documentation generator `_:: - $ pip install sphinx>=1.4 sphinx-autobuild sphinx_rtd_theme + $ pip install sphinx sphinx-autobuild sphinx_rtd_theme $ cd cretonne/docs $ make html $ open _build/html/index.html From c5c9f211df2ec9c6e2cf51f41f24799bbc3a20fd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 23 Mar 2017 09:50:26 -0700 Subject: [PATCH 622/968] Legalize EBBs in a reverse post-order. This means that whenever we need to split a value, it is either already defined by a concatenation instruction in a previously processed EBB, or it's an EBB argument. --- lib/cretonne/src/legalizer/mod.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index b9dc493527..00354822a1 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -29,10 +29,16 @@ mod split; pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: &TargetIsa) { boundary::legalize_signatures(func, isa); - // TODO: This is very simplified and incomplete. func.encodings.resize(func.dfg.num_insts()); + + // Process EBBs in a reverse post-order. This minimizes the number of split instructions we + // need. + let mut postorder = cfg.postorder_ebbs(); let mut pos = Cursor::new(&mut func.layout); - while let Some(_ebb) = pos.next_ebb() { + + while let Some(ebb) = postorder.pop() { + pos.goto_top(ebb); + // Keep track of the cursor position before the instruction being processed, so we can // double back when replacing instructions. let mut prev_pos = pos.position(); From f710f139493c5ef46ea751c699189dc98d7d5c81 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 23 Mar 2017 12:39:05 -0700 Subject: [PATCH 623/968] Add dfg.inst_args(_mut) methods. A shortcut for calling arguments() directly that goes with the existing inst_results() method. --- lib/cretonne/src/ir/dfg.rs | 10 ++++++++++ lib/cretonne/src/legalizer/boundary.rs | 3 +-- lib/cretonne/src/legalizer/split.rs | 8 ++++---- lib/cretonne/src/regalloc/coloring.rs | 2 +- lib/cretonne/src/regalloc/liveness.rs | 2 +- lib/cretonne/src/verifier.rs | 2 +- lib/cretonne/src/write.rs | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index ae5e10f6ab..24727362b8 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -344,6 +344,16 @@ impl DataFlowGraph { DisplayInst(self, inst) } + /// Get the value arguments on `inst` as a slice. + pub fn inst_args(&self, inst: Inst) -> &[Value] { + self.insts[inst].arguments(&self.value_lists) + } + + /// Get the value arguments on `inst` as a mutable slice. + pub fn inst_args_mut(&mut self, inst: Inst) -> &mut [Value] { + self.insts[inst].arguments_mut(&mut self.value_lists) + } + /// Create result values for an instruction that produces multiple results. /// /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 893e9097b7..8ec8ffeb9c 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -348,8 +348,7 @@ fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Result<(), SigRef> { fn check_return_signature(dfg: &DataFlowGraph, inst: Inst, sig: &Signature) -> bool { let fixed_values = dfg[inst].opcode().constraints().fixed_value_arguments(); check_arg_types(dfg, - dfg[inst] - .arguments(&dfg.value_lists) + dfg.inst_args(inst) .iter() .skip(fixed_values) .cloned(), diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index 43a2c4d385..3dda54abe7 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -289,7 +289,7 @@ fn resolve_splits(dfg: &DataFlowGraph, value: Value) -> Value { Opcode::Vsplit => Opcode::Vconcat, _ => return value, }; - split_arg = dfg[inst].arguments(&dfg.value_lists)[0]; + split_arg = dfg.inst_args(inst)[0]; } else { return value; } @@ -297,7 +297,7 @@ fn resolve_splits(dfg: &DataFlowGraph, value: Value) -> Value { // See if split_arg is defined by a concatenation instruction. if let ValueDef::Res(inst, _) = dfg.value_def(split_arg) { if dfg[inst].opcode() == concat_opc { - return dfg[inst].arguments(&dfg.value_lists)[split_res]; + return dfg.inst_args(inst)[split_res]; } } @@ -316,10 +316,10 @@ fn resolve_splits(dfg: &DataFlowGraph, value: Value) -> Value { pub fn simplify_branch_arguments(dfg: &mut DataFlowGraph, branch: Inst) { let mut new_args = Vec::new(); - for &arg in dfg[branch].arguments(&dfg.value_lists) { + for &arg in dfg.inst_args(branch) { let new_arg = resolve_splits(dfg, arg); new_args.push(new_arg); } - dfg.insts[branch].arguments_mut(&mut dfg.value_lists).copy_from_slice(&new_args); + dfg.inst_args_mut(branch).copy_from_slice(&new_args); } diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 19800600b4..9bc10c30b3 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -302,7 +302,7 @@ impl<'a> Context<'a> { } ConstraintKind::Tied(arg_index) => { // This def must use the same register as a fixed instruction argument. - let arg = dfg[inst].arguments(&dfg.value_lists)[arg_index as usize]; + let arg = dfg.inst_args(inst)[arg_index as usize]; let loc = locations[arg]; *locations.ensure(lv.value) = loc; // Mark the reused register. It's not really clear if we support tied diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 0e384017c2..40faff0591 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -320,7 +320,7 @@ impl Liveness { .unwrap_or(&[]) .iter(); - for &arg in func.dfg[inst].arguments(&func.dfg.value_lists) { + for &arg in func.dfg.inst_args(inst) { // Get the live range, create it as a dead range if necessary. let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints); diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 0e59b14e2e..d3de786b6e 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -196,7 +196,7 @@ impl<'a> Verifier<'a> { fn verify_entity_references(&self, inst: Inst) -> Result<()> { use ir::instructions::InstructionData::*; - for &arg in self.func.dfg[inst].arguments(&self.func.dfg.value_lists) { + for &arg in self.func.dfg.inst_args(inst) { self.verify_value(inst, arg)?; } diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 5ff48ec91c..548f718099 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -160,7 +160,7 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { // Write out any value aliases appearing in `inst`. fn write_value_aliases(w: &mut Write, func: &Function, inst: Inst, indent: usize) -> Result { - for &arg in func.dfg[inst].arguments(&func.dfg.value_lists) { + for &arg in func.dfg.inst_args(inst) { let resolved = func.dfg.resolve_aliases(arg); if resolved != arg { writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)?; From bbbae4dc47484f93bfb934c281bd517915f7e51e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 23 Mar 2017 12:31:29 -0700 Subject: [PATCH 624/968] Start the binemit module. This module will provide supporting code for emitting binary machine code with relocations. --- lib/cretonne/src/binemit/mod.rs | 33 +++++++++++++++++++++++++++++++++ lib/cretonne/src/lib.rs | 1 + 2 files changed, 34 insertions(+) create mode 100644 lib/cretonne/src/binemit/mod.rs diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs new file mode 100644 index 0000000000..d3c97ec539 --- /dev/null +++ b/lib/cretonne/src/binemit/mod.rs @@ -0,0 +1,33 @@ +//! Binary machine code emission. +//! +//! The `binemit` module contains code for translating Cretonne's intermediate representation into +//! binary machine code. + +use ir::{FuncRef, JumpTable}; + +/// Relocation kinds depend on the current ISA. +pub struct Reloc(u16); + +/// Abstract interface for adding bytes to the code segment. +/// +/// A `CodeSink` will receive all of the machine code for a function. It also accepts relocations +/// which are locations in the code section that need to be fixed up when linking. +pub trait CodeSink { + /// Add 1 byte to the code section. + fn put1(&mut self, u8); + + /// Add 2 bytes to the code section. + fn put2(&mut self, u16); + + /// Add 4 bytes to the code section. + fn put4(&mut self, u32); + + /// Add 8 bytes to the code section. + fn put8(&mut self, u64); + + /// Add a relocation referencing an external function at the current offset. + fn reloc_func(&mut self, Reloc, FuncRef); + + /// Add a relocation referencing a jump table. + fn reloc_jt(&mut self, Reloc, JumpTable); +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 079b9a4559..b4c163b14d 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -10,6 +10,7 @@ pub use write::write_function; /// Version number of the cretonne crate. pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +pub mod binemit; pub mod flowgraph; pub mod dominator_tree; pub mod entity_list; From ca2b1c79d7337e1efa23778fd720e961b66292ef Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 27 Mar 2017 16:11:54 -0700 Subject: [PATCH 625/968] Generate binemit::emit_inst() functions. Use the meta language encoding recipes to generate an emit_inst() function for each ISA. The generated calls into recipe_*() functions that must be implemented by hand. Implement recipe_*() functions for the RISC-V recipes. Add the TargetIsa::emit_inst() entry point which emits an instruction to a CodeSink trait object. --- lib/cretonne/meta/build.py | 2 ++ lib/cretonne/meta/gen_binemit.py | 46 ++++++++++++++++++++++++ lib/cretonne/src/binemit/mod.rs | 10 +++++- lib/cretonne/src/ir/valueloc.rs | 8 +++++ lib/cretonne/src/isa/arm32/binemit.rs | 6 ++++ lib/cretonne/src/isa/arm32/mod.rs | 13 +++++-- lib/cretonne/src/isa/arm64/binemit.rs | 6 ++++ lib/cretonne/src/isa/arm64/mod.rs | 13 +++++-- lib/cretonne/src/isa/intel/binemit.rs | 6 ++++ lib/cretonne/src/isa/intel/mod.rs | 13 +++++-- lib/cretonne/src/isa/mod.rs | 9 ++++- lib/cretonne/src/isa/riscv/binemit.rs | 52 +++++++++++++++++++++++++++ lib/cretonne/src/isa/riscv/mod.rs | 8 ++++- 13 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 lib/cretonne/meta/gen_binemit.py create mode 100644 lib/cretonne/src/isa/arm32/binemit.rs create mode 100644 lib/cretonne/src/isa/arm64/binemit.rs create mode 100644 lib/cretonne/src/isa/intel/binemit.rs create mode 100644 lib/cretonne/src/isa/riscv/binemit.rs diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index dd7aeac5f9..08ba14a358 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -12,6 +12,7 @@ import gen_build_deps import gen_encoding import gen_legalizer import gen_registers +import gen_binemit parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') @@ -27,4 +28,5 @@ gen_settings.generate(isas, out_dir) gen_encoding.generate(isas, out_dir) gen_legalizer.generate(isas, out_dir) gen_registers.generate(isas, out_dir) +gen_binemit.generate(isas, out_dir) gen_build_deps.generate() diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py new file mode 100644 index 0000000000..8d1954ce55 --- /dev/null +++ b/lib/cretonne/meta/gen_binemit.py @@ -0,0 +1,46 @@ +""" +Generate binary emission code for each ISA. +""" + +from __future__ import absolute_import +import srcgen + +try: + from typing import Sequence, List # noqa + from cdsl.isa import TargetISA # noqa +except ImportError: + pass + + +def gen_isa(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Generate Binary emission code for `isa`. + """ + fmt.doc_comment( + ''' + Emit binary machine code for `inst` for the {} ISA. + '''.format(isa.name)) + if len(isa.all_recipes) == 0: + # No encoding recipes: Emit a stub. + with fmt.indented( + 'pub fn emit_inst' + '(func: &Function, inst: Inst, _sink: &mut CS) {', '}'): + fmt.line('bad_encoding(func, inst)') + else: + with fmt.indented( + 'pub fn emit_inst' + '(func: &Function, inst: Inst, sink: &mut CS) {', '}'): + with fmt.indented('match func.encodings[inst].recipe() {', '}'): + for i, recipe in enumerate(isa.all_recipes): + fmt.line('{} => recipe_{}(func, inst, sink),'.format( + i, recipe.name.lower())) + fmt.line('_ => bad_encoding(func, inst),') + + +def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None + for isa in isas: + fmt = srcgen.Formatter() + gen_isa(isa, fmt) + fmt.update_file('binemit-{}.rs'.format(isa.name), out_dir) diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index d3c97ec539..0b8b3475b7 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -3,7 +3,7 @@ //! The `binemit` module contains code for translating Cretonne's intermediate representation into //! binary machine code. -use ir::{FuncRef, JumpTable}; +use ir::{FuncRef, JumpTable, Function, Inst}; /// Relocation kinds depend on the current ISA. pub struct Reloc(u16); @@ -31,3 +31,11 @@ pub trait CodeSink { /// Add a relocation referencing a jump table. fn reloc_jt(&mut self, Reloc, JumpTable); } + +/// Report a bad encoding error. +#[inline(never)] +pub fn bad_encoding(func: &Function, inst: Inst) -> ! { + panic!("Bad encoding {} for {}", + func.encodings[inst], + func.dfg.display_inst(inst)); +} diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index 253c5ceb33..2d6fc644b1 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -25,6 +25,14 @@ impl Default for ValueLoc { } impl ValueLoc { + /// Get the register unit of this location, or panic. + pub fn unwrap_reg(self) -> RegUnit { + match self { + ValueLoc::Reg(ru) => ru, + _ => panic!("Expected register: {:?}", self), + } + } + /// Return an object that can display this value location, using the register info from the /// target ISA. pub fn display<'a, R: Into>>(self, regs: R) -> DisplayValueLoc<'a> { diff --git a/lib/cretonne/src/isa/arm32/binemit.rs b/lib/cretonne/src/isa/arm32/binemit.rs new file mode 100644 index 0000000000..dcc76331a2 --- /dev/null +++ b/lib/cretonne/src/isa/arm32/binemit.rs @@ -0,0 +1,6 @@ +//! Emitting binary ARM32 machine code. + +use binemit::{CodeSink, bad_encoding}; +use ir::{Function, Inst}; + +include!(concat!(env!("OUT_DIR"), "/binemit-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 7cc6a93358..df4f4c2570 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -1,14 +1,16 @@ //! ARM 32-bit Instruction Set Architecture. pub mod settings; +mod binemit; mod enc_tables; mod registers; +use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; -use ir::{InstructionData, DataFlowGraph}; +use ir; #[allow(dead_code)] struct Isa { @@ -53,7 +55,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + fn encode(&self, + dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData) + -> Result { lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), self.cpumode, @@ -74,4 +79,8 @@ impl TargetIsa for Isa { fn recipe_constraints(&self) -> &'static [RecipeConstraints] { &enc_tables::RECIPE_CONSTRAINTS } + + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { + binemit::emit_inst(func, inst, sink) + } } diff --git a/lib/cretonne/src/isa/arm64/binemit.rs b/lib/cretonne/src/isa/arm64/binemit.rs new file mode 100644 index 0000000000..13885307d4 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/binemit.rs @@ -0,0 +1,6 @@ +//! Emitting binary ARM64 machine code. + +use binemit::{CodeSink, bad_encoding}; +use ir::{Function, Inst}; + +include!(concat!(env!("OUT_DIR"), "/binemit-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 5f253c39c2..2c11b450bb 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -1,14 +1,16 @@ //! ARM 64-bit Instruction Set Architecture. pub mod settings; +mod binemit; mod enc_tables; mod registers; +use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; -use ir::{InstructionData, DataFlowGraph}; +use ir; #[allow(dead_code)] struct Isa { @@ -46,7 +48,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + fn encode(&self, + dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData) + -> Result { lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), &enc_tables::LEVEL1_A64[..], @@ -67,4 +72,8 @@ impl TargetIsa for Isa { fn recipe_constraints(&self) -> &'static [RecipeConstraints] { &enc_tables::RECIPE_CONSTRAINTS } + + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { + binemit::emit_inst(func, inst, sink) + } } diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs new file mode 100644 index 0000000000..8870abd8f1 --- /dev/null +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -0,0 +1,6 @@ +//! Emitting binary Intel machine code. + +use binemit::{CodeSink, bad_encoding}; +use ir::{Function, Inst}; + +include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 4a5ea8201a..46aa101ce0 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -1,14 +1,16 @@ //! Intel Instruction Set Architectures. pub mod settings; +mod binemit; mod enc_tables; mod registers; +use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; -use ir::{InstructionData, DataFlowGraph}; +use ir; #[allow(dead_code)] struct Isa { @@ -53,7 +55,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { + fn encode(&self, + dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData) + -> Result { lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), self.cpumode, @@ -74,4 +79,8 @@ impl TargetIsa for Isa { fn recipe_constraints(&self) -> &'static [RecipeConstraints] { &enc_tables::RECIPE_CONSTRAINTS } + + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { + binemit::emit_inst(func, inst, sink) + } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 37684addb1..776d5e4bca 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -44,8 +44,9 @@ pub use isa::encoding::Encoding; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind}; +use binemit::CodeSink; use settings; -use ir::{InstructionData, DataFlowGraph, Signature}; +use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature}; pub mod riscv; pub mod intel; @@ -181,4 +182,10 @@ pub trait TargetIsa { fn legalize_signature(&self, _sig: &mut Signature) { unimplemented!() } + + /// Emit binary machine code for a single instruction into the `sink` trait object. + /// + /// Note that this will call `put*` methods on the trait object via its vtable which is not the + /// fastest way of emitting code. + fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink); } diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs new file mode 100644 index 0000000000..e7dcc6021f --- /dev/null +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -0,0 +1,52 @@ +//! Emitting binary RISC-V machine code. + +use binemit::{CodeSink, bad_encoding}; +use ir::{Function, Inst, InstructionData}; + +include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); + +/// R-type instructions. +/// +/// 31 24 19 14 11 6 +/// funct7 rs2 rs1 funct3 rd opcode +/// 25 20 15 12 7 0 +/// +/// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`. +fn recipe_r(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Binary { args, .. } = func.dfg[inst] { + let bits = func.encodings[inst].bits(); + let rs1 = func.locations[args[0]].unwrap_reg(); + let rs2 = func.locations[args[1]].unwrap_reg(); + let rd = func.locations[func.dfg.first_result(inst)].unwrap_reg(); + + // 0-6: opcode + let mut i = 0x3; + i |= (bits as u32 & 0x1f) << 2; + // 7-11: rd + i |= (rd as u32 & 0x1f) << 7; + // 12-14: funct3 + i |= ((bits as u32 >> 5) & 0x7) << 12; + // 15-19: rs1 + i |= (rs1 as u32 & 0x1f) << 15; + // 20-24: rs1 + i |= (rs2 as u32 & 0x1f) << 20; + // 25-31: funct7 + i |= ((bits as u32 >> 8) & 0x7f) << 25; + + sink.put4(i); + } else { + panic!("Expected Binary format: {:?}", func.dfg[inst]); + } +} + +fn recipe_rshamt(_func: &Function, _inst: Inst, _sink: &mut CS) { + unimplemented!() +} + +fn recipe_i(_func: &Function, _inst: Inst, _sink: &mut CS) { + unimplemented!() +} + +fn recipe_iret(_func: &Function, _inst: Inst, _sink: &mut CS) { + unimplemented!() +} diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index ce04fe70b6..94808bfb70 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -2,14 +2,16 @@ pub mod settings; mod abi; +mod binemit; mod enc_tables; mod registers; use super::super::settings as shared_settings; +use binemit::CodeSink; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; -use ir::{InstructionData, DataFlowGraph, Signature}; +use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature}; #[allow(dead_code)] struct Isa { @@ -80,6 +82,10 @@ impl TargetIsa for Isa { // We can pass in `self.isa_flags` too, if we need it. abi::legalize_signature(sig, &self.shared_flags) } + + fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink) { + binemit::emit_inst(func, inst, sink) + } } #[cfg(test)] From 36eb39a1f8a5f6ceb756e285b70ba0c01b36c371 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 28 Mar 2017 15:43:41 -0700 Subject: [PATCH 626/968] Add a binemit test command. This makes it possible to write file tests that verify the binary encoding of machine code. --- docs/testing.rst | 22 ++++++ filetests/isa/riscv/binary32.cton | 13 ++++ src/filetest/binemit.rs | 107 ++++++++++++++++++++++++++++++ src/filetest/mod.rs | 2 + 4 files changed, 144 insertions(+) create mode 100644 filetests/isa/riscv/binary32.cton create mode 100644 src/filetest/binemit.rs diff --git a/docs/testing.rst b/docs/testing.rst index db027f8fbe..9a59f2c7be 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -314,3 +314,25 @@ Second, the register allocator is run on the function, inserting spill code and assigning registers and stack slots to all values. The resulting function is then run through filecheck. + +`test binemit` +-------------- + +Test the emission of binary machine code. + +The functions must contains instructions that are annotated with both encodings +and value locations (registers or stack slots). For instructions that are +annotated with a `bin:` directive, the emitted hexadecimal machine code for +that instruction is compared to the directive:: + + test binemit + isa riscv + + function int32() { + ebb0: + [-,%x5] v1 = iconst.i32 1 + [-,%x6] v2 = iconst.i32 2 + [R#0c,%x7] v10 = iadd v1, v2 ; bin: 006283b3 + [R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433 + return + } diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton new file mode 100644 index 0000000000..0d2805681a --- /dev/null +++ b/filetests/isa/riscv/binary32.cton @@ -0,0 +1,13 @@ +; Binary emission of 32-bit code. +test binemit +isa riscv + +function int32() { +ebb0: + [-,%x5] v1 = iconst.i32 1 + [-,%x6] v2 = iconst.i32 2 + [R#0c,%x7] v10 = iadd v1, v2 ; bin: 006283b3 + [R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433 + [R#10c] v12 = imul v1, v2 + return +} diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs new file mode 100644 index 0000000000..2c6a725431 --- /dev/null +++ b/src/filetest/binemit.rs @@ -0,0 +1,107 @@ +//! Test command for testing the binary machine code emission. +//! +//! The `binemit` test command generates binary machine code for every instruction in the input +//! functions and compares the results to the expected output. + +use std::borrow::{Borrow, Cow}; +use std::fmt::Write; +use cretonne::binemit; +use cretonne::ir; +use cretonne::ir::entities::AnyEntity; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result}; +use utils::match_directive; + +struct TestBinEmit; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "binemit"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestBinEmit)) + } +} + +// Code sink that generates text. +struct TextSink { + text: String, +} + +impl binemit::CodeSink for TextSink { + fn put1(&mut self, x: u8) { + write!(self.text, "{:02x} ", x).unwrap(); + } + + fn put2(&mut self, x: u16) { + write!(self.text, "{:04x} ", x).unwrap(); + } + + fn put4(&mut self, x: u32) { + write!(self.text, "{:08x} ", x).unwrap(); + } + + fn put8(&mut self, x: u64) { + write!(self.text, "{:016x} ", x).unwrap(); + } + + fn reloc_func(&mut self, _: binemit::Reloc, _: ir::FuncRef) { + unimplemented!() + } + + fn reloc_jt(&mut self, _: binemit::Reloc, _: ir::JumpTable) { + unimplemented!() + } +} + +impl SubTest for TestBinEmit { + fn name(&self) -> Cow { + Cow::from("binemit") + } + + fn is_mutating(&self) -> bool { + false + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let isa = context.isa.expect("binemit needs an ISA"); + // TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad + // value locations. The current error reporting is just crashing... + let func = func.borrow(); + + let mut sink = TextSink { text: String::new() }; + + for comment in &context.details.comments { + if let Some(want) = match_directive(comment.text, "bin:") { + let inst = match comment.entity { + AnyEntity::Inst(inst) => inst, + _ => { + return Err(format!("annotation on non-inst {}: {}", + comment.entity, + comment.text)) + } + }; + sink.text.clear(); + isa.emit_inst(&func, inst, &mut sink); + let have = sink.text.trim(); + if have != want { + return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}", + inst, + func.dfg.display_inst(inst), + want, + have)); + } + } + } + + if sink.text.is_empty() { + Err("No bin: directives found".to_string()) + } else { + Ok(()) + } + } +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 41b89948a1..7a379ec0fe 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -13,6 +13,7 @@ use filetest::runner::TestRunner; pub mod subtest; +mod binemit; mod concurrent; mod domtree; mod legalizer; @@ -60,6 +61,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "verifier" => verifier::subtest(parsed), "legalizer" => legalizer::subtest(parsed), "regalloc" => regalloc::subtest(parsed), + "binemit" => binemit::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } From 1d6049b8f839039b44d2134ffe572902b2aae66d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 09:48:25 -0700 Subject: [PATCH 627/968] Allow for unencoded instructions in the binemit tests. If an instruction doesn't have an associated encoding, use the standard TargetIsa hook to encode it. The test still fails if an instruction can't be encoded. There is no legalization step. --- docs/testing.rst | 8 ++++++++ filetests/isa/riscv/binary32.cton | 2 +- src/filetest/binemit.rs | 22 +++++++++++++++++++--- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 9a59f2c7be..f58a413033 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -336,3 +336,11 @@ that instruction is compared to the directive:: [R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433 return } + +If any instructions are unencoded (indicated with a `[-]` encoding field), they +will be encoded using the same mechanism as the legalizer uses. However, +illegal instructions for the ISA won't be expanded into other instruction +sequences. Instead the test will fail. + +Value locations must be present if they are required to compute the binary +bits. Missing value locations will cause the test to crash. diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 0d2805681a..c2f117f5da 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -6,7 +6,7 @@ function int32() { ebb0: [-,%x5] v1 = iconst.i32 1 [-,%x6] v2 = iconst.i32 2 - [R#0c,%x7] v10 = iadd v1, v2 ; bin: 006283b3 + [-,%x7] v10 = iadd v1, v2 ; bin: 006283b3 [R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433 [R#10c] v12 = imul v1, v2 return diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 2c6a725431..16c2613b20 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -3,7 +3,7 @@ //! The `binemit` test command generates binary machine code for every instruction in the input //! functions and compares the results to the expected output. -use std::borrow::{Borrow, Cow}; +use std::borrow::Cow; use std::fmt::Write; use cretonne::binemit; use cretonne::ir; @@ -60,7 +60,7 @@ impl SubTest for TestBinEmit { } fn is_mutating(&self) -> bool { - false + true } fn needs_isa(&self) -> bool { @@ -71,7 +71,7 @@ impl SubTest for TestBinEmit { let isa = context.isa.expect("binemit needs an ISA"); // TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad // value locations. The current error reporting is just crashing... - let func = func.borrow(); + let mut func = func.into_owned(); let mut sink = TextSink { text: String::new() }; @@ -85,6 +85,22 @@ impl SubTest for TestBinEmit { comment.text)) } }; + + // Compute an encoding for `inst` if one wasn't provided. + if !func.encodings + .get(inst) + .map(|e| e.is_legal()) + .unwrap_or(false) { + match isa.encode(&func.dfg, &func.dfg[inst]) { + Ok(enc) => *func.encodings.ensure(inst) = enc, + Err(_) => { + return Err(format!("{} can't be encoded: {}", + inst, + func.dfg.display_inst(inst))) + } + } + } + sink.text.clear(); isa.emit_inst(&func, inst, &mut sink); let have = sink.text.trim(); From b5fda64b49e5bf889344360ae459658680519b2b Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Wed, 29 Mar 2017 21:14:42 +0100 Subject: [PATCH 628/968] Type checking and Dominator Tree integrity checks in Verifier (#66) * Verify that a recomputed dominator tree is identical to the existing one. * The verifier now typechecks instruction results and arguments. * The verifier now typechecks instruction results and arguments. * The verifier now typechecks instruction results and arguments. * Added `inst_{fixed,variable}_args` accessor functions. * Improved error messages in verifier. * Type check return statements against the function signature. --- lib/cretonne/meta/gen_instr.py | 4 - lib/cretonne/src/ir/dfg.rs | 28 ++- lib/cretonne/src/ir/instructions.rs | 1 - lib/cretonne/src/legalizer/boundary.rs | 6 +- lib/cretonne/src/verifier.rs | 245 ++++++++++++++++++++++++- 5 files changed, 269 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index f3c36a716f..0b473e60f3 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -210,10 +210,6 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment( """ Get the value arguments to this instruction. - - This is returned as two `Value` slices. The first one - represents the fixed arguments, the second any variable - arguments. """) gen_arguments_method(fmt, False) fmt.doc_comment( diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 24727362b8..f7d214182b 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -344,16 +344,40 @@ impl DataFlowGraph { DisplayInst(self, inst) } - /// Get the value arguments on `inst` as a slice. + /// Get all value arguments on `inst` as a slice. pub fn inst_args(&self, inst: Inst) -> &[Value] { self.insts[inst].arguments(&self.value_lists) } - /// Get the value arguments on `inst` as a mutable slice. + /// Get all value arguments on `inst` as a mutable slice. pub fn inst_args_mut(&mut self, inst: Inst) -> &mut [Value] { self.insts[inst].arguments_mut(&mut self.value_lists) } + /// Get the fixed value arguments on `inst` as a slice. + pub fn inst_fixed_args(&self, inst: Inst) -> &[Value] { + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + &self.inst_args(inst)[..fixed_args] + } + + /// Get the fixed value arguments on `inst` as a mutable slice. + pub fn inst_fixed_args_mut(&mut self, inst: Inst) -> &mut [Value] { + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + &mut self.inst_args_mut(inst)[..fixed_args] + } + + /// Get the variable value arguments on `inst` as a slice. + pub fn inst_variable_args(&self, inst: Inst) -> &[Value] { + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + &self.inst_args(inst)[fixed_args..] + } + + /// Get the variable value arguments on `inst` as a mutable slice. + pub fn inst_variable_args_mut(&mut self, inst: Inst) -> &mut [Value] { + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + &mut self.inst_args_mut(inst)[fixed_args..] + } + /// Create result values for an instruction that produces multiple results. /// /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index fe76791211..f5fd0c0a98 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -398,7 +398,6 @@ pub struct OpcodeConstraints { /// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first /// `fixed_results()` entries describe the result constraints, then follows constraints for the /// fixed `Value` input operands. (`fixed_value_arguments()` of them). - /// format. constraint_offset: u16, } diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 8ec8ffeb9c..2e3766057d 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -346,12 +346,8 @@ fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Result<(), SigRef> { /// Check if the arguments of the return `inst` match the signature. fn check_return_signature(dfg: &DataFlowGraph, inst: Inst, sig: &Signature) -> bool { - let fixed_values = dfg[inst].opcode().constraints().fixed_value_arguments(); check_arg_types(dfg, - dfg.inst_args(inst) - .iter() - .skip(fixed_values) - .cloned(), + dfg.inst_variable_args(inst).iter().cloned(), &sig.return_types) } diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index d3de786b6e..433b4bc84d 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -26,7 +26,6 @@ //! //! - All predecessors in the CFG must be branches to the EBB. //! - All branches to an EBB must be present in the CFG. -//! TODO: //! - A recomputed dominator tree is identical to the existing one. //! //! Type checking @@ -42,6 +41,7 @@ //! - All return instructions must have return value operands matching the current //! function signature. //! +//! TODO: //! Ad hoc checking //! //! - Stack slot loads and stores must be in-bounds. @@ -56,8 +56,9 @@ use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::entities::AnyEntity; -use ir::instructions::{InstructionFormat, BranchInfo}; -use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value}; +use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; +use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value, Type}; +use Context; use std::fmt::{self, Display, Formatter}; use std::result; @@ -101,6 +102,13 @@ pub fn verify_function(func: &Function) -> Result<()> { Verifier::new(func).run() } +/// Verify `ctx`. +pub fn verify_context(ctx: &Context) -> Result<()> { + let verifier = Verifier::new(&ctx.func); + verifier.domtree_integrity(&ctx.domtree)?; + verifier.run() +} + struct Verifier<'a> { func: &'a Function, cfg: ControlFlowGraph, @@ -377,11 +385,242 @@ impl<'a> Verifier<'a> { Ok(()) } + fn domtree_integrity(&self, domtree: &DominatorTree) -> Result<()> { + // We consider two `DominatorTree`s to be equal if they return the same immediate + // dominator for each EBB. Therefore the current domtree is valid if it matches the freshly + // computed one. + for ebb in self.func.layout.ebbs() { + let expected = domtree.idom(ebb); + let got = self.domtree.idom(ebb); + if got != expected { + return err!(ebb, + "invalid domtree, expected idom({}) = {:?}, got {:?}", + ebb, + expected, + got); + } + } + Ok(()) + } + + fn typecheck_entry_block_arguments(&self) -> Result<()> { + if let Some(ebb) = self.func.layout.entry_block() { + let expected_types = &self.func.signature.argument_types; + let ebb_arg_count = self.func.dfg.num_ebb_args(ebb); + + if ebb_arg_count != expected_types.len() { + return err!(ebb, "entry block arguments must match function signature"); + } + + for (i, arg) in self.func + .dfg + .ebb_args(ebb) + .enumerate() { + let arg_type = self.func.dfg.value_type(arg); + if arg_type != expected_types[i].value_type { + return err!(ebb, + "entry block argument {} expected to have type {}, got {}", + i, + expected_types[i], + arg_type); + } + } + } + Ok(()) + } + + fn typecheck(&self, inst: Inst) -> Result<()> { + let inst_data = &self.func.dfg[inst]; + let constraints = inst_data.opcode().constraints(); + + let ctrl_type = if let Some(value_typeset) = constraints.ctrl_typeset() { + // For polymorphic opcodes, determine the controlling type variable first. + let ctrl_type = inst_data.ctrl_typevar(&self.func.dfg); + + if !value_typeset.contains(ctrl_type) { + return err!(inst, "has an invalid controlling type {}", ctrl_type); + } + + ctrl_type + } else { + // Non-polymorphic instructions don't check the controlling type variable, so `Option` + // is unnecessary and we can just make it `VOID`. + types::VOID + }; + + self.typecheck_results(inst, ctrl_type)?; + self.typecheck_fixed_args(inst, ctrl_type)?; + self.typecheck_variable_args(inst)?; + self.typecheck_return(inst)?; + + Ok(()) + } + + fn typecheck_results(&self, inst: Inst, ctrl_type: Type) -> Result<()> { + let mut i = 0; + for result in self.func.dfg.inst_results(inst) { + let result_type = self.func.dfg.value_type(result); + let expected_type = self.func.dfg.compute_result_type(inst, i, ctrl_type); + if let Some(expected_type) = expected_type { + if result_type != expected_type { + return err!(inst, + "expected result {} ({}) to have type {}, found {}", + i, + result, + expected_type, + result_type); + } + } else { + return err!(inst, "has more result values than expected"); + } + i += 1; + } + + // There aren't any more result types left. + if self.func.dfg.compute_result_type(inst, i, ctrl_type) != None { + return err!(inst, "has fewer result values than expected"); + } + Ok(()) + } + + fn typecheck_fixed_args(&self, inst: Inst, ctrl_type: Type) -> Result<()> { + let constraints = self.func.dfg[inst].opcode().constraints(); + + for (i, &arg) in self.func + .dfg + .inst_fixed_args(inst) + .iter() + .enumerate() { + let arg_type = self.func.dfg.value_type(arg); + match constraints.value_argument_constraint(i, ctrl_type) { + ResolvedConstraint::Bound(expected_type) => { + if arg_type != expected_type { + return err!(inst, + "arg {} ({}) has type {}, expected {}", + i, + arg, + arg_type, + expected_type); + } + } + ResolvedConstraint::Free(type_set) => { + if !type_set.contains(arg_type) { + return err!(inst, + "arg {} ({}) with type {} failed to satisfy type set {:?}", + i, + arg, + arg_type, + type_set); + } + } + } + } + Ok(()) + } + + fn typecheck_variable_args(&self, inst: Inst) -> Result<()> { + match self.func.dfg[inst].analyze_branch(&self.func.dfg.value_lists) { + BranchInfo::SingleDest(ebb, _) => { + let iter = self.func + .dfg + .ebb_args(ebb) + .map(|v| self.func.dfg.value_type(v)); + self.typecheck_variable_args_iterator(inst, iter)?; + } + BranchInfo::Table(table) => { + for (_, ebb) in self.func.jump_tables[table].entries() { + let arg_count = self.func.dfg.num_ebb_args(ebb); + if arg_count != 0 { + return err!(inst, + "takes no arguments, but had target {} with {} arguments", + ebb, + arg_count); + } + } + } + BranchInfo::NotABranch => {} + } + + match self.func.dfg[inst].analyze_call(&self.func.dfg.value_lists) { + CallInfo::Direct(func_ref, _) => { + let sig_ref = self.func.dfg.ext_funcs[func_ref].signature; + let arg_types = + self.func.dfg.signatures[sig_ref].argument_types.iter().map(|a| a.value_type); + self.typecheck_variable_args_iterator(inst, arg_types)?; + } + CallInfo::Indirect(sig_ref, _) => { + let arg_types = + self.func.dfg.signatures[sig_ref].argument_types.iter().map(|a| a.value_type); + self.typecheck_variable_args_iterator(inst, arg_types)?; + } + CallInfo::NotACall => {} + } + Ok(()) + } + + fn typecheck_variable_args_iterator>(&self, + inst: Inst, + iter: I) + -> Result<()> { + let variable_args = self.func.dfg.inst_variable_args(inst); + let mut i = 0; + + for expected_type in iter { + if i >= variable_args.len() { + // Result count mismatch handled below, we want the full argument count first though + i += 1; + continue; + } + let arg = variable_args[i]; + let arg_type = self.func.dfg.value_type(arg); + if expected_type != arg_type { + return err!(inst, + "arg {} ({}) has type {}, expected {}", + i, + variable_args[i], + arg_type, + expected_type); + } + i += 1; + } + if i != variable_args.len() { + return err!(inst, + "mismatched argument count, got {}, expected {}", + variable_args.len(), + i); + } + Ok(()) + } + + fn typecheck_return(&self, inst: Inst) -> Result<()> { + if self.func.dfg[inst].opcode().is_return() { + let args = self.func.dfg.inst_variable_args(inst); + let expected_types = &self.func.signature.return_types; + if args.len() != expected_types.len() { + return err!(inst, "arguments of return must match function signature"); + } + for (i, (&arg, &expected_type)) in args.iter().zip(expected_types).enumerate() { + let arg_type = self.func.dfg.value_type(arg); + if arg_type != expected_type.value_type { + return err!(inst, + "arg {} ({}) has type {}, must match function signature of {}", + i, + arg, + arg_type, + expected_type); + } + } + } + Ok(()) + } + pub fn run(&self) -> Result<()> { + self.typecheck_entry_block_arguments()?; for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { self.ebb_integrity(ebb, inst)?; self.instruction_integrity(inst)?; + self.typecheck(inst)?; } self.cfg_integrity(ebb)?; } From f968c607242e31899182e03a46a4c785700afe1f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 10:30:22 -0700 Subject: [PATCH 629/968] Add Vim syntax support for Name and HexSequence tokens. Also disable spell checking for .cton files. They tend to contain very few words, and even comments are pretty cryptic. --- misc/vim/syntax/cton.vim | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/misc/vim/syntax/cton.vim b/misc/vim/syntax/cton.vim index 2d2dcd0fef..7d6bdab08c 100644 --- a/misc/vim/syntax/cton.vim +++ b/misc/vim/syntax/cton.vim @@ -9,16 +9,22 @@ elseif exists("b:current_syntax") finish endif +" Disable spell checking even in comments. +" They tend to refer to weird stuff like assembler mnemonics anyway. +syn spell notoplevel + syn keyword ctonHeader test isa set syn keyword ctonDecl function stack_slot jump_table syn keyword ctonFilecheck check sameln nextln unordered not regex contained syn match ctonType /\<[bif]\d\+\(x\d\+\)\?\>/ -syn match ctonEntity /\<\(v\|vx\|ss\|jt\|\)\d\+\>/ +syn match ctonEntity /\<\(v\|vx\|ss\|jt\|fn\|sig\)\d\+\>/ syn match ctonLabel /\/ +syn match ctonName /%\w\+\>/ syn match ctonNumber /-\?\<\d\+\>/ syn match ctonNumber /-\?\<0x\x\+\(\.\x*\)\(p[+-]\?\d\+\)\?\>/ +syn match ctonHexSeq /#\x\+\>/ syn region ctonCommentLine start=";" end="$" contains=ctonFilecheck @@ -27,7 +33,9 @@ hi def link ctonDecl Keyword hi def link ctonType Type hi def link ctonEntity Identifier hi def link ctonLabel Label +hi def link ctonName String hi def link ctonNumber Number +hi def link ctonHexSeq Number hi def link ctonCommentLine Comment hi def link ctonFilecheck SpecialComment From a5d483900240872f60669830c769a38c9b1d1a7a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 11:00:05 -0700 Subject: [PATCH 630/968] Test binary encodings of some RV32I instructions. These are the R-format instructions in RV32I. --- filetests/isa/riscv/binary32.cton | 36 +++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index c2f117f5da..ede783b7df 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -2,12 +2,36 @@ test binemit isa riscv -function int32() { +function RV32I() { ebb0: - [-,%x5] v1 = iconst.i32 1 - [-,%x6] v2 = iconst.i32 2 - [-,%x7] v10 = iadd v1, v2 ; bin: 006283b3 - [R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433 - [R#10c] v12 = imul v1, v2 + [-,%x10] v1 = iconst.i32 1 + [-,%x21] v2 = iconst.i32 2 + + ; Integer Register-Register Operations. + ; add + [-,%x7] v10 = iadd v1, v2 ; bin: 015503b3 + [-,%x16] v11 = iadd v2, v1 ; bin: 00aa8833 + ; sub + [-,%x7] v12 = isub v1, v2 ; bin: 415503b3 + [-,%x16] v13 = isub v2, v1 ; bin: 40aa8833 + ; TBD: slt/sltu + ; and + [-,%x7] v20 = band v1, v2 ; bin: 015573b3 + [-,%x16] v21 = band v2, v1 ; bin: 00aaf833 + ; or + [-,%x7] v22 = bor v1, v2 ; bin: 015563b3 + [-,%x16] v23 = bor v2, v1 ; bin: 00aae833 + ; xor + [-,%x7] v24 = bxor v1, v2 ; bin: 015543b3 + [-,%x16] v25 = bxor v2, v1 ; bin: 00aac833 + ; sll + [-,%x7] v30 = ishl v1, v2 ; bin: 015513b3 + [-,%x16] v31 = ishl v2, v1 ; bin: 00aa9833 + ; srl + [-,%x7] v32 = ushr v1, v2 ; bin: 015553b3 + [-,%x16] v33 = ushr v2, v1 ; bin: 00aad833 + ; sra + [-,%x7] v34 = sshr v1, v2 ; bin: 415553b3 + [-,%x16] v35 = sshr v2, v1 ; bin: 40aad833 return } From 62641d4553b7cc673cd1543f92b32ef0f8ebe2d5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 14:40:12 -0700 Subject: [PATCH 631/968] Fix a type error in the legalizer patterns. The carry and borrow values are boolean, so we have to convert them to an integer type with bint(c) before we can add them to the result. Also tweak the default legalizer action for unsupported types: Only attempt a narrowing pattern for lane types > 32 bits. This was found by @angusholder's new type checks in the verifier. --- filetests/isa/riscv/legalize-abi.cton | 2 +- filetests/isa/riscv/legalize-i64.cton | 4 ++-- lib/cretonne/meta/base/legalize.py | 16 +++++++++++----- lib/cretonne/src/isa/enc_tables.rs | 6 +++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 06fcfd5f6c..2e7da079bf 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -96,7 +96,7 @@ ebb0(v0: i64x4): ; check: $(v0d=$V) = iconcat $v0dl, $v0dh ; check: $(v0cd=$V) = vconcat $v0c, $v0d ; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd - v1 = iadd v0, v0 + v1 = bxor v0, v0 ; check: $(v1ab=$V), $(v1cd=$V) = vsplit ; check: $(v1a=$V), $(v1b=$V) = vsplit $v1ab ; check: $(v1al=$V), $(v1ah=$V) = isplit $v1a diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index dfa78447af..e2d5a763a2 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -57,8 +57,8 @@ ebb0(v1: i64, v2: i64): ; check: $(c=$V) = icmp ult, $v3l, $v1l ; check: [R#0c ; sameln: $(v3h1=$V) = iadd $v1h, $v2h -; TODO: This doesn't typecheck. We need to convert the b1 result to i32. +; check: $(c_int=$V) = bint.i32 $c ; check: [R#0c -; sameln: $(v3h=$V) = iadd $v3h1, $c +; sameln: $(v3h=$V) = iadd $v3h1, $c_int ; check: $v3 = iconcat $v3l, $v3h ; check: return $v3l, $v3h diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index 9b0def9ae9..dc8185daf2 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -10,7 +10,7 @@ from __future__ import absolute_import from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm from .instructions import isub, isub_bin, isub_bout, isub_borrow from .instructions import band, bor, bxor, isplit, iconcat -from .instructions import icmp, iconst +from .instructions import icmp, iconst, bint from cdsl.ast import Var from cdsl.xform import Rtl, XFormGroup @@ -40,10 +40,12 @@ b = Var('b') b1 = Var('b1') b2 = Var('b2') b_in = Var('b_in') +b_int = Var('b_int') c = Var('c') c1 = Var('c1') c2 = Var('c2') c_in = Var('c_in') +c_int = Var('c_int') xl = Var('xl') xh = Var('xh') yl = Var('yl') @@ -102,21 +104,24 @@ expand.legalize( a << iadd_cin(x, y, c), Rtl( a1 << iadd(x, y), - a << iadd(a1, c) + c_int << bint(c), + a << iadd(a1, c_int) )) expand.legalize( a << isub_bin(x, y, b), Rtl( a1 << isub(x, y), - a << isub(a1, b) + b_int << bint(b), + a << isub(a1, b_int) )) expand.legalize( (a, c) << iadd_carry(x, y, c_in), Rtl( (a1, c1) << iadd_cout(x, y), - (a, c2) << iadd_cout(a1, c_in), + c_int << bint(c_in), + (a, c2) << iadd_cout(a1, c_int), c << bor(c1, c2) )) @@ -124,7 +129,8 @@ expand.legalize( (a, b) << isub_borrow(x, y, b_in), Rtl( (a1, b1) << isub_bout(x, y), - (a, b2) << isub_bout(a1, b_in), + b_int << bint(b_in), + (a, b2) << isub_bout(a1, b_int), b << bor(b1, b2) )) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index dd4e67875f..71a3eba2e0 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -84,7 +84,11 @@ pub fn lookup_enclist(ctrl_typevar: Type, { // TODO: The choice of legalization actions here is naive. This needs to be configurable. probe(level1_table, ctrl_typevar, ctrl_typevar.index()) - .ok_or(Legalize::Narrow) + .ok_or_else(|| if ctrl_typevar.lane_type().bits() > 32 { + Legalize::Narrow + } else { + Legalize::Expand + }) .and_then(|l1idx| { let l1ent = &level1_table[l1idx]; let l2off = l1ent.offset.into() as usize; From de543bb30843cffd20516b23fb0c2f8ceeecdc06 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 15:13:19 -0700 Subject: [PATCH 632/968] Allow vector types for isplit and iconcat. These two instructions make sense for vector types by simply performing the same operation on each lane, like most other vector operations. Problem found by @angusholder's verifier. --- lib/cretonne/meta/base/instructions.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index ade9b540bd..541889f7d5 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -1278,8 +1278,8 @@ fcvt_from_sint = Instruction( # WideInt = TypeVar( - 'WideInt', 'A scalar integer type from `i16` upwards', - ints=(16, 64)) + 'WideInt', 'An integer type with lanes from `i16` upwards', + ints=(16, 64), simd=True) x = Operand('x', WideInt) lo = Operand( 'lo', WideInt.half_width(), 'The low bits of `x`') @@ -1288,7 +1288,10 @@ hi = Operand( isplit = Instruction( 'isplit', r""" - Split a scalar integer into low and high parts. + Split an integer into low and high parts. + + Vectors of integers are split lane-wise, so the results have the same + number of lanes as the input, but the lanes are half the size. Returns the low half of `x` and the high half of `x` as two independent values. @@ -1297,8 +1300,8 @@ isplit = Instruction( NarrowInt = TypeVar( - 'NarrowInt', 'A scalar integer type up to `i32`', - ints=(8, 32)) + 'NarrowInt', 'An integer type with lanes type to `i32`', + ints=(8, 32), simd=True) lo = Operand('lo', NarrowInt) hi = Operand('hi', NarrowInt) a = Operand( @@ -1308,6 +1311,10 @@ a = Operand( iconcat = Instruction( 'iconcat', r""" Concatenate low and high bits to form a larger integer type. + + Vectors of integers are concatenated lane-wise such that the result has + the same number of lanes as the inputs, but the lanes are twice the + size. """, ins=(lo, hi), outs=a) From e1711f42f6891a5faaf804b0ed25eb9d31e9ddc2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 15:16:59 -0700 Subject: [PATCH 633/968] Run verifier after legalizer and regalloc file tests. Run the verify_contexti() function after invoking the legalize() and regalloc() context functions. This will help catch bad code produced by these passes. --- lib/cretonne/src/context.rs | 11 +++++++++++ src/filetest/legalizer.rs | 15 +++++++++------ src/filetest/regalloc.rs | 6 ++++-- src/utils.rs | 18 +++++++++++++++++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 9ce9d4912e..2d5631f62c 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -15,6 +15,7 @@ use ir::Function; use isa::TargetIsa; use legalize_function; use regalloc; +use verifier; /// Persistent data structures and compilation pipeline. pub struct Context { @@ -45,6 +46,16 @@ impl Context { } } + /// Run the verifier on the function. + /// + /// Also check that the dominator tree and control flow graph are consistent with the function. + /// + /// The `TargetIsa` argument is currently unused, but the verifier will soon be able to also + /// check ISA-dependent constraints. + pub fn verify<'a, ISA: Into>>(&self, _isa: ISA) -> verifier::Result<()> { + verifier::verify_context(self) + } + /// Run the legalizer for `isa` on the function. pub fn legalize(&mut self, isa: &TargetIsa) { legalize_function(&mut self.func, &mut self.cfg, isa); diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index 25247a5aba..39854769ac 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -4,11 +4,11 @@ //! the result to filecheck. use std::borrow::Cow; -use cretonne::{legalize_function, write_function}; -use cretonne::flowgraph::ControlFlowGraph; +use cretonne::{self, write_function}; use cretonne::ir::Function; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use utils::pretty_verifier_error; struct TestLegalizer; @@ -35,13 +35,16 @@ impl SubTest for TestLegalizer { } fn run(&self, func: Cow, context: &Context) -> Result<()> { - let mut func = func.into_owned(); + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); let isa = context.isa.expect("legalizer needs an ISA"); - let mut cfg = ControlFlowGraph::with_function(&func); - legalize_function(&mut func, &mut cfg, isa); + + comp_ctx.flowgraph(); + comp_ctx.legalize(isa); + comp_ctx.verify(isa).map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; let mut text = String::new(); - write_function(&mut text, &func, Some(isa)).map_err(|e| e.to_string())?; + write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; run_filecheck(&text, context) } } diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index f64d7bb830..5f70f51100 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -5,11 +5,12 @@ //! //! The resulting function is sent to `filecheck`. -use std::borrow::Cow; -use cretonne::{self, write_function}; use cretonne::ir::Function; +use cretonne::{self, write_function}; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use std::borrow::Cow; +use utils::pretty_verifier_error; struct TestRegalloc; @@ -46,6 +47,7 @@ impl SubTest for TestRegalloc { // TODO: Should we have an option to skip legalization? comp_ctx.legalize(isa); comp_ctx.regalloc(isa); + comp_ctx.verify(isa).map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; let mut text = String::new(); write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; diff --git a/src/utils.rs b/src/utils.rs index 9cbe20b4bd..d2a6fae3c7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,11 @@ //! Utility functions. -use std::path::Path; +use cretonne::ir::entities::AnyEntity; +use cretonne::{ir, verifier, write_function}; +use std::fmt::Write; use std::fs::File; use std::io::{Result, Read}; +use std::path::Path; /// Read an entire file into a string. pub fn read_to_string>(path: P) -> Result { @@ -29,6 +32,19 @@ pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> } } +/// Pretty-print a verifier error. +pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> String { + let mut msg = err.to_string(); + match err.location { + AnyEntity::Inst(inst) => { + write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst)).unwrap() + } + _ => {} + } + write_function(&mut msg, func, None).unwrap(); + msg +} + #[test] fn test_match_directive() { assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar")); From ffeeba7c93ad3764312b6ba6b0534053aafa4c6f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 29 Mar 2017 15:30:44 -0700 Subject: [PATCH 634/968] Use pretty_verifier_error in runone too. --- src/filetest/runone.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index ed4cae1ee7..ec61c98240 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -9,7 +9,7 @@ use cretonne::settings::Flags; use cretonne::verify_function; use cton_reader::parse_test; use cton_reader::IsaSpec; -use utils::read_to_string; +use utils::{read_to_string, pretty_verifier_error}; use filetest::{TestResult, new_subtest}; use filetest::subtest::{SubTest, Context, Result}; @@ -116,7 +116,7 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { - verify_function(&func).map_err(|e| e.to_string())?; + verify_function(&func).map_err(|e| pretty_verifier_error(&func, e))?; context.verified = true; } From d2eb745265b0fdbe68f1f47938affd51be58b73a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 11:33:28 -0700 Subject: [PATCH 635/968] Allow dot syntax notation for enumerated immediate operands. The meta language patterns sometimes need to refer to specific values of enumerated immediate operands. The dot syntax provides a namespaced, typed way of doing that: icmp(intcc.ult, a, x). Add an ast.Enumerator class for representing this kind of AST leaf node. Add value definitions for the intcc and floatcc immediate operand kinds. --- lib/cretonne/meta/base/immediates.py | 32 ++++++++++++++++++++++-- lib/cretonne/meta/base/legalize.py | 5 ++-- lib/cretonne/meta/cdsl/ast.py | 36 ++++++++++++++++++++++++++- lib/cretonne/meta/cdsl/operands.py | 37 +++++++++++++++++++++++++--- 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 05a542b6d9..ff2eb972a5 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -34,7 +34,19 @@ ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') intcc = ImmediateKind( 'intcc', 'An integer comparison condition code.', - default_member='cond', rust_type='IntCC') + default_member='cond', rust_type='IntCC', + values={ + 'eq': 'Equal', + 'ne': 'NotEqual', + 'sge': 'SignedGreaterThanOrEqual', + 'sgt': 'SignedGreaterThan', + 'sle': 'SignedLessThanOrEqual', + 'slt': 'SignedLessThan', + 'uge': 'UnsignedGreaterThanOrEqual', + 'ugt': 'UnsignedGreaterThan', + 'ule': 'UnsignedLessThanOrEqual', + 'ult': 'UnsignedLessThan', + }) #: A condition code for comparing floating point values. #: @@ -43,4 +55,20 @@ intcc = ImmediateKind( floatcc = ImmediateKind( 'floatcc', 'A floating point comparison condition code.', - default_member='cond', rust_type='FloatCC') + default_member='cond', rust_type='FloatCC', + values={ + 'ord': 'Ordered', + 'uno': 'Unordered', + 'eq': 'Equal', + 'ne': 'NotEqual', + 'one': 'OrderedNotEqual', + 'ueq': 'UnorderedOrEqual', + 'lt': 'LessThan', + 'le': 'LessThanOrEqual', + 'gt': 'GreaterThan', + 'ge': 'GreaterThanOrEqual', + 'ult': 'UnorderedOrLessThan', + 'ule': 'UnorderedOrLessThanOrEqual', + 'ugt': 'UnorderedOrGreaterThan', + 'uge': 'UnorderedOrGreaterThanOrEqual', + }) diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index dc8185daf2..8211a0039f 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -7,6 +7,7 @@ patterns that describe how base instructions can be transformed to other base instructions that are legal. """ from __future__ import absolute_import +from .immediates import intcc from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm from .instructions import isub, isub_bin, isub_bout, isub_borrow from .instructions import band, bor, bxor, isplit, iconcat @@ -90,14 +91,14 @@ expand.legalize( (a, c) << iadd_cout(x, y), Rtl( a << iadd(x, y), - c << icmp('IntCC::UnsignedLessThan', a, x) + c << icmp(intcc.ult, a, x) )) expand.legalize( (a, b) << isub_bout(x, y), Rtl( a << isub(x, y), - b << icmp('IntCC::UnsignedGreaterThan', a, x) + b << icmp(intcc.ugt, a, x) )) expand.legalize( diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 931cc1e3a2..e70e36388a 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -9,7 +9,9 @@ from . import instructions from .typevar import TypeVar try: - from typing import Union, Tuple, Sequence # noqa + from typing import Union, Tuple, Sequence, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from .operands import ImmediateKind # noqa except ImportError: pass @@ -404,3 +406,35 @@ class Apply(Expr): args = defs[0].rust_type() + ', ' + args method = self.inst.snake_name() return '{}({})'.format(method, args) + + +class Enumerator(Expr): + """ + A value of an enumerated immediate operand. + + Some immediate operand kinds like `intcc` and `floatcc` have an enumerated + range of values corresponding to a Rust enum type. An `Enumerator` object + is an AST leaf node representing one of the values. + + :param kind: The enumerated `ImmediateKind` containing the value. + :param value: The textual IL representation of the value. + + `Enumerator` nodes are not usually created directly. They are created by + using the dot syntax on immediate kinds: `intcc.ult`. + """ + + def __init__(self, kind, value): + # type: (ImmediateKind, str) -> None + self.kind = kind + self.value = value + + def __str__(self): + # type: () -> str + """ + Get the Rust expression form of this enumerator. + """ + return self.kind.rust_enumerator(self.value) + + def __repr__(self): + # type: () -> str + return '{}.{}'.format(self.kind, self.value) diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index 49e3e588af..44bae5d500 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -5,8 +5,10 @@ from .types import ValueType from .typevar import TypeVar try: - from typing import Union + from typing import Union, Dict, TYPE_CHECKING # noqa OperandSpec = Union['OperandKind', ValueType, TypeVar] + if TYPE_CHECKING: + from .ast import Enumerator # noqa except ImportError: pass @@ -74,15 +76,44 @@ class ImmediateKind(OperandKind): `InstructionData` data structure. """ - def __init__(self, name, doc, default_member='imm', rust_type=None): - # type: (str, str, str, str) -> None + def __init__( + self, name, doc, + default_member='imm', + rust_type=None, + values=None): + # type: (str, str, str, str, Dict[str, str]) -> None super(ImmediateKind, self).__init__( name, doc, default_member, rust_type) + self.values = values def __repr__(self): # type: () -> str return 'ImmediateKind({})'.format(self.name) + def __getattr__(self, value): + # type: (str) -> Enumerator + """ + Enumerated immediate kinds allow the use of dot syntax to produce + `Enumerator` AST nodes: `icmp.i32(intcc.ult, a, b)`. + """ + from .ast import Enumerator # noqa + if not self.values: + raise AssertionError( + '{n} is not an enumerated operand kind: {n}.{a}'.format( + n=self.name, a=value)) + if value not in self.values: + raise AssertionError( + 'No such {n} enumerator: {n}.{a}'.format( + n=self.name, a=value)) + return Enumerator(self, value) + + def rust_enumerator(self, value): + # type: (str) -> str + """ + Get the qualified Rust name of the enumerator value `value`. + """ + return '{}::{}'.format(self.rust_type, self.values[value]) + # Instances of entity reference operand types are provided in the # `cretonne.entities` module. From ae12c94d04307c0c5f8cebd7d684295374ffb525 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 14:11:19 -0700 Subject: [PATCH 636/968] Add mypy annotations to cdsl.predicates, settings. Wherein we learned that only BoolSettings can be used as predicates. --- lib/cretonne/meta/cdsl/isa.py | 11 ++++--- lib/cretonne/meta/cdsl/predicates.py | 39 +++++++++++++++++++++++-- lib/cretonne/meta/cdsl/settings.py | 43 ++++++++++++++++++++++------ 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index ef667015ce..0109c1f12f 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -8,11 +8,10 @@ from .registers import RegClass, Register try: from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, TYPE_CHECKING # noqa from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa - from .predicates import Predicate, FieldPredicate # noqa + from .predicates import PredNode # noqa from .settings import SettingGroup # noqa from .types import ValueType # noqa from .registers import RegBank # noqa - AnyPredicate = Union[Predicate, FieldPredicate] OperandConstraint = Union[RegClass, Register, int] ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] except ImportError: @@ -80,8 +79,8 @@ class TargetISA(object): Ensures that all ISA predicates have an assigned bit number in `self.settings`. """ - self.all_instps = list() # type: List[AnyPredicate] - instps = set() # type: Set[AnyPredicate] + self.all_instps = list() # type: List[PredNode] + instps = set() # type: Set[PredNode] for cpumode in self.cpumodes: for enc in cpumode.encodings: instp = enc.instp @@ -166,7 +165,7 @@ class EncRecipe(object): """ def __init__(self, name, format, ins, outs, instp=None, isap=None): - # type: (str, InstructionFormat, ConstraintSeq, ConstraintSeq, AnyPredicate, AnyPredicate) -> None # noqa + # type: (str, InstructionFormat, ConstraintSeq, ConstraintSeq, PredNode, PredNode) -> None # noqa self.name = name self.format = format self.instp = instp @@ -219,7 +218,7 @@ class Encoding(object): """ def __init__(self, cpumode, inst, recipe, encbits, instp=None, isap=None): - # type: (CPUMode, MaybeBoundInst, EncRecipe, int, AnyPredicate, AnyPredicate) -> None # noqa + # type: (CPUMode, MaybeBoundInst, EncRecipe, int, PredNode, PredNode) -> None # noqa assert isinstance(cpumode, CPUMode) assert isinstance(recipe, EncRecipe) self.inst, self.typevars = inst.fully_bound() diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 8a1b18807a..82a54c6cf7 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -10,7 +10,7 @@ function determine the kind of predicate: - An *Instruction predicate* is evaluated on an instruction instance, so it can inspect all the immediate fields and type variables of the instruction. - Instruction predicates can be evaluatd before register allocation, so they + Instruction predicates can be evaluated before register allocation, so they can not depend on specific register assignments to the value operands or outputs. @@ -24,6 +24,17 @@ predicate, the context is the instruction format. from __future__ import absolute_import from functools import reduce +try: + from typing import Sequence, Tuple, Set, Any, Union, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from .formats import InstructionFormat, FormatField # noqa + from .settings import BoolSetting, SettingGroup # noqa + PredContext = Union[SettingGroup, InstructionFormat] + PredLeaf = Union[BoolSetting, 'FieldPredicate'] + PredNode = Union[PredLeaf, 'Predicate'] +except ImportError: + pass + def _is_parent(a, b): """ @@ -58,7 +69,9 @@ class Predicate(object): """ def __init__(self, parts): - self.name = None + # type: (Sequence[PredNode]) -> None + self.name = None # type: str + self.number = None # type: int self.parts = parts self.context = reduce( _descendant, @@ -66,6 +79,7 @@ class Predicate(object): assert self.context, "Incompatible predicate parts" def __str__(self): + # type: () -> str if self.name: return '{}.{}'.format(self.context.name, self.name) else: @@ -74,15 +88,21 @@ class Predicate(object): ', '.join(map(str, self.parts))) def predicate_context(self): + # type: () -> PredContext return self.context def predicate_leafs(self, leafs): + # type: (Set[PredLeaf]) -> None """ Collect all leaf predicates into the `leafs` set. """ for part in self.parts: part.predicate_leafs(leafs) + def rust_predicate(self, prec): + # type: (int) -> str + raise NotImplementedError("rust_predicate is an abstract method") + class And(Predicate): """ @@ -92,9 +112,11 @@ class And(Predicate): precedence = 2 def __init__(self, *args): + # type: (*PredNode) -> None super(And, self).__init__(args) def rust_predicate(self, prec): + # type: (int) -> str """ Return a Rust expression computing the value of this predicate. @@ -112,6 +134,7 @@ class And(Predicate): @staticmethod def combine(*args): + # type: (*PredNode) -> PredNode """ Combine a sequence of predicates, allowing for `None` members. @@ -135,9 +158,11 @@ class Or(Predicate): precedence = 1 def __init__(self, *args): + # type: (*PredNode) -> None super(Or, self).__init__(args) def rust_predicate(self, prec): + # type: (int) -> str s = ' || '.join(p.rust_predicate(Or.precedence) for p in self.parts) if prec > Or.precedence: s = '({})'.format(s) @@ -152,9 +177,11 @@ class Not(Predicate): precedence = 3 def __init__(self, part): + # type: (PredNode) -> None super(Not, self).__init__((part,)) def rust_predicate(self, prec): + # type: (int) -> str return '!' + self.parts[0].rust_predicate(Not.precedence) @@ -168,15 +195,19 @@ class FieldPredicate(object): """ def __init__(self, field, function, args): + # type: (FormatField, str, Sequence[Any]) -> None + self.number = None # type: int self.field = field self.function = function self.args = args def __str__(self): + # type: () -> str args = (self.field.name,) + tuple(map(str, self.args)) return '{}({})'.format(self.function, ', '.join(args)) def predicate_context(self): + # type: () -> PredContext """ This predicate can be evaluated in the context of an instruction format. @@ -184,9 +215,11 @@ class FieldPredicate(object): return self.field.format def predicate_leafs(self, leafs): + # type: (Set[PredLeaf]) -> None leafs.add(self) def rust_predicate(self, prec): + # type: (int) -> str """ Return a string of Rust code that evaluates this predicate. """ @@ -210,6 +243,7 @@ class IsSignedInt(FieldPredicate): """ def __init__(self, field, width, scale=0): + # type: (FormatField, int, int) -> None super(IsSignedInt, self).__init__( field, 'is_signed_int', (width, scale)) self.width = width @@ -232,6 +266,7 @@ class IsUnsignedInt(FieldPredicate): """ def __init__(self, field, width, scale=0): + # type: (FormatField, int, int) -> None super(IsUnsignedInt, self).__init__( field, 'is_unsigned_int', (width, scale)) self.width = width diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index 1c1e31643c..ea50afa44b 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -3,6 +3,13 @@ from __future__ import absolute_import from collections import OrderedDict from .predicates import Predicate +try: + from typing import Set, List, Dict, Any, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from .predicates import PredLeaf, PredNode # noqa +except ImportError: + pass + class Setting(object): """ @@ -13,25 +20,26 @@ class Setting(object): """ def __init__(self, doc): - self.name = None # Assigned later by `extract_names()`. + # type: (str) -> None + self.name = None # type: str # Assigned later by `extract_names()`. + self.number = None # type: int self.__doc__ = doc # Offset of byte in settings vector containing this setting. - self.byte_offset = None + self.byte_offset = None # type: int self.group = SettingGroup.append(self) def __str__(self): + # type: () -> str return '{}.{}'.format(self.group.name, self.name) def predicate_context(self): + # type: () -> SettingGroup """ Return the context where this setting can be evaluated as a (leaf) predicate. """ return self.group - def predicate_leafs(self, leafs): - leafs.add(self) - class BoolSetting(Setting): """ @@ -42,10 +50,13 @@ class BoolSetting(Setting): """ def __init__(self, doc, default=False): + # type: (str, bool) -> None super(BoolSetting, self).__init__(doc) self.default = default + self.bit_offset = None # type: int def default_byte(self): + # type: () -> int """ Get the default value of this setting, as a byte that can be bitwise or'ed with the other booleans sharing the same byte. @@ -55,7 +66,12 @@ class BoolSetting(Setting): else: return 0 + def predicate_leafs(self, leafs): + # type: (Set[PredLeaf]) -> None + leafs.add(self) + def rust_predicate(self, prec): + # type: (int) -> str """ Return the Rust code to compute the value of this setting. @@ -74,12 +90,14 @@ class NumSetting(Setting): """ def __init__(self, doc, default=0): + # type: (str, int) -> None super(NumSetting, self).__init__(doc) assert default == int(default) assert default >= 0 and default <= 255 self.default = default def default_byte(self): + # type: () -> int return self.default @@ -94,12 +112,14 @@ class EnumSetting(Setting): """ def __init__(self, doc, *args): + # type: (str, *str) -> None super(EnumSetting, self).__init__(doc) assert len(args) > 0, "EnumSetting must have at least one value" self.values = tuple(str(x) for x in args) self.default = self.values[0] def default_byte(self): + # type: () -> int return 0 @@ -119,23 +139,25 @@ class SettingGroup(object): _current = None # type: SettingGroup def __init__(self, name, parent=None): + # type: (str, SettingGroup) -> None self.name = name self.parent = parent - self.settings = [] + self.settings = [] # type: List[Setting] # Named predicates computed from settings in this group or its # parents. - self.named_predicates = [] + self.named_predicates = [] # type: List[Predicate] # All boolean predicates that can be accessed by number. This includes: # - All boolean settings in this group. # - All named predicates. # - Added anonymous predicates, see `number_predicate()`. # - Added parent predicates that are replicated in this group. # Maps predicate -> number. - self.predicate_number = OrderedDict() + self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa self.open() def open(self): + # type: () -> None """ Open this setting group such that future new settings are added to this group. @@ -146,6 +168,7 @@ class SettingGroup(object): SettingGroup._current = self def close(self, globs=None): + # type: (Dict[str, Any]) -> None """ Close this setting group. This function must be called before opening another setting group. @@ -170,12 +193,14 @@ class SettingGroup(object): @staticmethod def append(setting): + # type: (Setting) -> SettingGroup g = SettingGroup._current assert g, "Open a setting group before defining settings." g.settings.append(setting) return g def number_predicate(self, pred): + # type: (PredNode) -> int """ Make sure that `pred` has an assigned number, and will be included in this group's bit vector. @@ -200,6 +225,7 @@ class SettingGroup(object): return number def layout(self): + # type: () -> None """ Compute the layout of the byte vector used to represent this settings group. @@ -250,6 +276,7 @@ class SettingGroup(object): self.number_predicate(p) def byte_size(self): + # type: () -> int """ Compute the number of bytes required to hold all settings and precomputed predicates. From a82e521291b336fafd4dee46bcf00ba715b3e119 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 15:15:53 -0700 Subject: [PATCH 637/968] Add more mypy annotations. --- lib/cretonne/meta/cdsl/ast.py | 5 +++++ lib/cretonne/meta/cdsl/formats.py | 1 + lib/cretonne/meta/cdsl/instructions.py | 25 +++++++++++++++++-------- lib/cretonne/meta/cdsl/isa.py | 6 ++++-- lib/cretonne/meta/cdsl/predicates.py | 2 ++ lib/cretonne/meta/cdsl/registers.py | 14 +++++++++----- lib/cretonne/meta/cdsl/types.py | 3 ++- lib/cretonne/meta/cdsl/typevar.py | 20 +++++++++++++++----- lib/cretonne/meta/cdsl/xform.py | 9 +++------ lib/cretonne/meta/constant_hash.py | 9 ++++++++- lib/cretonne/meta/gen_build_deps.py | 7 +++++++ lib/cretonne/meta/gen_legalizer.py | 2 ++ lib/cretonne/meta/gen_types.py | 9 +++++++++ lib/cretonne/meta/srcgen.py | 4 ++++ lib/cretonne/meta/unique_table.py | 17 +++++++++++++---- 15 files changed, 101 insertions(+), 32 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index e70e36388a..d2296e8fdb 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -47,9 +47,11 @@ class Def(object): self.expr = expr def __repr__(self): + # type: () -> str return "{} << {!r}".format(self.defs, self.expr) def __str__(self): + # type: () -> str if len(self.defs) == 1: return "{!s} << {!s}".format(self.defs[0], self.expr) else: @@ -379,15 +381,18 @@ class Apply(Expr): return Def(other, self) def instname(self): + # type: () -> str i = self.inst.name for t in self.typevars: i += '.{}'.format(t) return i def __repr__(self): + # type: () -> str return "Apply({}, {})".format(self.instname(), self.args) def __str__(self): + # type: () -> str args = ', '.join(map(str, self.args)) return '{}({})'.format(self.instname(), args) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 6233dbf7a6..9549eac1e0 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -193,6 +193,7 @@ class InstructionFormat(object): @staticmethod def extract_names(globs): + # type: (Dict[str, Any]) -> None """ Given a dict mapping name -> object as returned by `globals()`, find all the InstructionFormat objects and set their name from the dict key. diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 30596fd1fb..a4b0f7fe1f 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -6,11 +6,13 @@ from .operands import Operand from .formats import InstructionFormat try: - from typing import Union, Sequence, List # noqa - # List of operands for ins/outs: - OpList = Union[Sequence[Operand], Operand] - MaybeBoundInst = Union['Instruction', 'BoundInstruction'] - from typing import Tuple, Any # noqa + from typing import Union, Sequence, List, Tuple, Any, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from .ast import Expr, Apply # noqa + from .typevar import TypeVar # noqa + # List of operands for ins/outs: + OpList = Union[Sequence[Operand], Operand] + MaybeBoundInst = Union['Instruction', 'BoundInstruction'] except ImportError: pass @@ -122,6 +124,7 @@ class Instruction(object): InstructionGroup.append(self) def __str__(self): + # type: () -> str prefix = ', '.join(o.name for o in self.outs) if prefix: prefix = prefix + ' = ' @@ -141,6 +144,7 @@ class Instruction(object): return self.name def blurb(self): + # type: () -> str """Get the first line of the doc comment""" for line in self.__doc__.split('\n'): line = line.strip() @@ -149,6 +153,7 @@ class Instruction(object): return "" def _verify_polymorphic(self): + # type: () -> None """ Check if this instruction is polymorphic, and verify its use of type variables. @@ -193,6 +198,7 @@ class Instruction(object): self.ctrl_typevar = tv def _verify_ctrl_typevar(self, ctrl_typevar): + # type: (TypeVar) -> List[TypeVar] """ Verify that the use of TypeVars is consistent with `ctrl_typevar` as the controlling type variable. @@ -204,7 +210,7 @@ class Instruction(object): Return list of other type variables used, or raise an error. """ - other_tvs = [] + other_tvs = [] # type: List[TypeVar] # Check value inputs. for opnum in self.value_opnums: typ = self.ins[opnum].typevar @@ -283,11 +289,12 @@ class Instruction(object): return (self, ()) def __call__(self, *args): + # type: (*Expr) -> Apply """ Create an `ast.Apply` AST node representing the application of this instruction to the arguments. """ - from .ast import Apply + from .ast import Apply # noqa return Apply(self, args) @@ -303,6 +310,7 @@ class BoundInstruction(object): assert len(typevars) <= 1 + len(inst.other_typevars) def __str__(self): + # type: () -> str return '.'.join([self.inst.name, ] + list(map(str, self.typevars))) def bind(self, *args): @@ -336,9 +344,10 @@ class BoundInstruction(object): return (self.inst, self.typevars) def __call__(self, *args): + # type: (*Expr) -> Apply """ Create an `ast.Apply` AST node representing the application of this instruction to the arguments. """ - from .ast import Apply + from .ast import Apply # noqa return Apply(self, args) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 0109c1f12f..18abd9807a 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -97,6 +97,7 @@ class TargetISA(object): self.settings.number_predicate(enc.isap) def _collect_regclasses(self): + # type: () -> None """ Collect and number register classes. @@ -132,6 +133,7 @@ class CPUMode(object): return self.name def enc(self, *args, **kwargs): + # type: (*Any, **Any) -> None """ Add a new encoding to this CPU mode. @@ -186,7 +188,7 @@ class EncRecipe(object): return self.name def _verify_constraints(self, seq): - # (ConstraintSeq) -> Sequence[OperandConstraint] + # type: (ConstraintSeq) -> Sequence[OperandConstraint] if not isinstance(seq, tuple): seq = (seq,) for c in seq: @@ -194,7 +196,7 @@ class EncRecipe(object): # An integer constraint is bound to a value operand. # Check that it is in range. assert c >= 0 - if not format.has_value_list: + if not self.format.has_value_list: assert c < self.format.num_value_operands else: assert isinstance(c, RegClass) or isinstance(c, Register) diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 82a54c6cf7..4cfe8fffb5 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -37,6 +37,7 @@ except ImportError: def _is_parent(a, b): + # type: (PredContext, PredContext) -> bool """ Return true if a is a parent of b, or equal to it. """ @@ -46,6 +47,7 @@ def _is_parent(a, b): def _descendant(a, b): + # type: (PredContext, PredContext) -> PredContext """ If a is a parent of b or b is a parent of a, return the descendant of the two. diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 003ce6379c..b21d2348a6 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -26,11 +26,12 @@ from . import is_power_of_two, next_power_of_two try: - from typing import Sequence, Tuple, List, Dict # noqa - from .isa import TargetISA # noqa - # A tuple uniquely identifying a register class inside a register bank. - # (count, width, start) - RCTup = Tuple[int, int, int] + from typing import Sequence, Tuple, List, Dict, Any, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from .isa import TargetISA # noqa + # A tuple uniquely identifying a register class inside a register bank. + # (count, width, start) + RCTup = Tuple[int, int, int] except ImportError: pass @@ -189,6 +190,7 @@ class RegClass(object): bank.classes.append(self) def __str__(self): + # type: () -> str return self.name def rctup(self): @@ -223,6 +225,7 @@ class RegClass(object): return (count, self.width, start) def __getitem__(self, sliced): + # type: (slice) -> RegClass """ Create a sub-class of a register class using slice notation. The slice indexes refer to allocations in the parent register class, not register @@ -273,6 +276,7 @@ class RegClass(object): @staticmethod def extract_names(globs): + # type: (Dict[str, Any]) -> None """ Given a dict mapping name -> object as returned by `globals()`, find all the RegClass objects and set their name from the dict key. diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index f96c5a2ef0..e42460af28 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -21,11 +21,12 @@ class ValueType(object): _registry = dict() # type: Dict[str, ValueType] # List of all the scalar types. - all_scalars = list() # type: List[ValueType] + all_scalars = list() # type: List[ScalarType] def __init__(self, name, membytes, doc): # type: (str, int, str) -> None self.name = name + self.number = None # type: int self.membytes = membytes self.__doc__ = doc assert name not in ValueType._registry diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 908ebbd8cd..84d099657c 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -9,10 +9,12 @@ import math from . import types, is_power_of_two try: - from typing import Tuple, Union # noqa - Interval = Tuple[int, int] - # An Interval where `True` means 'everything' - BoolInterval = Union[bool, Interval] + from typing import Tuple, Union, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from srcgen import Formatter # noqa + Interval = Tuple[int, int] + # An Interval where `True` means 'everything' + BoolInterval = Union[bool, Interval] except ImportError: pass @@ -143,7 +145,11 @@ class TypeSet(object): return h def __eq__(self, other): - return self.typeset_key() == other.typeset_key() + # type: (object) -> bool + if isinstance(other, TypeSet): + return self.typeset_key() == other.typeset_key() + else: + return False def __repr__(self): # type: () -> str @@ -157,6 +163,7 @@ class TypeSet(object): return s + ')' def emit_fields(self, fmt): + # type: (Formatter) -> None """Emit field initializers for this typeset.""" fmt.comment(repr(self)) fields = ('lanes', 'int', 'float', 'bool') @@ -299,6 +306,9 @@ class TypeVar(object): .format(self.name, self.type_set)) def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, TypeVar): + return False if self.is_derived and other.is_derived: return ( self.derived_func == other.derived_func and diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 21c6103bcd..9d284bc1e2 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -40,10 +40,6 @@ class Rtl(object): # type: (*DefApply) -> None self.rtl = tuple(map(canonicalize_defapply, args)) - def __iter__(self): - # type: () -> Iterator[Def] - return iter(self.rtl) - class XForm(object): """ @@ -105,10 +101,11 @@ class XForm(object): self._collect_typevars() def __repr__(self): + # type: () -> str s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs) - s += '\n '.join(str(n) for n in self.src) + s += '\n '.join(str(n) for n in self.src.rtl) s += '\n=>\n ' - s += '\n '.join(str(n) for n in self.dst) + s += '\n '.join(str(n) for n in self.dst.rtl) s += '\n)' return s diff --git a/lib/cretonne/meta/constant_hash.py b/lib/cretonne/meta/constant_hash.py index 8c3c355aa1..96560ffc87 100644 --- a/lib/cretonne/meta/constant_hash.py +++ b/lib/cretonne/meta/constant_hash.py @@ -8,8 +8,14 @@ quadratically probed hash table. from __future__ import absolute_import from cdsl import next_power_of_two +try: + from typing import Any, List, Sequence, Callable # noqa +except ImportError: + pass + def simple_hash(s): + # type: (str) -> int """ Compute a primitive hash of a string. @@ -26,6 +32,7 @@ def simple_hash(s): def compute_quadratic(items, hash_function): + # type: (Sequence[Any], Callable[[Any], int]) -> List[Any] """ Compute an open addressed, quadratically probed hash table containing `items`. The returned table is a list containing the elements of the @@ -43,7 +50,7 @@ def compute_quadratic(items, hash_function): items = list(items) # Table size must be a power of two. Aim for >20% unused slots. size = next_power_of_two(int(1.20*len(items))) - table = [None] * size + table = [None] * size # type: List[Any] for i in items: h = hash_function(i) % size diff --git a/lib/cretonne/meta/gen_build_deps.py b/lib/cretonne/meta/gen_build_deps.py index 191113a759..5e1419284c 100644 --- a/lib/cretonne/meta/gen_build_deps.py +++ b/lib/cretonne/meta/gen_build_deps.py @@ -16,8 +16,14 @@ from __future__ import absolute_import, print_function import os from os.path import dirname, abspath, join +try: + from typing import Iterable # noqa +except ImportError: + pass + def source_files(top): + # type: (str) -> Iterable[str] """ Recursively find all interesting source files and directories in the directory tree starting at top. Yield a path to each file. @@ -30,6 +36,7 @@ def source_files(top): def generate(): + # type: () -> None print("Dependencies from meta language directory:") meta = dirname(abspath(__file__)) for path in source_files(meta): diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 9131b5d19e..93f2bc857c 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -14,6 +14,7 @@ from cdsl.ast import Var try: from typing import Sequence # noqa + from cdsl.isa import TargetISA # noqa from cdsl.ast import Def # noqa from cdsl.xform import XForm, XFormGroup # noqa except ImportError: @@ -240,6 +241,7 @@ def gen_xform_group(xgrp, fmt): def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None fmt = Formatter() gen_xform_group(legalize.narrow, fmt) gen_xform_group(legalize.expand, fmt) diff --git a/lib/cretonne/meta/gen_types.py b/lib/cretonne/meta/gen_types.py index 5ffed14cf4..fa033c5bdb 100644 --- a/lib/cretonne/meta/gen_types.py +++ b/lib/cretonne/meta/gen_types.py @@ -12,8 +12,14 @@ import srcgen from cdsl.types import ValueType import base.types # noqa +try: + from typing import Iterable # noqa +except ImportError: + pass + def emit_type(ty, fmt): + # type: (ValueType, srcgen.Formatter) -> None """ Emit a constant definition of a single value type. """ @@ -25,6 +31,7 @@ def emit_type(ty, fmt): def emit_vectors(bits, fmt): + # type: (int, srcgen.Formatter) -> None """ Emit definition for all vector types with `bits` total size. """ @@ -37,6 +44,7 @@ def emit_vectors(bits, fmt): def emit_types(fmt): + # type: (srcgen.Formatter) -> None for ty in ValueType.all_scalars: emit_type(ty, fmt) # Emit vector definitions for common SIMD sizes. @@ -47,6 +55,7 @@ def emit_types(fmt): def generate(out_dir): + # type: (str) -> None fmt = srcgen.Formatter() emit_types(fmt) fmt.update_file('types.rs', out_dir) diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py index e87c16a577..d91ecc4ab2 100644 --- a/lib/cretonne/meta/srcgen.py +++ b/lib/cretonne/meta/srcgen.py @@ -101,6 +101,7 @@ class Formatter(object): self.fmt.indent_push() def __exit__(self, t, v, tb): + # type: (object, object, object) -> None self.fmt.indent_pop() if self.after: self.fmt.line(self.after) @@ -126,13 +127,16 @@ class Formatter(object): return Formatter._IndentedScope(self, after) def format(self, fmt, *args): + # type: (str, *Any) -> None self.line(fmt.format(*args)) def comment(self, s): + # type: (str) -> None """Add a comment line.""" self.line('// ' + s) def doc_comment(self, s): + # type: (str) -> None """Add a (multi-line) documentation comment.""" s = re.sub('^', self.indent + '/// ', s, flags=re.M) + '\n' self.lines.append(s) diff --git a/lib/cretonne/meta/unique_table.py b/lib/cretonne/meta/unique_table.py index cbc45af200..0e18ae3afd 100644 --- a/lib/cretonne/meta/unique_table.py +++ b/lib/cretonne/meta/unique_table.py @@ -7,18 +7,25 @@ item is mapped to its offset in the final array. This is a compression technique for compile-time generated tables. """ +try: + from typing import Any, List, Dict, Tuple, Sequence # noqa +except ImportError: + pass + class UniqueTable: """ Collect items into the `table` list, removing duplicates. """ def __init__(self): + # type: () -> None # List of items added in order. - self.table = list() + self.table = list() # type: List[Any] # Map item -> index. - self.index = dict() + self.index = dict() # type: Dict[Any, int] def add(self, item): + # type: (Any) -> int """ Add a single item to the table if it isn't already there. @@ -40,11 +47,13 @@ class UniqueSeqTable: Sequences don't have to be of the same length. """ def __init__(self): - self.table = list() + # type: () -> None + self.table = list() # type: List[Any] # Map seq -> index. - self.index = dict() + self.index = dict() # type: Dict[Tuple[Any, ...], int] def add(self, seq): + # type: (Sequence[Any]) -> int """ Add a sequence of items to the table. If the table already contains the items in `seq` in the same order, use those instead. From b94d01f892644f8ac92861251298498ed82bcab4 Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Thu, 30 Mar 2017 23:34:50 +0100 Subject: [PATCH 638/968] Verify integrity of the existing control flow graph of the context. (#70) * Verify integrity of the existing control flow graph of the context. * Make checking more thorough. --- lib/cretonne/src/ir/entities.rs | 2 +- lib/cretonne/src/verifier.rs | 88 +++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 51ca70ad7a..42d109bf7a 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -170,7 +170,7 @@ impl Display for Value { } /// An opaque reference to an instruction in a function. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Inst(u32); entity_impl!(Inst, "inst"); diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 433b4bc84d..37dc5e20f7 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -61,6 +61,7 @@ use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpT use Context; use std::fmt::{self, Display, Formatter}; use std::result; +use std::collections::BTreeSet; /// A verifier error. #[derive(Debug, PartialEq, Eq)] @@ -106,6 +107,7 @@ pub fn verify_function(func: &Function) -> Result<()> { pub fn verify_context(ctx: &Context) -> Result<()> { let verifier = Verifier::new(&ctx.func); verifier.domtree_integrity(&ctx.domtree)?; + verifier.cfg_integrity(&ctx.cfg)?; verifier.run() } @@ -350,41 +352,6 @@ impl<'a> Verifier<'a> { Ok(()) } - fn cfg_integrity(&self, ebb: Ebb) -> Result<()> { - for &(pred_ebb, pred_inst) in self.cfg.get_predecessors(ebb) { - // All predecessors in the CFG must be branches to the EBB - match self.func.dfg[pred_inst].analyze_branch(&self.func.dfg.value_lists) { - BranchInfo::SingleDest(target_ebb, _) => { - if target_ebb != ebb { - return err!(ebb, - "has predecessor {} in {} which does not branch here", - pred_inst, - pred_ebb); - } - } - BranchInfo::Table(jt) => { - if !self.func.jump_tables[jt].branches_to(ebb) { - return err!(ebb, - "has predecessor {} using {} in {} which never branches here", - pred_inst, - jt, - pred_ebb); - } - } - BranchInfo::NotABranch => { - return err!(ebb, "has predecessor {} which is not a branch", pred_inst); - } - } - // All EBBs branching to `ebb` have it recorded as a successor in the CFG. - if !self.cfg.get_successors(pred_ebb).contains(&ebb) { - return err!(ebb, - "predecessor {} does not have this EBB recorded as a successor", - pred_ebb); - } - } - Ok(()) - } - fn domtree_integrity(&self, domtree: &DominatorTree) -> Result<()> { // We consider two `DominatorTree`s to be equal if they return the same immediate // dominator for each EBB. Therefore the current domtree is valid if it matches the freshly @@ -614,6 +581,54 @@ impl<'a> Verifier<'a> { Ok(()) } + fn cfg_integrity(&self, cfg: &ControlFlowGraph) -> Result<()> { + let mut expected_succs = BTreeSet::::new(); + let mut got_succs = BTreeSet::::new(); + let mut expected_preds = BTreeSet::::new(); + let mut got_preds = BTreeSet::::new(); + + for ebb in self.func.layout.ebbs() { + expected_succs.extend(self.cfg.get_successors(ebb)); + got_succs.extend(cfg.get_successors(ebb)); + + let missing_succs: Vec = expected_succs.difference(&got_succs).cloned().collect(); + if missing_succs.len() != 0 { + return err!(ebb, + "cfg lacked the following successor(s) {:?}", + missing_succs); + } + + let excess_succs: Vec = got_succs.difference(&expected_succs).cloned().collect(); + if excess_succs.len() != 0 { + return err!(ebb, "cfg had unexpected successor(s) {:?}", excess_succs); + } + + expected_preds.extend(self.cfg + .get_predecessors(ebb) + .iter() + .map(|&(_, inst)| inst)); + got_preds.extend(cfg.get_predecessors(ebb).iter().map(|&(_, inst)| inst)); + + let missing_preds: Vec = expected_preds.difference(&got_preds).cloned().collect(); + if missing_preds.len() != 0 { + return err!(ebb, + "cfg lacked the following predecessor(s) {:?}", + missing_preds); + } + + let excess_preds: Vec = got_preds.difference(&expected_preds).cloned().collect(); + if excess_preds.len() != 0 { + return err!(ebb, "cfg had unexpected predecessor(s) {:?}", excess_preds); + } + + expected_succs.clear(); + got_succs.clear(); + expected_preds.clear(); + got_preds.clear(); + } + Ok(()) + } + pub fn run(&self) -> Result<()> { self.typecheck_entry_block_arguments()?; for ebb in self.func.layout.ebbs() { @@ -622,7 +637,6 @@ impl<'a> Verifier<'a> { self.instruction_integrity(inst)?; self.typecheck(inst)?; } - self.cfg_integrity(ebb)?; } Ok(()) } @@ -668,4 +682,4 @@ mod tests { let verifier = Verifier::new(&func); assert_err_with_msg!(verifier.run(), "instruction format"); } -} +} \ No newline at end of file From c5f2ef8edba802bf52c22854eeb4cbf02fcc625d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 16:20:40 -0700 Subject: [PATCH 639/968] Add mypy types for gen_instr.py. Declare the Instruction.number opcode number field. --- lib/cretonne/meta/cdsl/instructions.py | 3 +++ lib/cretonne/meta/gen_instr.py | 22 ++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index a4b0f7fe1f..a900c95ab6 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -107,6 +107,9 @@ class Instruction(object): self.outs = self._to_operand_tuple(outs) self.format = InstructionFormat.lookup(self.ins, self.outs) + # Opcode number, assigned by gen_instr.py. + self.number = None # type: int + # Indexes into `self.outs` for value results. # Other results are `variable_args`. self.value_results = tuple( diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 0b473e60f3..dc5257ffef 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -8,15 +8,18 @@ from unique_table import UniqueTable, UniqueSeqTable from cdsl import camel_case from cdsl.operands import ImmediateKind from cdsl.formats import InstructionFormat - -from cdsl.instructions import Instruction # noqa -from cdsl.operands import Operand # noqa -from cdsl.typevar import TypeVar # noqa +from cdsl.instructions import Instruction # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: - from typing import List # noqa + from typing import List, Sequence, Set, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from cdsl.isa import TargetISA # noqa + from cdsl.instructions import InstructionGroup # noqa + from cdsl.operands import Operand # noqa + from cdsl.typevar import TypeVar # noqa + except ImportError: pass @@ -260,7 +263,8 @@ def gen_instruction_data_impl(fmt): def collect_instr_groups(isas): - seen = set() + # type: (Sequence[TargetISA]) -> List[InstructionGroup] + seen = set() # type: Set[InstructionGroup] groups = [] for isa in isas: for g in isa.instruction_groups: @@ -271,6 +275,7 @@ def collect_instr_groups(isas): def gen_opcodes(groups, fmt): + # type: (Sequence[InstructionGroup], srcgen.Formatter) -> Sequence[Instruction] # noqa """ Generate opcode enumerations. @@ -389,6 +394,7 @@ def get_constraint(op, ctrl_typevar, type_sets): def gen_type_constraints(fmt, instrs): + # type: (srcgen.Formatter, Sequence[Instruction]) -> None """ Generate value type constraints for all instructions. @@ -487,6 +493,7 @@ def gen_type_constraints(fmt, instrs): def gen_format_constructor(iform, fmt): + # type: (InstructionFormat, srcgen.Formatter) -> None """ Emit a method for creating and inserting inserting an `iform` instruction, where `iform` is an instruction format. @@ -543,6 +550,7 @@ def gen_format_constructor(iform, fmt): def gen_member_inits(iform, fmt): + # type: (InstructionFormat, srcgen.Formatter) -> None """ Emit member initializers for an `iform` instruction. """ @@ -696,6 +704,7 @@ def gen_inst_builder(inst, fmt): def gen_builder(insts, fmt): + # type: (Sequence[Instruction], srcgen.Formatter) -> None """ Generate a Builder trait with methods for all instructions. """ @@ -723,6 +732,7 @@ def gen_builder(insts, fmt): def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None groups = collect_instr_groups(isas) # opcodes.rs From 49766bae1215dfd2cbef489680aca16d044b1ac4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 17:07:24 -0700 Subject: [PATCH 640/968] Add mypy types for gen_encoding.py. --- lib/cretonne/meta/gen_encoding.py | 72 ++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 978519efaf..6ee10eb632 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -57,15 +57,21 @@ from collections import OrderedDict, defaultdict import math import itertools from cdsl.registers import RegClass, Register +from cdsl.predicates import FieldPredicate try: - from typing import Sequence # noqa - from cdsl.isa import TargetISA, OperandConstraint # noqa + from typing import Sequence, Set, Tuple, List, Iterable, DefaultDict, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode # noqa + from cdsl.predicates import PredNode, PredLeaf # noqa + from cdsl.types import ValueType # noqa + from cdsl.instructions import Instruction # noqa except ImportError: pass def emit_instp(instp, fmt): + # type: (PredNode, srcgen.Formatter) -> None """ Emit code for matching an instruction predicate against an `InstructionData` reference called `inst`. @@ -77,11 +83,15 @@ def emit_instp(instp, fmt): # Which fields do we need in the InstructionData pattern match? # Collect the leaf predicates. - leafs = set() + leafs = set() # type: Set[PredLeaf] instp.predicate_leafs(leafs) # All the leafs are FieldPredicate instances. Here we just care about # the field names. - fields = ', '.join(sorted(set(p.field.name for p in leafs))) + fnames = set() # type: Set[str] + for p in leafs: + assert isinstance(p, FieldPredicate) + fnames.add(p.field.name) + fields = ', '.join(sorted(fnames)) with fmt.indented('{} => {{'.format(instp.number), '}'): with fmt.indented( @@ -91,6 +101,7 @@ def emit_instp(instp, fmt): def emit_instps(instps, fmt): + # type: (Sequence[PredNode], srcgen.Formatter) -> None """ Emit a function for matching instruction predicates. """ @@ -141,6 +152,7 @@ CODE_FAIL = (1 << CODE_BITS) - 1 def seq_doc(enc): + # type: (Encoding) -> Tuple[Tuple[int, int, int], str] """ Return a tuple containing u16 representations of the instruction predicate an recipe / encbits. @@ -169,13 +181,15 @@ class EncList(object): """ def __init__(self, inst, ty): + # type: (Instruction, ValueType) -> None self.inst = inst self.ty = ty # List of applicable Encoding instances. # These will have different predicates. - self.encodings = [] + self.encodings = [] # type: List[Encoding] def name(self): + # type: () -> str name = self.inst.name if self.ty: name = '{}.{}'.format(name, self.ty.name) @@ -184,6 +198,7 @@ class EncList(object): return name def by_isap(self): + # type: () -> Iterable[Tuple[PredNode, Tuple[Encoding, ...]]] """ Group the encodings by ISA predicate without reordering them. @@ -192,17 +207,18 @@ class EncList(object): have the same ISA predicate. """ maxlen = CODE_FAIL >> PRED_BITS - for isap, group in itertools.groupby( + for isap, groupi in itertools.groupby( self.encodings, lambda enc: enc.isap): - group = tuple(group) + group = tuple(groupi) # This probably never happens, but we can't express more than # maxlen encodings per isap. while len(group) > maxlen: - yield (isap, group[0..maxlen]) + yield (isap, group[0:maxlen]) group = group[maxlen:] yield (isap, group) def encode(self, seq_table, doc_table, isa): + # type: (UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa """ Encode this list as a sequence of u16 numbers. @@ -211,8 +227,8 @@ class EncList(object): Adds comment lines to `doc_table` keyed by seq_table offsets. """ - words = list() - docs = list() + words = list() # type: List[int] + docs = list() # type: List[Tuple[int, str]] # Group our encodings by isap. for isap, group in self.by_isap(): @@ -249,21 +265,25 @@ class Level2Table(object): """ def __init__(self, ty): + # type: (ValueType) -> None self.ty = ty # Maps inst -> EncList - self.lists = OrderedDict() + self.lists = OrderedDict() # type: OrderedDict[Instruction, EncList] def __getitem__(self, inst): + # type: (Instruction) -> EncList ls = self.lists.get(inst) if not ls: ls = EncList(inst, self.ty) self.lists[inst] = ls return ls - def __iter__(self): + def enclists(self): + # type: () -> Iterable[EncList] return iter(self.lists.values()) def layout_hashtable(self, level2_hashtables, level2_doc): + # type: (List[EncList], DefaultDict[int, List[str]]) -> None """ Compute the hash table mapping opcode -> enclist. @@ -290,20 +310,24 @@ class Level1Table(object): """ def __init__(self): - self.tables = OrderedDict() + # type: () -> None + self.tables = OrderedDict() # type: OrderedDict[ValueType, Level2Table] # noqa def __getitem__(self, ty): + # type: (ValueType) -> Level2Table tbl = self.tables.get(ty) if not tbl: tbl = Level2Table(ty) self.tables[ty] = tbl return tbl - def __iter__(self): + def l2tables(self): + # type: () -> Iterable[Level2Table] return iter(self.tables.values()) def make_tables(cpumode): + # type: (CPUMode) -> Level1Table """ Generate tables for `cpumode` as described above. """ @@ -316,15 +340,17 @@ def make_tables(cpumode): def encode_enclists(level1, seq_table, doc_table, isa): + # type: (Level1Table, UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa """ Compute encodings and doc comments for encoding lists in `level1`. """ - for level2 in level1: - for enclist in level2: + for level2 in level1.l2tables(): + for enclist in level2.enclists(): enclist.encode(seq_table, doc_table, isa) def emit_enclists(seq_table, doc_table, fmt): + # type: (UniqueSeqTable, DefaultDict[int, List[str]], srcgen.Formatter) -> None # noqa with fmt.indented( 'pub static ENCLISTS: [u16; {}] = ['.format(len(seq_table.table)), '];'): @@ -342,11 +368,13 @@ def emit_enclists(seq_table, doc_table, fmt): def encode_level2_hashtables(level1, level2_hashtables, level2_doc): - for level2 in level1: + # type: (Level1Table, List[EncList], DefaultDict[int, List[str]]) -> None + for level2 in level1.l2tables(): level2.layout_hashtable(level2_hashtables, level2_doc) def emit_level2_hashtables(level2_hashtables, offt, level2_doc, fmt): + # type: (List[EncList], str, DefaultDict[int, List[str]], srcgen.Formatter) -> None # noqa """ Emit the big concatenation of level 2 hash tables. """ @@ -370,6 +398,7 @@ def emit_level2_hashtables(level2_hashtables, offt, level2_doc, fmt): def emit_level1_hashtable(cpumode, level1, offt, fmt): + # type: (CPUMode, Level1Table, str, srcgen.Formatter) -> None # noqa """ Emit a level 1 hash table for `cpumode`. """ @@ -399,6 +428,7 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): def offset_type(length): + # type: (int) -> str """ Compute an appropriate Rust integer type to use for offsets into a table of the given length. @@ -467,6 +497,7 @@ def emit_operand_constraints(seq, field, fmt): def gen_isa(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. emit_instps(isa.all_instps, fmt) @@ -476,11 +507,11 @@ def gen_isa(isa, fmt): # Tables for enclists with comments. seq_table = UniqueSeqTable() - doc_table = defaultdict(list) + doc_table = defaultdict(list) # type: DefaultDict[int, List[str]] # Single table containing all the level2 hash tables. - level2_hashtables = list() - level2_doc = defaultdict(list) + level2_hashtables = list() # type: List[EncList] + level2_doc = defaultdict(list) # type: DefaultDict[int, List[str]] for cpumode in isa.cpumodes: level2_doc[len(level2_hashtables)].append(cpumode.name) @@ -505,6 +536,7 @@ def gen_isa(isa, fmt): def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None for isa in isas: fmt = srcgen.Formatter() gen_isa(isa, fmt) From a9ec28ab7c9526c3dfa477e3460a4c68326ad8d3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 18:42:06 -0700 Subject: [PATCH 641/968] Add mypy types for gen_settings.py. --- lib/cretonne/meta/cdsl/predicates.py | 2 ++ lib/cretonne/meta/cdsl/settings.py | 10 ++++++++++ lib/cretonne/meta/gen_settings.py | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 4cfe8fffb5..8ba4c8eb41 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -202,6 +202,8 @@ class FieldPredicate(object): self.field = field self.function = function self.args = args + # All PredNode members must have a name field. This will never be set. + self.name = None # type: str def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index ea50afa44b..d9404060dd 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -26,6 +26,9 @@ class Setting(object): self.__doc__ = doc # Offset of byte in settings vector containing this setting. self.byte_offset = None # type: int + # Index into the generated DESCRIPTORS table. + self.descriptor_index = None # type: int + self.group = SettingGroup.append(self) def __str__(self): @@ -40,6 +43,10 @@ class Setting(object): """ return self.group + def default_byte(self): + # type: () -> int + raise NotImplementedError("default_byte is an abstract method") + class BoolSetting(Setting): """ @@ -154,6 +161,9 @@ class SettingGroup(object): # Maps predicate -> number. self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa + # Fully qualified Rust module name. See gen_settings.py. + self.qual_mod = None # type: str + self.open() def open(self): diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 6429333d28..847a72399d 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -9,8 +9,18 @@ from cdsl import camel_case from cdsl.settings import BoolSetting, NumSetting, EnumSetting from base import settings +try: + from typing import Sequence, Set, Tuple, List, TYPE_CHECKING # noqa + if TYPE_CHECKING: + from cdsl.isa import TargetISA # noqa + from cdsl.settings import Setting, SettingGroup # noqa + from cdsl.predicates import Predicate, PredContext # noqa +except ImportError: + pass + def gen_enum_types(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None """ Emit enum types for any enum settings. """ @@ -27,6 +37,7 @@ def gen_enum_types(sgrp, fmt): def gen_getter(setting, sgrp, fmt): + # type: (Setting, SettingGroup, srcgen.Formatter) -> None """ Emit a getter function for `setting`. """ @@ -57,6 +68,7 @@ def gen_getter(setting, sgrp, fmt): def gen_pred_getter(pred, sgrp, fmt): + # type: (Predicate, SettingGroup, srcgen.Formatter) -> None """ Emit a getter for a pre-computed predicate. """ @@ -69,6 +81,7 @@ def gen_pred_getter(pred, sgrp, fmt): def gen_getters(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None """ Emit getter functions for all the settings in fmt. """ @@ -87,6 +100,7 @@ def gen_getters(sgrp, fmt): def gen_descriptors(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None """ Generate the DESCRIPTORS and ENUMERATORS tables. """ @@ -125,6 +139,7 @@ def gen_descriptors(sgrp, fmt): fmt.line('"{}",'.format(txt)) def hash_setting(s): + # type: (Setting) -> int return constant_hash.simple_hash(s.name) hash_table = constant_hash.compute_quadratic(sgrp.settings, hash_setting) @@ -140,6 +155,7 @@ def gen_descriptors(sgrp, fmt): def gen_template(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None """ Emit a Template constant. """ @@ -164,6 +180,7 @@ def gen_template(sgrp, fmt): def gen_display(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None """ Generate the Display impl for Flags. """ @@ -182,6 +199,7 @@ def gen_display(sgrp, fmt): def gen_constructor(sgrp, parent, fmt): + # type: (SettingGroup, PredContext, srcgen.Formatter) -> None """ Generate a Flags constructor. """ @@ -235,6 +253,7 @@ def gen_constructor(sgrp, parent, fmt): def gen_group(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None """ Generate a Flags struct representing `sgrp`. """ @@ -252,6 +271,7 @@ def gen_group(sgrp, fmt): def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None # Generate shared settings. fmt = srcgen.Formatter() settings.group.qual_mod = 'settings' From 3b0ac20ce20c80c26ed0b1461d9f103031822a3e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 30 Mar 2017 19:52:06 -0700 Subject: [PATCH 642/968] Add a mypy.ini file and enable some more warnings. Also require all Python functions to have a type declaration. --- lib/cretonne/meta/build.py | 2 +- lib/cretonne/meta/cdsl/typevar.py | 2 +- lib/cretonne/meta/gen_encoding.py | 14 ++++++++------ lib/cretonne/meta/mypy.ini | 5 +++++ 4 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 lib/cretonne/meta/mypy.ini diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index 08ba14a358..a94b9ce706 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -18,7 +18,7 @@ parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser.add_argument('--out-dir', help='set output directory') args = parser.parse_args() -out_dir = args.out_dir # type: ignore +out_dir = args.out_dir isas = isa.all_isas() diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 84d099657c..2c5141d78c 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -60,7 +60,7 @@ def decode_interval(intv, full_range, default=None): """ if isinstance(intv, tuple): # mypy buig here: 'builtins.None' object is not iterable - lo, hi = intv # type: ignore + lo, hi = intv assert is_power_of_two(lo) assert is_power_of_two(hi) assert lo <= hi diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 6ee10eb632..e78d8942dc 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -289,9 +289,10 @@ class Level2Table(object): Append the hash table to `level2_hashtables` and record the offset. """ - hash_table = compute_quadratic( - self.lists.values(), - lambda enclist: enclist.inst.number) + def hash_func(enclist): + # type: (EncList) -> int + return enclist.inst.number + hash_table = compute_quadratic(self.lists.values(), hash_func) self.hash_table_offset = len(level2_hashtables) self.hash_table_len = len(hash_table) @@ -402,9 +403,10 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): """ Emit a level 1 hash table for `cpumode`. """ - hash_table = compute_quadratic( - level1.tables.values(), - lambda level2: level2.ty.number) + def hash_func(level2): + # type: (Level2Table) -> int + return level2.ty.number + hash_table = compute_quadratic(level1.tables.values(), hash_func) with fmt.indented( 'pub static LEVEL1_{}: [Level1Entry<{}>; {}] = [' diff --git a/lib/cretonne/meta/mypy.ini b/lib/cretonne/meta/mypy.ini new file mode 100644 index 0000000000..ca7f5e4c00 --- /dev/null +++ b/lib/cretonne/meta/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +python_version = 2.7 +disallow_untyped_defs = True +warn_unused_ignores = True +warn_return_any = True From b31ca83b919f4724e9054ccfb6fb3ea7eeeb71d9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 10:12:44 -0700 Subject: [PATCH 643/968] Add an IsEqual FieldPredicate. Compare an immediate operand to a constant value. --- lib/cretonne/meta/cdsl/predicates.py | 15 +++++++++++++++ lib/cretonne/src/predicates.rs | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 8ba4c8eb41..e75920d948 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -232,6 +232,21 @@ class FieldPredicate(object): return 'predicates::{}({})'.format(self.function, ', '.join(args)) +class IsEqual(FieldPredicate): + """ + Instruction predicate that checks if an immediate instruction format field + is equal to a constant value. + + :param field: `FormatField` to be checked. + :param value: The constant value to compare against. + """ + + def __init__(self, field, value): + # type: (FormatField, Any) -> None + super(IsEqual, self).__init__(field, 'is_equal', (value,)) + self.value = value + + class IsSignedInt(FieldPredicate): """ Instruction predicate that checks if an immediate instruction format field diff --git a/lib/cretonne/src/predicates.rs b/lib/cretonne/src/predicates.rs index 748a75e23a..770857d100 100644 --- a/lib/cretonne/src/predicates.rs +++ b/lib/cretonne/src/predicates.rs @@ -9,6 +9,12 @@ //! Some of these predicates may be unused in certain ISA configurations, so we suppress the //! dead_code warning. +/// Check that `x` is the same as `y`. +#[allow(dead_code)] +pub fn is_equal(x: T, y: T) -> bool { + x == y +} + /// Check that `x` can be represented as a `wd`-bit signed integer with `sc` low zero bits. #[allow(dead_code)] pub fn is_signed_int>(x: T, wd: u8, sc: u8) -> bool { From 89ff979d75306cff5aa4e55ba0777d3cc58f3af3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 10:12:37 -0700 Subject: [PATCH 644/968] Add InstructionFormat.imm_fields. Consolidate the imm_members and imm_kinds into this list so the FormatField is the single definition of these properties. This makes it easier to access the precomputed FormatFields parametrically, avoiding going through getattr(). This is better for type checking too. --- lib/cretonne/meta/cdsl/formats.py | 73 ++++++++++++++-------------- lib/cretonne/meta/cdsl/predicates.py | 2 +- lib/cretonne/meta/gen_encoding.py | 2 +- lib/cretonne/meta/gen_instr.py | 8 +-- lib/cretonne/meta/gen_legalizer.py | 6 +-- 5 files changed, 46 insertions(+), 45 deletions(-) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 9549eac1e0..f08d6fa119 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -59,17 +59,15 @@ class InstructionFormat(object): self.name = kwargs.get('name', None) # type: str self.multiple_results = kwargs.get('multiple_results', False) - # Struct member names for the immediate operands. All other instruction - # operands are values or variable argument lists. They are all handled - # specially. - self.imm_members = list() # type: List[str] # The number of value operands stored in the format, or `None` when # `has_value_list` is set. self.num_value_operands = 0 # Does this format use a value list for storing value operands? self.has_value_list = False - # Operand kinds for the immediate operands. - self.imm_kinds = tuple(self._process_member_names(kinds)) + # Operand fields for the immediate operands. All other instruction + # operands are values or variable argument lists. They are all handled + # specially. + self.imm_fields = tuple(self._process_member_names(kinds)) # The typevar_operand argument must point to a 'value' operand. self.typevar_operand = kwargs.get('typevar_operand', None) # type: int @@ -82,9 +80,9 @@ class InstructionFormat(object): self.typevar_operand = 0 # Compute a signature for the global registry. + imm_kinds = tuple(f.kind for f in self.imm_fields) sig = ( - self.multiple_results, self.imm_kinds, - self.num_value_operands, + self.multiple_results, imm_kinds, self.num_value_operands, self.has_value_list) if sig in InstructionFormat._registry: raise RuntimeError( @@ -94,7 +92,7 @@ class InstructionFormat(object): InstructionFormat.all_formats.append(self) def _process_member_names(self, kinds): - # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[OperandKind] # noqa + # type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[FormatField] # noqa """ Extract names of all the immediate operands in the kinds tuple. @@ -102,10 +100,11 @@ class InstructionFormat(object): pair. The member names correspond to members in the Rust `InstructionData` data structure. - Update the fields `num_value_operands` and `imm_members`. + Updates the fields `self.num_value_operands` and `self.has_value_list`. - Yields the immediate operand kinds. + Yields the immediate operand fields. """ + inum = 0 for arg in kinds: if isinstance(arg, OperandKind): member = arg.default_member @@ -119,13 +118,13 @@ class InstructionFormat(object): elif k is VARIABLE_ARGS: self.has_value_list = True else: - self.imm_members.append(member) - yield k + yield FormatField(self, inum, k, member) + inum += 1 def __str__(self): # type: () -> str - args = ', '.join('{}: {}'.format(m, k) - for m, k in zip(self.imm_members, self.imm_kinds)) + args = ', '.join( + '{}: {}'.format(f.member, f.kind) for f in self.imm_fields) return '{}(imms=({}), vals={})'.format( self.name, args, self.num_value_operands) @@ -137,16 +136,16 @@ class InstructionFormat(object): Each non-value format member becomes a corresponding `FormatField` attribute. """ - try: - i = self.imm_members.index(attr) - except ValueError: - raise AttributeError( - '{} is neither a {} member or a ' - .format(attr, self.name) + - 'normal InstructionFormat attribute') - field = FormatField(self, i, attr) - setattr(self, attr, field) - return field + for f in self.imm_fields: + if f.member == attr: + # Cache this field attribute so we won't have to search again. + setattr(self, attr, f) + return f + + raise AttributeError( + '{} is neither a {} member or a ' + .format(attr, self.name) + + 'normal InstructionFormat attribute') @staticmethod def lookup(ins, outs): @@ -207,26 +206,28 @@ class InstructionFormat(object): class FormatField(object): """ - A field in an instruction format. + An immediate field in an instruction format. This corresponds to a single member of a variant of the `InstructionData` data type. - :param format: Parent `InstructionFormat`. - :param operand: Immediate operand number in parent. - :param name: Member name in `InstructionData` variant. + :param iformat: Parent `InstructionFormat`. + :param immnum: Immediate operand number in parent. + :param kind: Immediate Operand kind. + :param member: Member name in `InstructionData` variant. """ - def __init__(self, format, operand, name): - # type: (InstructionFormat, int, str) -> None - self.format = format - self.operand = operand - self.name = name + def __init__(self, iform, immnum, kind, member): + # type: (InstructionFormat, int, OperandKind, str) -> None + self.format = iform + self.immnum = immnum + self.kind = kind + self.member = member def __str__(self): # type: () -> str - return '{}.{}'.format(self.format.name, self.name) + return '{}.{}'.format(self.format.name, self.member) def rust_name(self): # type: () -> str - return self.name + return self.member diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index e75920d948..273f8be9a1 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -207,7 +207,7 @@ class FieldPredicate(object): def __str__(self): # type: () -> str - args = (self.field.name,) + tuple(map(str, self.args)) + args = (self.field.rust_name(),) + tuple(map(str, self.args)) return '{}({})'.format(self.function, ', '.join(args)) def predicate_context(self): diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index e78d8942dc..e4faa6410c 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -90,7 +90,7 @@ def emit_instp(instp, fmt): fnames = set() # type: Set[str] for p in leafs: assert isinstance(p, FieldPredicate) - fnames.add(p.field.name) + fnames.add(p.field.rust_name()) fields = ', '.join(sorted(fnames)) with fmt.indented('{} => {{'.format(instp.number), '}'): diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index dc5257ffef..9ce279dc52 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -515,8 +515,8 @@ def gen_format_constructor(iform, fmt): result_type = 'result_type' # Normal operand arguments. Start with the immediate operands. - for kind, name in zip(iform.imm_kinds, iform.imm_members): - args.append('{}: {}'.format(name, kind.rust_type)) + for f in iform.imm_fields: + args.append('{}: {}'.format(f.member, f.kind.rust_type)) # Then the value operands. if iform.has_value_list: # Take all value arguments as a finished value list. The value lists @@ -557,8 +557,8 @@ def gen_member_inits(iform, fmt): # Immediate operands. # We have local variables with the same names as the members. - for member in iform.imm_members: - fmt.line('{}: {},'.format(member, member)) + for f in iform.imm_fields: + fmt.line('{}: {},'.format(f.member, f.member)) # Value operands. if iform.has_value_list: diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 93f2bc857c..7beda84771 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -43,8 +43,8 @@ def unwrap_inst(iref, node, fmt): 'let ({}) = if let InstructionData::{} {{' .format(', '.join(map(str, expr.args)), iform.name), '};'): # Fields are encoded directly. - for m in iform.imm_members: - fmt.line('{},'.format(m)) + for f in iform.imm_fields: + fmt.line('{},'.format(f.member)) if nvops == 1: fmt.line('arg,') elif iform.has_value_list or nvops > 1: @@ -58,7 +58,7 @@ def unwrap_inst(iref, node, fmt): for opnum, op in enumerate(expr.inst.ins): if op.is_immediate(): n = expr.inst.imm_opnums.index(opnum) - outs.append(iform.imm_members[n]) + outs.append(iform.imm_fields[n].member) elif op.is_value(): if nvops == 1: arg = 'arg' From 3c3d06837966e3de8975782f30db88602c9dd392 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 10:12:23 -0700 Subject: [PATCH 645/968] Add Apply.inst_predicate(). Compute an instruction predicate from any constant values given as arguments for the immediate operands in an instruction pattern. Allows for patterns like icmp.i32(intcc.ugt, x, y) or iadd_imm.i32(x, 1) Trap these predicates in the legalizer code generator since we can't actually handle them yet. --- lib/cretonne/meta/cdsl/ast.py | 28 ++++++++++++++++++++++++++++ lib/cretonne/meta/cdsl/predicates.py | 3 ++- lib/cretonne/meta/gen_legalizer.py | 6 ++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index d2296e8fdb..2b671fc46e 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -7,11 +7,13 @@ for patern matching an rewriting of cretonne instructions. from __future__ import absolute_import from . import instructions from .typevar import TypeVar +from .predicates import IsEqual, And try: from typing import Union, Tuple, Sequence, TYPE_CHECKING # noqa if TYPE_CHECKING: from .operands import ImmediateKind # noqa + from .predicates import PredNode # noqa except ImportError: pass @@ -412,6 +414,32 @@ class Apply(Expr): method = self.inst.snake_name() return '{}({})'.format(method, args) + def inst_predicate(self): + # type: () -> PredNode + """ + Construct an instruction predicate that verifies the immediate operands + on this instruction. + + Immediate operands in a source pattern can be either free variables or + constants like `Enumerator`. We don't currently support constraints on + free variables, but we may in the future. + """ + pred = None # type: PredNode + iform = self.inst.format + + # Examine all of the immediate operands. + for ffield, opnum in zip(iform.imm_fields, self.inst.imm_opnums): + arg = self.args[opnum] + + # Ignore free variables for now. We may add variable predicates + # later. + if isinstance(arg, Var): + continue + + pred = And.combine(pred, IsEqual(ffield, arg)) + + return pred + class Enumerator(Expr): """ diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 273f8be9a1..8a1cc147f9 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -216,7 +216,8 @@ class FieldPredicate(object): This predicate can be evaluated in the context of an instruction format. """ - return self.field.format + iform = self.field.format # type: InstructionFormat + return iform def predicate_leafs(self, leafs): # type: (Set[PredLeaf]) -> None diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 7beda84771..41cd9bab56 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -211,6 +211,12 @@ def gen_xform(xform, fmt): # variables. unwrap_inst('inst', xform.src.rtl[0], fmt) + # We could support instruction predicates, but not yet. Should we just + # return false if it fails? What about multiple patterns with different + # predicates for the same opcode? + instp = xform.src.rtl[0].expr.inst_predicate() + assert instp is None, "Instruction predicates not supported in legalizer" + # Emit the destination pattern. for dst in xform.dst.rtl: emit_dst_inst(dst, fmt) From ebc418d25ebfbafd31b70904c078067a7b0add43 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 11:58:59 -0700 Subject: [PATCH 646/968] Allow for instructions with operands in encodings. When defining an instruction encoding, allow part of the instruction predicate to be provided as operands on the instruction opcode: icmp.i32(intcc.ult, x, y) This generates an instruction predicate that checks IntCompare.cond == IntCC::UnsignedLessThan --- lib/cretonne/meta/cdsl/isa.py | 39 +++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 18abd9807a..9db0faeb61 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -2,18 +2,23 @@ from __future__ import absolute_import from .predicates import And from .registers import RegClass, Register +from .ast import Apply # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, TYPE_CHECKING # noqa - from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa - from .predicates import PredNode # noqa - from .settings import SettingGroup # noqa - from .types import ValueType # noqa - from .registers import RegBank # noqa - OperandConstraint = Union[RegClass, Register, int] - ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] + if TYPE_CHECKING: + from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa + from .predicates import PredNode # noqa + from .settings import SettingGroup # noqa + from .types import ValueType # noqa + from .registers import RegBank # noqa + OperandConstraint = Union[RegClass, Register, int] + ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] + # Instruction specification for encodings. Allows for predicated + # instructions. + InstSpec = Union[MaybeBoundInst, Apply] except ImportError: pass @@ -210,6 +215,14 @@ class Encoding(object): An `Encoding` object ties an instruction opcode with concrete type variables together with and encoding recipe and encoding bits. + The concrete instruction can be in three different forms: + + 1. A naked opcode: `trap` for non-polymorphic instructions. + 2. With bound type variables: `iadd.i32` for polymorphic instructions. + 3. With operands providing constraints: `icmp.i32(intcc.eq, x, y)`. + + If the instruction is polymorphic, all type variables must be provided. + :param cpumode: The CPU mode where the encoding is active. :param inst: The :py:class:`Instruction` or :py:class:`BoundInstruction` being encoded. @@ -220,10 +233,18 @@ class Encoding(object): """ def __init__(self, cpumode, inst, recipe, encbits, instp=None, isap=None): - # type: (CPUMode, MaybeBoundInst, EncRecipe, int, PredNode, PredNode) -> None # noqa + # type: (CPUMode, InstSpec, EncRecipe, int, PredNode, PredNode) -> None # noqa assert isinstance(cpumode, CPUMode) assert isinstance(recipe, EncRecipe) - self.inst, self.typevars = inst.fully_bound() + + # Check for possible instruction predicates in `inst`. + if isinstance(inst, Apply): + instp = And.combine(instp, inst.inst_predicate()) + self.inst = inst.inst + self.typevars = inst.typevars + else: + self.inst, self.typevars = inst.fully_bound() + self.cpumode = cpumode assert self.inst.format == recipe.format, ( "Format {} must match recipe: {}".format( From 39fc0eb3cf7514c351dac81768a3356f3946afa2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 12:12:09 -0700 Subject: [PATCH 647/968] Add RISC-V encodings for supported icmp variants. Only slt and ult variants are in the instruction set. Other condition codes must be synthesized. --- filetests/isa/riscv/binary32.cton | 8 ++- lib/cretonne/meta/isa/riscv/encodings.py | 15 +++++- lib/cretonne/meta/isa/riscv/recipes.py | 5 +- lib/cretonne/src/isa/riscv/binemit.rs | 63 ++++++++++++++++-------- lib/cretonne/src/isa/riscv/enc_tables.rs | 1 + 5 files changed, 69 insertions(+), 23 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index ede783b7df..0c6863eae1 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -14,7 +14,6 @@ ebb0: ; sub [-,%x7] v12 = isub v1, v2 ; bin: 415503b3 [-,%x16] v13 = isub v2, v1 ; bin: 40aa8833 - ; TBD: slt/sltu ; and [-,%x7] v20 = band v1, v2 ; bin: 015573b3 [-,%x16] v21 = band v2, v1 ; bin: 00aaf833 @@ -33,5 +32,12 @@ ebb0: ; sra [-,%x7] v34 = sshr v1, v2 ; bin: 415553b3 [-,%x16] v35 = sshr v2, v1 ; bin: 40aad833 + ; slt + [-,%x7] v42 = icmp slt, v1, v2 ; bin: 015523b3 + [-,%x16] v43 = icmp slt, v2, v1 ; bin: 00aaa833 + ; sltu + [-,%x7] v44 = icmp ult, v1, v2 ; bin: 015533b3 + [-,%x16] v45 = icmp ult, v2, v1 ; bin: 00aab833 + return } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index bf84c468b0..b97c92386a 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -3,9 +3,15 @@ RISC-V Encodings. """ from __future__ import absolute_import from base import instructions as base +from base.immediates import intcc from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32, JALR, R, Rshamt, I, Iret +from .recipes import OPIMM, OPIMM32, OP, OP32, JALR, R, Rshamt, Ricmp, I, Iret from .settings import use_m +from cdsl.ast import Var + +# Dummies for instruction predicates. +x = Var('x') +y = Var('y') # Basic arithmetic binary instructions are encoded in an R-type instruction. for inst, inst_imm, f3, f7 in [ @@ -47,6 +53,13 @@ for inst, inst_imm, f3, f7 in [ RV64.enc(inst_imm.i64, Rshamt, OPIMM(f3, f7)) RV64.enc(inst_imm.i32, Rshamt, OPIMM32(f3, f7)) +# Signed and unsigned integer 'less than'. There are no 'w' variants for +# comparing 32-bit numbers in RV64. +RV32.enc(base.icmp.i32(intcc.slt, x, y), Ricmp, OP(0b010, 0b0000000)) +RV64.enc(base.icmp.i64(intcc.slt, x, y), Ricmp, OP(0b010, 0b0000000)) +RV32.enc(base.icmp.i32(intcc.ult, x, y), Ricmp, OP(0b011, 0b0000000)) +RV64.enc(base.icmp.i64(intcc.ult, x, y), Ricmp, OP(0b011, 0b0000000)) + # "M" Standard Extension for Integer Multiplication and Division. # Gated by the `use_m` flag. RV32.enc(base.imul.i32, R, OP(0b000, 0b0000001), isap=use_m) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 0dce091713..2fd326e5cc 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -11,7 +11,7 @@ instruction formats described in the reference: from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt -from base.formats import Binary, BinaryImm, MultiAry +from base.formats import Binary, BinaryImm, MultiAry, IntCompare from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -79,6 +79,9 @@ R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) # R-type with an immediate shift amount instead of rs2. Rshamt = EncRecipe('Rshamt', BinaryImm, ins=GPR, outs=GPR) +# R-type encoding of an integer comparison. +Ricmp = EncRecipe('Ricmp', IntCompare, ins=(GPR, GPR), outs=GPR) + I = EncRecipe( 'I', BinaryImm, ins=GPR, outs=GPR, instp=IsSignedInt(BinaryImm.imm, 12)) diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index e7dcc6021f..a2e29a847e 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -2,6 +2,7 @@ use binemit::{CodeSink, bad_encoding}; use ir::{Function, Inst, InstructionData}; +use isa::RegUnit; include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); @@ -12,33 +13,55 @@ include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); /// 25 20 15 12 7 0 /// /// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`. +fn put_r(bits: u16, + rs1: RegUnit, + rs2: RegUnit, + rd: RegUnit, + sink: &mut CS) { + let bits = bits as u32; + let opcode5 = bits & 0x1f; + let funct3 = (bits >> 5) & 0x7; + let funct7 = (bits >> 8) & 0x7f; + let rs1 = rs1 as u32 & 0x1f; + let rs2 = rs2 as u32 & 0x1f; + let rd = rd as u32 & 0x1f; + + // 0-6: opcode + let mut i = 0x3; + i |= opcode5 << 2; + i |= rd << 7; + i |= funct3 << 12; + i |= rs1 << 15; + i |= rs2 << 20; + i |= funct7 << 25; + + sink.put4(i); +} + fn recipe_r(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::Binary { args, .. } = func.dfg[inst] { - let bits = func.encodings[inst].bits(); - let rs1 = func.locations[args[0]].unwrap_reg(); - let rs2 = func.locations[args[1]].unwrap_reg(); - let rd = func.locations[func.dfg.first_result(inst)].unwrap_reg(); - - // 0-6: opcode - let mut i = 0x3; - i |= (bits as u32 & 0x1f) << 2; - // 7-11: rd - i |= (rd as u32 & 0x1f) << 7; - // 12-14: funct3 - i |= ((bits as u32 >> 5) & 0x7) << 12; - // 15-19: rs1 - i |= (rs1 as u32 & 0x1f) << 15; - // 20-24: rs1 - i |= (rs2 as u32 & 0x1f) << 20; - // 25-31: funct7 - i |= ((bits as u32 >> 8) & 0x7f) << 25; - - sink.put4(i); + put_r(func.encodings[inst].bits(), + func.locations[args[0]].unwrap_reg(), + func.locations[args[1]].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); } else { panic!("Expected Binary format: {:?}", func.dfg[inst]); } } +fn recipe_ricmp(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::IntCompare { args, .. } = func.dfg[inst] { + put_r(func.encodings[inst].bits(), + func.locations[args[0]].unwrap_reg(), + func.locations[args[1]].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected IntCompare format: {:?}", func.dfg[inst]); + } +} + fn recipe_rshamt(_func: &Function, _inst: Inst, _sink: &mut CS) { unimplemented!() } diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index f02ccf80ba..7940fce004 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -1,5 +1,6 @@ //! Encoding tables for RISC-V. +use ir::condcodes::IntCC; use ir::{Opcode, InstructionData}; use ir::types; use predicates; From 7c9d187b6dbd71a25f89adac3fe020589d4af37c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 15:15:20 -0700 Subject: [PATCH 648/968] Emit I-type instructions for RISC-V. These are the BinaryImm formats. --- filetests/isa/riscv/binary32.cton | 20 +++++++++++++++ lib/cretonne/src/isa/riscv/binemit.rs | 37 +++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 0c6863eae1..68bc718ea7 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -39,5 +39,25 @@ ebb0: [-,%x7] v44 = icmp ult, v1, v2 ; bin: 015533b3 [-,%x16] v45 = icmp ult, v2, v1 ; bin: 00aab833 + ; Integer Register-Immediate Instructions + + ; addi + [-,%x7] v100 = iadd_imm v1, 1000 ; bin: 3e850393 + [-,%x16] v101 = iadd_imm v2, -905 ; bin: c77a8813 + ; TBD: slti + ; andi + [-,%x7] v110 = band_imm v1, 1000 ; bin: 3e857393 + [-,%x16] v111 = band_imm v2, -905 ; bin: c77af813 + ; ori + [-,%x7] v112 = bor_imm v1, 1000 ; bin: 3e856393 + [-,%x16] v113 = bor_imm v2, -905 ; bin: c77ae813 + ; xori + [-,%x7] v114 = bxor_imm v1, 1000 ; bin: 3e854393 + [-,%x16] v115 = bxor_imm v2, -905 ; bin: c77ac813 + + ; TBD: slli + ; TBD: srli + ; TBD: srai + return } diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index a2e29a847e..480d377c6b 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -66,8 +66,41 @@ fn recipe_rshamt(_func: &Function, _inst: Inst, _sink: &m unimplemented!() } -fn recipe_i(_func: &Function, _inst: Inst, _sink: &mut CS) { - unimplemented!() +/// I-type instructions. +/// +/// 31 19 14 11 6 +/// imm rs1 funct3 rd opcode +/// 20 15 12 7 0 +/// +/// Encoding bits: `opcode[6:2] | (funct3 << 5)` +fn put_i(bits: u16, rs1: RegUnit, imm: i64, rd: RegUnit, sink: &mut CS) { + let bits = bits as u32; + let opcode5 = bits & 0x1f; + let funct3 = (bits >> 5) & 0x7; + let rs1 = rs1 as u32 & 0x1f; + let rd = rd as u32 & 0x1f; + + // 0-6: opcode + let mut i = 0x3; + i |= opcode5 << 2; + i |= rd << 7; + i |= funct3 << 12; + i |= rs1 << 15; + i |= (imm << 20) as u32; + + sink.put4(i); +} + +fn recipe_i(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { + put_i(func.encodings[inst].bits(), + func.locations[arg].unwrap_reg(), + imm.into(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected Binary format: {:?}", func.dfg[inst]); + } } fn recipe_iret(_func: &Function, _inst: Inst, _sink: &mut CS) { From 8187fd73719a02064f055ba6efd3b64c895588d1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 15:33:21 -0700 Subject: [PATCH 649/968] Emit Rshamt-type instructions for RISC-V. These are the shift-by-immediate instructions. --- filetests/isa/riscv/binary32.cton | 12 +++++-- lib/cretonne/src/isa/riscv/binemit.rs | 48 +++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 68bc718ea7..fd3d910a18 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -55,9 +55,15 @@ ebb0: [-,%x7] v114 = bxor_imm v1, 1000 ; bin: 3e854393 [-,%x16] v115 = bxor_imm v2, -905 ; bin: c77ac813 - ; TBD: slli - ; TBD: srli - ; TBD: srai + ; slli + [-,%x7] v120 = ishl_imm v1, 31 ; bin: 01f51393 + [-,%x16] v121 = ishl_imm v2, 8 ; bin: 008a9813 + ; srli + [-,%x7] v122 = ushr_imm v1, 31 ; bin: 01f55393 + [-,%x16] v123 = ushr_imm v2, 8 ; bin: 008ad813 + ; srai + [-,%x7] v124 = sshr_imm v1, 31 ; bin: 41f55393 + [-,%x16] v125 = sshr_imm v2, 8 ; bin: 408ad813 return } diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 480d377c6b..a3941af526 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -38,6 +38,40 @@ fn put_r(bits: u16, sink.put4(i); } +/// R-type instructions with a shift amount instead of rs2. +/// +/// 31 25 19 14 11 6 +/// funct7 shamt rs1 funct3 rd opcode +/// 25 20 15 12 7 0 +/// +/// Both funct7 and shamt contribute to bit 25. In RV64, shamt uses it for shifts > 31. +/// +/// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`. +fn put_rshamt(bits: u16, + rs1: RegUnit, + shamt: i64, + rd: RegUnit, + sink: &mut CS) { + let bits = bits as u32; + let opcode5 = bits & 0x1f; + let funct3 = (bits >> 5) & 0x7; + let funct7 = (bits >> 8) & 0x7f; + let rs1 = rs1 as u32 & 0x1f; + let shamt = shamt as u32 & 0x3f; + let rd = rd as u32 & 0x1f; + + // 0-6: opcode + let mut i = 0x3; + i |= opcode5 << 2; + i |= rd << 7; + i |= funct3 << 12; + i |= rs1 << 15; + i |= shamt << 20; + i |= funct7 << 25; + + sink.put4(i); +} + fn recipe_r(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::Binary { args, .. } = func.dfg[inst] { put_r(func.encodings[inst].bits(), @@ -62,8 +96,16 @@ fn recipe_ricmp(func: &Function, inst: Inst, sink: &mut C } } -fn recipe_rshamt(_func: &Function, _inst: Inst, _sink: &mut CS) { - unimplemented!() +fn recipe_rshamt(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { + put_rshamt(func.encodings[inst].bits(), + func.locations[arg].unwrap_reg(), + imm.into(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); + } } /// I-type instructions. @@ -99,7 +141,7 @@ fn recipe_i(func: &Function, inst: Inst, sink: &mut CS) { func.locations[func.dfg.first_result(inst)].unwrap_reg(), sink); } else { - panic!("Expected Binary format: {:?}", func.dfg[inst]); + panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); } } From 6532cbab6cc2636bd6af8023670d4f18dcede343 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 31 Mar 2017 15:45:21 -0700 Subject: [PATCH 650/968] Skip the Python checks if the Python files haven't changed. --- test-all.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test-all.sh b/test-all.sh index 6e2ceb2de1..14b712d9b4 100755 --- a/test-all.sh +++ b/test-all.sh @@ -46,8 +46,18 @@ else echo "If a newer version of rustfmt is available, update this script." fi -banner $(python --version 2>&1) -$topdir/lib/cretonne/meta/check.sh +# Check if any Python files have changed since we last checked them. +tsfile=$topdir/target/meta-checked +if [ -f $tsfile ]; then + needcheck=$(find $topdir/lib/cretonne/meta -name '*.py' -newer $tsfile) +else + needcheck=yes +fi +if [ -n "$needcheck" ]; then + banner $(python --version 2>&1) + $topdir/lib/cretonne/meta/check.sh + touch $tsfile +fi PKGS="cretonne cretonne-reader cretonne-tools filecheck" cd "$topdir" From 2e45365ee11b66a9bb23a1f6e20839bc44245e48 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 09:49:44 -0700 Subject: [PATCH 651/968] Add an icmp_imm instruction. Compare a scalar integer to an immediate constant. Both Intel and RISC-V ISAs have this operation. This requires the addition of a new IntCompareImm instruction format. --- docs/langref.rst | 1 + filetests/parser/tiny.cton | 4 ++-- lib/cretonne/meta/base/formats.py | 1 + lib/cretonne/meta/base/instructions.py | 16 ++++++++++++++++ lib/cretonne/src/ir/instructions.rs | 7 +++++++ lib/cretonne/src/verifier.rs | 3 ++- lib/cretonne/src/write.rs | 1 + lib/reader/src/parser.rs | 21 ++++++++++++++++++--- 8 files changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 19e17490a2..507cef3cc4 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -678,6 +678,7 @@ Integer operations ------------------ .. autoinst:: icmp +.. autoinst:: icmp_imm .. autoinst:: iadd .. autoinst:: iadd_imm .. autoinst:: iadd_cin diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 7158441ed2..eb6a28c982 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -54,14 +54,14 @@ function icmp(i32, i32) { ebb0(vx0: i32, vx1: i32): v0 = icmp eq, vx0, vx1 v1 = icmp ult, vx0, vx1 - v2 = icmp sge, vx0, vx1 + v2 = icmp_imm sge, vx0, -12 v3 = irsub_imm vx1, 45 } ; sameln: function icmp(i32, i32) { ; nextln: ebb0(vx0: i32, vx1: i32): ; nextln: v0 = icmp eq, vx0, vx1 ; nextln: v1 = icmp ult, vx0, vx1 -; nextln: v2 = icmp sge, vx0, vx1 +; nextln: v2 = icmp_imm sge, vx0, -12 ; nextln: v3 = irsub_imm vx1, 45 ; nextln: } diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 5ae806b936..a80fa03fd9 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -38,6 +38,7 @@ InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE) ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) IntCompare = InstructionFormat(intcc, VALUE, VALUE) +IntCompareImm = InstructionFormat(intcc, VALUE, imm64) FloatCompare = InstructionFormat(floatcc, VALUE, VALUE) Jump = InstructionFormat(ebb, VARIABLE_ARGS) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 541889f7d5..f618be7a75 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -367,6 +367,22 @@ icmp = Instruction( """, ins=(Cond, x, y), outs=a) +a = Operand('a', b1) +x = Operand('x', iB) +Y = Operand('Y', imm64) + +icmp_imm = Instruction( + 'icmp_imm', r""" + Compare scalar integer to a constant. + + This is the same as the :inst:`icmp` instruction, except one operand is + an immediate constant. + + This instruction can only compare scalars. Use :inst:`icmp` for + lane-wise vector comparisons. + """, + ins=(Cond, x, Y), outs=a) + a = Operand('a', Int) x = Operand('x', Int) y = Operand('y', Int) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index f5fd0c0a98..8502954cd1 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -175,6 +175,13 @@ pub enum InstructionData { cond: IntCC, args: [Value; 2], }, + IntCompareImm { + opcode: Opcode, + ty: Type, + cond: IntCC, + arg: Value, + imm: Imm64, + }, FloatCompare { opcode: Opcode, ty: Type, diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 37dc5e20f7..0267d297a2 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -251,6 +251,7 @@ impl<'a> Verifier<'a> { &InsertLane { .. } | &ExtractLane { .. } | &IntCompare { .. } | + &IntCompareImm { .. } | &FloatCompare { .. } => {} } @@ -682,4 +683,4 @@ mod tests { let verifier = Verifier::new(&func); assert_err_with_msg!(verifier.run(), "instruction format"); } -} \ No newline at end of file +} diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 548f718099..23889390e7 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -260,6 +260,7 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result InsertLane { lane, args, .. } => write!(w, " {}, {}, {}", args[0], lane, args[1]), ExtractLane { lane, arg, .. } => write!(w, " {}, {}", arg, lane), IntCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), + IntCompareImm { cond, arg, imm, .. } => write!(w, " {}, {}, {}", cond, arg, imm), FloatCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), Jump { destination, ref args, .. } => { if args.is_empty() { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 414dd291e2..2e523eeb01 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -216,11 +216,12 @@ impl<'a> Context<'a> { InstructionData::UnaryIeee32 { .. } | InstructionData::UnaryIeee64 { .. } => {} - InstructionData::Unary { ref mut arg, .. } | - InstructionData::UnarySplit { ref mut arg, .. } | InstructionData::BinaryImm { ref mut arg, .. } | + InstructionData::BranchTable { ref mut arg, .. } | InstructionData::ExtractLane { ref mut arg, .. } | - InstructionData::BranchTable { ref mut arg, .. } => { + InstructionData::IntCompareImm { ref mut arg, .. } | + InstructionData::Unary { ref mut arg, .. } | + InstructionData::UnarySplit { ref mut arg, .. } => { self.map.rewrite_value(arg, loc)?; } @@ -1557,6 +1558,20 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } + InstructionFormat::IntCompareImm => { + let cond = self.match_enum("expected intcc condition code")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_imm64("expected immediate second operand")?; + InstructionData::IntCompareImm { + opcode: opcode, + ty: VOID, + cond: cond, + arg: lhs, + imm: rhs, + } + } InstructionFormat::FloatCompare => { let cond = self.match_enum("expected floatcc condition code")?; self.match_token(Token::Comma, "expected ',' between operands")?; From ec29283abb0c18921d70402e6b01215d3af16c1c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 09:56:47 -0700 Subject: [PATCH 652/968] Use the right operand when documenting type variable inference. The meaning of format.typevar_operand changes recently to be relative to value operands only instead of all operands. The Sphinx cton domain wasn't updated. --- docs/cton_domain.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 23204f02e7..3dba386d90 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -304,16 +304,17 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): def add_content(self, more_content, no_docstring=False): super(InstDocumenter, self).add_content(more_content, no_docstring) sourcename = self.get_sourcename() + inst = self.object # Add inputs and outputs. - for op in self.object.ins: + for op in inst.ins: if op.is_value(): typ = op.typevar else: typ = op.kind self.add_line(u':in {} {}: {}'.format( typ, op.name, op.get_doc()), sourcename) - for op in self.object.outs: + for op in inst.outs: if op.is_value(): typ = op.typevar else: @@ -322,22 +323,22 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): typ, op.name, op.get_doc()), sourcename) # Document type inference for polymorphic instructions. - if self.object.is_polymorphic: - if self.object.ctrl_typevar is not None: - if self.object.use_typevar_operand: + if inst.is_polymorphic: + if inst.ctrl_typevar is not None: + if inst.use_typevar_operand: + tvopnum = inst.value_opnums[inst.format.typevar_operand] self.add_line( u':typevar {}: inferred from {}' .format( - self.object.ctrl_typevar.name, - self.object.ins[ - self.object.format.typevar_operand]), + inst.ctrl_typevar.name, + inst.ins[tvopnum]), sourcename) else: self.add_line( u':typevar {}: explicitly provided' - .format(self.object.ctrl_typevar.name), + .format(inst.ctrl_typevar.name), sourcename) - for tv in self.object.other_typevars: + for tv in inst.other_typevars: self.add_line( u':typevar {}: from input operand'.format(tv.name), sourcename) From d4d76c8d7695712d7ce6525db6795da3899b843a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 10:06:08 -0700 Subject: [PATCH 653/968] Give singleton type variables the type's doc string. This reads better than "typeof(b1)". --- lib/cretonne/meta/cdsl/typevar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 2c5141d78c..1dc8630f5f 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -285,7 +285,7 @@ class TypeVar(object): bools = (scalar.bits, scalar.bits) tv = TypeVar( - typ.name, 'typeof({})'.format(typ), + typ.name, typ.__doc__, ints, floats, bools, simd=lanes) tv.singleton_type = typ return tv From c13c318ec4745310a9893cf9b0aa793a1c4acb54 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 10:56:30 -0700 Subject: [PATCH 654/968] Add icmp_imm encodings for RISC-V. The ISA has icmp_imm slt/ult with 12-bit signed immediate operands. --- filetests/isa/riscv/binary32.cton | 8 +++++++- lib/cretonne/meta/isa/riscv/encodings.py | 8 +++++++- lib/cretonne/meta/isa/riscv/recipes.py | 7 ++++++- lib/cretonne/src/isa/riscv/binemit.rs | 12 ++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index fd3d910a18..aa0a7b7389 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -44,7 +44,6 @@ ebb0: ; addi [-,%x7] v100 = iadd_imm v1, 1000 ; bin: 3e850393 [-,%x16] v101 = iadd_imm v2, -905 ; bin: c77a8813 - ; TBD: slti ; andi [-,%x7] v110 = band_imm v1, 1000 ; bin: 3e857393 [-,%x16] v111 = band_imm v2, -905 ; bin: c77af813 @@ -65,5 +64,12 @@ ebb0: [-,%x7] v124 = sshr_imm v1, 31 ; bin: 41f55393 [-,%x16] v125 = sshr_imm v2, 8 ; bin: 408ad813 + ; slti + [-,%x7] v130 = icmp_imm slt, v1, 1000 ; bin: 3e852393 + [-,%x16] v131 = icmp_imm slt, v2, -905 ; bin: c77aa813 + ; sltiu + [-,%x7] v132 = icmp_imm ult, v1, 1000 ; bin: 3e853393 + [-,%x16] v133 = icmp_imm ult, v2, -905 ; bin: c77ab813 + return } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index b97c92386a..1966a576c2 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -5,7 +5,8 @@ from __future__ import absolute_import from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32, JALR, R, Rshamt, Ricmp, I, Iret +from .recipes import OPIMM, OPIMM32, OP, OP32 +from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret from .settings import use_m from cdsl.ast import Var @@ -60,6 +61,11 @@ RV64.enc(base.icmp.i64(intcc.slt, x, y), Ricmp, OP(0b010, 0b0000000)) RV32.enc(base.icmp.i32(intcc.ult, x, y), Ricmp, OP(0b011, 0b0000000)) RV64.enc(base.icmp.i64(intcc.ult, x, y), Ricmp, OP(0b011, 0b0000000)) +RV32.enc(base.icmp_imm.i32(intcc.slt, x, y), Iicmp, OPIMM(0b010)) +RV64.enc(base.icmp_imm.i64(intcc.slt, x, y), Iicmp, OPIMM(0b010)) +RV32.enc(base.icmp_imm.i32(intcc.ult, x, y), Iicmp, OPIMM(0b011)) +RV64.enc(base.icmp_imm.i64(intcc.ult, x, y), Iicmp, OPIMM(0b011)) + # "M" Standard Extension for Integer Multiplication and Division. # Gated by the `use_m` flag. RV32.enc(base.imul.i32, R, OP(0b000, 0b0000001), isap=use_m) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 2fd326e5cc..fb31b5d60a 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -11,7 +11,7 @@ instruction formats described in the reference: from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt -from base.formats import Binary, BinaryImm, MultiAry, IntCompare +from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -86,6 +86,11 @@ I = EncRecipe( 'I', BinaryImm, ins=GPR, outs=GPR, instp=IsSignedInt(BinaryImm.imm, 12)) +# I-type encoding of an integer comparison. +Iicmp = EncRecipe( + 'Iicmp', IntCompareImm, ins=GPR, outs=GPR, + instp=IsSignedInt(IntCompareImm.imm, 12)) + # I-type encoding for `jalr` as a return instruction. We won't use the # immediate offset. # The variable return values are not encoded. diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index a3941af526..5479a60898 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -145,6 +145,18 @@ fn recipe_i(func: &Function, inst: Inst, sink: &mut CS) { } } +fn recipe_iicmp(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::IntCompareImm { arg, imm, .. } = func.dfg[inst] { + put_i(func.encodings[inst].bits(), + func.locations[arg].unwrap_reg(), + imm.into(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected IntCompareImm format: {:?}", func.dfg[inst]); + } +} + fn recipe_iret(_func: &Function, _inst: Inst, _sink: &mut CS) { unimplemented!() } From 175b26976006309d85a22c552eae1502e23ac97b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 12:27:22 -0700 Subject: [PATCH 655/968] Add RISC-V encodings for lui. This instruction can materialize constants with the low 12 bits clear. --- filetests/isa/riscv/binary32.cton | 4 +++ lib/cretonne/meta/isa/riscv/encodings.py | 9 +++++-- lib/cretonne/meta/isa/riscv/recipes.py | 16 ++++++++++++ lib/cretonne/src/isa/riscv/binemit.rs | 32 ++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index aa0a7b7389..f7225c234e 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -71,5 +71,9 @@ ebb0: [-,%x7] v132 = icmp_imm ult, v1, 1000 ; bin: 3e853393 [-,%x16] v133 = icmp_imm ult, v2, -905 ; bin: c77ab813 + ; lui + [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 + [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 + return } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 1966a576c2..e1d186db5d 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -5,8 +5,8 @@ from __future__ import absolute_import from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32 -from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret +from .recipes import OPIMM, OPIMM32, OP, OP32, LUI +from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret, U from .settings import use_m from cdsl.ast import Var @@ -66,6 +66,11 @@ RV64.enc(base.icmp_imm.i64(intcc.slt, x, y), Iicmp, OPIMM(0b010)) RV32.enc(base.icmp_imm.i32(intcc.ult, x, y), Iicmp, OPIMM(0b011)) RV64.enc(base.icmp_imm.i64(intcc.ult, x, y), Iicmp, OPIMM(0b011)) +# Integer constants with the low 12 bits clear are materialized by lui. +RV32.enc(base.iconst.i32, U, LUI()) +RV64.enc(base.iconst.i32, U, LUI()) +RV64.enc(base.iconst.i64, U, LUI()) + # "M" Standard Extension for Integer Multiplication and Division. # Gated by the `use_m` flag. RV32.enc(base.imul.i32, R, OP(0b000, 0b0000001), isap=use_m) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index fb31b5d60a..ebeb9f9ee1 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,6 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm +from base.formats import UnaryImm from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -72,6 +73,16 @@ def OP32(funct3, funct7): return 0b01110 | (funct3 << 5) | (funct7 << 8) +def AIUPC(): + # type: () -> int + return 0b00101 + + +def LUI(): + # type: () -> int + return 0b01101 + + # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) @@ -95,3 +106,8 @@ Iicmp = EncRecipe( # immediate offset. # The variable return values are not encoded. Iret = EncRecipe('Iret', MultiAry, ins=GPR, outs=()) + +# U-type instructions have a 20-bit immediate that targets bits 12-31. +U = EncRecipe( + 'U', UnaryImm, ins=(), outs=GPR, + instp=IsSignedInt(UnaryImm.imm, 32, 12)) diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 5479a60898..a295a66386 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -160,3 +160,35 @@ fn recipe_iicmp(func: &Function, inst: Inst, sink: &mut C fn recipe_iret(_func: &Function, _inst: Inst, _sink: &mut CS) { unimplemented!() } + +/// U-type instructions. +/// +/// 31 11 6 +/// imm rd opcode +/// 12 7 0 +/// +/// Encoding bits: `opcode[6:2] | (funct3 << 5)` +fn put_u(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) { + let bits = bits as u32; + let opcode5 = bits & 0x1f; + let rd = rd as u32 & 0x1f; + + // 0-6: opcode + let mut i = 0x3; + i |= opcode5 << 2; + i |= rd << 7; + i |= imm as u32 & 0xfffff000; + + sink.put4(i); +} + +fn recipe_u(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::UnaryImm { imm, .. } = func.dfg[inst] { + put_u(func.encodings[inst].bits(), + imm.into(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); + } +} From d2ddc700a82f7c2033aac4b555792f03ec771c40 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 13:44:15 -0700 Subject: [PATCH 656/968] Add the br_icmp instruction. This instruction behaves like icmp fused with brnz, and it can be used to represent fused compare+branch instruction on Intel when optimizing for macro-op fusion. RISC-V provides compare-and-branch instructions directly, and it is needed there too. --- docs/langref.rst | 1 + filetests/parser/tiny.cton | 2 ++ lib/cretonne/meta/base/formats.py | 1 + lib/cretonne/meta/base/instructions.py | 23 +++++++++++++++++++ lib/cretonne/src/ir/instructions.rs | 10 +++++++++ lib/cretonne/src/verifier.rs | 8 +++---- lib/cretonne/src/write.rs | 20 ++++++++++------- lib/reader/src/parser.rs | 31 +++++++++++++++++--------- 8 files changed, 73 insertions(+), 23 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 507cef3cc4..47a0dae37c 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -321,6 +321,7 @@ instruction in the EBB. .. autoinst:: jump .. autoinst:: brz .. autoinst:: brnz +.. autoinst:: br_icmp .. autoinst:: br_table .. inst:: JT = jump_table EBB0, EBB1, ..., EBBn diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index eb6a28c982..d5790c0e89 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -56,6 +56,7 @@ ebb0(vx0: i32, vx1: i32): v1 = icmp ult, vx0, vx1 v2 = icmp_imm sge, vx0, -12 v3 = irsub_imm vx1, 45 + br_icmp eq, vx0, vx1, ebb0(vx1, vx0) } ; sameln: function icmp(i32, i32) { ; nextln: ebb0(vx0: i32, vx1: i32): @@ -63,6 +64,7 @@ ebb0(vx0: i32, vx1: i32): ; nextln: v1 = icmp ult, vx0, vx1 ; nextln: v2 = icmp_imm sge, vx0, -12 ; nextln: v3 = irsub_imm vx1, 45 +; nextln: br_icmp eq, vx0, vx1, ebb0(vx1, vx0) ; nextln: } ; Floating condition codes. diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index a80fa03fd9..cf1922627f 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -43,6 +43,7 @@ FloatCompare = InstructionFormat(floatcc, VALUE, VALUE) Jump = InstructionFormat(ebb, VARIABLE_ARGS) Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS) +BranchIcmp = InstructionFormat(intcc, VALUE, VALUE, ebb, VARIABLE_ARGS) BranchTable = InstructionFormat(VALUE, jump_table) Call = InstructionFormat( diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index f618be7a75..4776f576c1 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -33,6 +33,9 @@ Any = TypeVar( # Control flow # c = Operand('c', Testable, doc='Controlling value to test') +Cond = Operand('Cond', intcc) +x = Operand('x', iB) +y = Operand('y', iB) EBB = Operand('EBB', entities.ebb, doc='Destination extended basic block') args = Operand('args', VARIABLE_ARGS, doc='EBB arguments') @@ -64,6 +67,26 @@ brnz = Instruction( """, ins=(c, EBB, args), is_branch=True) +br_icmp = Instruction( + 'br_icmp', r""" + Compare scalar integers and branch. + + Compare ``x`` and ``y`` in the same way as the :inst:`icmp` instruction + and take the branch if the condition is true:: + + br_icmp ugt v1, v2, ebb4(v5, v6) + + is semantically equivalent to:: + + v10 = icmp ugt, v1, v2 + brnz v10, ebb4(v5, v6) + + Some RISC architectures like MIPS and RISC-V provide instructions that + implement all or some of the condition codes. The instruction can also + be used to represent *macro-op fusion* on architectures like Intel's. + """, + ins=(Cond, x, y, EBB, args), is_branch=True) + x = Operand('x', iB, doc='index into jump table') JT = Operand('JT', entities.jump_table) br_table = Instruction( diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 8502954cd1..d3ca0bc62f 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -200,6 +200,13 @@ pub enum InstructionData { destination: Ebb, args: ValueList, }, + BranchIcmp { + opcode: Opcode, + ty: Type, + cond: IntCC, + destination: Ebb, + args: ValueList, + }, BranchTable { opcode: Opcode, ty: Type, @@ -303,6 +310,9 @@ impl InstructionData { &InstructionData::Branch { destination, ref args, .. } => { BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]) } + &InstructionData::BranchIcmp { destination, ref args, .. } => { + BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]) + } &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), _ => BranchInfo::NotABranch, } diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 0267d297a2..589f117e73 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -218,11 +218,9 @@ impl<'a> Verifier<'a> { &MultiAry { ref args, .. } => { self.verify_value_list(inst, args)?; } - &Jump { destination, ref args, .. } => { - self.verify_ebb(inst, destination)?; - self.verify_value_list(inst, args)?; - } - &Branch { destination, ref args, .. } => { + &Jump { destination, ref args, .. } | + &Branch { destination, ref args, .. } | + &BranchIcmp { destination, ref args, .. } => { self.verify_ebb(inst, destination)?; self.verify_value_list(inst, args)?; } diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 23889390e7..e1ac42ac09 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -274,15 +274,19 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result } Branch { destination, ref args, .. } => { let args = args.as_slice(pool); - if args.len() == 1 { - write!(w, " {}, {}", args[0], destination) - } else { - write!(w, - " {}, {}({})", - args[0], - destination, - DisplayValues(&args[1..])) + write!(w, " {}, {}", args[0], destination)?; + if args.len() > 1 { + write!(w, "({})", DisplayValues(&args[1..]))?; } + Ok(()) + } + BranchIcmp { cond, destination, ref args, .. } => { + let args = args.as_slice(pool); + write!(w, " {}, {}, {}, {}", cond, args[0], args[1], destination)?; + if args.len() > 2 { + write!(w, "({})", DisplayValues(&args[2..]))?; + } + Ok(()) } BranchTable { arg, table, .. } => write!(w, " {}, {}", arg, table), Call { func_ref, ref args, .. } => { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 2e523eeb01..e8d88dde89 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -241,20 +241,14 @@ impl<'a> Context<'a> { self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::Jump { ref mut destination, ref mut args, .. } => { + InstructionData::Jump { ref mut destination, ref mut args, .. } | + InstructionData::Branch { ref mut destination, ref mut args, .. } | + InstructionData::BranchIcmp { ref mut destination, ref mut args, .. } => { self.map.rewrite_ebb(destination, loc)?; self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::Branch { ref mut destination, ref mut args, .. } => { - self.map.rewrite_ebb(destination, loc)?; - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; - } - - InstructionData::Call { ref mut args, .. } => { - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; - } - + InstructionData::Call { ref mut args, .. } | InstructionData::IndirectCall { ref mut args, .. } => { self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; } @@ -1521,6 +1515,23 @@ impl<'a> Parser<'a> { args: args.into_value_list(&[ctrl_arg], &mut ctx.function.dfg.value_lists), } } + InstructionFormat::BranchIcmp => { + let cond = self.match_enum("expected intcc condition code")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let lhs = self.match_value("expected SSA value first operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let rhs = self.match_value("expected SSA value second operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let ebb_num = self.match_ebb("expected branch destination EBB")?; + let args = self.parse_opt_value_list()?; + InstructionData::BranchIcmp { + opcode: opcode, + ty: VOID, + cond: cond, + destination: ebb_num, + args: args.into_value_list(&[lhs, rhs], &mut ctx.function.dfg.value_lists), + } + } InstructionFormat::InsertLane => { let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; From 39e102b155b192fcfdbcb6515f90545ba90b19dd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 15:07:08 -0700 Subject: [PATCH 657/968] Add conditional branch encodings for RISC-V. Not all br_icmp opcodes are present in the ISA. The missing ones can be reached by commuting operands. Don't attempt to encode EBB offsets yet. For now just emit an EBB relocation for the branch instruction. --- filetests/isa/riscv/binary32.cton | 16 +++++++ lib/cretonne/meta/isa/riscv/encodings.py | 18 +++++++- lib/cretonne/meta/isa/riscv/recipes.py | 7 ++- lib/cretonne/src/binemit/mod.rs | 7 ++- lib/cretonne/src/isa/mod.rs | 8 ++++ lib/cretonne/src/isa/riscv/binemit.rs | 56 +++++++++++++++++++++++- lib/cretonne/src/isa/riscv/mod.rs | 4 ++ src/filetest/binemit.rs | 18 +++++--- test-all.sh | 2 +- 9 files changed, 124 insertions(+), 12 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index f7225c234e..c2b300e07c 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -75,5 +75,21 @@ ebb0: [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 + + ; Control Transfer Instructions + + ; beq + br_icmp eq, v1, v2, ebb0 ; bin: Branch(ebb0) 01550063 + ; bne + br_icmp ne, v1, v2, ebb0 ; bin: Branch(ebb0) 01551063 + ; blt + br_icmp slt, v1, v2, ebb0 ; bin: Branch(ebb0) 01554063 + ; bge + br_icmp sge, v1, v2, ebb0 ; bin: Branch(ebb0) 01555063 + ; bltu + br_icmp ult, v1, v2, ebb0 ; bin: Branch(ebb0) 01556063 + ; bgeu + br_icmp uge, v1, v2, ebb0 ; bin: Branch(ebb0) 01557063 + return } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index e1d186db5d..0d677c54ca 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -5,14 +5,16 @@ from __future__ import absolute_import from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32, LUI -from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret, U +from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH +from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret, U, SB from .settings import use_m from cdsl.ast import Var # Dummies for instruction predicates. x = Var('x') y = Var('y') +dest = Var('dest') +args = Var('args') # Basic arithmetic binary instructions are encoded in an R-type instruction. for inst, inst_imm, f3, f7 in [ @@ -79,6 +81,18 @@ RV64.enc(base.imul.i32, R, OP32(0b000, 0b0000001), isap=use_m) # Control flow. +# Conditional branches. +for cond, f3 in [ + (intcc.eq, 0b000), + (intcc.ne, 0b001), + (intcc.slt, 0b100), + (intcc.sge, 0b101), + (intcc.ult, 0b110), + (intcc.uge, 0b111) + ]: + RV32.enc(base.br_icmp.i32(cond, x, y, dest, args), SB, BRANCH(f3)) + RV64.enc(base.br_icmp.i64(cond, x, y, dest, args), SB, BRANCH(f3)) + # Returns are a special case of JALR. # Note: Return stack predictors will only recognize this as a return when the # return address is provided in `x1`. We may want a special encoding to enforce diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index ebeb9f9ee1..9fe3c6f14d 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,7 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm -from base.formats import UnaryImm +from base.formats import UnaryImm, BranchIcmp from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -111,3 +111,8 @@ Iret = EncRecipe('Iret', MultiAry, ins=GPR, outs=()) U = EncRecipe( 'U', UnaryImm, ins=(), outs=GPR, instp=IsSignedInt(UnaryImm.imm, 32, 12)) + +# SB-type branch instructions. +# TODO: These instructions have a +/- 4 KB branch range. How to encode that +# constraint? +SB = EncRecipe('SB', BranchIcmp, ins=(GPR, GPR), outs=()) diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index 0b8b3475b7..ccad476f44 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -3,10 +3,10 @@ //! The `binemit` module contains code for translating Cretonne's intermediate representation into //! binary machine code. -use ir::{FuncRef, JumpTable, Function, Inst}; +use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; /// Relocation kinds depend on the current ISA. -pub struct Reloc(u16); +pub struct Reloc(pub u16); /// Abstract interface for adding bytes to the code segment. /// @@ -25,6 +25,9 @@ pub trait CodeSink { /// Add 8 bytes to the code section. fn put8(&mut self, u64); + /// Add a relocation referencing an EBB at the current offset. + fn reloc_ebb(&mut self, Reloc, Ebb); + /// Add a relocation referencing an external function at the current offset. fn reloc_func(&mut self, Reloc, FuncRef); diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 776d5e4bca..4eddae7b14 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -188,4 +188,12 @@ pub trait TargetIsa { /// Note that this will call `put*` methods on the trait object via its vtable which is not the /// fastest way of emitting code. fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink); + + /// Get a static array of names associated with relocations in this ISA. + /// + /// This array can be indexed by the contents of `binemit::Reloc` objects passed to a + /// `CodeSink`. + fn reloc_names(&self) -> &'static [&'static str] { + unimplemented!() + } } diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index a295a66386..888fa369ed 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -1,11 +1,25 @@ //! Emitting binary RISC-V machine code. -use binemit::{CodeSink, bad_encoding}; +use binemit::{CodeSink, Reloc, bad_encoding}; use ir::{Function, Inst, InstructionData}; use isa::RegUnit; include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); +/// RISC-V relocation kinds. +pub enum RelocKind { + /// A conditional (SB-type) branch to an EBB. + Branch, +} + +pub static RELOC_NAMES: [&'static str; 1] = ["Branch"]; + +impl Into for RelocKind { + fn into(self) -> Reloc { + Reloc(self as u16) + } +} + /// R-type instructions. /// /// 31 24 19 14 11 6 @@ -192,3 +206,43 @@ fn recipe_u(func: &Function, inst: Inst, sink: &mut CS) { panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); } } + +/// SB-type branch instructions. +/// +/// 31 24 19 14 11 6 +/// imm rs2 rs1 funct3 imm opcode +/// 25 20 15 12 7 0 +/// +/// The imm bits are not encoded by this function. They encode the relative distance to the +/// destination block, handled by a relocation. +/// +/// Encoding bits: `opcode[6:2] | (funct3 << 5)` +fn put_sb(bits: u16, rs1: RegUnit, rs2: RegUnit, sink: &mut CS) { + let bits = bits as u32; + let opcode5 = bits & 0x1f; + let funct3 = (bits >> 5) & 0x7; + let rs1 = rs1 as u32 & 0x1f; + let rs2 = rs2 as u32 & 0x1f; + + // 0-6: opcode + let mut i = 0x3; + i |= opcode5 << 2; + i |= funct3 << 12; + i |= rs1 << 15; + i |= rs2 << 20; + + sink.put4(i); +} + +fn recipe_sb(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::BranchIcmp { destination, ref args, .. } = func.dfg[inst] { + let args = &args.as_slice(&func.dfg.value_lists)[0..2]; + sink.reloc_ebb(RelocKind::Branch.into(), destination); + put_sb(func.encodings[inst].bits(), + func.locations[args[0]].unwrap_reg(), + func.locations[args[1]].unwrap_reg(), + sink); + } else { + panic!("Expected BranchIcmp format: {:?}", func.dfg[inst]); + } +} diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 94808bfb70..348e1af32f 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -86,6 +86,10 @@ impl TargetIsa for Isa { fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } + + fn reloc_names(&self) -> &'static [&'static str] { + &binemit::RELOC_NAMES + } } #[cfg(test)] diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 16c2613b20..3c9fc333e6 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -25,6 +25,7 @@ pub fn subtest(parsed: &TestCommand) -> Result> { // Code sink that generates text. struct TextSink { + rnames: &'static [&'static str], text: String, } @@ -45,12 +46,16 @@ impl binemit::CodeSink for TextSink { write!(self.text, "{:016x} ", x).unwrap(); } - fn reloc_func(&mut self, _: binemit::Reloc, _: ir::FuncRef) { - unimplemented!() + fn reloc_ebb(&mut self, reloc: binemit::Reloc, ebb: ir::Ebb) { + write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], ebb).unwrap(); } - fn reloc_jt(&mut self, _: binemit::Reloc, _: ir::JumpTable) { - unimplemented!() + fn reloc_func(&mut self, reloc: binemit::Reloc, fref: ir::FuncRef) { + write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], fref).unwrap(); + } + + fn reloc_jt(&mut self, reloc: binemit::Reloc, jt: ir::JumpTable) { + write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], jt).unwrap(); } } @@ -73,7 +78,10 @@ impl SubTest for TestBinEmit { // value locations. The current error reporting is just crashing... let mut func = func.into_owned(); - let mut sink = TextSink { text: String::new() }; + let mut sink = TextSink { + rnames: isa.reloc_names(), + text: String::new(), + }; for comment in &context.details.comments { if let Some(want) = match_directive(comment.text, "bin:") { diff --git a/test-all.sh b/test-all.sh index 14b712d9b4..7c4938902f 100755 --- a/test-all.sh +++ b/test-all.sh @@ -56,7 +56,7 @@ fi if [ -n "$needcheck" ]; then banner $(python --version 2>&1) $topdir/lib/cretonne/meta/check.sh - touch $tsfile + touch $tsfile || echo no target directory fi PKGS="cretonne cretonne-reader cretonne-tools filecheck" From 3a47b40ff8232202aa484912e8f40744e696b5db Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 3 Apr 2017 15:20:57 -0700 Subject: [PATCH 658/968] Add RISC-V encodings for brz and brnz. These branches compare a register to zero. RISC-V implements this with the %x0 hard-coded zero register. --- filetests/isa/riscv/binary32.cton | 5 +++++ lib/cretonne/meta/isa/riscv/encodings.py | 11 ++++++++++- lib/cretonne/meta/isa/riscv/recipes.py | 5 ++++- lib/cretonne/src/isa/riscv/binemit.rs | 13 +++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index c2b300e07c..93b1a9cc2d 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -91,5 +91,10 @@ ebb0: ; bgeu br_icmp uge, v1, v2, ebb0 ; bin: Branch(ebb0) 01557063 + ; beq x, %x0 + brz v1, ebb0 ; bin: Branch(ebb0) 00050063 + ; bne x, %x0 + brnz v1, ebb0 ; bin: Branch(ebb0) 00051063 + return } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 0d677c54ca..162ccbd3ea 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -6,7 +6,7 @@ from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH -from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret, U, SB +from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret, U, SB, SBzero from .settings import use_m from cdsl.ast import Var @@ -93,6 +93,15 @@ for cond, f3 in [ RV32.enc(base.br_icmp.i32(cond, x, y, dest, args), SB, BRANCH(f3)) RV64.enc(base.br_icmp.i64(cond, x, y, dest, args), SB, BRANCH(f3)) +for inst, f3 in [ + (base.brz, 0b000), + (base.brnz, 0b001) + ]: + RV32.enc(inst.i32, SBzero, BRANCH(f3)) + RV64.enc(inst.i64, SBzero, BRANCH(f3)) + RV32.enc(inst.b1, SBzero, BRANCH(f3)) + RV64.enc(inst.b1, SBzero, BRANCH(f3)) + # Returns are a special case of JALR. # Note: Return stack predictors will only recognize this as a return when the # return address is provided in `x1`. We may want a special encoding to enforce diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 9fe3c6f14d..2be2192002 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,7 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm -from base.formats import UnaryImm, BranchIcmp +from base.formats import UnaryImm, BranchIcmp, Branch from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -116,3 +116,6 @@ U = EncRecipe( # TODO: These instructions have a +/- 4 KB branch range. How to encode that # constraint? SB = EncRecipe('SB', BranchIcmp, ins=(GPR, GPR), outs=()) + +# SB-type branch instruction with rs2 fixed to zero. +SBzero = EncRecipe('SBzero', Branch, ins=(GPR), outs=()) diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 888fa369ed..057fac11aa 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -246,3 +246,16 @@ fn recipe_sb(func: &Function, inst: Inst, sink: &mut CS) panic!("Expected BranchIcmp format: {:?}", func.dfg[inst]); } } + +fn recipe_sbzero(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Branch { destination, ref args, .. } = func.dfg[inst] { + let args = &args.as_slice(&func.dfg.value_lists)[0..1]; + sink.reloc_ebb(RelocKind::Branch.into(), destination); + put_sb(func.encodings[inst].bits(), + func.locations[args[0]].unwrap_reg(), + 0, + sink); + } else { + panic!("Expected Branch format: {:?}", func.dfg[inst]); + } +} From 8353651559b3db82bac49c306855f015c7713226 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Apr 2017 09:00:11 -0700 Subject: [PATCH 659/968] rustfmt 0.8.1 --- lib/cretonne/src/abi.rs | 5 +- lib/cretonne/src/context.rs | 3 +- lib/cretonne/src/dominator_tree.rs | 8 +- lib/cretonne/src/entity_list.rs | 4 +- lib/cretonne/src/ir/builder.rs | 10 +- lib/cretonne/src/ir/dfg.rs | 63 ++++-- lib/cretonne/src/ir/entities.rs | 8 +- lib/cretonne/src/ir/extfunc.rs | 3 +- lib/cretonne/src/ir/instructions.rs | 42 ++-- lib/cretonne/src/ir/jumptable.rs | 14 +- lib/cretonne/src/ir/layout.rs | 31 ++- lib/cretonne/src/ir/types.rs | 14 +- lib/cretonne/src/isa/enc_tables.rs | 14 +- lib/cretonne/src/isa/riscv/binemit.rs | 12 +- lib/cretonne/src/legalizer/boundary.rs | 20 +- lib/cretonne/src/legalizer/split.rs | 10 +- lib/cretonne/src/partition_slice.rs | 5 +- lib/cretonne/src/regalloc/coloring.rs | 4 +- lib/cretonne/src/regalloc/context.rs | 3 +- .../src/regalloc/live_value_tracker.rs | 41 ++-- lib/cretonne/src/regalloc/liveness.rs | 6 +- lib/cretonne/src/regalloc/liverange.rs | 33 +-- lib/cretonne/src/verifier.rs | 71 ++++--- lib/cretonne/src/write.rs | 19 +- lib/filecheck/src/checker.rs | 26 ++- lib/filecheck/src/explain.rs | 74 +++---- lib/reader/src/parser.rs | 191 ++++++++++-------- lib/reader/src/testcommand.rs | 5 +- src/cton-util.rs | 6 +- src/filetest/concurrent.rs | 18 +- src/filetest/legalizer.rs | 3 +- src/filetest/regalloc.rs | 3 +- src/filetest/runner.rs | 25 ++- src/filetest/runone.rs | 3 +- src/filetest/subtest.rs | 12 +- src/rsfilecheck.rs | 15 +- test-all.sh | 2 +- tests/cfg_traversal.rs | 5 +- 38 files changed, 497 insertions(+), 334 deletions(-) diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs index 14a034b8fb..32210fecfb 100644 --- a/lib/cretonne/src/abi.rs +++ b/lib/cretonne/src/abi.rs @@ -108,7 +108,10 @@ pub fn legalize_args(args: &mut Vec, aa: &mut AA) } // Split this argument into two smaller ones. Then revisit both. ArgAction::Convert(conv) => { - let new_arg = ArgumentType { value_type: conv.apply(arg.value_type), ..arg }; + let new_arg = ArgumentType { + value_type: conv.apply(arg.value_type), + ..arg + }; args[argno].value_type = new_arg.value_type; if conv.is_split() { args.insert(argno + 1, new_arg); diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 2d5631f62c..051adc47aa 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -69,6 +69,7 @@ impl Context { /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) { - self.regalloc.run(isa, &mut self.func, &self.cfg, &self.domtree); + self.regalloc + .run(isa, &mut self.func, &self.cfg, &self.domtree); } } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index b014e85ec1..13d1175d8a 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -88,7 +88,8 @@ impl DominatorTree { // Run a finger up the dominator tree from b until we see a. // Do nothing if b is unreachable. while rpo_a < self.nodes[ebb_b].rpo_number { - b = self.idom(ebb_b).expect("Shouldn't meet unreachable here."); + b = self.idom(ebb_b) + .expect("Shouldn't meet unreachable here."); ebb_b = layout.inst_ebb(b).expect("Dominator got removed."); } @@ -209,8 +210,9 @@ impl DominatorTree { .filter(|&(ebb, _)| self.is_reachable(ebb)); // The RPO must visit at least one predecessor before this node. - let mut idom = - reachable_preds.next().expect("EBB node must have one reachable predecessor"); + let mut idom = reachable_preds + .next() + .expect("EBB node must have one reachable predecessor"); for pred in reachable_preds { idom = self.common_dominator(idom, pred, layout); diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 1a05464c21..0b90265470 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -135,7 +135,9 @@ impl ListPool { // The `wrapping_sub` handles the special case 0, which is the empty list. This way, the // cost of the bounds check that we have to pay anyway is co-opted to handle the special // case of the empty list. - self.data.get(idx.wrapping_sub(1)).map(|len| len.index()) + self.data + .get(idx.wrapping_sub(1)) + .map(|len| len.index()) } /// Allocate a storage block with a size given by `sclass`. diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 8880351d9b..fb5c4ba20a 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -65,10 +65,7 @@ impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { pub fn new(dfg: &'fd mut DataFlowGraph, pos: &'c mut Cursor<'fc>) -> InsertBuilder<'c, 'fc, 'fd> { - InsertBuilder { - dfg: dfg, - pos: pos, - } + InsertBuilder { dfg: dfg, pos: pos } } } @@ -182,8 +179,9 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { // Normally, make_inst_results() would also set the first result type, but we're not // going to call that, so set it manually. - *self.dfg[self.inst].first_type_mut() = - self.dfg.compute_result_type(self.inst, 0, ctrl_typevar).unwrap_or_default(); + *self.dfg[self.inst].first_type_mut() = self.dfg + .compute_result_type(self.inst, 0, ctrl_typevar) + .unwrap_or_default(); } (self.inst, self.dfg) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index f7d214182b..22ce7a116d 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -356,25 +356,37 @@ impl DataFlowGraph { /// Get the fixed value arguments on `inst` as a slice. pub fn inst_fixed_args(&self, inst: Inst) -> &[Value] { - let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + let fixed_args = self[inst] + .opcode() + .constraints() + .fixed_value_arguments(); &self.inst_args(inst)[..fixed_args] } /// Get the fixed value arguments on `inst` as a mutable slice. pub fn inst_fixed_args_mut(&mut self, inst: Inst) -> &mut [Value] { - let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + let fixed_args = self[inst] + .opcode() + .constraints() + .fixed_value_arguments(); &mut self.inst_args_mut(inst)[..fixed_args] } /// Get the variable value arguments on `inst` as a slice. pub fn inst_variable_args(&self, inst: Inst) -> &[Value] { - let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + let fixed_args = self[inst] + .opcode() + .constraints() + .fixed_value_arguments(); &self.inst_args(inst)[fixed_args..] } /// Get the variable value arguments on `inst` as a mutable slice. pub fn inst_variable_args_mut(&mut self, inst: Inst) -> &mut [Value] { - let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); + let fixed_args = self[inst] + .opcode() + .constraints() + .fixed_value_arguments(); &mut self.inst_args_mut(inst)[fixed_args..] } @@ -444,8 +456,8 @@ impl DataFlowGraph { // Update the second_result pointer in `inst`. if head.is_some() { *self.insts[inst] - .second_result_mut() - .expect("instruction format doesn't allow multiple results") = head.into(); + .second_result_mut() + .expect("instruction format doesn't allow multiple results") = head.into(); } *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); @@ -505,7 +517,12 @@ impl DataFlowGraph { (inst, 1) } ExpandedValue::Table(idx) => { - if let ValueData::Inst { num, inst, ref mut next, .. } = self.extended_values[idx] { + if let ValueData::Inst { + num, + inst, + ref mut next, + .. + } = self.extended_values[idx] { assert!(next.is_none(), "last_res is not the last result"); *next = res.into(); assert!(num < u16::MAX, "Too many arguments to EBB"); @@ -518,8 +535,12 @@ impl DataFlowGraph { // Now update `res` itself. if let ExpandedValue::Table(idx) = res.expand() { - if let ValueData::Inst { ref mut num, ref mut inst, ref mut next, .. } = - self.extended_values[idx] { + if let ValueData::Inst { + ref mut num, + ref mut inst, + ref mut next, + .. + } = self.extended_values[idx] { *num = res_num; *inst = res_inst; *next = None.into(); @@ -565,7 +586,8 @@ impl DataFlowGraph { /// /// Returns the new `Inst` reference where the original instruction has been moved. pub fn redefine_first_value(&mut self, pos: &mut Cursor) -> Inst { - let orig = pos.current_inst().expect("Cursor must point at an instruction"); + let orig = pos.current_inst() + .expect("Cursor must point at an instruction"); let data = self[orig].clone(); // After cloning, any secondary values are attached to both copies. Don't do that, we only // want them on the new clone. @@ -630,12 +652,13 @@ impl DataFlowGraph { } // Not a fixed result, try to extract a return type from the call signature. - self.call_signature(inst).and_then(|sigref| { - self.signatures[sigref] - .return_types - .get(result_idx - fixed_results) - .map(|&arg| arg.value_type) - }) + self.call_signature(inst) + .and_then(|sigref| { + self.signatures[sigref] + .return_types + .get(result_idx - fixed_results) + .map(|&arg| arg.value_type) + }) } } @@ -815,8 +838,12 @@ impl DataFlowGraph { // Now update `arg` itself. let arg_ebb = ebb; if let ExpandedValue::Table(idx) = arg.expand() { - if let ValueData::Arg { ref mut num, ebb, ref mut next, .. } = - self.extended_values[idx] { + if let ValueData::Arg { + ref mut num, + ebb, + ref mut next, + .. + } = self.extended_values[idx] { *num = arg_num; *next = None.into(); assert_eq!(arg_ebb, ebb, "{} should already belong to EBB", arg); diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 42d109bf7a..876598d13a 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -286,14 +286,18 @@ mod tests { assert_eq!(Value::table_with_number(1).unwrap().to_string(), "vx1"); assert_eq!(Value::direct_with_number(u32::MAX / 2), None); - assert_eq!(match Value::direct_with_number(u32::MAX / 2 - 1).unwrap().expand() { + assert_eq!(match Value::direct_with_number(u32::MAX / 2 - 1) + .unwrap() + .expand() { ExpandedValue::Direct(i) => i.index() as u32, _ => u32::MAX, }, u32::MAX / 2 - 1); assert_eq!(Value::table_with_number(u32::MAX / 2), None); - assert_eq!(match Value::table_with_number(u32::MAX / 2 - 1).unwrap().expand() { + assert_eq!(match Value::table_with_number(u32::MAX / 2 - 1) + .unwrap() + .expand() { ExpandedValue::Table(i) => i as u32, _ => u32::MAX, }, diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index bc293d1169..43daaed192 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -221,7 +221,8 @@ mod tests { assert_eq!(sig.to_string(), "(i32)"); sig.return_types.push(ArgumentType::new(F32)); assert_eq!(sig.to_string(), "(i32) -> f32"); - sig.argument_types.push(ArgumentType::new(I32.by(4).unwrap())); + sig.argument_types + .push(ArgumentType::new(I32.by(4).unwrap())); assert_eq!(sig.to_string(), "(i32, i32x4) -> f32"); sig.return_types.push(ArgumentType::new(B8)); assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8"); diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index d3ca0bc62f..1c292f473a 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -304,15 +304,21 @@ impl InstructionData { /// here. pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> { match self { - &InstructionData::Jump { destination, ref args, .. } => { - BranchInfo::SingleDest(destination, &args.as_slice(pool)) - } - &InstructionData::Branch { destination, ref args, .. } => { - BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]) - } - &InstructionData::BranchIcmp { destination, ref args, .. } => { - BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]) - } + &InstructionData::Jump { + destination, + ref args, + .. + } => BranchInfo::SingleDest(destination, &args.as_slice(pool)), + &InstructionData::Branch { + destination, + ref args, + .. + } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]), + &InstructionData::BranchIcmp { + destination, + ref args, + .. + } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]), &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), _ => BranchInfo::NotABranch, } @@ -601,9 +607,21 @@ impl OperandConstraint { Same => Bound(ctrl_type), LaneOf => Bound(ctrl_type.lane_type()), AsBool => Bound(ctrl_type.as_bool()), - HalfWidth => Bound(ctrl_type.half_width().expect("invalid type for half_width")), - DoubleWidth => Bound(ctrl_type.double_width().expect("invalid type for double_width")), - HalfVector => Bound(ctrl_type.half_vector().expect("invalid type for half_vector")), + HalfWidth => { + Bound(ctrl_type + .half_width() + .expect("invalid type for half_width")) + } + DoubleWidth => { + Bound(ctrl_type + .double_width() + .expect("invalid type for double_width")) + } + HalfVector => { + Bound(ctrl_type + .half_vector() + .expect("invalid type for half_vector")) + } DoubleVector => Bound(ctrl_type.by(2).expect("invalid type for double_vector")), } } diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index e7f98a329d..dd9bc5c187 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -66,15 +66,14 @@ impl JumpTableData { /// /// This returns an iterator that skips any empty slots in the table. pub fn entries<'a>(&'a self) -> Entries { - Entries(self.table - .iter() - .cloned() - .enumerate()) + Entries(self.table.iter().cloned().enumerate()) } /// Checks if any of the entries branch to `ebb`. pub fn branches_to(&self, ebb: Ebb) -> bool { - self.table.iter().any(|target_ebb| target_ebb.expand() == Some(ebb)) + self.table + .iter() + .any(|target_ebb| target_ebb.expand() == Some(ebb)) } /// Access the whole table as a mutable slice. @@ -109,10 +108,7 @@ impl Display for JumpTableData { Some(first) => write!(fmt, "jump_table {}", first)?, } - for dest in self.table - .iter() - .skip(1) - .map(|e| e.expand()) { + for dest in self.table.iter().skip(1).map(|e| e.expand()) { match dest { None => write!(fmt, ", 0")?, Some(ebb) => write!(fmt, ", {}", ebb)?, diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index c34d6a0aba..4f5d2355b2 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -125,7 +125,10 @@ impl Layout { /// Get the last sequence number in `ebb`. fn last_ebb_seq(&self, ebb: Ebb) -> SequenceNumber { // Get the seq of the last instruction if it exists, otherwise use the EBB header seq. - self.ebbs[ebb].last_inst.map(|inst| self.insts[inst].seq).unwrap_or(self.ebbs[ebb].seq) + self.ebbs[ebb] + .last_inst + .map(|inst| self.insts[inst].seq) + .unwrap_or(self.ebbs[ebb].seq) } /// Assign a valid sequence number to `ebb` such that the numbers are still monotonic. This may @@ -134,7 +137,10 @@ impl Layout { assert!(self.is_ebb_inserted(ebb)); // Get the sequence number immediately before `ebb`, or 0. - let prev_seq = self.ebbs[ebb].prev.map(|prev_ebb| self.last_ebb_seq(prev_ebb)).unwrap_or(0); + let prev_seq = self.ebbs[ebb] + .prev + .map(|prev_ebb| self.last_ebb_seq(prev_ebb)) + .unwrap_or(0); // Get the sequence number immediately following `ebb`. let next_seq = if let Some(inst) = self.ebbs[ebb].first_inst.expand() { @@ -159,7 +165,8 @@ impl Layout { /// Assign a valid sequence number to `inst` such that the numbers are still monotonic. This may /// require renumbering. fn assign_inst_seq(&mut self, inst: Inst) { - let ebb = self.inst_ebb(inst).expect("inst must be inserted before assigning an seq"); + let ebb = self.inst_ebb(inst) + .expect("inst must be inserted before assigning an seq"); // Get the sequence number immediately before `inst`. let prev_seq = match self.insts[inst].prev.expand() { @@ -436,8 +443,8 @@ impl Layout { /// Insert `inst` before the instruction `before` in the same EBB. pub fn insert_inst(&mut self, inst: Inst, before: Inst) { assert_eq!(self.inst_ebb(inst), None); - let ebb = - self.inst_ebb(before).expect("Instruction before insertion point not in the layout"); + let ebb = self.inst_ebb(before) + .expect("Instruction before insertion point not in the layout"); let after = self.insts[before].prev; { let inst_node = self.insts.ensure(inst); @@ -485,8 +492,8 @@ impl Layout { /// i4 /// ``` pub fn split_ebb(&mut self, new_ebb: Ebb, before: Inst) { - let old_ebb = - self.inst_ebb(before).expect("The `before` instruction must be in the layout"); + let old_ebb = self.inst_ebb(before) + .expect("The `before` instruction must be in the layout"); assert!(!self.is_ebb_inserted(new_ebb)); // Insert new_ebb after old_ebb. @@ -786,8 +793,9 @@ impl<'f> Cursor<'f> { self.pos = At(next); Some(next) } else { - self.pos = - After(self.layout.inst_ebb(inst).expect("current instruction removed?")); + self.pos = After(self.layout + .inst_ebb(inst) + .expect("current instruction removed?")); None } } @@ -837,8 +845,9 @@ impl<'f> Cursor<'f> { self.pos = At(prev); Some(prev) } else { - self.pos = - Before(self.layout.inst_ebb(inst).expect("current instruction removed?")); + self.pos = Before(self.layout + .inst_ebb(inst) + .expect("current instruction removed?")); None } } diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index 1321636add..550f25804b 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -348,12 +348,7 @@ mod tests { assert_eq!(big.bits(), 64 * 256); assert_eq!(big.half_vector().unwrap().to_string(), "f64x128"); - assert_eq!(B1.by(2) - .unwrap() - .half_vector() - .unwrap() - .to_string(), - "b1"); + assert_eq!(B1.by(2).unwrap().half_vector().unwrap().to_string(), "b1"); assert_eq!(I32.half_vector(), None); assert_eq!(VOID.half_vector(), None); @@ -383,12 +378,7 @@ mod tests { assert_eq!(B1.by(8).unwrap().to_string(), "b1x8"); assert_eq!(B8.by(1).unwrap().to_string(), "b8"); assert_eq!(B16.by(256).unwrap().to_string(), "b16x256"); - assert_eq!(B32.by(4) - .unwrap() - .by(2) - .unwrap() - .to_string(), - "b32x8"); + assert_eq!(B32.by(4).unwrap().by(2).unwrap().to_string(), "b32x8"); assert_eq!(B64.by(8).unwrap().to_string(), "b64x8"); assert_eq!(I8.by(64).unwrap().to_string(), "i8x64"); assert_eq!(F64.by(2).unwrap().to_string(), "f64x2"); diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 71a3eba2e0..cfb0066b8c 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -90,13 +90,13 @@ pub fn lookup_enclist(ctrl_typevar: Type, Legalize::Expand }) .and_then(|l1idx| { - let l1ent = &level1_table[l1idx]; - let l2off = l1ent.offset.into() as usize; - let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; - probe(l2tab, opcode, opcode as usize) - .map(|l2idx| l2tab[l2idx].offset.into() as usize) - .ok_or(Legalize::Expand) - }) + let l1ent = &level1_table[l1idx]; + let l2off = l1ent.offset.into() as usize; + let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; + probe(l2tab, opcode, opcode as usize) + .map(|l2idx| l2tab[l2idx].offset.into() as usize) + .ok_or(Legalize::Expand) + }) } /// Encoding list entry. diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 057fac11aa..213d8a132e 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -235,7 +235,11 @@ fn put_sb(bits: u16, rs1: RegUnit, rs2: RegUnit, sink: &m } fn recipe_sb(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::BranchIcmp { destination, ref args, .. } = func.dfg[inst] { + if let InstructionData::BranchIcmp { + destination, + ref args, + .. + } = func.dfg[inst] { let args = &args.as_slice(&func.dfg.value_lists)[0..2]; sink.reloc_ebb(RelocKind::Branch.into(), destination); put_sb(func.encodings[inst].bits(), @@ -248,7 +252,11 @@ fn recipe_sb(func: &Function, inst: Inst, sink: &mut CS) } fn recipe_sbzero(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Branch { destination, ref args, .. } = func.dfg[inst] { + if let InstructionData::Branch { + destination, + ref args, + .. + } = func.dfg[inst] { let args = &args.as_slice(&func.dfg.value_lists)[0..1]; sink.reloc_ebb(RelocKind::Branch.into(), destination); put_sb(func.encodings[inst].bits(), diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 2e3766057d..19e06b7ba4 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -107,7 +107,8 @@ fn legalize_inst_results(dfg: &mut DataFlowGraph, -> Inst where ResType: FnMut(&DataFlowGraph, usize) -> ArgumentType { - let mut call = pos.current_inst().expect("Cursor must point to a call instruction"); + let mut call = pos.current_inst() + .expect("Cursor must point to a call instruction"); // We theoretically allow for call instructions that return a number of fixed results before // the call return values. In practice, it doesn't happen. @@ -364,10 +365,13 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, mut get_abi_type: ArgType) where ArgType: FnMut(&DataFlowGraph, usize) -> ArgumentType { - let inst = pos.current_inst().expect("Cursor must point to a call instruction"); + let inst = pos.current_inst() + .expect("Cursor must point to a call instruction"); // Lift the value list out of the call instruction so we modify it. - let mut vlist = dfg[inst].take_value_list().expect("Call must have a value list"); + let mut vlist = dfg[inst] + .take_value_list() + .expect("Call must have a value list"); // The value list contains all arguments to the instruction, including the callee on an // indirect call which isn't part of the call arguments that must match the ABI signature. @@ -405,7 +409,9 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, let mut abi_arg = 0; for old_arg in 0..have_args { - let old_value = vlist.get(old_arg_offset + old_arg, &dfg.value_lists).unwrap(); + let old_value = vlist + .get(old_arg_offset + old_arg, &dfg.value_lists) + .unwrap(); let mut put_arg = |dfg: &mut DataFlowGraph, arg| { let abi_type = get_abi_type(dfg, abi_arg); if dfg.value_type(arg) == abi_type.value_type { @@ -435,7 +441,8 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, /// /// Returns `true` if any instructions were inserted. pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mut Cursor) -> bool { - let mut inst = pos.current_inst().expect("Cursor must point to a call instruction"); + let mut inst = pos.current_inst() + .expect("Cursor must point to a call instruction"); // Start by checking if the argument types already match the signature. let sig_ref = match check_call_signature(dfg, inst) { @@ -475,7 +482,8 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, sig: &Signature) -> bool { - let inst = pos.current_inst().expect("Cursor must point to a return instruction"); + let inst = pos.current_inst() + .expect("Cursor must point to a return instruction"); // Check if the returned types already match the signature. if check_return_signature(dfg, inst, sig) { diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index 3dda54abe7..d1f55a6266 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -125,7 +125,9 @@ fn split_any(dfg: &mut DataFlowGraph, "Predecessor not a branch: {}", dfg.display_inst(inst)); let fixed_args = branch_opc.constraints().fixed_value_arguments(); - let mut args = dfg[inst].take_value_list().expect("Branches must have value lists."); + let mut args = dfg[inst] + .take_value_list() + .expect("Branches must have value lists."); let num_args = args.len(&dfg.value_lists); // Get the old value passed to the EBB argument we're repairing. let old_arg = args.get(fixed_args + repair.num, &dfg.value_lists) @@ -142,12 +144,14 @@ fn split_any(dfg: &mut DataFlowGraph, let (lo, hi) = split_value(dfg, pos, old_arg, repair.concat, &mut repairs); // The `lo` part replaces the original argument. - *args.get_mut(fixed_args + repair.num, &mut dfg.value_lists).unwrap() = lo; + *args.get_mut(fixed_args + repair.num, &mut dfg.value_lists) + .unwrap() = lo; // The `hi` part goes at the end. Since multiple repairs may have been scheduled to the // same EBB, there could be multiple arguments missing. if num_args > fixed_args + repair.hi_num { - *args.get_mut(fixed_args + repair.hi_num, &mut dfg.value_lists).unwrap() = hi; + *args.get_mut(fixed_args + repair.hi_num, &mut dfg.value_lists) + .unwrap() = hi; } else { // We need to append one or more arguments. If we're adding more than one argument, // there must be pending repairs on the stack that will fill in the correct values diff --git a/lib/cretonne/src/partition_slice.rs b/lib/cretonne/src/partition_slice.rs index 0986613040..9626b5fd37 100644 --- a/lib/cretonne/src/partition_slice.rs +++ b/lib/cretonne/src/partition_slice.rs @@ -35,10 +35,7 @@ mod tests { fn check(x: &[u32], want: &[u32]) { assert_eq!(x.len(), want.len()); - let want_count = want.iter() - .cloned() - .filter(|&x| x % 10 == 0) - .count(); + let want_count = want.iter().cloned().filter(|&x| x % 10 == 0).count(); let mut v = Vec::new(); v.extend(x.iter().cloned()); let count = partition_slice(&mut v[..], |&x| x % 10 == 0); diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 9bc10c30b3..b8c08bd278 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -232,7 +232,9 @@ impl<'a> Context<'a> { if let Affinity::Reg(rc_index) = lv.affinity { let regclass = self.reginfo.rc(rc_index); // TODO: Fall back to a top-level super-class. Sub-classes are only hints. - let regunit = regs.iter(regclass).next().expect("Out of registers for arguments"); + let regunit = regs.iter(regclass) + .next() + .expect("Out of registers for arguments"); regs.take(regclass, regunit); *locations.ensure(lv.value) = ValueLoc::Reg(regunit); } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 195c480372..2c59ab9797 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -52,6 +52,7 @@ impl Context { // TODO: Second pass: Spilling. // Third pass: Reload and coloring. - self.coloring.run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + self.coloring + .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); } } diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 19ed62e8fb..75b18ef019 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -68,11 +68,12 @@ impl LiveValueVec { /// Add a new live value to `values`. fn push(&mut self, value: Value, endpoint: Inst, affinity: Affinity) { - self.values.push(LiveValue { - value: value, - endpoint: endpoint, - affinity: affinity, - }); + self.values + .push(LiveValue { + value: value, + endpoint: endpoint, + affinity: affinity, + }); } /// Remove all elements. @@ -163,11 +164,14 @@ impl LiveValueTracker { // If the immediate dominator exits, we must have a stored list for it. This is a // requirement to the order EBBs are visited: All dominators must have been processed // before the current EBB. - let idom_live_list = - self.idom_sets.get(&idom).expect("No stored live set for dominator"); + let idom_live_list = self.idom_sets + .get(&idom) + .expect("No stored live set for dominator"); // Get just the values that are live-in to `ebb`. for &value in idom_live_list.as_slice(&self.idom_pool) { - let lr = liveness.get(value).expect("Immediate dominator value has no live range"); + let lr = liveness + .get(value) + .expect("Immediate dominator value has no live range"); // Check if this value is live-in here. if let Some(endpoint) = lr.livein_local_end(ebb, program_order) { @@ -179,7 +183,9 @@ impl LiveValueTracker { // Now add all the live arguments to `ebb`. let first_arg = self.live.values.len(); for value in dfg.ebb_args(ebb) { - let lr = liveness.get(value).expect("EBB argument value has no live range"); + let lr = liveness + .get(value) + .expect("EBB argument value has no live range"); assert_eq!(lr.def(), ebb.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { @@ -259,16 +265,15 @@ impl LiveValueTracker { /// Save the current set of live values so it is associated with `idom`. fn save_idom_live_set(&mut self, idom: Inst) { - let values = self.live - .values - .iter() - .map(|lv| lv.value); + let values = self.live.values.iter().map(|lv| lv.value); let pool = &mut self.idom_pool; // If there already is a set saved for `idom`, just keep it. - self.idom_sets.entry(idom).or_insert_with(|| { - let mut list = ValueList::default(); - list.extend(values, pool); - list - }); + self.idom_sets + .entry(idom) + .or_insert_with(|| { + let mut list = ValueList::default(); + list.extend(values, pool); + list + }); } } diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 40faff0591..3344c5b841 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -205,7 +205,8 @@ fn get_or_create<'a>(lrset: &'a mut LiveRangeSet, def = inst.into(); // Initialize the affinity from the defining instruction's result constraints. // Don't do this for call return values which are always tied to a single register. - affinity = recipe_constraints.get(func.encodings[inst].recipe()) + affinity = recipe_constraints + .get(func.encodings[inst].recipe()) .and_then(|rc| rc.outs.get(rnum)) .map(Affinity::new) .unwrap_or_default(); @@ -315,7 +316,8 @@ impl Liveness { let recipe = func.encodings[inst].recipe(); // Iterator of constraints, one per value operand. // TODO: Should we fail here if the instruction doesn't have a valid encoding? - let mut operand_constraints = recipe_constraints.get(recipe) + let mut operand_constraints = recipe_constraints + .get(recipe) .map(|c| c.ins) .unwrap_or(&[]) .iter(); diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 116ccce4aa..94c16cb9ae 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -221,14 +221,16 @@ impl LiveRange { /// Return `Ok(n)` if `liveins[n]` already contains `ebb`. /// Otherwise, return `Err(n)` with the index where such an interval should be inserted. fn find_ebb_interval(&self, ebb: Ebb, order: &PO) -> Result { - self.liveins.binary_search_by(|intv| order.cmp(intv.begin, ebb)).or_else(|n| { - // The interval at `n-1` may cover `ebb`. - if n > 0 && order.cmp(self.liveins[n - 1].end, ebb) == Ordering::Greater { - Ok(n - 1) - } else { - Err(n) - } - }) + self.liveins + .binary_search_by(|intv| order.cmp(intv.begin, ebb)) + .or_else(|n| { + // The interval at `n-1` may cover `ebb`. + if n > 0 && order.cmp(self.liveins[n - 1].end, ebb) == Ordering::Greater { + Ok(n - 1) + } else { + Err(n) + } + }) } /// Extend the local interval for `ebb` so it reaches `to` which must belong to `ebb`. @@ -307,11 +309,12 @@ impl LiveRange { } // Cannot coalesce; insert new interval (false, false) => { - self.liveins.insert(n, - Interval { - begin: ebb, - end: to, - }); + self.liveins + .insert(n, + Interval { + begin: ebb, + end: to, + }); } } @@ -361,7 +364,9 @@ impl LiveRange { /// answer, but it is also possible that an even later program point is returned. So don't /// depend on the returned `Inst` to belong to `ebb`. pub fn livein_local_end(&self, ebb: Ebb, order: &PO) -> Option { - self.find_ebb_interval(ebb, order).ok().map(|n| self.liveins[n].end) + self.find_ebb_interval(ebb, order) + .ok() + .map(|n| self.liveins[n].end) } } diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 589f117e73..776755d4c4 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -177,8 +177,9 @@ impl<'a> Verifier<'a> { let fixed_results = inst_data.opcode().constraints().fixed_results(); // var_results is 0 if we aren't a call instruction - let var_results = - dfg.call_signature(inst).map(|sig| dfg.signatures[sig].return_types.len()).unwrap_or(0); + let var_results = dfg.call_signature(inst) + .map(|sig| dfg.signatures[sig].return_types.len()) + .unwrap_or(0); let total_results = fixed_results + var_results; if total_results == 0 { @@ -218,9 +219,21 @@ impl<'a> Verifier<'a> { &MultiAry { ref args, .. } => { self.verify_value_list(inst, args)?; } - &Jump { destination, ref args, .. } | - &Branch { destination, ref args, .. } | - &BranchIcmp { destination, ref args, .. } => { + &Jump { + destination, + ref args, + .. + } | + &Branch { + destination, + ref args, + .. + } | + &BranchIcmp { + destination, + ref args, + .. + } => { self.verify_ebb(inst, destination)?; self.verify_value_list(inst, args)?; } @@ -265,10 +278,7 @@ impl<'a> Verifier<'a> { } fn verify_sig_ref(&self, inst: Inst, s: SigRef) -> Result<()> { - if !self.func - .dfg - .signatures - .is_valid(s) { + if !self.func.dfg.signatures.is_valid(s) { err!(inst, "invalid signature reference {}", s) } else { Ok(()) @@ -276,10 +286,7 @@ impl<'a> Verifier<'a> { } fn verify_func_ref(&self, inst: Inst, f: FuncRef) -> Result<()> { - if !self.func - .dfg - .ext_funcs - .is_valid(f) { + if !self.func.dfg.ext_funcs.is_valid(f) { err!(inst, "invalid function reference {}", f) } else { Ok(()) @@ -326,7 +333,8 @@ impl<'a> Verifier<'a> { def_inst); } // Defining instruction dominates the instruction that uses the value. - if !self.domtree.dominates(def_inst, loc_inst, &self.func.layout) { + if !self.domtree + .dominates(def_inst, loc_inst, &self.func.layout) { return err!(loc_inst, "uses value from non-dominating {}", def_inst); } } @@ -343,7 +351,8 @@ impl<'a> Verifier<'a> { ebb); } // The defining EBB dominates the instruction using this value. - if !self.domtree.ebb_dominates(ebb, loc_inst, &self.func.layout) { + if !self.domtree + .ebb_dominates(ebb, loc_inst, &self.func.layout) { return err!(loc_inst, "uses value arg from non-dominating {}", ebb); } } @@ -378,10 +387,7 @@ impl<'a> Verifier<'a> { return err!(ebb, "entry block arguments must match function signature"); } - for (i, arg) in self.func - .dfg - .ebb_args(ebb) - .enumerate() { + for (i, arg) in self.func.dfg.ebb_args(ebb).enumerate() { let arg_type = self.func.dfg.value_type(arg); if arg_type != expected_types[i].value_type { return err!(ebb, @@ -452,11 +458,7 @@ impl<'a> Verifier<'a> { fn typecheck_fixed_args(&self, inst: Inst, ctrl_type: Type) -> Result<()> { let constraints = self.func.dfg[inst].opcode().constraints(); - for (i, &arg) in self.func - .dfg - .inst_fixed_args(inst) - .iter() - .enumerate() { + for (i, &arg) in self.func.dfg.inst_fixed_args(inst).iter().enumerate() { let arg_type = self.func.dfg.value_type(arg); match constraints.value_argument_constraint(i, ctrl_type) { ResolvedConstraint::Bound(expected_type) => { @@ -510,13 +512,17 @@ impl<'a> Verifier<'a> { match self.func.dfg[inst].analyze_call(&self.func.dfg.value_lists) { CallInfo::Direct(func_ref, _) => { let sig_ref = self.func.dfg.ext_funcs[func_ref].signature; - let arg_types = - self.func.dfg.signatures[sig_ref].argument_types.iter().map(|a| a.value_type); + let arg_types = self.func.dfg.signatures[sig_ref] + .argument_types + .iter() + .map(|a| a.value_type); self.typecheck_variable_args_iterator(inst, arg_types)?; } CallInfo::Indirect(sig_ref, _) => { - let arg_types = - self.func.dfg.signatures[sig_ref].argument_types.iter().map(|a| a.value_type); + let arg_types = self.func.dfg.signatures[sig_ref] + .argument_types + .iter() + .map(|a| a.value_type); self.typecheck_variable_args_iterator(inst, arg_types)?; } CallInfo::NotACall => {} @@ -673,10 +679,11 @@ mod tests { let mut func = Function::new(); let ebb0 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); - let nullary_with_bad_opcode = func.dfg.make_inst(InstructionData::Nullary { - opcode: Opcode::Jump, - ty: types::VOID, - }); + let nullary_with_bad_opcode = func.dfg + .make_inst(InstructionData::Nullary { + opcode: Opcode::Jump, + ty: types::VOID, + }); func.layout.append_inst(nullary_with_bad_opcode, ebb0); let verifier = Verifier::new(&func); assert_err_with_msg!(verifier.run(), "instruction format"); diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index e1ac42ac09..e2cd0fe3f5 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -262,7 +262,11 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result IntCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), IntCompareImm { cond, arg, imm, .. } => write!(w, " {}, {}, {}", cond, arg, imm), FloatCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), - Jump { destination, ref args, .. } => { + Jump { + destination, + ref args, + .. + } => { if args.is_empty() { write!(w, " {}", destination) } else { @@ -272,7 +276,11 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result DisplayValues(args.as_slice(pool))) } } - Branch { destination, ref args, .. } => { + Branch { + destination, + ref args, + .. + } => { let args = args.as_slice(pool); write!(w, " {}, {}", args[0], destination)?; if args.len() > 1 { @@ -280,7 +288,12 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result } Ok(()) } - BranchIcmp { cond, destination, ref args, .. } => { + BranchIcmp { + cond, + destination, + ref args, + .. + } => { let args = args.as_slice(pool); write!(w, " {}, {}, {}, {}", cond, args[0], args[1], destination)?; if args.len() > 2 { diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index c854f3b6b8..031a50d9e3 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -183,11 +183,13 @@ impl Checker { continue; } Directive::Regex(ref var, ref rx) => { - state.vars.insert(var.clone(), - VarDef { - value: Value::Regex(Cow::Borrowed(rx)), - offset: 0, - }); + state + .vars + .insert(var.clone(), + VarDef { + value: Value::Regex(Cow::Borrowed(rx)), + offset: 0, + }); continue; } }; @@ -208,10 +210,14 @@ impl Checker { state.recorder.directive(not_idx); if let Some((s, e)) = rx.find(&text[not_begin..match_begin]) { // Matched `not:` pattern. - state.recorder.matched_not(rx.as_str(), (not_begin + s, not_begin + e)); + state + .recorder + .matched_not(rx.as_str(), (not_begin + s, not_begin + e)); return Ok(false); } else { - state.recorder.missed_not(rx.as_str(), (not_begin, match_begin)); + state + .recorder + .missed_not(rx.as_str(), (not_begin, match_begin)); } } } @@ -410,9 +416,11 @@ mod tests { Ok(true)); assert_eq!(b.directive("regex: X = tommy").map_err(e2s), Err("expected '=' after variable 'X' in regex: X = tommy".to_string())); - assert_eq!(b.directive("[arm]not: patt $x $(y) here").map_err(e2s), + assert_eq!(b.directive("[arm]not: patt $x $(y) here") + .map_err(e2s), Ok(true)); - assert_eq!(b.directive("[x86]sameln: $x $(y=[^]]*) there").map_err(e2s), + assert_eq!(b.directive("[x86]sameln: $x $(y=[^]]*) there") + .map_err(e2s), Ok(true)); // Windows line ending sneaking in. assert_eq!(b.directive("regex: Y=foo\r").map_err(e2s), Ok(true)); diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs index 04a6cd233e..49780321b5 100644 --- a/lib/filecheck/src/explain.rs +++ b/lib/filecheck/src/explain.rs @@ -119,7 +119,8 @@ impl<'a> Display for Explainer<'a> { m.regex)?; // Emit any variable definitions. - if let Ok(found) = self.vardefs.binary_search_by_key(&m.directive, |v| v.directive) { + if let Ok(found) = self.vardefs + .binary_search_by_key(&m.directive, |v| v.directive) { let mut first = found; while first > 0 && self.vardefs[first - 1].directive == m.directive { first -= 1; @@ -147,50 +148,55 @@ impl<'a> Recorder for Explainer<'a> { } fn matched_check(&mut self, regex: &str, matched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: false, - regex: regex.to_owned(), - range: matched, - }); + self.matches + .push(Match { + directive: self.directive, + is_match: true, + is_not: false, + regex: regex.to_owned(), + range: matched, + }); } fn matched_not(&mut self, regex: &str, matched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: true, - regex: regex.to_owned(), - range: matched, - }); + self.matches + .push(Match { + directive: self.directive, + is_match: true, + is_not: true, + regex: regex.to_owned(), + range: matched, + }); } fn missed_check(&mut self, regex: &str, searched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: false, - regex: regex.to_owned(), - range: searched, - }); + self.matches + .push(Match { + directive: self.directive, + is_match: false, + is_not: false, + regex: regex.to_owned(), + range: searched, + }); } fn missed_not(&mut self, regex: &str, searched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: true, - regex: regex.to_owned(), - range: searched, - }); + self.matches + .push(Match { + directive: self.directive, + is_match: false, + is_not: true, + regex: regex.to_owned(), + range: searched, + }); } fn defined_var(&mut self, varname: &str, value: &str) { - self.vardefs.push(VarDef { - directive: self.directive, - varname: varname.to_owned(), - value: value.to_owned(), - }); + self.vardefs + .push(VarDef { + directive: self.directive, + varname: varname.to_owned(), + value: value.to_owned(), + }); } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index e8d88dde89..ca98bd98f2 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -107,7 +107,8 @@ impl<'a> Context<'a> { // Get the index of a recipe name if it exists. fn find_recipe_index(&self, recipe_name: &str) -> Option { if let Some(unique_isa) = self.unique_isa { - unique_isa.recipe_names() + unique_isa + .recipe_names() .iter() .position(|&name| name == recipe_name) .map(|idx| idx as u16) @@ -118,17 +119,14 @@ impl<'a> Context<'a> { // Allocate a new stack slot and add a mapping number -> StackSlot. fn add_ss(&mut self, number: u32, data: StackSlotData, loc: &Location) -> Result<()> { - self.map.def_ss(number, self.function.stack_slots.push(data), loc) + self.map + .def_ss(number, self.function.stack_slots.push(data), loc) } // Allocate a new signature and add a mapping number -> SigRef. fn add_sig(&mut self, number: u32, data: Signature, loc: &Location) -> Result<()> { - self.map.def_sig(number, - self.function - .dfg - .signatures - .push(data), - loc) + self.map + .def_sig(number, self.function.dfg.signatures.push(data), loc) } // Resolve a reference to a signature. @@ -141,12 +139,8 @@ impl<'a> Context<'a> { // Allocate a new external function and add a mapping number -> FuncRef. fn add_fn(&mut self, number: u32, data: ExtFuncData, loc: &Location) -> Result<()> { - self.map.def_fn(number, - self.function - .dfg - .ext_funcs - .push(data), - loc) + self.map + .def_fn(number, self.function.dfg.ext_funcs.push(data), loc) } // Resolve a reference to a function. @@ -159,7 +153,8 @@ impl<'a> Context<'a> { // Allocate a new jump table and add a mapping number -> JumpTable. fn add_jt(&mut self, number: u32, data: JumpTableData, loc: &Location) -> Result<()> { - self.map.def_jt(number, self.function.jump_tables.push(data), loc) + self.map + .def_jt(number, self.function.jump_tables.push(data), loc) } // Resolve a reference to a jump table. @@ -238,19 +233,34 @@ impl<'a> Context<'a> { } InstructionData::MultiAry { ref mut args, .. } => { - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; + self.map + .rewrite_values(args.as_mut_slice(value_lists), loc)?; } - InstructionData::Jump { ref mut destination, ref mut args, .. } | - InstructionData::Branch { ref mut destination, ref mut args, .. } | - InstructionData::BranchIcmp { ref mut destination, ref mut args, .. } => { + InstructionData::Jump { + ref mut destination, + ref mut args, + .. + } | + InstructionData::Branch { + ref mut destination, + ref mut args, + .. + } | + InstructionData::BranchIcmp { + ref mut destination, + ref mut args, + .. + } => { self.map.rewrite_ebb(destination, loc)?; - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; + self.map + .rewrite_values(args.as_mut_slice(value_lists), loc)?; } InstructionData::Call { ref mut args, .. } | InstructionData::IndirectCall { ref mut args, .. } => { - self.map.rewrite_values(args.as_mut_slice(value_lists), loc)?; + self.map + .rewrite_values(args.as_mut_slice(value_lists), loc)?; } } } @@ -307,10 +317,11 @@ impl<'a> Parser<'a> { Token::Comment(text) => { // Gather comments, associate them with `comment_entity`. if let Some(entity) = self.comment_entity { - self.comments.push(Comment { - entity: entity, - text: text, - }); + self.comments + .push(Comment { + entity: entity, + text: text, + }); } } _ => self.lookahead = Some(token), @@ -464,7 +475,8 @@ impl<'a> Parser<'a> { self.consume(); // Lexer just gives us raw text that looks like an integer. // Parse it as a u8 to check for overflow and other issues. - text.parse().map_err(|_| self.error("expected u8 decimal immediate")) + text.parse() + .map_err(|_| self.error("expected u8 decimal immediate")) } else { err!(self.loc, err_msg) } @@ -477,7 +489,8 @@ impl<'a> Parser<'a> { self.consume(); // Lexer just gives us raw text that looks like an integer. // Parse it as a u32 to check for overflow and other issues. - text.parse().map_err(|_| self.error("expected u32 decimal immediate")) + text.parse() + .map_err(|_| self.error("expected u32 decimal immediate")) } else { err!(self.loc, err_msg) } @@ -714,7 +727,9 @@ impl<'a> Parser<'a> { sig.return_types = self.parse_argument_list(unique_isa)?; } - if sig.argument_types.iter().all(|a| a.location.is_assigned()) { + if sig.argument_types + .iter() + .all(|a| a.location.is_assigned()) { sig.compute_argument_bytes(); } @@ -816,35 +831,23 @@ impl<'a> Parser<'a> { match self.token() { Some(Token::StackSlot(..)) => { self.gather_comments(ctx.function.stack_slots.next_key()); - self.parse_stack_slot_decl().and_then(|(num, dat)| { - ctx.add_ss(num, dat, &self.loc) - }) + self.parse_stack_slot_decl() + .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) } Some(Token::SigRef(..)) => { - self.gather_comments(ctx.function - .dfg - .signatures - .next_key()); - self.parse_signature_decl(ctx.unique_isa).and_then(|(num, dat)| { - ctx.add_sig(num, - dat, - &self.loc) - }) + self.gather_comments(ctx.function.dfg.signatures.next_key()); + self.parse_signature_decl(ctx.unique_isa) + .and_then(|(num, dat)| ctx.add_sig(num, dat, &self.loc)) } Some(Token::FuncRef(..)) => { - self.gather_comments(ctx.function - .dfg - .ext_funcs - .next_key()); - self.parse_function_decl(ctx).and_then(|(num, dat)| { - ctx.add_fn(num, dat, &self.loc) - }) + self.gather_comments(ctx.function.dfg.ext_funcs.next_key()); + self.parse_function_decl(ctx) + .and_then(|(num, dat)| ctx.add_fn(num, dat, &self.loc)) } Some(Token::JumpTable(..)) => { self.gather_comments(ctx.function.jump_tables.next_key()); - self.parse_jump_table_decl().and_then(|(num, dat)| { - ctx.add_jt(num, dat, &self.loc) - }) + self.parse_jump_table_decl() + .and_then(|(num, dat)| ctx.add_jt(num, dat, &self.loc)) } // More to come.. _ => return Ok(()), @@ -861,7 +864,8 @@ impl<'a> Parser<'a> { self.match_identifier("stack_slot", "expected 'stack_slot'")?; // stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag} - let bytes: i64 = self.match_imm64("expected byte-size in stack_slot decl")?.into(); + let bytes: i64 = self.match_imm64("expected byte-size in stack_slot decl")? + .into(); if bytes < 0 { return err!(self.loc, "negative stack slot size"); } @@ -903,11 +907,10 @@ impl<'a> Parser<'a> { let data = match self.token() { Some(Token::Identifier("function")) => { let (loc, name, sig) = self.parse_function_spec(ctx.unique_isa)?; - let sigref = ctx.function - .dfg - .signatures - .push(sig); - ctx.map.def_entity(sigref.into(), &loc).expect("duplicate SigRef entities created"); + let sigref = ctx.function.dfg.signatures.push(sig); + ctx.map + .def_entity(sigref.into(), &loc) + .expect("duplicate SigRef entities created"); ExtFuncData { name: name, signature: sigref, @@ -1224,7 +1227,9 @@ impl<'a> Parser<'a> { let inst = ctx.function.dfg.make_inst(inst_data); let num_results = ctx.function.dfg.make_inst_results(inst, ctrl_typevar); ctx.function.layout.append_inst(inst, ebb); - ctx.map.def_entity(inst.into(), &opcode_loc).expect("duplicate inst references created"); + ctx.map + .def_entity(inst.into(), &opcode_loc) + .expect("duplicate inst references created"); if let Some(encoding) = encoding { *ctx.function.encodings.ensure(inst) = encoding; @@ -1282,18 +1287,21 @@ impl<'a> Parser<'a> { inst_data: &InstructionData) -> Result { let constraints = opcode.constraints(); - let ctrl_type = match explicit_ctrl_type { - Some(t) => t, - None => { - if constraints.use_typevar_operand() { - // This is an opcode that supports type inference, AND there was no explicit - // type specified. Look up `ctrl_value` to see if it was defined already. - // TBD: If it is defined in another block, the type should have been specified - // explicitly. It is unfortunate that the correctness of IL depends on the - // layout of the blocks. - let ctrl_src_value = inst_data.typevar_operand(&ctx.function.dfg.value_lists) - .expect("Constraints <-> Format inconsistency"); - ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { + let ctrl_type = + match explicit_ctrl_type { + Some(t) => t, + None => { + if constraints.use_typevar_operand() { + // This is an opcode that supports type inference, AND there was no + // explicit type specified. Look up `ctrl_value` to see if it was defined + // already. + // TBD: If it is defined in another block, the type should have been + // specified explicitly. It is unfortunate that the correctness of IL + // depends on the layout of the blocks. + let ctrl_src_value = inst_data + .typevar_operand(&ctx.function.dfg.value_lists) + .expect("Constraints <-> Format inconsistency"); + ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { Some(v) => v, None => { if let Some(v) = ctx.aliases @@ -1308,19 +1316,19 @@ impl<'a> Parser<'a> { } } }) - } else if constraints.is_polymorphic() { - // This opcode does not support type inference, so the explicit type variable - // is required. - return err!(self.loc, - "type variable required for polymorphic opcode, e.g. '{}.{}'", - opcode, - constraints.ctrl_typeset().unwrap().example()); - } else { - // This is a non-polymorphic opcode. No typevar needed. - VOID + } else if constraints.is_polymorphic() { + // This opcode does not support type inference, so the explicit type + // variable is required. + return err!(self.loc, + "type variable required for polymorphic opcode, e.g. '{}.{}'", + opcode, + constraints.ctrl_typeset().unwrap().example()); + } else { + // This is a non-polymorphic opcode. No typevar needed. + VOID + } } - } - }; + }; // Verify that `ctrl_type` is valid for the controlling type variable. We don't want to // attempt deriving types from an incorrect basis. @@ -1629,7 +1637,8 @@ impl<'a> Parser<'a> { InstructionFormat::BranchTable => { let arg = self.match_value("expected SSA value operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; - let table = self.match_jt().and_then(|num| ctx.get_jt(num, &self.loc))?; + let table = self.match_jt() + .and_then(|num| ctx.get_jt(num, &self.loc))?; InstructionData::BranchTable { opcode: opcode, ty: VOID, @@ -1679,7 +1688,8 @@ mod tests { assert_eq!(v4.to_string(), "v0"); let vx3 = details.map.lookup_str("vx3").unwrap(); assert_eq!(vx3.to_string(), "vx0"); - let aliased_to = func.dfg.resolve_aliases(Value::table_with_number(0).unwrap()); + let aliased_to = func.dfg + .resolve_aliases(Value::table_with_number(0).unwrap()); assert_eq!(aliased_to.to_string(), "v0"); } @@ -1696,11 +1706,20 @@ mod tests { "(i8 uext inreg, f32, f64) -> i32 sext, f64"); // `void` is not recognized as a type by the lexer. It should not appear in files. - assert_eq!(Parser::new("() -> void").parse_signature(None).unwrap_err().to_string(), + assert_eq!(Parser::new("() -> void") + .parse_signature(None) + .unwrap_err() + .to_string(), "1: expected argument type"); - assert_eq!(Parser::new("i8 -> i8").parse_signature(None).unwrap_err().to_string(), + assert_eq!(Parser::new("i8 -> i8") + .parse_signature(None) + .unwrap_err() + .to_string(), "1: expected function signature: ( args... )"); - assert_eq!(Parser::new("(i8 -> i8").parse_signature(None).unwrap_err().to_string(), + assert_eq!(Parser::new("(i8 -> i8") + .parse_signature(None) + .unwrap_err() + .to_string(), "1: expected ')' after function arguments"); } diff --git a/lib/reader/src/testcommand.rs b/lib/reader/src/testcommand.rs index 6d9dd9969e..a147487c77 100644 --- a/lib/reader/src/testcommand.rs +++ b/lib/reader/src/testcommand.rs @@ -40,7 +40,10 @@ impl<'a> TestCommand<'a> { let cmd = parts.next().unwrap_or(""); TestCommand { command: cmd, - options: parts.filter(|s| !s.is_empty()).map(TestOption::new).collect(), + options: parts + .filter(|s| !s.is_empty()) + .map(TestOption::new) + .collect(), } } } diff --git a/src/cton-util.rs b/src/cton-util.rs index 681ab5b8bc..fea4d879be 100644 --- a/src/cton-util.rs +++ b/src/cton-util.rs @@ -51,7 +51,11 @@ pub type CommandResult = Result<(), String>; fn cton_util() -> CommandResult { // Parse comand line arguments. let args: Args = Docopt::new(USAGE) - .and_then(|d| d.help(true).version(Some(format!("Cretonne {}", VERSION))).decode()) + .and_then(|d| { + d.help(true) + .version(Some(format!("Cretonne {}", VERSION))) + .decode() + }) .unwrap_or_else(|e| e.exit()); // Find the sub-command to execute. diff --git a/src/filetest/concurrent.rs b/src/filetest/concurrent.rs index effe454d38..9ea1114000 100644 --- a/src/filetest/concurrent.rs +++ b/src/filetest/concurrent.rs @@ -119,10 +119,11 @@ fn worker_thread(thread_num: usize, // Tell them we're starting this job. // The receiver should always be present for this as long as we have jobs. - replies.send(Reply::Starting { - jobid: jobid, - thread_num: thread_num, - }) + replies + .send(Reply::Starting { + jobid: jobid, + thread_num: thread_num, + }) .unwrap(); let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| { @@ -141,10 +142,11 @@ fn worker_thread(thread_num: usize, dbg!("FAIL: {}", msg); } - replies.send(Reply::Done { - jobid: jobid, - result: result, - }) + replies + .send(Reply::Done { + jobid: jobid, + result: result, + }) .unwrap(); } }) diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index 39854769ac..1a5f774664 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -41,7 +41,8 @@ impl SubTest for TestLegalizer { comp_ctx.flowgraph(); comp_ctx.legalize(isa); - comp_ctx.verify(isa).map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; + comp_ctx.verify(isa) + .map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; let mut text = String::new(); write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index 5f70f51100..daf7adcb07 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -47,7 +47,8 @@ impl SubTest for TestRegalloc { // TODO: Should we have an option to skip legalization? comp_ctx.legalize(isa); comp_ctx.regalloc(isa); - comp_ctx.verify(isa).map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; + comp_ctx.verify(isa) + .map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; let mut text = String::new(); write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; diff --git a/src/filetest/runner.rs b/src/filetest/runner.rs index 91331a0299..1993ca5877 100644 --- a/src/filetest/runner.rs +++ b/src/filetest/runner.rs @@ -104,10 +104,11 @@ impl TestRunner { /// /// Any problems reading `file` as a test case file will be reported as a test failure. pub fn push_test>(&mut self, file: P) { - self.tests.push(QueueEntry { - path: file.into(), - state: State::New, - }); + self.tests + .push(QueueEntry { + path: file.into(), + state: State::New, + }); } /// Begin running tests concurrently. @@ -206,7 +207,9 @@ impl TestRunner { } // Check for any asynchronous replies without blocking. - while let Some(reply) = self.threads.as_mut().and_then(ConcurrentRunner::try_get) { + while let Some(reply) = self.threads + .as_mut() + .and_then(ConcurrentRunner::try_get) { self.handle_reply(reply); } } @@ -303,12 +306,12 @@ impl TestRunner { return; } - for t in self.tests.iter().filter(|entry| match **entry { - QueueEntry { state: State::Done(Ok(dur)), .. } => { - dur > cut - } - _ => false, - }) { + for t in self.tests + .iter() + .filter(|entry| match **entry { + QueueEntry { state: State::Done(Ok(dur)), .. } => dur > cut, + _ => false, + }) { println!("slow: {}", t) } diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index ec61c98240..bc16725d74 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -120,5 +120,6 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), context.verified = true; } - test.run(func, context).map_err(|e| format!("{}: {}", name, e)) + test.run(func, context) + .map_err(|e| format!("{}: {}", name, e)) } diff --git a/src/filetest/subtest.rs b/src/filetest/subtest.rs index ede013ef9f..923439d490 100644 --- a/src/filetest/subtest.rs +++ b/src/filetest/subtest.rs @@ -76,11 +76,13 @@ impl<'a> filecheck::VariableMap for Context<'a> { /// Run filecheck on `text`, using directives extracted from `context`. pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { let checker = build_filechecker(context)?; - if checker.check(&text, context).map_err(|e| format!("filecheck: {}", e))? { + if checker.check(&text, context) + .map_err(|e| format!("filecheck: {}", e))? { Ok(()) } else { // Filecheck mismatch. Emit an explanation as output. - let (_, explain) = checker.explain(&text, context).map_err(|e| format!("explain: {}", e))?; + let (_, explain) = checker.explain(&text, context) + .map_err(|e| format!("explain: {}", e))?; Err(format!("filecheck failed:\n{}{}", checker, explain)) } } @@ -90,10 +92,12 @@ pub fn build_filechecker(context: &Context) -> Result { let mut builder = CheckerBuilder::new(); // Preamble comments apply to all functions. for comment in context.preamble_comments { - builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))?; + builder.directive(comment.text) + .map_err(|e| format!("filecheck: {}", e))?; } for comment in &context.details.comments { - builder.directive(comment.text).map_err(|e| format!("filecheck: {}", e))?; + builder.directive(comment.text) + .map_err(|e| format!("filecheck: {}", e))?; } let checker = builder.finish(); if checker.is_empty() { diff --git a/src/rsfilecheck.rs b/src/rsfilecheck.rs index 734c067a36..79d913bce9 100644 --- a/src/rsfilecheck.rs +++ b/src/rsfilecheck.rs @@ -18,10 +18,12 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } let mut buffer = String::new(); - io::stdin().read_to_string(&mut buffer).map_err(|e| format!("stdin: {}", e))?; + io::stdin().read_to_string(&mut buffer) + .map_err(|e| format!("stdin: {}", e))?; if verbose { - let (success, explain) = checker.explain(&buffer, NO_VARIABLES).map_err(|e| e.to_string())?; + let (success, explain) = checker.explain(&buffer, NO_VARIABLES) + .map_err(|e| e.to_string())?; print!("{}", explain); if success { println!("OK"); @@ -29,10 +31,12 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } else { Err("Check failed".to_string()) } - } else if checker.check(&buffer, NO_VARIABLES).map_err(|e| e.to_string())? { + } else if checker.check(&buffer, NO_VARIABLES) + .map_err(|e| e.to_string())? { Ok(()) } else { - let (_, explain) = checker.explain(&buffer, NO_VARIABLES).map_err(|e| e.to_string())?; + let (_, explain) = checker.explain(&buffer, NO_VARIABLES) + .map_err(|e| e.to_string())?; print!("{}", explain); Err("Check failed".to_string()) } @@ -41,6 +45,7 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { fn read_checkfile(filename: &str) -> Result { let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; let mut builder = CheckerBuilder::new(); - builder.text(&buffer).map_err(|e| format!("{}: {}", filename, e))?; + builder.text(&buffer) + .map_err(|e| format!("{}: {}", filename, e))?; Ok(builder.finish()) } diff --git a/test-all.sh b/test-all.sh index 7c4938902f..522cf594a2 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.8.0" +RUSTFMT_VERSION="0.8.1" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index f7fb119463..903b1a1ec1 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -9,7 +9,10 @@ use self::cton_reader::parse_functions; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &parse_functions(function_source).unwrap()[0]; let cfg = ControlFlowGraph::with_function(&func); - let ebbs = ebb_order.iter().map(|n| Ebb::with_number(*n).unwrap()).collect::>(); + let ebbs = ebb_order + .iter() + .map(|n| Ebb::with_number(*n).unwrap()) + .collect::>(); let mut postorder_ebbs = cfg.postorder_ebbs(); let mut postorder_map = EntityMap::with_capacity(postorder_ebbs.len()); From 847c8045fd121c17ddd39d71cada2c0ae175a86f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Apr 2017 09:12:12 -0700 Subject: [PATCH 660/968] Add a CodeOffset type and CodeSink::offset() method. We need to keep track of code offsets in order to compute accurate branch displacements. --- lib/cretonne/src/binemit/mod.rs | 9 +++++++++ src/filetest/binemit.rs | 29 ++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index ccad476f44..9721586033 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -5,6 +5,12 @@ use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; +/// Offset in bytes from the beginning of the function. +/// +/// Cretonne can be used as a cross compiler, so we don't want to use a type like `usize` which +/// depends on the *host* platform, not the *target* platform. +pub type CodeOffset = u32; + /// Relocation kinds depend on the current ISA. pub struct Reloc(pub u16); @@ -13,6 +19,9 @@ pub struct Reloc(pub u16); /// A `CodeSink` will receive all of the machine code for a function. It also accepts relocations /// which are locations in the code section that need to be fixed up when linking. pub trait CodeSink { + /// Get the current position. + fn offset(&self) -> CodeOffset; + /// Add 1 byte to the code section. fn put1(&mut self, u8); diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 3c9fc333e6..50949811ec 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -8,6 +8,7 @@ use std::fmt::Write; use cretonne::binemit; use cretonne::ir; use cretonne::ir::entities::AnyEntity; +use cretonne::isa::TargetIsa; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result}; use utils::match_directive; @@ -26,24 +27,46 @@ pub fn subtest(parsed: &TestCommand) -> Result> { // Code sink that generates text. struct TextSink { rnames: &'static [&'static str], + offset: binemit::CodeOffset, text: String, } +impl TextSink { + /// Create a new empty TextSink. + pub fn new(isa: &TargetIsa) -> TextSink { + TextSink { + rnames: isa.reloc_names(), + offset: 0, + text: String::new(), + } + } +} + + + impl binemit::CodeSink for TextSink { + fn offset(&self) -> binemit::CodeOffset { + self.offset + } + fn put1(&mut self, x: u8) { write!(self.text, "{:02x} ", x).unwrap(); + self.offset += 1; } fn put2(&mut self, x: u16) { write!(self.text, "{:04x} ", x).unwrap(); + self.offset += 2; } fn put4(&mut self, x: u32) { write!(self.text, "{:08x} ", x).unwrap(); + self.offset += 4; } fn put8(&mut self, x: u64) { write!(self.text, "{:016x} ", x).unwrap(); + self.offset += 8; } fn reloc_ebb(&mut self, reloc: binemit::Reloc, ebb: ir::Ebb) { @@ -77,11 +100,7 @@ impl SubTest for TestBinEmit { // TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad // value locations. The current error reporting is just crashing... let mut func = func.into_owned(); - - let mut sink = TextSink { - rnames: isa.reloc_names(), - text: String::new(), - }; + let mut sink = TextSink::new(isa); for comment in &context.details.comments { if let Some(want) = match_directive(comment.text, "bin:") { From 6ab35e54b89227e7f3fde7dc9c6ee1c371f9b500 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Apr 2017 10:07:19 -0700 Subject: [PATCH 661/968] Move encoding-related information into an EncInfo struct. The tables returned by recipe_names() and recipe_constraints() are now collected into an EncInfo struct that is available from TargetIsa::encoding_info(). This is equivalent to the register bank tables available fro TargetIsa::register_info(). This cleans of the TargetIsa interface and makes it easier to add encoding-related information. --- lib/cretonne/meta/gen_encoding.py | 9 ++++++-- lib/cretonne/src/isa/arm32/enc_tables.rs | 3 ++- lib/cretonne/src/isa/arm32/mod.rs | 14 +++++-------- lib/cretonne/src/isa/arm64/enc_tables.rs | 3 ++- lib/cretonne/src/isa/arm64/mod.rs | 14 +++++-------- lib/cretonne/src/isa/encoding.rs | 26 ++++++++++++++++++++++++ lib/cretonne/src/isa/intel/enc_tables.rs | 3 ++- lib/cretonne/src/isa/intel/mod.rs | 14 +++++-------- lib/cretonne/src/isa/mod.rs | 25 ++++------------------- lib/cretonne/src/isa/riscv/enc_tables.rs | 8 +++++--- lib/cretonne/src/isa/riscv/mod.rs | 16 ++++++--------- lib/cretonne/src/regalloc/coloring.rs | 11 ++++++---- lib/cretonne/src/regalloc/liveness.rs | 20 ++++++++---------- lib/cretonne/src/write.rs | 2 +- lib/reader/src/parser.rs | 3 ++- 15 files changed, 88 insertions(+), 83 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index e4faa6410c..21b121487f 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -450,7 +450,7 @@ def emit_recipe_names(isa, fmt): This is used for pretty-printing encodings. """ with fmt.indented( - 'pub static RECIPE_NAMES: [&\'static str; {}] = [' + 'static RECIPE_NAMES: [&\'static str; {}] = [' .format(len(isa.all_recipes)), '];'): for r in isa.all_recipes: fmt.line('"{}",'.format(r.name)) @@ -465,7 +465,7 @@ def emit_recipe_constraints(isa, fmt): properly encoded. """ with fmt.indented( - 'pub static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = [' + 'static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = [' .format(len(isa.all_recipes)), '];'): for r in isa.all_recipes: fmt.comment(r.name) @@ -536,6 +536,11 @@ def gen_isa(isa, fmt): emit_recipe_names(isa, fmt) emit_recipe_constraints(isa, fmt) + # Finally, tie it all together in an `EncInfo`. + with fmt.indented('pub static INFO: EncInfo = EncInfo {', '};'): + fmt.line('constraints: &RECIPE_CONSTRAINTS,') + fmt.line('names: &RECIPE_NAMES,') + def generate(isas, out_dir): # type: (Sequence[TargetISA], str) -> None diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index c464d92f23..67cde87ed4 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -2,7 +2,8 @@ use ir::InstructionData; use ir::types; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::EncInfo; use isa::constraints::*; +use isa::enc_tables::{Level1Entry, Level2Entry}; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index df4f4c2570..c9c7f2af5c 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -9,7 +9,7 @@ use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; +use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; use ir; #[allow(dead_code)] @@ -55,6 +55,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + fn encode(&self, dfg: &ir::DataFlowGraph, inst: &ir::InstructionData) @@ -72,14 +76,6 @@ impl TargetIsa for Isa { }) } - fn recipe_names(&self) -> &'static [&'static str] { - &enc_tables::RECIPE_NAMES[..] - } - - fn recipe_constraints(&self) -> &'static [RecipeConstraints] { - &enc_tables::RECIPE_CONSTRAINTS - } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index 064a00f2e2..d092c0eb8f 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -2,7 +2,8 @@ use ir::InstructionData; use ir::types; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::EncInfo; use isa::constraints::*; +use isa::enc_tables::{Level1Entry, Level2Entry}; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 2c11b450bb..a8cfe834b9 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -9,7 +9,7 @@ use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; +use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; use ir; #[allow(dead_code)] @@ -48,6 +48,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + fn encode(&self, dfg: &ir::DataFlowGraph, inst: &ir::InstructionData) @@ -65,14 +69,6 @@ impl TargetIsa for Isa { }) } - fn recipe_names(&self) -> &'static [&'static str] { - &enc_tables::RECIPE_NAMES[..] - } - - fn recipe_constraints(&self) -> &'static [RecipeConstraints] { - &enc_tables::RECIPE_CONSTRAINTS - } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index 4c8e33a764..88040925ad 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -1,6 +1,7 @@ //! The `Encoding` struct. use std::fmt; +use isa::constraints::RecipeConstraints; /// Bits needed to encode an instruction as binary machine code. /// @@ -76,3 +77,28 @@ impl fmt::Display for DisplayEncoding { } } } + +/// Information about all the encodings in this ISA. +#[derive(Clone)] +pub struct EncInfo { + /// Constraints on value operands per recipe. + pub constraints: &'static [RecipeConstraints], + + /// Names of encoding recipes. + pub names: &'static [&'static str], +} + +impl EncInfo { + /// Get the value operand constraints for `enc` if it is a legal encoding. + pub fn operand_constraints(&self, enc: Encoding) -> Option<&RecipeConstraints> { + self.constraints.get(enc.recipe()) + } + + /// Create an object that can display an ISA-dependent encoding properly. + pub fn display(&self, enc: Encoding) -> DisplayEncoding { + DisplayEncoding { + encoding: enc, + recipe_names: self.names, + } + } +} diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index a29394a3a9..8abc8cd9d9 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -2,7 +2,8 @@ use ir::InstructionData; use ir::types; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::EncInfo; use isa::constraints::*; +use isa::enc_tables::{Level1Entry, Level2Entry}; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 46aa101ce0..b48a119632 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -9,7 +9,7 @@ use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; +use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; use ir; #[allow(dead_code)] @@ -55,6 +55,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + fn encode(&self, dfg: &ir::DataFlowGraph, inst: &ir::InstructionData) @@ -72,14 +76,6 @@ impl TargetIsa for Isa { }) } - fn recipe_names(&self) -> &'static [&'static str] { - &enc_tables::RECIPE_NAMES[..] - } - - fn recipe_constraints(&self) -> &'static [RecipeConstraints] { - &enc_tables::RECIPE_CONSTRAINTS - } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 4eddae7b14..4fc08906ae 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -40,9 +40,9 @@ //! The configured target ISA trait object is a `Box` which can be used for multiple //! concurrent function compilations. -pub use isa::encoding::Encoding; -pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind}; +pub use isa::encoding::{Encoding, EncInfo}; +pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; use binemit::CodeSink; use settings; @@ -143,25 +143,8 @@ pub trait TargetIsa { /// This is also the main entry point for determining if an instruction is legal. fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result; - /// Get a static array of names associated with encoding recipes in this ISA. Encoding recipes - /// are numbered starting from 0, corresponding to indexes into the name array. - /// - /// This is just used for printing and parsing encodings in the textual IL format. - fn recipe_names(&self) -> &'static [&'static str]; - - /// Get a static array of value operand constraints associated with encoding recipes in this - /// ISA. - /// - /// The constraints describe which registers can be used with an encoding recipe. - fn recipe_constraints(&self) -> &'static [RecipeConstraints]; - - /// Create an object that can display an ISA-dependent encoding properly. - fn display_enc(&self, enc: Encoding) -> encoding::DisplayEncoding { - encoding::DisplayEncoding { - encoding: enc, - recipe_names: self.recipe_names(), - } - } + /// Get a data structure describing the instruction encodings in this ISA. + fn encoding_info(&self) -> EncInfo; /// Legalize a function signature. /// diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 7940fce004..87c62bd63c 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -1,11 +1,12 @@ //! Encoding tables for RISC-V. use ir::condcodes::IntCC; -use ir::{Opcode, InstructionData}; use ir::types; -use predicates; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use ir::{Opcode, InstructionData}; +use isa::EncInfo; use isa::constraints::*; +use isa::enc_tables::{Level1Entry, Level2Entry}; +use predicates; use super::registers::*; // Include the generated encoding tables: @@ -13,4 +14,5 @@ use super::registers::*; // - `LEVEL1_RV64` // - `LEVEL2` // - `ENCLIST` +// - `INFO` include!(concat!(env!("OUT_DIR"), "/encoding-riscv.rs")); diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 348e1af32f..7658db392e 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -10,7 +10,7 @@ use super::super::settings as shared_settings; use binemit::CodeSink; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; +use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature}; #[allow(dead_code)] @@ -56,6 +56,10 @@ impl TargetIsa for Isa { registers::INFO.clone() } + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { lookup_enclist(inst.ctrl_typevar(dfg), inst.opcode(), @@ -70,14 +74,6 @@ impl TargetIsa for Isa { }) } - fn recipe_names(&self) -> &'static [&'static str] { - &enc_tables::RECIPE_NAMES[..] - } - - fn recipe_constraints(&self) -> &'static [RecipeConstraints] { - &enc_tables::RECIPE_CONSTRAINTS - } - fn legalize_signature(&self, sig: &mut Signature) { // We can pass in `self.isa_flags` too, if we need it. abi::legalize_signature(sig, &self.shared_flags) @@ -100,7 +96,7 @@ mod tests { use ir::{types, immediates}; fn encstr(isa: &isa::TargetIsa, enc: isa::Encoding) -> String { - isa.display_enc(enc).to_string() + isa.encoding_info().display(enc).to_string() } #[test] diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index b8c08bd278..b9e5fe7148 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -38,7 +38,7 @@ use entity_map::EntityMap; use dominator_tree::DominatorTree; use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph}; -use isa::{TargetIsa, RegInfo, Encoding, RecipeConstraints, ConstraintKind}; +use isa::{TargetIsa, RegInfo, Encoding, EncInfo, ConstraintKind}; use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; @@ -69,7 +69,7 @@ struct Context<'a> { // Cached ISA information. // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. reginfo: RegInfo, - recipe_constraints: &'a [RecipeConstraints], + encinfo: EncInfo, // References to contextual data structures we need. domtree: &'a DominatorTree, @@ -98,7 +98,7 @@ impl Coloring { tracker: &mut LiveValueTracker) { let mut ctx = Context { reginfo: isa.register_info(), - recipe_constraints: isa.recipe_constraints(), + encinfo: isa.encoding_info(), domtree: domtree, liveness: liveness, // TODO: Ask the target ISA about reserved registers etc. @@ -259,7 +259,10 @@ impl<'a> Context<'a> { let (kills, defs) = tracker.process_inst(inst, dfg, self.liveness); // Get the operand constraints for `inst` that we are trying to satisfy. - let constraints = self.recipe_constraints[encoding.recipe()].clone(); + let constraints = self.encinfo + .operand_constraints(encoding) + .expect("Missing instruction encoding") + .clone(); // Get rid of the killed values. for lv in kills { diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 3344c5b841..90b817a825 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -178,7 +178,7 @@ use flowgraph::ControlFlowGraph; use ir::dfg::ValueDef; use ir::{Function, Value, Inst, Ebb}; -use isa::{TargetIsa, RecipeConstraints}; +use isa::{TargetIsa, EncInfo}; use regalloc::affinity::Affinity; use regalloc::liverange::LiveRange; use sparse_map::SparseMap; @@ -191,7 +191,7 @@ type LiveRangeSet = SparseMap; fn get_or_create<'a>(lrset: &'a mut LiveRangeSet, value: Value, func: &Function, - recipe_constraints: &[RecipeConstraints]) + enc_info: &EncInfo) -> &'a mut LiveRange { // It would be better to use `get_mut()` here, but that leads to borrow checker fighting // which can probably only be resolved by non-lexical lifetimes. @@ -205,8 +205,8 @@ fn get_or_create<'a>(lrset: &'a mut LiveRangeSet, def = inst.into(); // Initialize the affinity from the defining instruction's result constraints. // Don't do this for call return values which are always tied to a single register. - affinity = recipe_constraints - .get(func.encodings[inst].recipe()) + affinity = enc_info + .operand_constraints(func.encodings[inst]) .and_then(|rc| rc.outs.get(rnum)) .map(Affinity::new) .unwrap_or_default(); @@ -296,7 +296,7 @@ impl Liveness { self.ranges.clear(); // Get ISA data structures used for computing live range affinities. - let recipe_constraints = isa.recipe_constraints(); + let enc_info = isa.encoding_info(); let reg_info = isa.register_info(); // The liveness computation needs to visit all uses, but the order doesn't matter. @@ -309,22 +309,20 @@ impl Liveness { // TODO: When we implement DCE, we can use the absence of a live range to indicate // an unused value. for def in func.dfg.inst_results(inst) { - get_or_create(&mut self.ranges, def, func, recipe_constraints); + get_or_create(&mut self.ranges, def, func, &enc_info); } - // The instruction encoding is used to compute affinities. - let recipe = func.encodings[inst].recipe(); // Iterator of constraints, one per value operand. // TODO: Should we fail here if the instruction doesn't have a valid encoding? - let mut operand_constraints = recipe_constraints - .get(recipe) + let mut operand_constraints = enc_info + .operand_constraints(func.encodings[inst]) .map(|c| c.ins) .unwrap_or(&[]) .iter(); for &arg in func.dfg.inst_args(inst) { // Get the live range, create it as a dead range if necessary. - let lr = get_or_create(&mut self.ranges, arg, func, recipe_constraints); + let lr = get_or_create(&mut self.ranges, arg, func, &enc_info); // Extend the live range to reach this use. extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg); diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index e2cd0fe3f5..7dfe743ead 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -184,7 +184,7 @@ fn write_instruction(w: &mut Write, if let Some(enc) = func.encodings.get(inst).cloned() { let mut s = String::with_capacity(16); if let Some(isa) = isa { - write!(s, "[{}", isa.display_enc(enc))?; + write!(s, "[{}", isa.encoding_info().display(enc))?; // Write value locations, if we have them. if !func.locations.is_empty() { let regs = isa.register_info(); diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index ca98bd98f2..1843c78776 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -108,7 +108,8 @@ impl<'a> Context<'a> { fn find_recipe_index(&self, recipe_name: &str) -> Option { if let Some(unique_isa) = self.unique_isa { unique_isa - .recipe_names() + .encoding_info() + .names .iter() .position(|&name| name == recipe_name) .map(|idx| idx as u16) From eb1052718bf0d2997724b3f6a0b18aeea2052272 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Apr 2017 11:04:17 -0700 Subject: [PATCH 662/968] Add encoding size information to EncInfo. Two new pieces of information are available for all encoding recipes: - The size in bytes of an encoded instruction, and - The range of a branch encoded with the recipe, if any. In the meta language, EncRecipe takes two new constructor arguments. The size is required for all encodings and branch_range is required for all recipes used to encode branches. --- lib/cretonne/meta/cdsl/isa.py | 32 ++++++++++++- lib/cretonne/meta/gen_encoding.py | 23 +++++++++ lib/cretonne/meta/isa/riscv/recipes.py | 24 ++++++---- lib/cretonne/src/isa/arm32/enc_tables.rs | 1 + lib/cretonne/src/isa/arm64/enc_tables.rs | 1 + lib/cretonne/src/isa/constraints.rs | 60 +++++++++++++++++++++++- lib/cretonne/src/isa/encoding.rs | 38 ++++++++++++++- lib/cretonne/src/isa/intel/enc_tables.rs | 1 + lib/cretonne/src/isa/mod.rs | 2 +- lib/cretonne/src/isa/riscv/enc_tables.rs | 1 + 10 files changed, 169 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 9db0faeb61..1ead583c5a 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -19,6 +19,7 @@ try: # Instruction specification for encodings. Allows for predicated # instructions. InstSpec = Union[MaybeBoundInst, Apply] + BranchRange = Sequence[int] except ImportError: pass @@ -164,17 +165,38 @@ class EncRecipe(object): - An integer indicating that this result is tied to a value operand, so they must use the same register. + The `branch_range` argument must be provided for recipes that can encode + branch instructions. It is an `(origin, bits)` tuple describing the exact + range that can be encoded in a branch instruction. + :param name: Short mnemonic name for this recipe. :param format: All encoded instructions must have this :py:class:`InstructionFormat`. + :param size: Number of bytes in the binary encoded instruction. :param: ins Tuple of register constraints for value operands. :param: outs Tuple of register constraints for results. + :param: branch_range `(origin, bits)` range for branches. + :param: instp Instruction predicate. + :param: isap ISA predicate. """ - def __init__(self, name, format, ins, outs, instp=None, isap=None): - # type: (str, InstructionFormat, ConstraintSeq, ConstraintSeq, PredNode, PredNode) -> None # noqa + def __init__( + self, + name, # type: str + format, # type: InstructionFormat + size, # type: int + ins, # type: ConstraintSeq + outs, # type: ConstraintSeq + branch_range=None, # type: BranchRange + instp=None, # type: PredNode + isap=None # type: PredNode + ): + # type: (...) -> None self.name = name self.format = format + assert size >= 0 + self.size = size + self.branch_range = branch_range self.instp = instp self.isap = isap if instp: @@ -249,6 +271,12 @@ class Encoding(object): assert self.inst.format == recipe.format, ( "Format {} must match recipe: {}".format( self.inst.format, recipe.format)) + + if self.inst.is_branch: + assert recipe.branch_range, ( + 'Recipe {} for {} must have a branch_range' + .format(recipe, self.inst.name)) + self.recipe = recipe self.encbits = encbits # Combine recipe predicates with the manually specified ones. diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 21b121487f..a68710f540 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -498,6 +498,27 @@ def emit_operand_constraints(seq, field, fmt): 'Unsupported constraint {}'.format(cons)) +def emit_recipe_sizing(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Emit a table of encoding recipe code size information. + """ + with fmt.indented( + 'static RECIPE_SIZING: [RecipeSizing; {}] = [' + .format(len(isa.all_recipes)), '];'): + for r in isa.all_recipes: + fmt.comment(r.name) + with fmt.indented('RecipeSizing {', '},'): + fmt.format('bytes: {},', r.size) + if r.branch_range: + fmt.format( + 'branch_range: ' + 'Some(BranchRange {{ origin: {}, bits: {} }}),', + *r.branch_range) + else: + fmt.line('branch_range: None,') + + def gen_isa(isa, fmt): # type: (TargetISA, srcgen.Formatter) -> None # First assign numbers to relevant instruction predicates and generate the @@ -535,10 +556,12 @@ def gen_isa(isa, fmt): emit_recipe_names(isa, fmt) emit_recipe_constraints(isa, fmt) + emit_recipe_sizing(isa, fmt) # Finally, tie it all together in an `EncInfo`. with fmt.indented('pub static INFO: EncInfo = EncInfo {', '};'): fmt.line('constraints: &RECIPE_CONSTRAINTS,') + fmt.line('sizing: &RECIPE_SIZING,') fmt.line('names: &RECIPE_NAMES,') diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 2be2192002..63ca9b78ba 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -85,37 +85,43 @@ def LUI(): # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) -R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) +R = EncRecipe('R', Binary, size=4, ins=(GPR, GPR), outs=GPR) # R-type with an immediate shift amount instead of rs2. -Rshamt = EncRecipe('Rshamt', BinaryImm, ins=GPR, outs=GPR) +Rshamt = EncRecipe('Rshamt', BinaryImm, size=4, ins=GPR, outs=GPR) # R-type encoding of an integer comparison. -Ricmp = EncRecipe('Ricmp', IntCompare, ins=(GPR, GPR), outs=GPR) +Ricmp = EncRecipe('Ricmp', IntCompare, size=4, ins=(GPR, GPR), outs=GPR) I = EncRecipe( - 'I', BinaryImm, ins=GPR, outs=GPR, + 'I', BinaryImm, size=4, ins=GPR, outs=GPR, instp=IsSignedInt(BinaryImm.imm, 12)) # I-type encoding of an integer comparison. Iicmp = EncRecipe( - 'Iicmp', IntCompareImm, ins=GPR, outs=GPR, + 'Iicmp', IntCompareImm, size=4, ins=GPR, outs=GPR, instp=IsSignedInt(IntCompareImm.imm, 12)) # I-type encoding for `jalr` as a return instruction. We won't use the # immediate offset. # The variable return values are not encoded. -Iret = EncRecipe('Iret', MultiAry, ins=GPR, outs=()) +Iret = EncRecipe('Iret', MultiAry, size=4, ins=GPR, outs=()) # U-type instructions have a 20-bit immediate that targets bits 12-31. U = EncRecipe( - 'U', UnaryImm, ins=(), outs=GPR, + 'U', UnaryImm, size=4, ins=(), outs=GPR, instp=IsSignedInt(UnaryImm.imm, 32, 12)) # SB-type branch instructions. # TODO: These instructions have a +/- 4 KB branch range. How to encode that # constraint? -SB = EncRecipe('SB', BranchIcmp, ins=(GPR, GPR), outs=()) +SB = EncRecipe( + 'SB', BranchIcmp, size=4, + ins=(GPR, GPR), outs=(), + branch_range=(0, 13)) # SB-type branch instruction with rs2 fixed to zero. -SBzero = EncRecipe('SBzero', Branch, ins=(GPR), outs=()) +SBzero = EncRecipe( + 'SBzero', Branch, size=4, + ins=(GPR), outs=(), + branch_range=(0, 13)) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index 67cde87ed4..9fdfbcda95 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -5,5 +5,6 @@ use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index d092c0eb8f..cdfc255748 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -5,5 +5,6 @@ use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index 2b8700ec4a..23cd923f93 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -7,6 +7,7 @@ //! It is the register allocator's job to make sure that the register constraints on value operands //! are satisfied. +use binemit::CodeOffset; use isa::{RegClass, RegUnit}; /// Register constraint for a single value operand or instruction result. @@ -48,7 +49,7 @@ pub enum ConstraintKind { Stack, } -/// Constraints for an encoding recipe. +/// Value operand constraints for an encoding recipe. #[derive(Clone)] pub struct RecipeConstraints { /// Constraints for the instruction's fixed value operands. @@ -66,3 +67,60 @@ pub struct RecipeConstraints { /// constraints must be derived from the calling convention ABI. pub outs: &'static [OperandConstraint], } + +/// Constraints on the range of a branch instruction. +/// +/// A branch instruction usually encodes its destination as a signed n-bit offset from an origin. +/// The origin depends on the ISA and the specific instruction: +/// +/// - RISC-V and ARM Aarch64 use the address of the branch instruction, `origin = 0`. +/// - Intel uses the address of the instruction following the branch, `origin = 2` for a 2-byte +/// branch instruction. +/// - ARM's A32 encoding uses the address of the branch instruction + 8 bytes, `origin = 8`. +#[derive(Clone, Copy)] +pub struct BranchRange { + /// Offset in bytes from the address of the branch instruction to the origin used for computing + /// the branch displacement. This is the destination of a branch that encodes a 0 displacement. + pub origin: u8, + + /// Number of bits in the signed byte displacement encoded in the instruction. This does not + /// account for branches that can only target aligned addresses. + pub bits: u8, +} + +impl BranchRange { + /// Determine if this branch range can represent the range from `branch` to `dest`, where + /// `branch` is the code offset of the branch instruction itself and `dest` is the code offset + /// of the destination EBB header. + /// + /// This method does not detect if the range is larger than 2 GB. + pub fn contains(self, branch: CodeOffset, dest: CodeOffset) -> bool { + let d = dest.wrapping_sub(branch + self.origin as CodeOffset) as i32; + let s = 32 - self.bits; + d == d << s >> s + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn branch_range() { + // ARM T1 branch. + let t1 = BranchRange { origin: 4, bits: 9 }; + assert!(t1.contains(0, 0)); + assert!(t1.contains(0, 2)); + assert!(t1.contains(2, 0)); + assert!(t1.contains(1000, 1000)); + + // Forward limit. + assert!(t1.contains(1000, 1258)); + assert!(!t1.contains(1000, 1260)); + + // Backward limit + assert!(t1.contains(1000, 748)); + assert!(!t1.contains(1000, 746)); + + } +} diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index 88040925ad..c5151978aa 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -1,7 +1,8 @@ //! The `Encoding` struct. +use binemit::CodeOffset; +use isa::constraints::{RecipeConstraints, BranchRange}; use std::fmt; -use isa::constraints::RecipeConstraints; /// Bits needed to encode an instruction as binary machine code. /// @@ -78,12 +79,28 @@ impl fmt::Display for DisplayEncoding { } } +/// Code size information for an encoding recipe. +/// +/// All encoding recipes correspond to an exact instruction size. +pub struct RecipeSizing { + /// Size in bytes of instructions encoded with this recipe. + pub bytes: u8, + + /// Allowed branch range in this recipe, if any. + /// + /// All encoding recipes for branches have exact branch range information. + pub branch_range: Option, +} + /// Information about all the encodings in this ISA. #[derive(Clone)] pub struct EncInfo { /// Constraints on value operands per recipe. pub constraints: &'static [RecipeConstraints], + /// Code size information per recipe. + pub sizing: &'static [RecipeSizing], + /// Names of encoding recipes. pub names: &'static [&'static str], } @@ -101,4 +118,23 @@ impl EncInfo { recipe_names: self.names, } } + + /// Get the exact size in bytes of instructions encoded with `enc`. + /// + /// Returns 0 for illegal encodings. + pub fn bytes(&self, enc: Encoding) -> CodeOffset { + self.sizing + .get(enc.recipe()) + .map(|s| s.bytes as CodeOffset) + .unwrap_or(0) + } + + /// Get the branch range that is supported by `enc`, if any. + /// + /// This will never return `None` for a legal branch encoding. + pub fn branch_range(&self, enc: Encoding) -> Option { + self.sizing + .get(enc.recipe()) + .and_then(|s| s.branch_range) + } } diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 8abc8cd9d9..1ee1fbd4b3 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -5,5 +5,6 @@ use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 4fc08906ae..469142ab56 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -40,7 +40,7 @@ //! The configured target ISA trait object is a `Box` which can be used for multiple //! concurrent function compilations. -pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind}; +pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, BranchRange}; pub use isa::encoding::{Encoding, EncInfo}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 87c62bd63c..8ced0c681f 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -6,6 +6,7 @@ use ir::{Opcode, InstructionData}; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::encoding::RecipeSizing; use predicates; use super::registers::*; From b5c74fdc25614a73565a68bceb70e1437d570941 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Apr 2017 14:54:17 -0700 Subject: [PATCH 663/968] Add a branch relaxation pass for #72. Compute exact EBB header offsets and check that branches are in range. Not implemented yet: Relax branches that are not in range. Invoke the relax_branches() pass from the 'test binemit' file tests so they can verify the proper encoding of branch instructions too. --- lib/cretonne/src/binemit/mod.rs | 4 + lib/cretonne/src/binemit/relaxation.rs | 110 +++++++++++++++++++++++ lib/cretonne/src/ir/function.rs | 17 +++- lib/cretonne/src/ir/instructions.rs | 13 +++ lib/cretonne/src/lib.rs | 6 +- src/filetest/binemit.rs | 116 +++++++++++++++++-------- 6 files changed, 224 insertions(+), 42 deletions(-) create mode 100644 lib/cretonne/src/binemit/relaxation.rs diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index 9721586033..33cce8d2b0 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -3,6 +3,10 @@ //! The `binemit` module contains code for translating Cretonne's intermediate representation into //! binary machine code. +mod relaxation; + +pub use self::relaxation::relax_branches; + use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; /// Offset in bytes from the beginning of the function. diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs new file mode 100644 index 0000000000..ef3bfdb257 --- /dev/null +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -0,0 +1,110 @@ +//! Branch relaxation and offset computation. +//! +//! # EBB header offsets +//! +//! Before we can generate binary machine code for branch instructions, we need to know the final +//! offsets of all the EBB headers in the function. This information is encoded in the +//! `func.offsets` table. +//! +//! # Branch relaxation +//! +//! Branch relaxation is the process of ensuring that all branches in the function have enough +//! range to encode their destination. It is common to have multiple branch encodings in an ISA. +//! For example, Intel branches can have either an 8-bit or a 32-bit displacement. +//! +//! On RISC architectures, it can happen that conditional branches have a shorter range than +//! unconditional branches: +//! +//! ```cton +//! brz v1, ebb17 +//! ``` +//! +//! can be transformed into: +//! +//! ```cton +//! brnz v1, ebb23 +//! jump ebb17 +//! ebb23: +//! ``` + +use binemit::CodeOffset; +use entity_map::EntityMap; +use ir::{Function, DataFlowGraph, Cursor, Inst}; +use isa::{TargetIsa, EncInfo, Encoding}; + +/// Relax branches and compute the final layout of EBB headers in `func`. +/// +/// Fill in the `func.offsets` table so the function is ready for binary emission. +pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { + let encinfo = isa.encoding_info(); + + // Clear all offsets so we can recognize EBBs that haven't been visited yet. + func.offsets.clear(); + func.offsets.resize(func.dfg.num_ebbs()); + + // The relaxation algorithm iterates to convergence. + let mut go_again = true; + while go_again { + go_again = false; + + // Visit all instructions in layout order + let mut offset = 0; + let mut pos = Cursor::new(&mut func.layout); + while let Some(ebb) = pos.next_ebb() { + // Record the offset for `ebb` and make sure we iterate until offsets are stable. + if func.offsets[ebb] != offset { + assert!(func.offsets[ebb] < offset, + "Code shrinking during relaxation"); + func.offsets[ebb] = offset; + go_again = true; + } + + while let Some(inst) = pos.next_inst() { + let enc = func.encodings.get(inst).cloned().unwrap_or_default(); + let size = encinfo.bytes(enc); + + // See if this might be a branch that is out of range. + if let Some(range) = encinfo.branch_range(enc) { + if let Some(dest) = func.dfg[inst].branch_destination() { + let dest_offset = func.offsets[dest]; + if !range.contains(offset, dest_offset) { + // This is an out-of-range branch. + // Relax it unless the destination offset has not been computed yet. + if dest_offset != 0 || Some(dest) == pos.layout.entry_block() { + offset += relax_branch(&mut func.dfg, + &mut func.encodings, + &encinfo, + &mut pos, + offset, + dest_offset); + continue; + } + } + } + } + + offset += size; + } + } + } +} + +/// Relax the branch instruction at `pos` so it can cover the range `offset - dest_offset`. +/// +/// Return the size of the replacement instructions up to and including the location where `pos` is +/// left. +fn relax_branch(dfg: &mut DataFlowGraph, + encodings: &mut EntityMap, + encinfo: &EncInfo, + pos: &mut Cursor, + offset: CodeOffset, + dest_offset: CodeOffset) + -> CodeOffset { + let inst = pos.current_inst().unwrap(); + dbg!("Relaxing [{}] {} for {:#x}-{:#x} range", + encinfo.display(encodings[inst]), + dfg.display_inst(inst), + offset, + dest_offset); + unimplemented!(); +} diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 1abf9c4b35..177ca0b5f1 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -3,11 +3,12 @@ //! The `Function` struct defined in this module owns all of its extended basic blocks and //! instructions. -use std::fmt::{self, Display, Debug, Formatter}; -use ir::{FunctionName, Signature, Value, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData, - ValueLoc, DataFlowGraph, Layout}; -use isa::{TargetIsa, Encoding}; +use binemit::CodeOffset; use entity_map::{EntityMap, PrimaryEntityData}; +use ir::{FunctionName, Signature, Value, Inst, Ebb, StackSlot, StackSlotData, JumpTable, + JumpTableData, ValueLoc, DataFlowGraph, Layout}; +use isa::{TargetIsa, Encoding}; +use std::fmt::{self, Display, Debug, Formatter}; use write::write_function; /// A function. @@ -40,6 +41,13 @@ pub struct Function { /// Location assigned to every value. pub locations: EntityMap, + + /// Code offsets of the EBB headers. + /// + /// This information is only transiently available after the `binemit::relax_branches` function + /// computes it, and it can easily be recomputed by calling that function. It is not included + /// in the textual IL format. + pub offsets: EntityMap, } impl PrimaryEntityData for StackSlotData {} @@ -57,6 +65,7 @@ impl Function { layout: Layout::new(), encodings: EntityMap::new(), locations: EntityMap::new(), + offsets: EntityMap::new(), } } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 1c292f473a..9816ccbc91 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -324,6 +324,19 @@ impl InstructionData { } } + /// Get the single destination of this branch instruction, if it is a single destination + /// branch or jump. + /// + /// Multi-destination branches like `br_table` return `None`. + pub fn branch_destination(&self) -> Option { + match self { + &InstructionData::Jump { destination, .. } => Some(destination), + &InstructionData::Branch { destination, .. } => Some(destination), + &InstructionData::BranchIcmp { destination, .. } => Some(destination), + _ => None, + } + } + /// Return information about a call instruction. /// /// Any instruction that can call another function reveals its call signature here. diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index b4c163b14d..1c983c74e6 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -10,6 +10,9 @@ pub use write::write_function; /// Version number of the cretonne crate. pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +#[macro_use] +pub mod dbg; + pub mod binemit; pub mod flowgraph; pub mod dominator_tree; @@ -22,9 +25,6 @@ pub mod settings; pub mod sparse_map; pub mod verifier; -#[macro_use] -pub mod dbg; - mod abi; mod constant_hash; mod context; diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 50949811ec..eee96e6ee5 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -4,6 +4,7 @@ //! functions and compares the results to the expected output. use std::borrow::Cow; +use std::collections::HashMap; use std::fmt::Write; use cretonne::binemit; use cretonne::ir; @@ -97,54 +98,99 @@ impl SubTest for TestBinEmit { fn run(&self, func: Cow, context: &Context) -> Result<()> { let isa = context.isa.expect("binemit needs an ISA"); + let encinfo = isa.encoding_info(); // TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad // value locations. The current error reporting is just crashing... let mut func = func.into_owned(); - let mut sink = TextSink::new(isa); - for comment in &context.details.comments { - if let Some(want) = match_directive(comment.text, "bin:") { - let inst = match comment.entity { - AnyEntity::Inst(inst) => inst, - _ => { - return Err(format!("annotation on non-inst {}: {}", - comment.entity, - comment.text)) - } - }; - - // Compute an encoding for `inst` if one wasn't provided. + // Give an encoding to any instruction that doesn't already have one. + for ebb in func.layout.ebbs() { + for inst in func.layout.ebb_insts(ebb) { if !func.encodings .get(inst) .map(|e| e.is_legal()) .unwrap_or(false) { - match isa.encode(&func.dfg, &func.dfg[inst]) { - Ok(enc) => *func.encodings.ensure(inst) = enc, - Err(_) => { - return Err(format!("{} can't be encoded: {}", - inst, - func.dfg.display_inst(inst))) - } + if let Ok(enc) = isa.encode(&func.dfg, &func.dfg[inst]) { + *func.encodings.ensure(inst) = enc; } } - - sink.text.clear(); - isa.emit_inst(&func, inst, &mut sink); - let have = sink.text.trim(); - if have != want { - return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}", - inst, - func.dfg.display_inst(inst), - want, - have)); - } } } - if sink.text.is_empty() { - Err("No bin: directives found".to_string()) - } else { - Ok(()) + // Relax branches and compute EBB offsets based on the encodings. + binemit::relax_branches(&mut func, isa); + + // Collect all of the 'bin:' directives on instructions. + let mut bins = HashMap::new(); + for comment in &context.details.comments { + if let Some(want) = match_directive(comment.text, "bin:") { + match comment.entity { + AnyEntity::Inst(inst) => { + if let Some(prev) = bins.insert(inst, want) { + return Err(format!("multiple 'bin:' directives on {}: '{}' and '{}'", + func.dfg.display_inst(inst), + prev, + want)); + } + } + _ => { + return Err(format!("'bin:' directive on non-inst {}: {}", + comment.entity, + comment.text)) + } + } + } } + if bins.is_empty() { + return Err("No 'bin:' directives found".to_string()); + } + + // Now emit all instructions. + let mut sink = TextSink::new(isa); + for ebb in func.layout.ebbs() { + // Correct header offsets should have been computed by `relax_branches()`. + assert_eq!(sink.offset, + func.offsets[ebb], + "Inconsistent {} header offset", + ebb); + for inst in func.layout.ebb_insts(ebb) { + sink.text.clear(); + let enc = func.encodings.get(inst).cloned().unwrap_or_default(); + + // Send legal encodings into the emitter. + if enc.is_legal() { + let before = sink.offset; + isa.emit_inst(&func, inst, &mut sink); + let emitted = sink.offset - before; + // Verify the encoding recipe sizes against the ISAs emit_inst implementation. + assert_eq!(emitted, + encinfo.bytes(enc), + "Inconsistent size for [{}] {}", + encinfo.display(enc), + func.dfg.display_inst(inst)); + } + + // Check against bin: directives. + if let Some(want) = bins.remove(&inst) { + if !enc.is_legal() { + return Err(format!("{} can't be encoded: {}", + inst, + func.dfg.display_inst(inst))); + } + sink.text.clear(); + isa.emit_inst(&func, inst, &mut sink); + let have = sink.text.trim(); + if have != want { + return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}", + inst, + func.dfg.display_inst(inst), + want, + have)); + } + } + } + } + + Ok(()) } } From e56482d0fdfd4e229e5d4fca61232c4c8b13bc22 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 6 Apr 2017 11:13:13 -0700 Subject: [PATCH 664/968] Fix a bug in the binemit file test. Only emit each instruction once, or the offset computations go all wrong. --- filetests/isa/riscv/binary32.cton | 2 ++ src/filetest/binemit.rs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 93b1a9cc2d..8055289c65 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -75,9 +75,11 @@ ebb0: [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 + jump ebb1 ; Control Transfer Instructions +ebb1: ; beq br_icmp eq, v1, v2, ebb0 ; bin: Branch(ebb0) 01550063 ; bne diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index eee96e6ee5..f4ec6262c6 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -177,8 +177,6 @@ impl SubTest for TestBinEmit { inst, func.dfg.display_inst(inst))); } - sink.text.clear(); - isa.emit_inst(&func, inst, &mut sink); let have = sink.text.trim(); if have != want { return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}", From 1440b673fc0088ff3c35731c2f09fd26bf85813e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 6 Apr 2017 11:18:38 -0700 Subject: [PATCH 665/968] Use EBB offsets for encoding RISC-V branches. Stop emitting EBB relocations. Use the offsets computed by relax_branches() to encode the correct displacements immediately. --- filetests/isa/riscv/binary32.cton | 45 ++++++++++++++++++--------- lib/cretonne/src/isa/riscv/binemit.rs | 24 ++++++++++---- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 8055289c65..bdcbde9dd2 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -80,23 +80,40 @@ ebb0: ; Control Transfer Instructions ebb1: - ; beq - br_icmp eq, v1, v2, ebb0 ; bin: Branch(ebb0) 01550063 - ; bne - br_icmp ne, v1, v2, ebb0 ; bin: Branch(ebb0) 01551063 - ; blt - br_icmp slt, v1, v2, ebb0 ; bin: Branch(ebb0) 01554063 - ; bge - br_icmp sge, v1, v2, ebb0 ; bin: Branch(ebb0) 01555063 - ; bltu - br_icmp ult, v1, v2, ebb0 ; bin: Branch(ebb0) 01556063 - ; bgeu - br_icmp uge, v1, v2, ebb0 ; bin: Branch(ebb0) 01557063 + ; beq 0x000 + br_icmp eq, v1, v2, ebb1 ; bin: 01550063 + ; bne 0xffc + br_icmp ne, v1, v2, ebb1 ; bin: ff551ee3 + ; blt 0xff8 + br_icmp slt, v1, v2, ebb1 ; bin: ff554ce3 + ; bge 0xff4 + br_icmp sge, v1, v2, ebb1 ; bin: ff555ae3 + ; bltu 0xff0 + br_icmp ult, v1, v2, ebb1 ; bin: ff5568e3 + ; bgeu 0xfec + br_icmp uge, v1, v2, ebb1 ; bin: ff5576e3 + ; Forward branches. + ; beq 0x018 + br_icmp eq, v2, v1, ebb2 ; bin: 00aa8c63 + ; bne 0x014 + br_icmp ne, v2, v1, ebb2 ; bin: 00aa9a63 + ; blt 0x010 + br_icmp slt, v2, v1, ebb2 ; bin: 00aac863 + ; bge 0x00c + br_icmp sge, v2, v1, ebb2 ; bin: 00aad663 + ; bltu 0x008 + br_icmp ult, v2, v1, ebb2 ; bin: 00aae463 + ; bgeu 0x004 + br_icmp uge, v2, v1, ebb2 ; bin: 00aaf263 + + jump ebb2 + +ebb2: ; beq x, %x0 - brz v1, ebb0 ; bin: Branch(ebb0) 00050063 + brz v1, ebb2 ; bin: 00050063 ; bne x, %x0 - brnz v1, ebb0 ; bin: Branch(ebb0) 00051063 + brnz v1, ebb2 ; bin: fe051ee3 return } diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 213d8a132e..a392d76a07 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -3,10 +3,12 @@ use binemit::{CodeSink, Reloc, bad_encoding}; use ir::{Function, Inst, InstructionData}; use isa::RegUnit; +use predicates::is_signed_int; include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); /// RISC-V relocation kinds. +#[allow(dead_code)] pub enum RelocKind { /// A conditional (SB-type) branch to an EBB. Branch, @@ -213,17 +215,17 @@ fn recipe_u(func: &Function, inst: Inst, sink: &mut CS) { /// imm rs2 rs1 funct3 imm opcode /// 25 20 15 12 7 0 /// -/// The imm bits are not encoded by this function. They encode the relative distance to the -/// destination block, handled by a relocation. -/// /// Encoding bits: `opcode[6:2] | (funct3 << 5)` -fn put_sb(bits: u16, rs1: RegUnit, rs2: RegUnit, sink: &mut CS) { +fn put_sb(bits: u16, imm: i64, rs1: RegUnit, rs2: RegUnit, sink: &mut CS) { let bits = bits as u32; let opcode5 = bits & 0x1f; let funct3 = (bits >> 5) & 0x7; let rs1 = rs1 as u32 & 0x1f; let rs2 = rs2 as u32 & 0x1f; + assert!(is_signed_int(imm, 13, 1), "SB out of range {:#x}", imm); + let imm = imm as u32; + // 0-6: opcode let mut i = 0x3; i |= opcode5 << 2; @@ -231,6 +233,12 @@ fn put_sb(bits: u16, rs1: RegUnit, rs2: RegUnit, sink: &m i |= rs1 << 15; i |= rs2 << 20; + // The displacement is completely hashed up. + i |= ((imm >> 11) & 0x1) << 7; + i |= ((imm >> 1) & 0xf) << 8; + i |= ((imm >> 5) & 0x3f) << 25; + i |= ((imm >> 12) & 0x1) << 31; + sink.put4(i); } @@ -240,9 +248,11 @@ fn recipe_sb(func: &Function, inst: Inst, sink: &mut CS) ref args, .. } = func.dfg[inst] { + let dest = func.offsets[destination] as i64; + let disp = dest - sink.offset() as i64; let args = &args.as_slice(&func.dfg.value_lists)[0..2]; - sink.reloc_ebb(RelocKind::Branch.into(), destination); put_sb(func.encodings[inst].bits(), + disp, func.locations[args[0]].unwrap_reg(), func.locations[args[1]].unwrap_reg(), sink); @@ -257,9 +267,11 @@ fn recipe_sbzero(func: &Function, inst: Inst, sink: &mut ref args, .. } = func.dfg[inst] { + let dest = func.offsets[destination] as i64; + let disp = dest - sink.offset() as i64; let args = &args.as_slice(&func.dfg.value_lists)[0..1]; - sink.reloc_ebb(RelocKind::Branch.into(), destination); put_sb(func.encodings[inst].bits(), + disp, func.locations[args[0]].unwrap_reg(), 0, sink); From c6e027207c64bd6da431982a12217c29556d32bd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 6 Apr 2017 13:58:48 -0700 Subject: [PATCH 666/968] Add an iterators module with extra Iterator methods. Start with an adjacent_pairs() iterator adapter. --- lib/cretonne/src/iterators.rs | 90 +++++++++++++++++++++++++++++++++++ lib/cretonne/src/lib.rs | 3 +- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 lib/cretonne/src/iterators.rs diff --git a/lib/cretonne/src/iterators.rs b/lib/cretonne/src/iterators.rs new file mode 100644 index 0000000000..3bdc3cc1cb --- /dev/null +++ b/lib/cretonne/src/iterators.rs @@ -0,0 +1,90 @@ +//! Iterator utilities. + +/// Extra methods for iterators. +pub trait IteratorExtras: Iterator { + /// Create an iterator that produces adjacent pairs of elements from the iterator. + fn adjacent_pairs(mut self) -> AdjacentPairs + where Self: Sized, + Self::Item: Clone + { + let elem = self.next(); + AdjacentPairs { + iter: self, + elem: elem, + } + } +} + +impl IteratorExtras for T where T: Iterator {} + +/// Adjacent pairs iterator returned by `adjacent_pairs()`. +/// +/// This wraps another iterator and produces a sequence of adjacent pairs of elements. +pub struct AdjacentPairs + where I: Iterator, + I::Item: Clone +{ + iter: I, + elem: Option, +} + +impl Iterator for AdjacentPairs + where I: Iterator, + I::Item: Clone +{ + type Item = (I::Item, I::Item); + + fn next(&mut self) -> Option { + self.elem + .take() + .and_then(|e| { + self.elem = self.iter.next(); + self.elem.clone().map(|n| (e, n)) + }) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn adjpairs() { + use super::IteratorExtras; + + assert_eq!([1, 2, 3, 4] + .iter() + .cloned() + .adjacent_pairs() + .collect::>(), + vec![(1, 2), (2, 3), (3, 4)]); + assert_eq!([2, 3, 4] + .iter() + .cloned() + .adjacent_pairs() + .collect::>(), + vec![(2, 3), (3, 4)]); + assert_eq!([2, 3, 4] + .iter() + .cloned() + .adjacent_pairs() + .collect::>(), + vec![(2, 3), (3, 4)]); + assert_eq!([3, 4] + .iter() + .cloned() + .adjacent_pairs() + .collect::>(), + vec![(3, 4)]); + assert_eq!([4] + .iter() + .cloned() + .adjacent_pairs() + .collect::>(), + vec![]); + assert_eq!([] + .iter() + .cloned() + .adjacent_pairs() + .collect::>(), + vec![]); + } +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 1c983c74e6..e44899f8c2 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -14,10 +14,10 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod dbg; pub mod binemit; -pub mod flowgraph; pub mod dominator_tree; pub mod entity_list; pub mod entity_map; +pub mod flowgraph; pub mod ir; pub mod isa; pub mod regalloc; @@ -28,6 +28,7 @@ pub mod verifier; mod abi; mod constant_hash; mod context; +mod iterators; mod legalizer; mod packed_option; mod partition_slice; From e5e5b30315098c7f382c7cc873866921fd0bef3c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 6 Apr 2017 14:22:32 -0700 Subject: [PATCH 667/968] Add a fallthrough instruction. Change jumps to fallthroughs in the branch relaxation pass before computing the EBB offsets. --- docs/langref.rst | 1 + filetests/isa/riscv/binary32.cton | 2 +- lib/cretonne/meta/base/instructions.py | 13 +++++++++ lib/cretonne/src/binemit/relaxation.rs | 37 +++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 47a0dae37c..398141fb5b 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -319,6 +319,7 @@ condition is satisfied, otherwise execution continues at the following instruction in the EBB. .. autoinst:: jump +.. autoinst:: fallthrough .. autoinst:: brz .. autoinst:: brnz .. autoinst:: br_icmp diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index bdcbde9dd2..4c9f9439b9 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -107,7 +107,7 @@ ebb1: ; bgeu 0x004 br_icmp uge, v2, v1, ebb2 ; bin: 00aaf263 - jump ebb2 + fallthrough ebb2 ebb2: ; beq x, %x0 diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 4776f576c1..d9eda7a220 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -49,6 +49,19 @@ jump = Instruction( """, ins=(EBB, args), is_branch=True, is_terminator=True) +fallthrough = Instruction( + 'fallthrough', r""" + Fall through to the next EBB. + + This is the same as :inst:`jump`, except the destination EBB must be + the next one in the layout. + + Jumps are turned into fall-through instructions by the branch + relaxation pass. There is no reason to use this instruction outside + that pass. + """, + ins=(EBB, args), is_branch=True, is_terminator=True) + brz = Instruction( 'brz', r""" Branch when zero. diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index ef3bfdb257..179540dad0 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -29,8 +29,9 @@ use binemit::CodeOffset; use entity_map::EntityMap; -use ir::{Function, DataFlowGraph, Cursor, Inst}; +use ir::{Function, DataFlowGraph, Cursor, Inst, InstructionData, Opcode}; use isa::{TargetIsa, EncInfo, Encoding}; +use iterators::IteratorExtras; /// Relax branches and compute the final layout of EBB headers in `func`. /// @@ -42,6 +43,9 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { func.offsets.clear(); func.offsets.resize(func.dfg.num_ebbs()); + // Start by inserting fall through instructions. + fallthroughs(func); + // The relaxation algorithm iterates to convergence. let mut go_again = true; while go_again { @@ -89,6 +93,37 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { } } +/// Convert `jump` instructions to `fallthrough` instructions where possible and verify that any +/// existing `fallthrough` instructions are correct. +fn fallthroughs(func: &mut Function) { + for (ebb, succ) in func.layout.ebbs().adjacent_pairs() { + let term = func.layout + .last_inst(ebb) + .expect("EBB has no terminator."); + if let InstructionData::Jump { + ref mut opcode, + destination, + .. + } = func.dfg[term] { + match *opcode { + Opcode::Fallthrough => { + // Somebody used a fall-through instruction before the branch relaxation pass. + // Make sure it is correct, i.e. the destination is the layout successor. + assert_eq!(destination, succ, "Illegal fall-through in {}", ebb) + } + Opcode::Jump => { + // If this is a jump to the successor EBB, change it to a fall-through. + if destination == succ { + *opcode = Opcode::Fallthrough; + func.encodings[term] = Default::default(); + } + } + _ => {} + } + } + } +} + /// Relax the branch instruction at `pos` so it can cover the range `offset - dest_offset`. /// /// Return the size of the replacement instructions up to and including the location where `pos` is From d2f575b54ab9b59c0280e104790fbf052eca8f60 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 6 Apr 2017 15:17:57 -0700 Subject: [PATCH 668/968] Add jump encodings to RISC-V. Fix a bug in gen_encoding.py when dealing with non-polymorphic instructions where the type variable is None in Python, VOID in Rust. --- filetests/isa/riscv/binary32.cton | 18 +++++++---- lib/cretonne/meta/gen_encoding.py | 7 +++-- lib/cretonne/meta/isa/riscv/encodings.py | 8 +++-- lib/cretonne/meta/isa/riscv/recipes.py | 10 +++++- lib/cretonne/src/isa/riscv/binemit.rs | 39 ++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 4c9f9439b9..d34fe34e21 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -75,7 +75,8 @@ ebb0: [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 - jump ebb1 + brz v1, ebb3 + fallthrough ebb1 ; Control Transfer Instructions @@ -110,10 +111,15 @@ ebb1: fallthrough ebb2 ebb2: - ; beq x, %x0 - brz v1, ebb2 ; bin: 00050063 - ; bne x, %x0 - brnz v1, ebb2 ; bin: fe051ee3 + ; jal %x0, 0x00000 + jump ebb2 ; bin: 0000006f - return +ebb3: + ; beq x, %x0 + brz v1, ebb3 ; bin: 00050063 + ; bne x, %x0 + brnz v1, ebb3 ; bin: fe051ee3 + + ; jal %x0, 0x1ffff4 + jump ebb2 ; bin: ff5ff06f } diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index a68710f540..706ad46780 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -300,7 +300,7 @@ class Level2Table(object): level2_doc[self.hash_table_offset].append( '{:06x}: {}, {} entries'.format( self.hash_table_offset, - self.ty.name, + self.ty, self.hash_table_len)) level2_hashtables.extend(hash_table) @@ -405,7 +405,7 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): """ def hash_func(level2): # type: (Level2Table) -> int - return level2.ty.number + return level2.ty.number if level2.ty is not None else 0 hash_table = compute_quadratic(level1.tables.values(), hash_func) with fmt.indented( @@ -415,11 +415,12 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): if level2: l2l = int(math.log(level2.hash_table_len, 2)) assert l2l > 0, "Hash table too small" + tyname = level2.ty.name if level2.ty is not None else 'void' fmt.line( 'Level1Entry ' + '{{ ty: types::{}, log2len: {}, offset: {:#08x} }},' .format( - level2.ty.name.upper(), + tyname.upper(), l2l, level2.hash_table_offset)) else: diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 162ccbd3ea..21ff09e177 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -5,8 +5,8 @@ from __future__ import absolute_import from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 -from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH -from .recipes import JALR, R, Rshamt, Ricmp, I, Iicmp, Iret, U, SB, SBzero +from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL +from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret, U, UJ, SB, SBzero from .settings import use_m from cdsl.ast import Var @@ -81,6 +81,10 @@ RV64.enc(base.imul.i32, R, OP32(0b000, 0b0000001), isap=use_m) # Control flow. +# Unconditional branches. +RV32.enc(base.jump, UJ, JAL()) +RV64.enc(base.jump, UJ, JAL()) + # Conditional branches. for cond, f3 in [ (intcc.eq, 0b000), diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 63ca9b78ba..e30922613f 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,7 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm -from base.formats import UnaryImm, BranchIcmp, Branch +from base.formats import UnaryImm, BranchIcmp, Branch, Jump from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -47,6 +47,11 @@ def JALR(funct3=0): return 0b11001 | (funct3 << 5) +def JAL(): + # type: () -> int + return 0b11011 + + def OPIMM(funct3, funct7=0): # type: (int, int) -> int assert funct3 <= 0b111 @@ -112,6 +117,9 @@ U = EncRecipe( 'U', UnaryImm, size=4, ins=(), outs=GPR, instp=IsSignedInt(UnaryImm.imm, 32, 12)) +# UJ-type unconditional branch instructions. +UJ = EncRecipe('UJ', Jump, size=4, ins=(), outs=(), branch_range=(0, 21)) + # SB-type branch instructions. # TODO: These instructions have a +/- 4 KB branch range. How to encode that # constraint? diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index a392d76a07..e4cc5c05c6 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -279,3 +279,42 @@ fn recipe_sbzero(func: &Function, inst: Inst, sink: &mut panic!("Expected Branch format: {:?}", func.dfg[inst]); } } + +/// UJ-type jump instructions. +/// +/// 31 11 6 +/// imm rd opcode +/// 12 7 0 +/// +/// Encoding bits: `opcode[6:2]` +fn put_uj(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) { + let bits = bits as u32; + let opcode5 = bits & 0x1f; + let rd = rd as u32 & 0x1f; + + assert!(is_signed_int(imm, 21, 1), "UJ out of range {:#x}", imm); + let imm = imm as u32; + + // 0-6: opcode + let mut i = 0x3; + i |= opcode5 << 2; + i |= rd << 7; + + // The displacement is completely hashed up. + i |= imm & 0xff000; + i |= ((imm >> 11) & 0x1) << 20; + i |= ((imm >> 1) & 0x3ff) << 21; + i |= ((imm >> 20) & 0x1) << 31; + + sink.put4(i); +} + +fn recipe_uj(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Jump { destination, .. } = func.dfg[inst] { + let dest = func.offsets[destination] as i64; + let disp = dest - sink.offset() as i64; + put_uj(func.encodings[inst].bits(), disp, 0, sink); + } else { + panic!("Expected Jump format: {:?}", func.dfg[inst]); + } +} From 13b0046ed754731ce6262273c2db1bff56d0669b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 10:28:37 -0700 Subject: [PATCH 669/968] Syntax tweak: Omit comma after an initial enum immediate. This affects the comparison instructions which now read "icmp ult a, b". This mimics LLVM's style and makes it simpler to add instruction flags in the future, such as "load v1" -> "load aligned v1". These enumerated operands and flags feel like opcode modifiers rather than value operands, so displaying them differently makes sense. Value and numeric operands are still comma separated. --- filetests/cfg/loop.cton | 2 +- filetests/isa/riscv/binary32.cton | 40 +++++++++++++-------------- filetests/isa/riscv/expand-i32.cton | 2 +- filetests/isa/riscv/legalize-i64.cton | 2 +- filetests/parser/tiny.cton | 28 +++++++++---------- lib/cretonne/src/write.rs | 8 +++--- lib/reader/src/parser.rs | 4 --- 7 files changed, 41 insertions(+), 45 deletions(-) diff --git a/filetests/cfg/loop.cton b/filetests/cfg/loop.cton index 9a1ca6d7e3..3f7892a9d9 100644 --- a/filetests/cfg/loop.cton +++ b/filetests/cfg/loop.cton @@ -21,7 +21,7 @@ ebb1(v5: i32): v10 = f32const 0.0 v11 = fadd v9, v10 v12 = iadd_imm v5, 1 - v13 = icmp ult, v12, v2 + v13 = icmp ult v12, v2 brnz v13, ebb1(v12) ; unordered: ebb1:inst12 -> ebb1 v14 = f64const 0.0 v15 = f64const 0.0 diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index d34fe34e21..89771d8165 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -33,11 +33,11 @@ ebb0: [-,%x7] v34 = sshr v1, v2 ; bin: 415553b3 [-,%x16] v35 = sshr v2, v1 ; bin: 40aad833 ; slt - [-,%x7] v42 = icmp slt, v1, v2 ; bin: 015523b3 - [-,%x16] v43 = icmp slt, v2, v1 ; bin: 00aaa833 + [-,%x7] v42 = icmp slt v1, v2 ; bin: 015523b3 + [-,%x16] v43 = icmp slt v2, v1 ; bin: 00aaa833 ; sltu - [-,%x7] v44 = icmp ult, v1, v2 ; bin: 015533b3 - [-,%x16] v45 = icmp ult, v2, v1 ; bin: 00aab833 + [-,%x7] v44 = icmp ult v1, v2 ; bin: 015533b3 + [-,%x16] v45 = icmp ult v2, v1 ; bin: 00aab833 ; Integer Register-Immediate Instructions @@ -65,11 +65,11 @@ ebb0: [-,%x16] v125 = sshr_imm v2, 8 ; bin: 408ad813 ; slti - [-,%x7] v130 = icmp_imm slt, v1, 1000 ; bin: 3e852393 - [-,%x16] v131 = icmp_imm slt, v2, -905 ; bin: c77aa813 + [-,%x7] v130 = icmp_imm slt v1, 1000 ; bin: 3e852393 + [-,%x16] v131 = icmp_imm slt v2, -905 ; bin: c77aa813 ; sltiu - [-,%x7] v132 = icmp_imm ult, v1, 1000 ; bin: 3e853393 - [-,%x16] v133 = icmp_imm ult, v2, -905 ; bin: c77ab813 + [-,%x7] v132 = icmp_imm ult v1, 1000 ; bin: 3e853393 + [-,%x16] v133 = icmp_imm ult v2, -905 ; bin: c77ab813 ; lui [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 @@ -82,31 +82,31 @@ ebb0: ebb1: ; beq 0x000 - br_icmp eq, v1, v2, ebb1 ; bin: 01550063 + br_icmp eq v1, v2, ebb1 ; bin: 01550063 ; bne 0xffc - br_icmp ne, v1, v2, ebb1 ; bin: ff551ee3 + br_icmp ne v1, v2, ebb1 ; bin: ff551ee3 ; blt 0xff8 - br_icmp slt, v1, v2, ebb1 ; bin: ff554ce3 + br_icmp slt v1, v2, ebb1 ; bin: ff554ce3 ; bge 0xff4 - br_icmp sge, v1, v2, ebb1 ; bin: ff555ae3 + br_icmp sge v1, v2, ebb1 ; bin: ff555ae3 ; bltu 0xff0 - br_icmp ult, v1, v2, ebb1 ; bin: ff5568e3 + br_icmp ult v1, v2, ebb1 ; bin: ff5568e3 ; bgeu 0xfec - br_icmp uge, v1, v2, ebb1 ; bin: ff5576e3 + br_icmp uge v1, v2, ebb1 ; bin: ff5576e3 ; Forward branches. ; beq 0x018 - br_icmp eq, v2, v1, ebb2 ; bin: 00aa8c63 + br_icmp eq v2, v1, ebb2 ; bin: 00aa8c63 ; bne 0x014 - br_icmp ne, v2, v1, ebb2 ; bin: 00aa9a63 + br_icmp ne v2, v1, ebb2 ; bin: 00aa9a63 ; blt 0x010 - br_icmp slt, v2, v1, ebb2 ; bin: 00aac863 + br_icmp slt v2, v1, ebb2 ; bin: 00aac863 ; bge 0x00c - br_icmp sge, v2, v1, ebb2 ; bin: 00aad663 + br_icmp sge v2, v1, ebb2 ; bin: 00aad663 ; bltu 0x008 - br_icmp ult, v2, v1, ebb2 ; bin: 00aae463 + br_icmp ult v2, v1, ebb2 ; bin: 00aae463 ; bgeu 0x004 - br_icmp uge, v2, v1, ebb2 ; bin: 00aaf263 + br_icmp uge v2, v1, ebb2 ; bin: 00aaf263 fallthrough ebb2 diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index 166b058eb2..0415c6026c 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -15,7 +15,7 @@ ebb0(v1: i32, v2: i32): return v3, v4 } ; check: $v3 = iadd $v1, $v2 -; check: $(cout=$V) = icmp ult, $v3, $v1 +; check: $(cout=$V) = icmp ult $v3, $v1 ; It's possible the legalizer will rewrite these value aliases in the future. ; check: $v4 -> $cout ; check: return $v3, $v4 diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index e2d5a763a2..dbe26f824c 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -54,7 +54,7 @@ ebb0(v1: i64, v2: i64): ; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): ; check: [R#0c ; sameln: $(v3l=$V) = iadd $v1l, $v2l -; check: $(c=$V) = icmp ult, $v3l, $v1l +; check: $(c=$V) = icmp ult $v3l, $v1l ; check: [R#0c ; sameln: $(v3h1=$V) = iadd $v1h, $v2h ; check: $(c_int=$V) = bint.i32 $c diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index d5790c0e89..21d3a59815 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -52,33 +52,33 @@ ebb0: ; Integer condition codes. function icmp(i32, i32) { ebb0(vx0: i32, vx1: i32): - v0 = icmp eq, vx0, vx1 - v1 = icmp ult, vx0, vx1 - v2 = icmp_imm sge, vx0, -12 + v0 = icmp eq vx0, vx1 + v1 = icmp ult vx0, vx1 + v2 = icmp_imm sge vx0, -12 v3 = irsub_imm vx1, 45 - br_icmp eq, vx0, vx1, ebb0(vx1, vx0) + br_icmp eq vx0, vx1, ebb0(vx1, vx0) } ; sameln: function icmp(i32, i32) { ; nextln: ebb0(vx0: i32, vx1: i32): -; nextln: v0 = icmp eq, vx0, vx1 -; nextln: v1 = icmp ult, vx0, vx1 -; nextln: v2 = icmp_imm sge, vx0, -12 +; nextln: v0 = icmp eq vx0, vx1 +; nextln: v1 = icmp ult vx0, vx1 +; nextln: v2 = icmp_imm sge vx0, -12 ; nextln: v3 = irsub_imm vx1, 45 -; nextln: br_icmp eq, vx0, vx1, ebb0(vx1, vx0) +; nextln: br_icmp eq vx0, vx1, ebb0(vx1, vx0) ; nextln: } ; Floating condition codes. function fcmp(f32, f32) { ebb0(vx0: f32, vx1: f32): - v0 = fcmp eq, vx0, vx1 - v1 = fcmp uno, vx0, vx1 - v2 = fcmp lt, vx0, vx1 + v0 = fcmp eq vx0, vx1 + v1 = fcmp uno vx0, vx1 + v2 = fcmp lt vx0, vx1 } ; sameln: function fcmp(f32, f32) { ; nextln: ebb0(vx0: f32, vx1: f32): -; nextln: v0 = fcmp eq, vx0, vx1 -; nextln: v1 = fcmp uno, vx0, vx1 -; nextln: v2 = fcmp lt, vx0, vx1 +; nextln: v0 = fcmp eq vx0, vx1 +; nextln: v1 = fcmp uno vx0, vx1 +; nextln: v2 = fcmp lt vx0, vx1 ; nextln: } ; The bitcast instruction has two type variables: The controlling type variable diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 7dfe743ead..eaa5248408 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -259,9 +259,9 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result } InsertLane { lane, args, .. } => write!(w, " {}, {}, {}", args[0], lane, args[1]), ExtractLane { lane, arg, .. } => write!(w, " {}, {}", arg, lane), - IntCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), - IntCompareImm { cond, arg, imm, .. } => write!(w, " {}, {}, {}", cond, arg, imm), - FloatCompare { cond, args, .. } => write!(w, " {}, {}, {}", cond, args[0], args[1]), + IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), + IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, imm), + FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), Jump { destination, ref args, @@ -295,7 +295,7 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result .. } => { let args = args.as_slice(pool); - write!(w, " {}, {}, {}, {}", cond, args[0], args[1], destination)?; + write!(w, " {} {}, {}, {}", cond, args[0], args[1], destination)?; if args.len() > 2 { write!(w, "({})", DisplayValues(&args[2..]))?; } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 1843c78776..b9ee4acd58 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1526,7 +1526,6 @@ impl<'a> Parser<'a> { } InstructionFormat::BranchIcmp => { let cond = self.match_enum("expected intcc condition code")?; - self.match_token(Token::Comma, "expected ',' between operands")?; let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value second operand")?; @@ -1567,7 +1566,6 @@ impl<'a> Parser<'a> { } InstructionFormat::IntCompare => { let cond = self.match_enum("expected intcc condition code")?; - self.match_token(Token::Comma, "expected ',' between operands")?; let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value second operand")?; @@ -1580,7 +1578,6 @@ impl<'a> Parser<'a> { } InstructionFormat::IntCompareImm => { let cond = self.match_enum("expected intcc condition code")?; - self.match_token(Token::Comma, "expected ',' between operands")?; let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_imm64("expected immediate second operand")?; @@ -1594,7 +1591,6 @@ impl<'a> Parser<'a> { } InstructionFormat::FloatCompare => { let cond = self.match_enum("expected floatcc condition code")?; - self.match_token(Token::Comma, "expected ',' between operands")?; let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value second operand")?; From ab1e51002d20a22104a353d4f3209c71566c3ccc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 11:53:46 -0700 Subject: [PATCH 670/968] Add an Offset32 immediate operand kind. This will be used to represent an immediate 32-bit signed address offset for load/store instructions. --- docs/langref.rst | 14 +- lib/cretonne/meta/base/immediates.py | 6 + lib/cretonne/src/ir/immediates.rs | 290 ++++++++++++++++++--------- 3 files changed, 208 insertions(+), 102 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 398141fb5b..24890c8ada 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -249,6 +249,13 @@ indicate the different kinds of immediate operands on an instruction. In the textual format, :type:`imm64` immediates appear as decimal or hexadecimal literals using the same syntax as C. +.. type:: offset32 + + A signed 32-bit immediate address offset. + + In the textual format, :type:`offset32` immediates always have an explicit + sign, and a 0 offset may beomitted. + .. type:: ieee32 A 32-bit immediate floating point number in the IEEE 754-2008 binary32 @@ -259,13 +266,6 @@ indicate the different kinds of immediate operands on an instruction. A 64-bit immediate floating point number in the IEEE 754-2008 binary64 interchange format. All bit patterns are allowed. -.. type:: immvector - - An immediate SIMD vector. This operand supplies all the bits of a SIMD - type, so it can have different sizes depending on the type produced. The - bits of the operand are interpreted as if the SIMD vector was loaded from - memory containing the immediate. - .. type:: intcc An integer condition code. See the :inst:`icmp` instruction for details. diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index ff2eb972a5..e11ed78e28 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -17,6 +17,12 @@ imm64 = ImmediateKind('imm64', 'A 64-bit immediate integer.') #: immediate bit counts on shift instructions. uimm8 = ImmediateKind('uimm8', 'An 8-bit immediate unsigned integer.') +#: A 32-bit immediate signed offset. +#: +#: This is used to represent an immediate address offset in load/store +#: instructions. +offset32 = ImmediateKind('offset32', 'A 32-bit immediate signed offset.') + #: A 32-bit immediate floating point operand. #: #: IEEE 754-2008 binary32 interchange format. diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 84f5978429..8b779dde60 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -6,6 +6,7 @@ //! module in the meta language. use std::fmt::{self, Display, Formatter}; +use std::i32; use std::mem; use std::str::FromStr; @@ -35,6 +36,22 @@ impl From for Imm64 { } } +// Hexadecimal with a multiple of 4 digits and group separators: +// +// 0xfff0 +// 0x0001_ffff +// 0xffff_ffff_fff8_4400 +// +fn write_hex(x: i64, f: &mut Formatter) -> fmt::Result { + let mut pos = (64 - x.leading_zeros() - 1) & 0xf0; + write!(f, "0x{:04x}", (x >> pos) & 0xffff)?; + while pos > 0 { + pos -= 16; + write!(f, "_{:04x}", (x >> pos) & 0xffff)?; + } + Ok(()) +} + impl Display for Imm64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let x = self.0; @@ -42,91 +59,88 @@ impl Display for Imm64 { // Use decimal for small numbers. write!(f, "{}", x) } else { - // Hexadecimal with a multiple of 4 digits and group separators: - // - // 0xfff0 - // 0x0001_ffff - // 0xffff_ffff_fff8_4400 - // - let mut pos = (64 - x.leading_zeros() - 1) & 0xf0; - write!(f, "0x{:04x}", (x >> pos) & 0xffff)?; - while pos > 0 { - pos -= 16; - write!(f, "_{:04x}", (x >> pos) & 0xffff)?; - } - Ok(()) + write_hex(x, f) } } } +/// Parse a 64-bit number. +fn parse_i64(s: &str) -> Result { + let mut value: u64 = 0; + let mut digits = 0; + let negative = s.starts_with('-'); + let s2 = if negative || s.starts_with('+') { + &s[1..] + } else { + s + }; + + if s2.starts_with("0x") { + // Hexadecimal. + for ch in s2[2..].chars() { + match ch.to_digit(16) { + Some(digit) => { + digits += 1; + if digits > 16 { + return Err("Too many hexadecimal digits"); + } + // This can't overflow given the digit limit. + value = (value << 4) | digit as u64; + } + None => { + // Allow embedded underscores, but fail on anything else. + if ch != '_' { + return Err("Invalid character in hexadecimal number"); + } + } + } + } + } else { + // Decimal number, possibly negative. + for ch in s2.chars() { + match ch.to_digit(16) { + Some(digit) => { + digits += 1; + match value.checked_mul(10) { + None => return Err("Too large decimal number"), + Some(v) => value = v, + } + match value.checked_add(digit as u64) { + None => return Err("Too large decimal number"), + Some(v) => value = v, + } + } + None => { + // Allow embedded underscores, but fail on anything else. + if ch != '_' { + return Err("Invalid character in decimal number"); + } + } + } + } + } + + if digits == 0 { + return Err("No digits in number"); + } + + // We support the range-and-a-half from -2^63 .. 2^64-1. + if negative { + value = value.wrapping_neg(); + // Don't allow large negative values to wrap around and become positive. + if value as i64 > 0 { + return Err("Negative number too small"); + } + } + Ok(value as i64) +} + impl FromStr for Imm64 { type Err = &'static str; // Parse a decimal or hexadecimal `Imm64`, formatted as above. fn from_str(s: &str) -> Result { - let mut value: u64 = 0; - let mut digits = 0; - let negative = s.starts_with('-'); - let s2 = if negative { &s[1..] } else { s }; - - if s2.starts_with("0x") { - // Hexadecimal. - for ch in s2[2..].chars() { - match ch.to_digit(16) { - Some(digit) => { - digits += 1; - if digits > 16 { - return Err("Too many hexadecimal digits in Imm64"); - } - // This can't overflow given the digit limit. - value = (value << 4) | digit as u64; - } - None => { - // Allow embedded underscores, but fail on anything else. - if ch != '_' { - return Err("Invalid character in hexadecimal Imm64"); - } - } - } - } - } else { - // Decimal number, possibly negative. - for ch in s2.chars() { - match ch.to_digit(16) { - Some(digit) => { - digits += 1; - match value.checked_mul(10) { - None => return Err("Too large decimal Imm64"), - Some(v) => value = v, - } - match value.checked_add(digit as u64) { - None => return Err("Too large decimal Imm64"), - Some(v) => value = v, - } - } - None => { - // Allow embedded underscores, but fail on anything else. - if ch != '_' { - return Err("Invalid character in decimal Imm64"); - } - } - } - } - } - - if digits == 0 { - return Err("No digits in Imm64"); - } - - // We support the range-and-a-half from -2^63 .. 2^64-1. - if negative { - value = value.wrapping_neg(); - // Don't allow large negative values to wrap around and become positive. - if value as i64 > 0 { - return Err("Negative number too small for Imm64"); - } - } - Ok(Imm64::new(value as i64)) + parse_i64(s).map(Imm64::new) } } @@ -135,6 +149,68 @@ impl FromStr for Imm64 { /// This is used to indicate lane indexes typically. pub type Uimm8 = u8; +/// 32-bit signed immediate offset. +/// +/// This is used to encode an immediate offset for load/store instructions. All supported ISAs have +/// a maximum load/store offset that fits in an `i32`. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Offset32(i32); + +impl Offset32 { + /// Create a new `Offset32` representing the signed number `x`. + pub fn new(x: i32) -> Offset32 { + Offset32(x) + } +} + +impl Into for Offset32 { + fn into(self) -> i32 { + self.0 + } +} + +impl From for Offset32 { + fn from(x: i32) -> Self { + Offset32(x) + } +} + +impl Display for Offset32 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // 0 displays as an empty offset. + if self.0 == 0 { + return Ok(()); + } + + // Always include a sign. + write!(f, "{}", if self.0 < 0 { '-' } else { '+' })?; + + let val = (self.0 as i64).abs(); + if val < 10_000 { + write!(f, "{}", val) + } else { + write_hex(val, f) + } + + } +} + +impl FromStr for Offset32 { + type Err = &'static str; + + // Parse a decimal or hexadecimal `Offset32`, formatted as above. + fn from_str(s: &str) -> Result { + if !(s.starts_with('-') || s.starts_with('+')) { + return Err("Offset must begin with sign"); + } + parse_i64(s).and_then(|x| if i32::MIN as i64 <= x && x <= i32::MAX as i64 { + Ok(Offset32::new(x as i32)) + } else { + Err("Offset out of range") + }) + } +} + /// An IEEE binary32 immediate floating point value. /// /// All bit patterns are allowed. @@ -486,15 +562,13 @@ mod tests { parse_ok::("0xffffffff_ffffffff", "-1"); parse_ok::("0x80000000_00000000", "0x8000_0000_0000_0000"); parse_ok::("-0x80000000_00000000", "0x8000_0000_0000_0000"); - parse_err::("-0x80000000_00000001", - "Negative number too small for Imm64"); + parse_err::("-0x80000000_00000001", "Negative number too small"); parse_ok::("18446744073709551615", "-1"); parse_ok::("-9223372036854775808", "0x8000_0000_0000_0000"); // Overflow both the `checked_add` and `checked_mul`. - parse_err::("18446744073709551616", "Too large decimal Imm64"); - parse_err::("184467440737095516100", "Too large decimal Imm64"); - parse_err::("-9223372036854775809", - "Negative number too small for Imm64"); + parse_err::("18446744073709551616", "Too large decimal number"); + parse_err::("184467440737095516100", "Too large decimal number"); + parse_err::("-9223372036854775809", "Negative number too small"); // Underscores are allowed where digits go. parse_ok::("0_0", "0"); @@ -503,21 +577,47 @@ mod tests { parse_ok::("0x97_88_bb", "0x0097_88bb"); parse_ok::("0x_97_", "151"); - parse_err::("", "No digits in Imm64"); - parse_err::("-", "No digits in Imm64"); - parse_err::("_", "No digits in Imm64"); - parse_err::("0x", "No digits in Imm64"); - parse_err::("0x_", "No digits in Imm64"); - parse_err::("-0x", "No digits in Imm64"); - parse_err::(" ", "Invalid character in decimal Imm64"); - parse_err::("0 ", "Invalid character in decimal Imm64"); - parse_err::(" 0", "Invalid character in decimal Imm64"); - parse_err::("--", "Invalid character in decimal Imm64"); - parse_err::("-0x-", "Invalid character in hexadecimal Imm64"); + parse_err::("", "No digits in number"); + parse_err::("-", "No digits in number"); + parse_err::("_", "No digits in number"); + parse_err::("0x", "No digits in number"); + parse_err::("0x_", "No digits in number"); + parse_err::("-0x", "No digits in number"); + parse_err::(" ", "Invalid character in decimal number"); + parse_err::("0 ", "Invalid character in decimal number"); + parse_err::(" 0", "Invalid character in decimal number"); + parse_err::("--", "Invalid character in decimal number"); + parse_err::("-0x-", "Invalid character in hexadecimal number"); // Hex count overflow. - parse_err::("0x0_0000_0000_0000_0000", - "Too many hexadecimal digits in Imm64"); + parse_err::("0x0_0000_0000_0000_0000", "Too many hexadecimal digits"); + } + + #[test] + fn format_offset32() { + assert_eq!(Offset32(0).to_string(), ""); + assert_eq!(Offset32(1).to_string(), "+1"); + assert_eq!(Offset32(-1).to_string(), "-1"); + assert_eq!(Offset32(9999).to_string(), "+9999"); + assert_eq!(Offset32(10000).to_string(), "+0x2710"); + assert_eq!(Offset32(-9999).to_string(), "-9999"); + assert_eq!(Offset32(-10000).to_string(), "-0x2710"); + assert_eq!(Offset32(0xffff).to_string(), "+0xffff"); + assert_eq!(Offset32(0x10000).to_string(), "+0x0001_0000"); + } + + #[test] + fn parse_offset32() { + parse_ok::("+0", ""); + parse_ok::("+1", "+1"); + parse_ok::("-0", ""); + parse_ok::("-1", "-1"); + parse_ok::("+0x0", ""); + parse_ok::("+0xf", "+15"); + parse_ok::("-0x9", "-9"); + parse_ok::("-0x8000_0000", "-0x8000_0000"); + + parse_err::("+0x8000_0000", "Offset out of range"); } #[test] From af2516e996a48ae8c89447a39195d6f51388d610 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 13:03:46 -0700 Subject: [PATCH 671/968] Add Offset32 support to the parser. --- lib/reader/src/lexer.rs | 24 ++++++++++++++++-------- lib/reader/src/parser.rs | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index e1e4cc31c4..0e8eaccd0f 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -223,7 +223,7 @@ impl<'a> Lexer<'a> { // - `0x0.4p-34`: Float // // This function does not filter out all invalid numbers. It depends in the context-sensitive - // decoding of the text for that. For example, the number of allowed digits an an Ieee32` and + // decoding of the text for that. For example, the number of allowed digits in an `Ieee32` and // an `Ieee64` constant are different. fn scan_number(&mut self) -> Result, LocatedError> { let begin = self.pos; @@ -231,15 +231,21 @@ impl<'a> Lexer<'a> { let mut is_float = false; // Skip a leading sign. - if self.lookahead == Some('-') { - self.next_ch(); + match self.lookahead { + Some('-') => { + self.next_ch(); - if let Some(c) = self.lookahead { - // If the next character won't parse as a number, we return Token::Minus - if !c.is_alphanumeric() && c != '.' { - return token(Token::Minus, loc); + if let Some(c) = self.lookahead { + // If the next character won't parse as a number, we return Token::Minus + if !c.is_alphanumeric() && c != '.' { + return token(Token::Minus, loc); + } } } + Some('+') => { + self.next_ch(); + } + _ => {} } // Check for NaNs with payloads. @@ -395,6 +401,7 @@ impl<'a> Lexer<'a> { Some('.') => Some(self.scan_char(Token::Dot)), Some(':') => Some(self.scan_char(Token::Colon)), Some('=') => Some(self.scan_char(Token::Equal)), + Some('+') => Some(self.scan_number()), Some('-') => { if self.looking_at("->") { Some(self.scan_chars(2, Token::Arrow)) @@ -506,7 +513,7 @@ mod tests { #[test] fn lex_numbers() { - let mut lex = Lexer::new(" 0 2_000 -1,0xf -0x0 0.0 0x0.4p-34"); + let mut lex = Lexer::new(" 0 2_000 -1,0xf -0x0 0.0 0x0.4p-34 +5"); assert_eq!(lex.next(), token(Token::Integer("0"), 1)); assert_eq!(lex.next(), token(Token::Integer("2_000"), 1)); assert_eq!(lex.next(), token(Token::Integer("-1"), 1)); @@ -515,6 +522,7 @@ mod tests { assert_eq!(lex.next(), token(Token::Integer("-0x0"), 1)); assert_eq!(lex.next(), token(Token::Float("0.0"), 1)); assert_eq!(lex.next(), token(Token::Float("0x0.4p-34"), 1)); + assert_eq!(lex.next(), token(Token::Integer("+5"), 1)); assert_eq!(lex.next(), None); } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index b9ee4acd58..741621ed08 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -13,7 +13,7 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, FuncRef, ValueLoc, ArgumentLoc}; use cretonne::ir::types::VOID; -use cretonne::ir::immediates::{Imm64, Ieee32, Ieee64}; +use cretonne::ir::immediates::{Imm64, Offset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; use cretonne::isa::{self, TargetIsa, Encoding}; @@ -497,6 +497,22 @@ impl<'a> Parser<'a> { } } + // Match and consume an optional offset32 immediate. + // + // Note that that this will match an empty string as an empty offset, and that if an offset is + // present, it must contain a sign. + fn optional_offset32(&mut self) -> Result { + if let Some(Token::Integer(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like an integer. + // Parse it as an `Offset32` to check for overflow and other issues. + text.parse().map_err(|e| self.error(e)) + } else { + // An offset32 operand can be absent. + Ok(Offset32::new(0)) + } + } + // Match and consume an Ieee32 immediate. fn match_ieee32(&mut self, err_msg: &str) -> Result { if let Some(Token::Float(text)) = self.token() { From c52e3e0b3f1dce0837fa0da8da54c961ad16d3a8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 13:20:41 -0700 Subject: [PATCH 672/968] Define stack_load, stack_store, and stack_addr instructions. --- docs/langref.rst | 40 ++--------------- filetests/parser/tiny.cton | 21 +++++++++ lib/cretonne/meta/base/formats.py | 7 ++- lib/cretonne/meta/base/immediates.py | 5 ++- lib/cretonne/meta/base/instructions.py | 61 ++++++++++++++++++++++---- lib/cretonne/src/ir/builder.rs | 4 +- lib/cretonne/src/ir/instructions.rs | 17 ++++++- lib/cretonne/src/verifier.rs | 16 ++++++- lib/cretonne/src/write.rs | 7 +++ lib/reader/src/parser.rs | 41 +++++++++++++++-- 10 files changed, 163 insertions(+), 56 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 24890c8ada..911714751f 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -489,33 +489,8 @@ simply represent a contiguous sequence of bytes in the stack frame. :flag align(N): Request at least N bytes alignment. :result SS: Stack slot index. -.. inst:: a = stack_load SS, Offset - - Load a value from a stack slot at the constant offset. - - This is a polymorphic instruction that can load any value type which has a - memory representation. - - The offset is an immediate constant, not an SSA value. The memory access - cannot go out of bounds, i.e. ``sizeof(a) + Offset <= sizeof(SS)``. - - :arg SS: Stack slot declared with :inst:`stack_slot`. - :arg Offset: Immediate non-negative offset. - :result T a: Value loaded. - -.. inst:: stack_store x, SS, Offset - - Store a value to a stack slot at a constant offset. - - This is a polymorphic instruction that can store any value type with a - memory representation. - - The offset is an immediate constant, not an SSA value. The memory access - cannot go out of bounds, i.e. ``sizeof(a) + Offset <= sizeof(SS)``. - - :arg T x: Value to be stored. - :arg SS: Stack slot declared with :inst:`stack_slot`. - :arg Offset: Immediate non-negative offset. +.. autoinst:: stack_load +.. autoinst:: stack_store The dedicated stack access instructions are easy for the compiler to reason about because stack slots and offsets are fixed at compile time. For example, @@ -525,16 +500,7 @@ and stack slot alignments. It can be necessary to escape from the safety of the restricted instructions by taking the address of a stack slot. -.. inst:: a = stack_addr SS, Offset - - Get the address of a stack slot. - - Compute the absolute address of a byte in a stack slot. The offset must - refer to a byte inside the stack slot: ``0 <= Offset < sizeof(SS)``. - - :arg SS: Stack slot declared with :inst:`stack_slot`. - :arg Offset: Immediate non-negative offset. - :result iPtr a: Address. +.. autoinst:: stack_addr The :inst:`stack_addr` instruction can be used to macro-expand the stack access instructions before instruction selection:: diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 21d3a59815..e89efb4cbe 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -93,3 +93,24 @@ ebb0(vx0: i32, vx1: f32): ; nextln: v0 = bitcast.i8x4 vx0 ; nextln: v1 = bitcast.i32 vx1 ; nextln: } + +; Stack slot references +function stack() { + ss10 = stack_slot 8 + ss2 = stack_slot 4 + +ebb0: + v1 = stack_load.i32 ss10 + v2 = stack_load.i32 ss10+4 + stack_store v1, ss10+2 + stack_store v2, ss2 +} +; sameln: function stack() { +; nextln: $ss10 = stack_slot 8 +; nextln: $ss2 = stack_slot 4 + +; check: ebb0: +; nextln: $v1 = stack_load.i32 $ss10 +; nextln: $v2 = stack_load.i32 $ss10+4 +; nextln: stack_store $v1, $ss10+2 +; nextln: stack_store $v2, $ss2 diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index cf1922627f..c840e1b360 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -8,8 +8,8 @@ in this module. from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS -from .immediates import imm64, uimm8, ieee32, ieee64, intcc, floatcc -from .entities import ebb, sig_ref, func_ref, jump_table +from .immediates import imm64, uimm8, ieee32, ieee64, offset32, intcc, floatcc +from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot Nullary = InstructionFormat() @@ -52,5 +52,8 @@ IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True) +StackLoad = InstructionFormat(stack_slot, offset32) +StackStore = InstructionFormat(VALUE, stack_slot, offset32) + # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index e11ed78e28..ad1bda9989 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -21,7 +21,10 @@ uimm8 = ImmediateKind('uimm8', 'An 8-bit immediate unsigned integer.') #: #: This is used to represent an immediate address offset in load/store #: instructions. -offset32 = ImmediateKind('offset32', 'A 32-bit immediate signed offset.') +offset32 = ImmediateKind( + 'offset32', + 'A 32-bit immediate signed offset.', + default_member='offset') #: A 32-bit immediate floating point operand. #: diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index d9eda7a220..29d2f44a16 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -9,7 +9,7 @@ from cdsl.operands import Operand, VARIABLE_ARGS from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 -from base.immediates import imm64, uimm8, ieee32, ieee64 +from base.immediates import imm64, uimm8, ieee32, ieee64, offset32 from base.immediates import intcc, floatcc from base import entities import base.formats # noqa @@ -28,6 +28,12 @@ TxN = TypeVar( Any = TypeVar( 'Any', 'Any integer, float, or boolean scalar or vector type', ints=True, floats=True, bools=True, scalars=True, simd=True) +Mem = TypeVar( + 'Mem', 'Any type that can be stored in memory', + ints=True, floats=True, simd=True) +MemTo = TypeVar( + 'MemTo', 'Any type that can be stored in memory', + ints=True, floats=True, simd=True) # # Control flow @@ -195,6 +201,52 @@ call_indirect = Instruction( """, ins=(SIG, callee, args), outs=rvals, is_call=True) +# +# Memory operations +# + +SS = Operand('SS', entities.stack_slot) +Offset = Operand('Offset', offset32, 'In-bounds offset into stack slot') +x = Operand('x', Mem, doc='Value to be stored') +a = Operand('a', Mem, doc='Value loaded') +addr = Operand('addr', iAddr) + +stack_load = Instruction( + 'stack_load', r""" + Load a value from a stack slot at the constant offset. + + This is a polymorphic instruction that can load any value type which + has a memory representation. + + The offset is an immediate constant, not an SSA value. The memory + access cannot go out of bounds, i.e. + :math:`sizeof(a) + Offset <= sizeof(SS)`. + """, + ins=(SS, Offset), outs=a) + +stack_store = Instruction( + 'stack_store', r""" + Store a value to a stack slot at a constant offset. + + This is a polymorphic instruction that can store any value type with a + memory representation. + + The offset is an immediate constant, not an SSA value. The memory + access cannot go out of bounds, i.e. + :math:`sizeof(a) + Offset <= sizeof(SS)`. + """, + ins=(x, SS, Offset)) + +stack_addr = Instruction( + 'stack_addr', r""" + Get the address of a stack slot. + + Compute the absolute address of a byte in a stack slot. The offset must + refer to a byte inside the stack slot: + :math:`0 <= Offset < sizeof(SS)`. + """, + ins=(SS, Offset), outs=addr) + # # Materializing constants. # @@ -1095,13 +1147,6 @@ nearest = Instruction( # Conversions # -Mem = TypeVar( - 'Mem', 'Any type that can be stored in memory', - ints=True, floats=True, simd=True) -MemTo = TypeVar( - 'MemTo', 'Any type that can be stored in memory', - ints=True, floats=True, simd=True) - x = Operand('x', Mem) a = Operand('a', MemTo, 'Bits of `x` reinterpreted') diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index fb5c4ba20a..e15a93bf30 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,8 +5,8 @@ use ir::types; use ir::{InstructionData, DataFlowGraph, Cursor}; -use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, ValueList}; -use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32}; use ir::condcodes::{IntCC, FloatCC}; /// Base trait for instruction builders. diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 9816ccbc91..a0ff3a3be4 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -10,8 +10,8 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef}; -use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64}; +use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32}; use ir::condcodes::*; use ir::types; use ir::DataFlowGraph; @@ -227,6 +227,19 @@ pub enum InstructionData { sig_ref: SigRef, args: ValueList, }, + StackLoad { + opcode: Opcode, + ty: Type, + stack_slot: StackSlot, + offset: Offset32, + }, + StackStore { + opcode: Opcode, + ty: Type, + arg: Value, + stack_slot: StackSlot, + offset: Offset32, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 776755d4c4..29a40d55b9 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -57,7 +57,8 @@ use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::entities::AnyEntity; use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; -use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, Value, Type}; +use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, + Value, Type}; use Context; use std::fmt::{self, Display, Formatter}; use std::result; @@ -248,6 +249,11 @@ impl<'a> Verifier<'a> { self.verify_sig_ref(inst, sig_ref)?; self.verify_value_list(inst, args)?; } + &StackLoad { stack_slot, .. } | + &StackStore { stack_slot, .. } => { + self.verify_stack_slot(inst, stack_slot)?; + } + // Exhaustive list so we can't forget to add new formats &Nullary { .. } | &Unary { .. } | @@ -293,6 +299,14 @@ impl<'a> Verifier<'a> { } } + fn verify_stack_slot(&self, inst: Inst, ss: StackSlot) -> Result<()> { + if !self.func.stack_slots.is_valid(ss) { + err!(inst, "invalid stack slot {}", ss) + } else { + Ok(()) + } + } + fn verify_value_list(&self, inst: Inst, l: &ValueList) -> Result<()> { if !l.is_valid(&self.func.dfg.value_lists) { err!(inst, "invalid value list reference {:?}", l) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index eaa5248408..1ab481cdd1 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -313,6 +313,13 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result args[0], DisplayValues(&args[1..])) } + StackLoad { stack_slot, offset, .. } => write!(w, " {}{}", stack_slot, offset), + StackStore { + arg, + stack_slot, + offset, + .. + } => write!(w, " {}, {}{}", arg, stack_slot, offset), } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 741621ed08..9a36ec2451 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -11,7 +11,7 @@ use std::{u16, u32}; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, - FuncRef, ValueLoc, ArgumentLoc}; + FuncRef, StackSlot, ValueLoc, ArgumentLoc}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Offset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; @@ -124,6 +124,14 @@ impl<'a> Context<'a> { .def_ss(number, self.function.stack_slots.push(data), loc) } + // Resolve a reference to a stack slot. + fn get_ss(&self, number: u32, loc: &Location) -> Result { + match self.map.get_ss(number) { + Some(sig) => Ok(sig), + None => err!(loc, "undefined stack slot ss{}", number), + } + } + // Allocate a new signature and add a mapping number -> SigRef. fn add_sig(&mut self, number: u32, data: Signature, loc: &Location) -> Result<()> { self.map @@ -210,14 +218,16 @@ impl<'a> Context<'a> { InstructionData::Nullary { .. } | InstructionData::UnaryImm { .. } | InstructionData::UnaryIeee32 { .. } | - InstructionData::UnaryIeee64 { .. } => {} + InstructionData::UnaryIeee64 { .. } | + InstructionData::StackLoad { .. } => {} InstructionData::BinaryImm { ref mut arg, .. } | InstructionData::BranchTable { ref mut arg, .. } | InstructionData::ExtractLane { ref mut arg, .. } | InstructionData::IntCompareImm { ref mut arg, .. } | InstructionData::Unary { ref mut arg, .. } | - InstructionData::UnarySplit { ref mut arg, .. } => { + InstructionData::UnarySplit { ref mut arg, .. } | + InstructionData::StackStore { ref mut arg, .. } => { self.map.rewrite_value(arg, loc)?; } @@ -1659,6 +1669,31 @@ impl<'a> Parser<'a> { table: table, } } + InstructionFormat::StackLoad => { + let ss = self.match_ss("expected stack slot number: ss«n»") + .and_then(|num| ctx.get_ss(num, &self.loc))?; + let offset = self.optional_offset32()?; + InstructionData::StackLoad { + opcode: opcode, + ty: VOID, + stack_slot: ss, + offset: offset, + } + } + InstructionFormat::StackStore => { + let arg = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let ss = self.match_ss("expected stack slot number: ss«n»") + .and_then(|num| ctx.get_ss(num, &self.loc))?; + let offset = self.optional_offset32()?; + InstructionData::StackStore { + opcode: opcode, + ty: VOID, + arg: arg, + stack_slot: ss, + offset: offset, + } + } }; Ok(idata) } From ca12a683ac6976c9db14435e61fecc7eeae03d1e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 14:32:06 -0700 Subject: [PATCH 673/968] Add a Uoffset32 immediate operand kind. WebAssembly memory instructions encode a 32-bit unsigned offset that is used to compute an effective address. --- lib/cretonne/meta/base/immediates.py | 9 +++ lib/cretonne/src/ir/immediates.rs | 95 +++++++++++++++++++++++++++- lib/reader/src/parser.rs | 18 +++++- 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index ad1bda9989..7f1662f5c1 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -26,6 +26,15 @@ offset32 = ImmediateKind( 'A 32-bit immediate signed offset.', default_member='offset') +#: A 32-bit immediate unsigned offset. +#: +#: This is used to represent an immediate address offset in WebAssembly memory +#: instructions. +uoffset32 = ImmediateKind( + 'uoffset32', + 'A 32-bit immediate unsigned offset.', + default_member='offset') + #: A 32-bit immediate floating point operand. #: #: IEEE 754-2008 binary32 interchange format. diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 8b779dde60..120409f3b8 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -6,7 +6,7 @@ //! module in the meta language. use std::fmt::{self, Display, Formatter}; -use std::i32; +use std::{i32, u32}; use std::mem; use std::str::FromStr; @@ -169,6 +169,12 @@ impl Into for Offset32 { } } +impl Into for Offset32 { + fn into(self) -> i64 { + self.0 as i64 + } +} + impl From for Offset32 { fn from(x: i32) -> Self { Offset32(x) @@ -211,6 +217,71 @@ impl FromStr for Offset32 { } } +/// 32-bit unsigned immediate offset. +/// +/// This is used to encode an immediate offset for WebAssembly heap_load/heap_store instructions. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct Uoffset32(u32); + +impl Uoffset32 { + /// Create a new `Uoffset32` representing the number `x`. + pub fn new(x: u32) -> Uoffset32 { + Uoffset32(x) + } +} + +impl Into for Uoffset32 { + fn into(self) -> u32 { + self.0 + } +} + +impl Into for Uoffset32 { + fn into(self) -> i64 { + self.0 as i64 + } +} + +impl From for Uoffset32 { + fn from(x: u32) -> Self { + Uoffset32(x) + } +} + +impl Display for Uoffset32 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // 0 displays as an empty offset. + if self.0 == 0 { + return Ok(()); + } + + // Always include a sign. + if self.0 < 10_000 { + write!(f, "+{}", self.0) + } else { + write!(f, "+")?; + write_hex(self.0 as i64, f) + } + + } +} + +impl FromStr for Uoffset32 { + type Err = &'static str; + + // Parse a decimal or hexadecimal `Uoffset32`, formatted as above. + fn from_str(s: &str) -> Result { + if !s.starts_with('+') { + return Err("Unsigned offset must begin with '+' sign"); + } + parse_i64(s).and_then(|x| if 0 <= x && x <= u32::MAX as i64 { + Ok(Uoffset32::new(x as u32)) + } else { + Err("Offset out of range") + }) + } +} + /// An IEEE binary32 immediate floating point value. /// /// All bit patterns are allowed. @@ -620,6 +691,28 @@ mod tests { parse_err::("+0x8000_0000", "Offset out of range"); } + #[test] + fn format_uoffset32() { + assert_eq!(Uoffset32(0).to_string(), ""); + assert_eq!(Uoffset32(1).to_string(), "+1"); + assert_eq!(Uoffset32(9999).to_string(), "+9999"); + assert_eq!(Uoffset32(10000).to_string(), "+0x2710"); + assert_eq!(Uoffset32(0xffff).to_string(), "+0xffff"); + assert_eq!(Uoffset32(0x10000).to_string(), "+0x0001_0000"); + } + + #[test] + fn parse_uoffset32() { + parse_ok::("+0", ""); + parse_ok::("+1", "+1"); + parse_ok::("+0x0", ""); + parse_ok::("+0xf", "+15"); + parse_ok::("+0x8000_0000", "+0x8000_0000"); + parse_ok::("+0xffff_ffff", "+0xffff_ffff"); + + parse_err::("+0x1_0000_0000", "Offset out of range"); + } + #[test] fn format_ieee32() { assert_eq!(Ieee32::new(0.0).to_string(), "0.0"); diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 9a36ec2451..8775f1f0b4 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -13,7 +13,7 @@ use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotDa JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, FuncRef, StackSlot, ValueLoc, ArgumentLoc}; use cretonne::ir::types::VOID; -use cretonne::ir::immediates::{Imm64, Offset32, Ieee32, Ieee64}; +use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; use cretonne::isa::{self, TargetIsa, Encoding}; @@ -523,6 +523,22 @@ impl<'a> Parser<'a> { } } + // Match and consume an optional uoffset32 immediate. + // + // Note that that this will match an empty string as an empty offset, and that if an offset is + // present, it must contain a `+` sign. + fn optional_uoffset32(&mut self) -> Result { + if let Some(Token::Integer(text)) = self.token() { + self.consume(); + // Lexer just gives us raw text that looks like an integer. + // Parse it as a `Uoffset32` to check for overflow and other issues. + text.parse().map_err(|e| self.error(e)) + } else { + // A uoffset32 operand can be absent. + Ok(Uoffset32::new(0)) + } + } + // Match and consume an Ieee32 immediate. fn match_ieee32(&mut self, err_msg: &str) -> Result { if let Some(Token::Float(text)) = self.token() { From b5237b6a4afe49b771582608e283f762bee21037 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 15:03:10 -0700 Subject: [PATCH 674/968] Add heap_load, heap_store, and heap_addr instructions. These are used when lowering WebAssembly sandbox code. --- docs/langref.rst | 42 ++------------------------ filetests/parser/tiny.cton | 14 +++++++++ lib/cretonne/meta/base/formats.py | 8 ++++- lib/cretonne/meta/base/instructions.py | 41 ++++++++++++++++++++++++- lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/instructions.rs | 14 ++++++++- lib/cretonne/src/verifier.rs | 4 ++- lib/cretonne/src/write.rs | 2 ++ lib/reader/src/parser.rs | 29 ++++++++++++++++-- 9 files changed, 110 insertions(+), 46 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 911714751f..4156a95aa3 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -533,49 +533,13 @@ than the native pointer size, for example unsigned :type:`i32` offsets on a :arg Name: String identifying the heap in the runtime environment. :result H: Heap identifier. -.. inst:: a = heap_load H, p, Offset - - Load a value at the address ``p + Offset`` in the heap H. - - Trap if the heap access would be out of bounds. - - :arg H: Heap identifier created by :inst:`heap`. - :arg iN p: Unsigned base address in heap. - :arg Offset: Immediate signed offset. - :flag align(N): Expected alignment of ``p + Offset``. Power of two. - :flag aligntrap: Always trap if the memory access is misaligned. - :result T a: Loaded value. - -.. inst:: a = heap_store H, x, p, Offset - - Store a value at the address ``p + Offset`` in the heap H. - - Trap if the heap access would be out of bounds. - - :arg H: Heap identifier created by :inst:`heap`. - :arg T x: Value to be stored. - :arg iN p: Unsigned base address in heap. - :arg Offset: Immediate signed offset. - :flag align(N): Expected alignment of ``p + Offset``. Power of two. - :flag aligntrap: Always trap if the memory access is misaligned. +.. autoinst:: heap_load +.. autoinst:: heap_store When optimizing heap accesses, Cretonne may separate the heap bounds checking and address computations from the memory accesses. -.. inst:: a = heap_addr H, p, Size - - Bounds check and compute absolute address of heap memory. - - Verify that the address range ``p .. p + Size - 1`` is valid in the heap H, - and trap if not. - - Convert the heap-relative address in ``p`` to a real absolute address and - return it. - - :arg H: Heap identifier created by :inst:`heap`. - :arg iN p: Unsigned base address in heap. - :arg Size: Immediate unsigned byte count for range to verify. - :result iPtr a: Absolute address corresponding to ``p``. +.. autoinst:: heap_addr A small example using heaps:: diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index e89efb4cbe..92842cd140 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -114,3 +114,17 @@ ebb0: ; nextln: $v2 = stack_load.i32 $ss10+4 ; nextln: stack_store $v1, $ss10+2 ; nextln: stack_store $v2, $ss2 + +; Heap access instructions. +function heap(i32) { + ; TODO: heap0 = heap %foo +ebb0(v1: i32): + v2 = heap_load.f32 v1 + v3 = heap_load.f32 v1+12 + heap_store v3, v1 +} +; sameln: function heap(i32) { +; nextln: ebb0($v1: i32): +; nextln: $v2 = heap_load.f32 $v1 +; nextln: $v3 = heap_load.f32 $v1+12 +; nextln: heap_store $v3, $v1 diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index c840e1b360..767be3be7e 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -8,7 +8,8 @@ in this module. from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS -from .immediates import imm64, uimm8, ieee32, ieee64, offset32, intcc, floatcc +from .immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 +from .immediates import intcc, floatcc from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot Nullary = InstructionFormat() @@ -55,5 +56,10 @@ IndirectCall = InstructionFormat( StackLoad = InstructionFormat(stack_slot, offset32) StackStore = InstructionFormat(VALUE, stack_slot, offset32) +# Accessing a WebAssembly heap. +# TODO: Add a reference to a `heap` declared in the preamble. +HeapLoad = InstructionFormat(VALUE, uoffset32) +HeapStore = InstructionFormat(VALUE, VALUE, uoffset32) + # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 29d2f44a16..d3de015311 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -9,7 +9,7 @@ from cdsl.operands import Operand, VARIABLE_ARGS from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 -from base.immediates import imm64, uimm8, ieee32, ieee64, offset32 +from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 from base.immediates import intcc, floatcc from base import entities import base.formats # noqa @@ -209,6 +209,7 @@ SS = Operand('SS', entities.stack_slot) Offset = Operand('Offset', offset32, 'In-bounds offset into stack slot') x = Operand('x', Mem, doc='Value to be stored') a = Operand('a', Mem, doc='Value loaded') +p = Operand('p', iAddr) addr = Operand('addr', iAddr) stack_load = Instruction( @@ -247,6 +248,44 @@ stack_addr = Instruction( """, ins=(SS, Offset), outs=addr) +# +# WebAssembly bounds-checked heap accesses. +# +# TODO: Add a `heap` operand that selects between multiple heaps. +# TODO: Should the immediate offset be a `u32`? +# TODO: Distinguish between `iAddr` for a heap and for a target address? i.e., +# 32-bit WebAssembly on a 64-bit target has two different types. + +Offset = Operand('Offset', uoffset32, 'Unsigned offset to effective address') + +heap_load = Instruction( + 'heap_load', r""" + Load a value at the address :math:`p + Offset` in the heap H. + + Trap if the heap access would be out of bounds. + """, + ins=(p, Offset), outs=a) + +heap_store = Instruction( + 'heap_store', r""" + Store a value at the address :math:`p + Offset` in the heap H. + + Trap if the heap access would be out of bounds. + """, + ins=(x, p, Offset)) + +heap_addr = Instruction( + 'heap_addr', r""" + Bounds check and compute absolute address of heap memory. + + Verify that the address range ``p .. p + Size - 1`` is valid in the + heap H, and trap if not. + + Convert the heap-relative address in ``p`` to a real absolute address + and return it. + """, + ins=(p, Offset), outs=addr) + # # Materializing constants. # diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index e15a93bf30..8b95f20272 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -6,7 +6,7 @@ use ir::types; use ir::{InstructionData, DataFlowGraph, Cursor}; use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList}; -use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::{IntCC, FloatCC}; /// Base trait for instruction builders. diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index a0ff3a3be4..5e8d66a09c 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use std::ops::{Deref, DerefMut}; use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot}; -use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32}; +use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::*; use ir::types; use ir::DataFlowGraph; @@ -240,6 +240,18 @@ pub enum InstructionData { stack_slot: StackSlot, offset: Offset32, }, + HeapLoad { + opcode: Opcode, + ty: Type, + arg: Value, + offset: Uoffset32, + }, + HeapStore { + opcode: Opcode, + ty: Type, + args: [Value; 2], + offset: Uoffset32, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 29a40d55b9..b1b4429d1a 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -269,7 +269,9 @@ impl<'a> Verifier<'a> { &ExtractLane { .. } | &IntCompare { .. } | &IntCompareImm { .. } | - &FloatCompare { .. } => {} + &FloatCompare { .. } | + &HeapLoad { .. } | + &HeapStore { .. } => {} } Ok(()) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 1ab481cdd1..f4b7de5f75 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -320,6 +320,8 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result offset, .. } => write!(w, " {}, {}{}", arg, stack_slot, offset), + HeapLoad { arg, offset, .. } => write!(w, " {}{}", arg, offset), + HeapStore { args, offset, .. } => write!(w, " {}, {}{}", args[0], args[1], offset), } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 8775f1f0b4..e813404130 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -227,15 +227,18 @@ impl<'a> Context<'a> { InstructionData::IntCompareImm { ref mut arg, .. } | InstructionData::Unary { ref mut arg, .. } | InstructionData::UnarySplit { ref mut arg, .. } | - InstructionData::StackStore { ref mut arg, .. } => { + InstructionData::StackStore { ref mut arg, .. } | + InstructionData::HeapLoad { ref mut arg, .. } => { self.map.rewrite_value(arg, loc)?; } + // `args: Value[2]` InstructionData::Binary { ref mut args, .. } | InstructionData::BinaryOverflow { ref mut args, .. } | InstructionData::InsertLane { ref mut args, .. } | InstructionData::IntCompare { ref mut args, .. } | - InstructionData::FloatCompare { ref mut args, .. } => { + InstructionData::FloatCompare { ref mut args, .. } | + InstructionData::HeapStore { ref mut args, .. } => { self.map.rewrite_values(args, loc)?; } @@ -1710,6 +1713,28 @@ impl<'a> Parser<'a> { offset: offset, } } + InstructionFormat::HeapLoad => { + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_uoffset32()?; + InstructionData::HeapLoad { + opcode: opcode, + ty: VOID, + arg: addr, + offset: offset, + } + } + InstructionFormat::HeapStore => { + let arg = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_uoffset32()?; + InstructionData::HeapStore { + opcode: opcode, + ty: VOID, + args: [arg, addr], + offset: offset, + } + } }; Ok(idata) } From 46f03934170c33b51e1cf173965ca4118246609a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Apr 2017 12:47:16 -0700 Subject: [PATCH 675/968] Ensure that the docs examples verify as Cretonne IL. Any *.cton files in the docs directory are now included when running the test-all.sh script. This is to ensure that the examples are in fact correct IL. Always print NaN and Inf floats with a sign. Print the positive ones as +NaN and +Inf to make them easier to parse. --- docs/cton_lexer.py | 2 +- docs/example.cton | 14 ++++--- docs/langref.rst | 3 +- lib/cretonne/src/ir/immediates.rs | 64 ++++++++++++++++++------------- test-all.sh | 2 +- 5 files changed, 49 insertions(+), 36 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 868bb23241..b40cb70e41 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -39,7 +39,7 @@ class CretonneLexer(RegexLexer): # Numbers. (r'[-+]?0[xX][0-9a-fA-F]+', Number.Hex), (r'[-+]?0[xX][0-9a-fA-F]*\.[0-9a-fA-F]*([pP]\d+)?', Number.Hex), - (r'[-+]?(\d+\.\d+([eE]\d+)?|[sq]NaN|Inf)', Number.Float), + (r'[-+]?(\d+\.\d+([eE]\d+)?|s?NaN|Inf)', Number.Float), (r'[-+]?\d+', Number.Integer), # Known attributes. (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), diff --git a/docs/example.cton b/docs/example.cton index cdfc61bf4b..db87e891d1 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -1,18 +1,20 @@ +test verifier + function average(i32, i32) -> f32 { - ss1 = stack_slot 8, align 4 ; Stack slot for ``sum``. + ss1 = stack_slot 8 ; Stack slot for ``sum``. ebb1(v1: i32, v2: i32): v3 = f64const 0x0.0 stack_store v3, ss1 brz v2, ebb3 ; Handle count == 0. v4 = iconst.i32 0 - br ebb2(v4) + jump ebb2(v4) ebb2(v5: i32): v6 = imul_imm v5, 4 v7 = iadd v1, v6 v8 = heap_load.f32 v7 ; array[i] - v9 = fext.f64 v8 + v9 = fpromote.f64 v8 v10 = stack_load.f64 ss1 v11 = fadd v9, v10 stack_store v11, ss1 @@ -20,12 +22,12 @@ ebb2(v5: i32): v13 = icmp ult v12, v2 brnz v13, ebb2(v12) ; Loop backedge. v14 = stack_load.f64 ss1 - v15 = cvt_utof.f64 v2 + v15 = fcvt_from_uint.f64 v2 v16 = fdiv v14, v15 - v17 = ftrunc.f32 v16 + v17 = fdemote.f32 v16 return v17 ebb3: - v100 = f32const qNaN + v100 = f32const +NaN return v100 } diff --git a/docs/langref.rst b/docs/langref.rst index 4156a95aa3..6ded3a8bfd 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -31,8 +31,7 @@ Here is the same function compiled into Cretonne IL: .. literalinclude:: example.cton :language: cton - :linenos: - :emphasize-lines: 2 + :lines: 2- The first line of a function definition provides the function *name* and the :term:`function signature` which declares the argument and return types. diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 120409f3b8..e061b88482 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -337,6 +337,11 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { write!(f, "0x0.{0:01$x}p{2}", left_t_bits, digits as usize, emin) } } else if e_bits == max_e_bits { + // Always print a `+` or `-` sign for these special values. + // This makes them easier to parse as they can't be confused as identifiers. + if sign_bit == 0 { + write!(f, "+")?; + } if t_bits == 0 { // Infinity. write!(f, "Inf") @@ -375,6 +380,8 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { let (sign_bit, s2) = if s.starts_with('-') { (1u64 << t + w, &s[1..]) + } else if s.starts_with('+') { + (0, &s[1..]) } else { (0, s) }; @@ -731,27 +738,29 @@ mod tests { "0x0.800000p-126"); assert_eq!(Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON).to_string(), "0x0.000002p-126"); - assert_eq!(Ieee32::new(f32::INFINITY).to_string(), "Inf"); + assert_eq!(Ieee32::new(f32::INFINITY).to_string(), "+Inf"); assert_eq!(Ieee32::new(f32::NEG_INFINITY).to_string(), "-Inf"); - assert_eq!(Ieee32::new(f32::NAN).to_string(), "NaN"); + assert_eq!(Ieee32::new(f32::NAN).to_string(), "+NaN"); assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "NaN:0x1"); - assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "NaN:0x300001"); + assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "+NaN:0x1"); + assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "+NaN:0x300001"); // Signaling NaNs. - assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "sNaN:0x1"); - assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "sNaN:0x200001"); + assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "+sNaN:0x200001"); } #[test] fn parse_ieee32() { parse_ok::("0.0", "0.0"); + parse_ok::("+0.0", "0.0"); parse_ok::("-0.0", "-0.0"); parse_ok::("0x0", "0.0"); parse_ok::("0x0.0", "0.0"); parse_ok::("0x.0", "0.0"); parse_ok::("0x0.", "0.0"); parse_ok::("0x1", "0x1.000000p0"); + parse_ok::("+0x1", "0x1.000000p0"); parse_ok::("-0x1", "-0x1.000000p0"); parse_ok::("0x10", "0x1.000000p4"); parse_ok::("0x10.0", "0x1.000000p4"); @@ -793,20 +802,22 @@ mod tests { parse_err::("0x1.0p-150", "Magnitude too small"); // NaNs and Infs. - parse_ok::("Inf", "Inf"); + parse_ok::("Inf", "+Inf"); + parse_ok::("+Inf", "+Inf"); parse_ok::("-Inf", "-Inf"); - parse_ok::("NaN", "NaN"); + parse_ok::("NaN", "+NaN"); + parse_ok::("+NaN", "+NaN"); parse_ok::("-NaN", "-NaN"); - parse_ok::("NaN:0x0", "NaN"); + parse_ok::("NaN:0x0", "+NaN"); parse_err::("NaN:", "Float must be hexadecimal"); parse_err::("NaN:0", "Float must be hexadecimal"); parse_err::("NaN:0x", "Invalid NaN payload"); - parse_ok::("NaN:0x000001", "NaN:0x1"); - parse_ok::("NaN:0x300001", "NaN:0x300001"); + parse_ok::("NaN:0x000001", "+NaN:0x1"); + parse_ok::("NaN:0x300001", "+NaN:0x300001"); parse_err::("NaN:0x400001", "Invalid NaN payload"); - parse_ok::("sNaN:0x1", "sNaN:0x1"); + parse_ok::("sNaN:0x1", "+sNaN:0x1"); parse_err::("sNaN:0x0", "Invalid sNaN payload"); - parse_ok::("sNaN:0x200001", "sNaN:0x200001"); + parse_ok::("sNaN:0x200001", "+sNaN:0x200001"); parse_err::("sNaN:0x400001", "Invalid sNaN payload"); } @@ -829,19 +840,20 @@ mod tests { "0x0.8000000000000p-1022"); assert_eq!(Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON).to_string(), "0x0.0000000000001p-1022"); - assert_eq!(Ieee64::new(f64::INFINITY).to_string(), "Inf"); + assert_eq!(Ieee64::new(f64::INFINITY).to_string(), "+Inf"); assert_eq!(Ieee64::new(f64::NEG_INFINITY).to_string(), "-Inf"); - assert_eq!(Ieee64::new(f64::NAN).to_string(), "NaN"); + assert_eq!(Ieee64::new(f64::NAN).to_string(), "+NaN"); assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), "NaN:0x1"); + assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), + "+NaN:0x1"); assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(), - "NaN:0x4000000000001"); + "+NaN:0x4000000000001"); // Signaling NaNs. assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(), - "sNaN:0x1"); + "+sNaN:0x1"); assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(), - "sNaN:0x4000000000001"); + "+sNaN:0x4000000000001"); } #[test] @@ -894,20 +906,20 @@ mod tests { parse_err::("0x1.0p-1075", "Magnitude too small"); // NaNs and Infs. - parse_ok::("Inf", "Inf"); + parse_ok::("Inf", "+Inf"); parse_ok::("-Inf", "-Inf"); - parse_ok::("NaN", "NaN"); + parse_ok::("NaN", "+NaN"); parse_ok::("-NaN", "-NaN"); - parse_ok::("NaN:0x0", "NaN"); + parse_ok::("NaN:0x0", "+NaN"); parse_err::("NaN:", "Float must be hexadecimal"); parse_err::("NaN:0", "Float must be hexadecimal"); parse_err::("NaN:0x", "Invalid NaN payload"); - parse_ok::("NaN:0x000001", "NaN:0x1"); - parse_ok::("NaN:0x4000000000001", "NaN:0x4000000000001"); + parse_ok::("NaN:0x000001", "+NaN:0x1"); + parse_ok::("NaN:0x4000000000001", "+NaN:0x4000000000001"); parse_err::("NaN:0x8000000000001", "Invalid NaN payload"); - parse_ok::("sNaN:0x1", "sNaN:0x1"); + parse_ok::("sNaN:0x1", "+sNaN:0x1"); parse_err::("sNaN:0x0", "Invalid sNaN payload"); - parse_ok::("sNaN:0x4000000000001", "sNaN:0x4000000000001"); + parse_ok::("sNaN:0x4000000000001", "+sNaN:0x4000000000001"); parse_err::("sNaN:0x8000000000001", "Invalid sNaN payload"); } } diff --git a/test-all.sh b/test-all.sh index 522cf594a2..5b718d8bc4 100755 --- a/test-all.sh +++ b/test-all.sh @@ -79,6 +79,6 @@ export CTONUTIL="$topdir/target/release/cton-util" cd "$topdir" banner "File tests" -"$CTONUTIL" test filetests +"$CTONUTIL" test filetests docs banner "OK" From 9d9807688c58db5020f278365c7fa7d24adf6e58 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 09:54:55 -0700 Subject: [PATCH 676/968] Add load and store instructions. Define a MemFlags class, currently holding a notrap and aligned flag. --- docs/langref.rst | 36 +--------- filetests/parser/tiny.cton | 27 ++++++++ lib/cretonne/meta/base/formats.py | 5 +- lib/cretonne/meta/base/immediates.py | 6 ++ lib/cretonne/meta/base/instructions.py | 21 +++++- lib/cretonne/src/ir/builder.rs | 3 +- lib/cretonne/src/ir/instructions.rs | 16 ++++- lib/cretonne/src/ir/memflags.rs | 92 ++++++++++++++++++++++++++ lib/cretonne/src/ir/mod.rs | 8 ++- lib/cretonne/src/verifier.rs | 4 +- lib/cretonne/src/write.rs | 7 ++ lib/reader/src/parser.rs | 47 ++++++++++++- 12 files changed, 227 insertions(+), 45 deletions(-) create mode 100644 lib/cretonne/src/ir/memflags.rs diff --git a/docs/langref.rst b/docs/langref.rst index 6ded3a8bfd..12949ab691 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -428,46 +428,14 @@ accessing memory. However, it can be very complicated to verify the safety of general loads and stores when compiling code for a sandboxed environment, so Cretonne also provides more restricted memory operations that are always safe. -.. inst:: a = load p, Offset, Flags... - - Load from memory at ``p + Offset``. - - This is a polymorphic instruction that can load any value type which has a - memory representation. - - :arg iPtr p: Base address. - :arg Offset: Immediate signed offset. - :flag align(N): Expected alignment of ``p + Offset``. Power of two. - :flag aligntrap: Always trap if the memory access is misaligned. - :result T a: Loaded value. - -.. inst:: store x, p, Offset, Flags... - - Store ``x`` to memory at ``p + Offset``. - - This is a polymorphic instruction that can store any value type with a - memory representation. - - :arg T x: Value to store. - :arg iPtr p: Base address. - :arg Offset: Immediate signed offset. - :flag align(N): Expected alignment of ``p + Offset``. Power of two. - :flag aligntrap: Always trap if the memory access is misaligned. +.. autoinst:: load +.. autoinst:: store Loads and stores are *misaligned* if the resultant address is not a multiple of the expected alignment. Depending on the target architecture, misaligned memory accesses may trap, or they may work. Sometimes, operating systems catch alignment traps and emulate the misaligned memory access. -On target architectures like x86 that don't check alignment, Cretonne expands -the `aligntrap` flag into a conditional trap instruction:: - - v5 = load.i32 v1, 4, align(4), aligntrap - ; Becomes: - v10 = and_imm v1, 3 - trapnz v10 - v5 = load.i32 v1, 4 - Local variables --------------- diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 92842cd140..868a7ba0b1 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -128,3 +128,30 @@ ebb0(v1: i32): ; nextln: $v2 = heap_load.f32 $v1 ; nextln: $v3 = heap_load.f32 $v1+12 ; nextln: heap_store $v3, $v1 + +; Memory access instructions. +function memory(i32) { +ebb0(v1: i32): + v2 = load.i64 v1 + v3 = load.i64 aligned v1 + v4 = load.i64 notrap v1 + v5 = load.i64 notrap aligned v1 + v6 = load.i64 aligned notrap v1 + v7 = load.i64 v1-12 + v8 = load.i64 notrap v1+0x1_0000 + store v2, v1 + store aligned v3, v1+12 + store notrap aligned v3, v1-12 +} +; sameln: function memory(i32) { +; nextln: ebb0($v1: i32): +; nextln: $v2 = load.i64 $v1 +; nextln: $v3 = load.i64 aligned $v1 +; nextln: $v4 = load.i64 notrap $v1 +; nextln: $v5 = load.i64 notrap aligned $v1 +; nextln: $v6 = load.i64 notrap aligned $v1 +; nextln: $v7 = load.i64 $v1-12 +; nextln: $v8 = load.i64 notrap $v1+0x0001_0000 +; nextln: store $v2, $v1 +; nextln: store aligned $v3, $v1+12 +; nextln: store notrap aligned $v3, $v1-12 diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 767be3be7e..acb9ce0131 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS from .immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from .immediates import intcc, floatcc +from .immediates import intcc, floatcc, memflags from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot Nullary = InstructionFormat() @@ -53,6 +53,9 @@ IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True) +Load = InstructionFormat(memflags, VALUE, offset32) +Store = InstructionFormat(memflags, VALUE, VALUE, offset32) + StackLoad = InstructionFormat(stack_slot, offset32) StackStore = InstructionFormat(VALUE, stack_slot, offset32) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 7f1662f5c1..2f8803e245 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -90,3 +90,9 @@ floatcc = ImmediateKind( 'ugt': 'UnorderedOrGreaterThan', 'uge': 'UnorderedOrGreaterThanOrEqual', }) + +#: Flags for memory operations like :inst:`load` and :inst:`store`. +memflags = ImmediateKind( + 'memflags', + 'Memory operation flags', + default_member='flags', rust_type='MemFlags') diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index d3de015311..487dca1a89 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -10,7 +10,7 @@ from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from base.immediates import intcc, floatcc +from base.immediates import intcc, floatcc, memflags from base import entities import base.formats # noqa @@ -211,6 +211,25 @@ x = Operand('x', Mem, doc='Value to be stored') a = Operand('a', Mem, doc='Value loaded') p = Operand('p', iAddr) addr = Operand('addr', iAddr) +Flags = Operand('Flags', memflags) + +load = Instruction( + 'load', r""" + Load from memory at ``p + Offset``. + + This is a polymorphic instruction that can load any value type which + has a memory representation. + """, + ins=(Flags, p, Offset), outs=a) + +store = Instruction( + 'store', r""" + Store ``x`` to memory at ``p + Offset``. + + This is a polymorphic instruction that can store any value type with a + memory representation. + """, + ins=(Flags, x, p, Offset)) stack_load = Instruction( 'stack_load', r""" diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 8b95f20272..dcec1bbb91 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,7 +5,8 @@ use ir::types; use ir::{InstructionData, DataFlowGraph, Cursor}; -use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList, + MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::{IntCC, FloatCC}; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 5e8d66a09c..f48cbcae6f 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -10,7 +10,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot}; +use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::*; use ir::types; @@ -252,6 +252,20 @@ pub enum InstructionData { args: [Value; 2], offset: Uoffset32, }, + Load { + opcode: Opcode, + ty: Type, + flags: MemFlags, + arg: Value, + offset: Offset32, + }, + Store { + opcode: Opcode, + ty: Type, + flags: MemFlags, + args: [Value; 2], + offset: Offset32, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to diff --git a/lib/cretonne/src/ir/memflags.rs b/lib/cretonne/src/ir/memflags.rs new file mode 100644 index 0000000000..8cef887da2 --- /dev/null +++ b/lib/cretonne/src/ir/memflags.rs @@ -0,0 +1,92 @@ +//! Memory operation flags. + +use std::fmt; + +enum FlagBit { + Notrap, + Aligned, +} + +const NAMES: [&'static str; 2] = ["notrap", "aligned"]; + +/// Flags for memory operations like load/store. +/// +/// Each of these flags introduce a limited form of undefined behavior. The flags each enable +/// certain optimizations that need to make additional assumptions. Generally, the semantics of a +/// program does not change when a flag is removed, but adding a flag will. +#[derive(Clone, Copy, Debug)] +pub struct MemFlags { + bits: u8, +} + +impl MemFlags { + /// Create a new empty set of flags. + pub fn new() -> MemFlags { + MemFlags { bits: 0 } + } + + /// Read a flag bit. + fn read(self, bit: FlagBit) -> bool { + self.bits & (1 << bit as usize) != 0 + } + + /// Set a flag bit. + fn set(&mut self, bit: FlagBit) { + self.bits |= 1 << bit as usize + } + + /// Set a flag bit by name. + /// + /// Returns true if the flag was found and set, false for an unknown flag name. + pub fn set_by_name(&mut self, name: &str) -> bool { + match NAMES.iter().position(|&s| s == name) { + Some(bit) => { + self.bits |= 1 << bit; + true + } + None => false, + } + } + + /// Test if the `notrap` flag is set. + /// + /// Normally, trapping is part of the semantics of a load/store operation. If the platform + /// would cause a trap when accessing the effective address, the Cretonne memory operation is + /// also required to trap. + /// + /// The `notrap` flag gives a Cretonne operation permission to not trap. This makes it possible + /// to delete an unused load or a dead store instruction. + pub fn notrap(self) -> bool { + self.read(FlagBit::Notrap) + } + + /// Set the `notrap` flag. + pub fn set_notrap(&mut self) { + self.set(FlagBit::Notrap) + } + + /// Test if the `aligned` flag is set. + /// + /// By default, Cretonne memory instructions work with any unaligned effective address. If the + /// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the + /// effective address is misaligned. + pub fn aligned(self) -> bool { + self.read(FlagBit::Aligned) + } + + /// Set the `aligned` flag. + pub fn set_aligned(&mut self) { + self.set(FlagBit::Aligned) + } +} + +impl fmt::Display for MemFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, n) in NAMES.iter().enumerate() { + if self.bits & (1 << i) != 0 { + write!(f, " {}", n)?; + } + } + Ok(()) + } +} diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 302978d81b..488b4dab32 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -10,11 +10,12 @@ pub mod jumptable; pub mod dfg; pub mod layout; pub mod function; -mod funcname; -mod extfunc; mod builder; -mod valueloc; +mod extfunc; +mod funcname; +mod memflags; mod progpoint; +mod valueloc; pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; @@ -29,3 +30,4 @@ pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; pub use ir::builder::InstBuilder; pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; +pub use ir::memflags::MemFlags; diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index b1b4429d1a..e6dbf4c86d 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -271,7 +271,9 @@ impl<'a> Verifier<'a> { &IntCompareImm { .. } | &FloatCompare { .. } | &HeapLoad { .. } | - &HeapStore { .. } => {} + &HeapStore { .. } | + &Load { .. } | + &Store { .. } => {} } Ok(()) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index f4b7de5f75..bc9299d796 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -322,6 +322,13 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result } => write!(w, " {}, {}{}", arg, stack_slot, offset), HeapLoad { arg, offset, .. } => write!(w, " {}{}", arg, offset), HeapStore { args, offset, .. } => write!(w, " {}, {}{}", args[0], args[1], offset), + Load { flags, arg, offset, .. } => write!(w, "{} {}{}", flags, arg, offset), + Store { + flags, + args, + offset, + .. + } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index e813404130..a0e1f71719 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -11,7 +11,7 @@ use std::{u16, u32}; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, - FuncRef, StackSlot, ValueLoc, ArgumentLoc}; + FuncRef, StackSlot, ValueLoc, ArgumentLoc, MemFlags}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; @@ -228,7 +228,8 @@ impl<'a> Context<'a> { InstructionData::Unary { ref mut arg, .. } | InstructionData::UnarySplit { ref mut arg, .. } | InstructionData::StackStore { ref mut arg, .. } | - InstructionData::HeapLoad { ref mut arg, .. } => { + InstructionData::HeapLoad { ref mut arg, .. } | + InstructionData::Load { ref mut arg, .. } => { self.map.rewrite_value(arg, loc)?; } @@ -238,7 +239,8 @@ impl<'a> Context<'a> { InstructionData::InsertLane { ref mut args, .. } | InstructionData::IntCompare { ref mut args, .. } | InstructionData::FloatCompare { ref mut args, .. } | - InstructionData::HeapStore { ref mut args, .. } => { + InstructionData::HeapStore { ref mut args, .. } | + InstructionData::Store { ref mut args, .. } => { self.map.rewrite_values(args, loc)?; } @@ -576,6 +578,19 @@ impl<'a> Parser<'a> { } } + // Match and a consume a possibly empty sequence of memory operation flags. + fn optional_memflags(&mut self) -> MemFlags { + let mut flags = MemFlags::new(); + while let Some(Token::Identifier(text)) = self.token() { + if flags.set_by_name(text) { + self.consume(); + } else { + break; + } + } + flags + } + // Match and consume an identifier. fn match_any_identifier(&mut self, err_msg: &str) -> Result<&'a str> { if let Some(Token::Identifier(text)) = self.token() { @@ -1735,6 +1750,32 @@ impl<'a> Parser<'a> { offset: offset, } } + InstructionFormat::Load => { + let flags = self.optional_memflags(); + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_offset32()?; + InstructionData::Load { + opcode: opcode, + ty: VOID, + flags: flags, + arg: addr, + offset: offset, + } + } + InstructionFormat::Store => { + let flags = self.optional_memflags(); + let arg = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_offset32()?; + InstructionData::Store { + opcode: opcode, + ty: VOID, + flags: flags, + args: [arg, addr], + offset: offset, + } + } }; Ok(idata) } From ca448d4ede5030b341dc385bbf1a5f49d6a681ef Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 10:30:03 -0700 Subject: [PATCH 677/968] Extending loads and truncating stores --- docs/langref.rst | 20 ++++++ lib/cretonne/meta/base/immediates.py | 2 +- lib/cretonne/meta/base/instructions.py | 93 ++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/docs/langref.rst b/docs/langref.rst index 12949ab691..d4b30780b4 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -437,6 +437,26 @@ accesses may trap, or they may work. Sometimes, operating systems catch alignment traps and emulate the misaligned memory access. +Extending loads and truncating stores +------------------------------------- + +Most ISAs provide instructions that load an integer value smaller than a register +and extends it to the width of the register. Similarly, store instructions that +only write the low bits of an integer register are common. + +Cretonne provides extending loads and truncation stores for 8, 16, and 32-bit +memory accesses. + +.. autoinst:: uload8 +.. autoinst:: sload8 +.. autoinst:: istore8 +.. autoinst:: uload16 +.. autoinst:: sload16 +.. autoinst:: istore16 +.. autoinst:: uload32 +.. autoinst:: sload32 +.. autoinst:: istore32 + Local variables --------------- diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 2f8803e245..8e643bb9ee 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -91,7 +91,7 @@ floatcc = ImmediateKind( 'uge': 'UnorderedOrGreaterThanOrEqual', }) -#: Flags for memory operations like :inst:`load` and :inst:`store`. +#: Flags for memory operations like :cton:inst:`load` and :cton:inst:`store`. memflags = ImmediateKind( 'memflags', 'Memory operation flags', diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 487dca1a89..a7c549ce93 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -231,6 +231,99 @@ store = Instruction( """, ins=(Flags, x, p, Offset)) +iExt8 = TypeVar( + 'iExt8', 'An integer type with more than 8 bits', + ints=(16, 64)) +x = Operand('x', iExt8) +a = Operand('a', iExt8) + +uload8 = Instruction( + 'uload8', r""" + Load 8 bits from memory at ``p + Offset`` and zero-extend. + + This is equivalent to ``load.i8`` followed by ``uextend``. + """, + ins=(Flags, p, Offset), outs=a) + +sload8 = Instruction( + 'sload8', r""" + Load 8 bits from memory at ``p + Offset`` and sign-extend. + + This is equivalent to ``load.i8`` followed by ``uextend``. + """, + ins=(Flags, p, Offset), outs=a) + +istore8 = Instruction( + 'istore8', r""" + Store the low 8 bits of ``x`` to memory at ``p + Offset``. + + This is equivalent to ``ireduce.i8`` followed by ``store.i8``. + """, + ins=(Flags, x, p, Offset)) + +iExt16 = TypeVar( + 'iExt16', 'An integer type with more than 16 bits', + ints=(32, 64)) +x = Operand('x', iExt16) +a = Operand('a', iExt16) + +uload16 = Instruction( + 'uload16', r""" + Load 16 bits from memory at ``p + Offset`` and zero-extend. + + This is equivalent to ``load.i16`` followed by ``uextend``. + """, + ins=(Flags, p, Offset), outs=a) + +sload16 = Instruction( + 'sload16', r""" + Load 16 bits from memory at ``p + Offset`` and sign-extend. + + This is equivalent to ``load.i16`` followed by ``uextend``. + """, + ins=(Flags, p, Offset), outs=a) + +istore16 = Instruction( + 'istore16', r""" + Store the low 16 bits of ``x`` to memory at ``p + Offset``. + + This is equivalent to ``ireduce.i16`` followed by ``store.i8``. + """, + ins=(Flags, x, p, Offset)) + +iExt32 = TypeVar( + 'iExt32', 'An integer type with more than 32 bits', + ints=(64, 64)) +x = Operand('x', iExt32) +a = Operand('a', iExt32) + +uload32 = Instruction( + 'uload32', r""" + Load 32 bits from memory at ``p + Offset`` and zero-extend. + + This is equivalent to ``load.i32`` followed by ``uextend``. + """, + ins=(Flags, p, Offset), outs=a) + +sload32 = Instruction( + 'sload32', r""" + Load 32 bits from memory at ``p + Offset`` and sign-extend. + + This is equivalent to ``load.i32`` followed by ``uextend``. + """, + ins=(Flags, p, Offset), outs=a) + +istore32 = Instruction( + 'istore32', r""" + Store the low 32 bits of ``x`` to memory at ``p + Offset``. + + This is equivalent to ``ireduce.i32`` followed by ``store.i8``. + """, + ins=(Flags, x, p, Offset)) + +x = Operand('x', Mem, doc='Value to be stored') +a = Operand('a', Mem, doc='Value loaded') + stack_load = Instruction( 'stack_load', r""" Load a value from a stack slot at the constant offset. From 1c890f317dfe4f177f8466ff34a4ee1520f6982a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 6 Apr 2017 15:36:03 -0700 Subject: [PATCH 678/968] Add RISC-V call instruction encodings. Calls are jal with a fixed %x1 link register. --- filetests/isa/riscv/binary32.cton | 9 +++++++-- lib/cretonne/meta/isa/riscv/encodings.py | 5 ++++- lib/cretonne/meta/isa/riscv/recipes.py | 3 ++- lib/cretonne/src/isa/riscv/binemit.rs | 17 +++++++++++++---- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 89771d8165..984b7b8a80 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -3,6 +3,8 @@ test binemit isa riscv function RV32I() { + fn0 = function foo() + ebb0: [-,%x10] v1 = iconst.i32 1 [-,%x21] v2 = iconst.i32 2 @@ -75,11 +77,14 @@ ebb0: [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 + ; Control Transfer Instructions + + ; jal %x1, fn0 + call fn0() ; bin: Call(fn0) 000000ef + brz v1, ebb3 fallthrough ebb1 - ; Control Transfer Instructions - ebb1: ; beq 0x000 br_icmp eq v1, v2, ebb1 ; bin: 01550063 diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 21ff09e177..294015b4ea 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -6,7 +6,8 @@ from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL -from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret, U, UJ, SB, SBzero +from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret +from .recipes import U, UJ, UJcall, SB, SBzero from .settings import use_m from cdsl.ast import Var @@ -84,6 +85,8 @@ RV64.enc(base.imul.i32, R, OP32(0b000, 0b0000001), isap=use_m) # Unconditional branches. RV32.enc(base.jump, UJ, JAL()) RV64.enc(base.jump, UJ, JAL()) +RV32.enc(base.call, UJcall, JAL()) +RV64.enc(base.call, UJcall, JAL()) # Conditional branches. for cond, f3 in [ diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index e30922613f..ad682fa74b 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,7 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm -from base.formats import UnaryImm, BranchIcmp, Branch, Jump +from base.formats import UnaryImm, BranchIcmp, Branch, Jump, Call from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -119,6 +119,7 @@ U = EncRecipe( # UJ-type unconditional branch instructions. UJ = EncRecipe('UJ', Jump, size=4, ins=(), outs=(), branch_range=(0, 21)) +UJcall = EncRecipe('UJcall', Call, size=4, ins=(), outs=()) # SB-type branch instructions. # TODO: These instructions have a +/- 4 KB branch range. How to encode that diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index e4cc5c05c6..94ba8b53bf 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -8,13 +8,12 @@ use predicates::is_signed_int; include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); /// RISC-V relocation kinds. -#[allow(dead_code)] pub enum RelocKind { - /// A conditional (SB-type) branch to an EBB. - Branch, + /// A jal call to a function. + Call, } -pub static RELOC_NAMES: [&'static str; 1] = ["Branch"]; +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; impl Into for RelocKind { fn into(self) -> Reloc { @@ -318,3 +317,13 @@ fn recipe_uj(func: &Function, inst: Inst, sink: &mut CS) panic!("Expected Jump format: {:?}", func.dfg[inst]); } } + +fn recipe_ujcall(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Call { func_ref, .. } = func.dfg[inst] { + sink.reloc_func(RelocKind::Call.into(), func_ref); + // rd=%x1 is the standard link register. + put_uj(func.encodings[inst].bits(), 0, 1, sink); + } else { + panic!("Expected Call format: {:?}", func.dfg[inst]); + } +} From ccba325b6ccbc84e8fa53908c199a428a2cbbc36 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 11:36:50 -0700 Subject: [PATCH 679/968] Keep EBB arguments in a ValueList. This is the first step of the value list refactoring which will replace linked lists of values with value lists. - Keep a ValueList in the EbbData struct containing all the EBB arguments. - Change dfg.ebb_args() to return a slice instead of an iterator. This leaves us in a temporary hybrid state where we maintain both a linked list and a ValueList vector of the EBB arguments. --- lib/cretonne/src/entity_list.rs | 5 ++ lib/cretonne/src/ir/dfg.rs | 63 ++++++++----------- .../src/regalloc/live_value_tracker.rs | 2 +- lib/cretonne/src/verifier.rs | 7 ++- lib/cretonne/src/write.rs | 2 +- lib/reader/src/parser.rs | 9 ++- 6 files changed, 40 insertions(+), 48 deletions(-) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 0b90265470..d6f0e49119 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -222,6 +222,11 @@ impl ListPool { } impl EntityList { + /// Create a new empty list. + pub fn new() -> Self { + Default::default() + } + /// Returns `true` if the list has a length of 0. pub fn is_empty(&self) -> bool { // 0 is a magic value for the empty list. Any list in the pool array must have a positive diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 22ce7a116d..ffa842e5a5 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,6 +1,6 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueListPool}; +use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueListPool}; use ir::entities::ExpandedValue; use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; @@ -687,17 +687,7 @@ impl DataFlowGraph { /// Get the number of arguments on `ebb`. pub fn num_ebb_args(&self, ebb: Ebb) -> usize { - match self.ebbs[ebb].last_arg.expand() { - None => 0, - Some(last_arg) => { - if let ExpandedValue::Table(idx) = last_arg.expand() { - if let ValueData::Arg { num, .. } = self.extended_values[idx] { - return num as usize + 1; - } - } - panic!("inconsistent value table entry for EBB argument"); - } - } + self.ebbs[ebb].args.len(&self.value_lists) } /// Append an argument with type `ty` to `ebb`. @@ -712,12 +702,9 @@ impl DataFlowGraph { val } - /// Iterate through the arguments to an EBB. - pub fn ebb_args(&self, ebb: Ebb) -> Values { - Values { - dfg: self, - cur: self.ebbs[ebb].first_arg.into(), - } + /// Get the arguments to an EBB. + pub fn ebb_args(&self, ebb: Ebb) -> &[Value] { + self.ebbs[ebb].args.as_slice(&self.value_lists) } /// Replace an EBB argument with a new value of type `ty`. @@ -752,6 +739,8 @@ impl DataFlowGraph { old_data); }; + self.ebbs[ebb].args.as_mut_slice(&mut self.value_lists)[num as usize] = new_arg; + // Now fix up the linked lists. if self.ebbs[ebb].last_arg.expand() == Some(old_arg) { self.ebbs[ebb].last_arg = new_arg.into(); @@ -804,6 +793,7 @@ impl DataFlowGraph { let first = self.ebbs[ebb].first_arg.into(); self.ebbs[ebb].first_arg = None.into(); self.ebbs[ebb].last_arg = None.into(); + self.ebbs[ebb].args.clear(&mut self.value_lists); first } @@ -815,6 +805,7 @@ impl DataFlowGraph { /// /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. pub fn attach_ebb_arg(&mut self, ebb: Ebb, arg: Value) { + self.ebbs[ebb].args.push(arg, &mut self.value_lists); let arg_num = match self.ebbs[ebb].last_arg.map(|v| v.expand()) { // If last_argument is `None`, we're adding the first EBB argument. None => { @@ -861,6 +852,9 @@ impl DataFlowGraph { // match the function arguments. #[derive(Clone)] struct EbbData { + // List of arguments to this EBB. + args: ValueList, + // First argument to this EBB, or `None` if the block has no arguments. // // The arguments are all `ValueData::Argument` entries that form a linked list from `first_arg` @@ -874,6 +868,7 @@ struct EbbData { impl EbbData { fn new() -> EbbData { EbbData { + args: ValueList::new(), first_arg: None.into(), last_arg: None.into(), } @@ -966,28 +961,20 @@ mod tests { let ebb = dfg.make_ebb(); assert_eq!(ebb.to_string(), "ebb0"); assert_eq!(dfg.num_ebb_args(ebb), 0); - assert_eq!(dfg.ebb_args(ebb).next(), None); + assert_eq!(dfg.ebb_args(ebb), &[]); assert_eq!(dfg.detach_ebb_args(ebb), None); assert_eq!(dfg.num_ebb_args(ebb), 0); - assert_eq!(dfg.ebb_args(ebb).next(), None); + assert_eq!(dfg.ebb_args(ebb), &[]); let arg1 = dfg.append_ebb_arg(ebb, types::F32); assert_eq!(arg1.to_string(), "vx0"); assert_eq!(dfg.num_ebb_args(ebb), 1); - { - let mut args1 = dfg.ebb_args(ebb); - assert_eq!(args1.next(), Some(arg1)); - assert_eq!(args1.next(), None); - } + assert_eq!(dfg.ebb_args(ebb), &[arg1]); + let arg2 = dfg.append_ebb_arg(ebb, types::I16); assert_eq!(arg2.to_string(), "vx1"); assert_eq!(dfg.num_ebb_args(ebb), 2); - { - let mut args2 = dfg.ebb_args(ebb); - assert_eq!(args2.next(), Some(arg1)); - assert_eq!(args2.next(), Some(arg2)); - assert_eq!(args2.next(), None); - } + assert_eq!(dfg.ebb_args(ebb), &[arg1, arg2]); assert_eq!(dfg.value_def(arg1), ValueDef::Arg(ebb, 0)); assert_eq!(dfg.value_def(arg2), ValueDef::Arg(ebb, 1)); @@ -997,7 +984,7 @@ mod tests { // Swap the two EBB arguments. let take1 = dfg.detach_ebb_args(ebb).unwrap(); assert_eq!(dfg.num_ebb_args(ebb), 0); - assert_eq!(dfg.ebb_args(ebb).next(), None); + assert_eq!(dfg.ebb_args(ebb), &[]); let take2 = dfg.next_ebb_arg(take1).unwrap(); assert_eq!(take1, arg1); assert_eq!(take2, arg2); @@ -1005,7 +992,7 @@ mod tests { dfg.attach_ebb_arg(ebb, take2); let arg3 = dfg.append_ebb_arg(ebb, types::I32); dfg.attach_ebb_arg(ebb, take1); - assert_eq!(dfg.ebb_args(ebb).collect::>(), [take2, arg3, take1]); + assert_eq!(dfg.ebb_args(ebb), &[take2, arg3, take1]); } #[test] @@ -1018,24 +1005,24 @@ mod tests { let new1 = dfg.replace_ebb_arg(arg1, types::I64); assert_eq!(dfg.value_type(arg1), types::F32); assert_eq!(dfg.value_type(new1), types::I64); - assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1]); + assert_eq!(dfg.ebb_args(ebb), &[new1]); dfg.attach_ebb_arg(ebb, arg1); - assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, arg1]); + assert_eq!(dfg.ebb_args(ebb), &[new1, arg1]); let new2 = dfg.replace_ebb_arg(arg1, types::I8); assert_eq!(dfg.value_type(arg1), types::F32); assert_eq!(dfg.value_type(new2), types::I8); - assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, new2]); + assert_eq!(dfg.ebb_args(ebb), &[new1, new2]); dfg.attach_ebb_arg(ebb, arg1); - assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, new2, arg1]); + assert_eq!(dfg.ebb_args(ebb), &[new1, new2, arg1]); let new3 = dfg.replace_ebb_arg(new2, types::I16); assert_eq!(dfg.value_type(new1), types::I64); assert_eq!(dfg.value_type(new2), types::I8); assert_eq!(dfg.value_type(new3), types::I16); - assert_eq!(dfg.ebb_args(ebb).collect::>(), [new1, new3, arg1]); + assert_eq!(dfg.ebb_args(ebb), &[new1, new3, arg1]); } #[test] diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 75b18ef019..458fe14e26 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -182,7 +182,7 @@ impl LiveValueTracker { // Now add all the live arguments to `ebb`. let first_arg = self.live.values.len(); - for value in dfg.ebb_args(ebb) { + for &value in dfg.ebb_args(ebb) { let lr = liveness .get(value) .expect("EBB argument value has no live range"); diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index e6dbf4c86d..371ada0440 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -151,7 +151,7 @@ impl<'a> Verifier<'a> { } // Arguments belong to the correct ebb. - for arg in self.func.dfg.ebb_args(ebb) { + for &arg in self.func.dfg.ebb_args(ebb) { match self.func.dfg.value_def(arg) { ValueDef::Arg(arg_ebb, _) => { if ebb != arg_ebb { @@ -405,7 +405,7 @@ impl<'a> Verifier<'a> { return err!(ebb, "entry block arguments must match function signature"); } - for (i, arg) in self.func.dfg.ebb_args(ebb).enumerate() { + for (i, &arg) in self.func.dfg.ebb_args(ebb).iter().enumerate() { let arg_type = self.func.dfg.value_type(arg); if arg_type != expected_types[i].value_type { return err!(ebb, @@ -510,7 +510,8 @@ impl<'a> Verifier<'a> { let iter = self.func .dfg .ebb_args(ebb) - .map(|v| self.func.dfg.value_type(v)); + .iter() + .map(|&v| self.func.dfg.value_type(v)); self.typecheck_variable_args_iterator(inst, iter)?; } BranchInfo::Table(table) => { diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index bc9299d796..09a4410f43 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -95,7 +95,7 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { write!(w, " ")?; } - let mut args = func.dfg.ebb_args(ebb); + let mut args = func.dfg.ebb_args(ebb).iter().cloned(); match args.next() { None => return writeln!(w, "{}:", ebb), Some(arg) => { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index a0e1f71719..a0302a1a30 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1895,13 +1895,12 @@ mod tests { let mut ebbs = func.layout.ebbs(); let ebb0 = ebbs.next().unwrap(); - assert_eq!(func.dfg.ebb_args(ebb0).next(), None); + assert_eq!(func.dfg.ebb_args(ebb0), &[]); let ebb4 = ebbs.next().unwrap(); - let mut ebb4_args = func.dfg.ebb_args(ebb4); - let arg0 = ebb4_args.next().unwrap(); - assert_eq!(func.dfg.value_type(arg0), types::I32); - assert_eq!(ebb4_args.next(), None); + let ebb4_args = func.dfg.ebb_args(ebb4); + assert_eq!(ebb4_args.len(), 1); + assert_eq!(func.dfg.value_type(ebb4_args[0]), types::I32); } #[test] From 77a26b43456aab25fd411fe9ce7df6dc499f9017 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 12:14:33 -0700 Subject: [PATCH 680/968] Return the whole value list from detach_ebb_args(). Rather than returning the head of a linked list of EBB arguments, just return the whole value list of all the arguments. Delete the next_ebb_arg() method which was only used for traversing that list. --- lib/cretonne/src/ir/dfg.rs | 42 ++++++-------------------- lib/cretonne/src/legalizer/boundary.rs | 8 ++--- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index ffa842e5a5..45485d63e2 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -751,10 +751,7 @@ impl DataFlowGraph { self.ebbs[ebb].first_arg = new_arg.into(); } else { // We need to find the num-1 argument value and change its next link. - let mut arg = self.ebbs[ebb].first_arg.expect("EBB has no arguments"); - for _ in 1..num { - arg = self.next_ebb_arg(arg).expect("Too few EBB arguments"); - } + let arg = self.ebbs[ebb].args.as_slice(&self.value_lists)[(num - 1) as usize]; if let ExpandedValue::Table(index) = arg.expand() { if let ValueData::Arg { ref mut next, .. } = self.extended_values[index] { assert_eq!(next.expand(), Some(old_arg)); @@ -770,31 +767,15 @@ impl DataFlowGraph { new_arg } - /// Given a value that is known to be an EBB argument, return the next EBB argument. - pub fn next_ebb_arg(&self, arg: Value) -> Option { - if let ExpandedValue::Table(index) = arg.expand() { - if let ValueData::Arg { next, .. } = self.extended_values[index] { - return next.into(); - } - } - panic!("{} is not an EBB argument", arg); - } - - /// Detach all the arguments from `ebb` and return the first one. - /// - /// The whole list of detached arguments can be traversed with `next_ebb_arg`. This method does - /// not return a `Values` iterator since it is often necessary to mutate the DFG while - /// processing the list of arguments. + /// Detach all the arguments from `ebb` and return them as a `ValueList`. /// /// This is a quite low-level operation. Sensible things to do with the detached EBB arguments /// is to put them back on the same EBB with `attach_ebb_arg()` or change them into aliases /// with `change_to_alias()`. - pub fn detach_ebb_args(&mut self, ebb: Ebb) -> Option { - let first = self.ebbs[ebb].first_arg.into(); + pub fn detach_ebb_args(&mut self, ebb: Ebb) -> ValueList { self.ebbs[ebb].first_arg = None.into(); self.ebbs[ebb].last_arg = None.into(); - self.ebbs[ebb].args.clear(&mut self.value_lists); - first + self.ebbs[ebb].args.take() } /// Append an existing argument value to `ebb`. @@ -962,7 +943,7 @@ mod tests { assert_eq!(ebb.to_string(), "ebb0"); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb), &[]); - assert_eq!(dfg.detach_ebb_args(ebb), None); + assert!(dfg.detach_ebb_args(ebb).is_empty()); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb), &[]); @@ -982,17 +963,14 @@ mod tests { assert_eq!(dfg.value_type(arg2), types::I16); // Swap the two EBB arguments. - let take1 = dfg.detach_ebb_args(ebb).unwrap(); + let vlist = dfg.detach_ebb_args(ebb); assert_eq!(dfg.num_ebb_args(ebb), 0); assert_eq!(dfg.ebb_args(ebb), &[]); - let take2 = dfg.next_ebb_arg(take1).unwrap(); - assert_eq!(take1, arg1); - assert_eq!(take2, arg2); - assert_eq!(dfg.next_ebb_arg(take2), None); - dfg.attach_ebb_arg(ebb, take2); + assert_eq!(vlist.as_slice(&dfg.value_lists), &[arg1, arg2]); + dfg.attach_ebb_arg(ebb, arg2); let arg3 = dfg.append_ebb_arg(ebb, types::I32); - dfg.attach_ebb_arg(ebb, take1); - assert_eq!(dfg.ebb_args(ebb), &[take2, arg3, take1]); + dfg.attach_ebb_arg(ebb, arg1); + assert_eq!(dfg.ebb_args(ebb), &[arg2, arg3, arg1]); } #[test] diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 19e06b7ba4..42386cab83 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -63,10 +63,10 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { // Process the EBB arguments one at a time, possibly replacing one argument with multiple new // ones. We do this by detaching the entry EBB arguments first. - let mut next_arg = func.dfg.detach_ebb_args(entry); - while let Some(arg) = next_arg { - // Get the next argument before we mutate `arg`. - next_arg = func.dfg.next_ebb_arg(arg); + let ebb_args = func.dfg.detach_ebb_args(entry); + let mut old_arg = 0; + while let Some(arg) = ebb_args.get(old_arg, &func.dfg.value_lists) { + old_arg += 1; let arg_type = func.dfg.value_type(arg); if arg_type == abi_types[abi_arg].value_type { From 20ca22f6eb41e9f98be59f6b07c9aa625fb24e8d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 12:30:37 -0700 Subject: [PATCH 681/968] Stop maintaining a linked list of EBB arguments. Now that we have a value list of the arguments, we can get rid of: - The first_arg and last_arg members in EbbData, - The next member in the ValueData::Arg variant. --- lib/cretonne/src/entity_list.rs | 13 +++-- lib/cretonne/src/ir/dfg.rs | 95 ++++++--------------------------- 2 files changed, 23 insertions(+), 85 deletions(-) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index d6f0e49119..7e11019529 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -295,7 +295,8 @@ impl EntityList { } /// Appends an element to the back of the list. - pub fn push(&mut self, element: T, pool: &mut ListPool) { + /// Returns the index where the element was inserted. + pub fn push(&mut self, element: T, pool: &mut ListPool) -> usize { let idx = self.index as usize; match pool.len_of(self) { None => { @@ -305,6 +306,7 @@ impl EntityList { pool.data[block] = T::new(1); pool.data[block + 1] = element; self.index = (block + 1) as u32; + 0 } Some(len) => { // Do we need to reallocate? @@ -320,6 +322,7 @@ impl EntityList { } pool.data[block + new_len] = element; pool.data[block] = T::new(new_len); + len } } } @@ -516,14 +519,14 @@ mod tests { let i3 = Inst::new(3); let i4 = Inst::new(4); - list.push(i1, pool); + assert_eq!(list.push(i1, pool), 0); assert_eq!(list.len(pool), 1); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1]); assert_eq!(list.get(0, pool), Some(i1)); assert_eq!(list.get(1, pool), None); - list.push(i2, pool); + assert_eq!(list.push(i2, pool), 1); assert_eq!(list.len(pool), 2); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1, i2]); @@ -531,7 +534,7 @@ mod tests { assert_eq!(list.get(1, pool), Some(i2)); assert_eq!(list.get(2, pool), None); - list.push(i3, pool); + assert_eq!(list.push(i3, pool), 2); assert_eq!(list.len(pool), 3); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1, i2, i3]); @@ -541,7 +544,7 @@ mod tests { assert_eq!(list.get(3, pool), None); // This triggers a reallocation. - list.push(i4, pool); + assert_eq!(list.push(i4, pool), 3); assert_eq!(list.len(pool), 4); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1, i2, i3, i4]); diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 45485d63e2..9178aeeb38 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -276,7 +276,6 @@ enum ValueData { ty: Type, num: u16, // Argument number, starting from 0. ebb: Ebb, - next: PackedOption, // Next argument to `ebb`. }, // Value is an alias of another value. @@ -308,7 +307,9 @@ impl<'a> Iterator for Values<'a> { ExpandedValue::Table(index) => { match self.dfg.extended_values[index] { ValueData::Inst { next, .. } => next.into(), - ValueData::Arg { next, .. } => next.into(), + ValueData::Arg { .. } => { + panic!("EBB argument {} appeared in value list", prev) + } ValueData::Alias { .. } => { panic!("Alias value {} appeared in value list", prev) } @@ -696,7 +697,6 @@ impl DataFlowGraph { ty: ty, ebb: ebb, num: 0, - next: None.into(), }); self.attach_ebb_arg(ebb, val); val @@ -724,46 +724,20 @@ impl DataFlowGraph { }; // Create new value identical to the old one except for the type. - let (ebb, num, new_arg) = if let ValueData::Arg { num, ebb, next, .. } = old_data { - (ebb, - num, - self.make_value(ValueData::Arg { - ty: new_type, - num: num, - ebb: ebb, - next: next, - })) + let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = old_data { + (ebb, num) } else { panic!("old_arg: {} must be an EBB argument: {:?}", old_arg, old_data); }; + let new_arg = self.make_value(ValueData::Arg { + ty: new_type, + num: num, + ebb: ebb, + }); self.ebbs[ebb].args.as_mut_slice(&mut self.value_lists)[num as usize] = new_arg; - - // Now fix up the linked lists. - if self.ebbs[ebb].last_arg.expand() == Some(old_arg) { - self.ebbs[ebb].last_arg = new_arg.into(); - } - - if self.ebbs[ebb].first_arg.expand() == Some(old_arg) { - assert_eq!(num, 0); - self.ebbs[ebb].first_arg = new_arg.into(); - } else { - // We need to find the num-1 argument value and change its next link. - let arg = self.ebbs[ebb].args.as_slice(&self.value_lists)[(num - 1) as usize]; - if let ExpandedValue::Table(index) = arg.expand() { - if let ValueData::Arg { ref mut next, .. } = self.extended_values[index] { - assert_eq!(next.expand(), Some(old_arg)); - *next = new_arg.into(); - } else { - panic!("{} is not an EBB argument", arg); - } - } else { - panic!("{} is not an EBB argument", arg); - } - } - new_arg } @@ -773,8 +747,6 @@ impl DataFlowGraph { /// is to put them back on the same EBB with `attach_ebb_arg()` or change them into aliases /// with `change_to_alias()`. pub fn detach_ebb_args(&mut self, ebb: Ebb) -> ValueList { - self.ebbs[ebb].first_arg = None.into(); - self.ebbs[ebb].last_arg = None.into(); self.ebbs[ebb].args.take() } @@ -786,38 +758,14 @@ impl DataFlowGraph { /// /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. pub fn attach_ebb_arg(&mut self, ebb: Ebb, arg: Value) { - self.ebbs[ebb].args.push(arg, &mut self.value_lists); - let arg_num = match self.ebbs[ebb].last_arg.map(|v| v.expand()) { - // If last_argument is `None`, we're adding the first EBB argument. - None => { - self.ebbs[ebb].first_arg = arg.into(); - 0 - } - // Append to the linked list of arguments. - Some(ExpandedValue::Table(idx)) => { - if let ValueData::Arg { ref mut next, num, .. } = self.extended_values[idx] { - *next = arg.into(); - assert!(num < u16::MAX, "Too many arguments to EBB"); - num + 1 - } else { - panic!("inconsistent value table entry for EBB argument"); - } - } - _ => panic!("inconsistent value table entry for EBB argument"), - }; - self.ebbs[ebb].last_arg = arg.into(); + let arg_num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); + assert!(arg_num <= u16::MAX as usize, "Too many arguments to EBB"); // Now update `arg` itself. let arg_ebb = ebb; if let ExpandedValue::Table(idx) = arg.expand() { - if let ValueData::Arg { - ref mut num, - ebb, - ref mut next, - .. - } = self.extended_values[idx] { - *num = arg_num; - *next = None.into(); + if let ValueData::Arg { ref mut num, ebb, .. } = self.extended_values[idx] { + *num = arg_num as u16; assert_eq!(arg_ebb, ebb, "{} should already belong to EBB", arg); return; } @@ -835,24 +783,11 @@ impl DataFlowGraph { struct EbbData { // List of arguments to this EBB. args: ValueList, - - // First argument to this EBB, or `None` if the block has no arguments. - // - // The arguments are all `ValueData::Argument` entries that form a linked list from `first_arg` - // to `last_arg`. - first_arg: PackedOption, - - // Last argument to this EBB, or `None` if the block has no arguments. - last_arg: PackedOption, } impl EbbData { fn new() -> EbbData { - EbbData { - args: ValueList::new(), - first_arg: None.into(), - last_arg: None.into(), - } + EbbData { args: ValueList::new() } } } From 84be6706443084291f299bc72c0f8feb840e1eb0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 15:03:43 -0700 Subject: [PATCH 682/968] Make the dfg.insts table private again. - Add a dfg.is_inst_valid() method for the verifier. - Use the inst_args_mut() method when rewriting values in the parser. - Add a new branch_destination_mut() to use when rewriting EBBs. This also gets rid of one of the large instruction format switches in the parser. --- lib/cretonne/src/ir/dfg.rs | 7 ++- lib/cretonne/src/ir/instructions.rs | 13 ++++++ lib/cretonne/src/verifier.rs | 2 +- lib/reader/src/parser.rs | 69 ++--------------------------- 4 files changed, 24 insertions(+), 67 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 9178aeeb38..63ae3a5e97 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -26,7 +26,7 @@ pub struct DataFlowGraph { /// Data about all of the instructions in the function, including opcodes and operands. /// The instructions in this map are not in program order. That is tracked by `Layout`, along /// with the EBB containing each instruction. - pub insts: EntityMap, + insts: EntityMap, /// Memory pool of value lists referenced by instructions in `insts`. pub value_lists: ValueListPool, @@ -77,6 +77,11 @@ impl DataFlowGraph { self.insts.len() } + /// Returns `true` if the given instruction reference is valid. + pub fn inst_is_valid(&self, inst: Inst) -> bool { + self.insts.is_valid(inst) + } + /// Get the total number of extended basic blocks created in this function, whether they are /// currently inserted in the layout or not. /// diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index f48cbcae6f..626c130740 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -376,6 +376,19 @@ impl InstructionData { } } + /// Get a mutable reference to the single destination of this branch instruction, if it is a + /// single destination branch or jump. + /// + /// Multi-destination branches like `br_table` return `None`. + pub fn branch_destination_mut(&mut self) -> Option<&mut Ebb> { + match *self { + InstructionData::Jump { ref mut destination, .. } => Some(destination), + InstructionData::Branch { ref mut destination, .. } => Some(destination), + InstructionData::BranchIcmp { ref mut destination, .. } => Some(destination), + _ => None, + } + } + /// Return information about a call instruction. /// /// Any instruction that can call another function reveals its call signature here. diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 371ada0440..ace83235df 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -337,7 +337,7 @@ impl<'a> Verifier<'a> { match dfg.value_def(v) { ValueDef::Res(def_inst, _) => { // Value is defined by an instruction that exists. - if !dfg.insts.is_valid(def_inst) { + if !dfg.inst_is_valid(def_inst) { return err!(loc_inst, "{} is defined by invalid instruction {}", v, diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index a0302a1a30..a5c3b4244c 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -213,71 +213,10 @@ impl<'a> Context<'a> { for ebb in self.function.layout.ebbs() { for inst in self.function.layout.ebb_insts(ebb) { let loc = inst.into(); - let value_lists = &mut self.function.dfg.value_lists; - match self.function.dfg.insts[inst] { - InstructionData::Nullary { .. } | - InstructionData::UnaryImm { .. } | - InstructionData::UnaryIeee32 { .. } | - InstructionData::UnaryIeee64 { .. } | - InstructionData::StackLoad { .. } => {} - - InstructionData::BinaryImm { ref mut arg, .. } | - InstructionData::BranchTable { ref mut arg, .. } | - InstructionData::ExtractLane { ref mut arg, .. } | - InstructionData::IntCompareImm { ref mut arg, .. } | - InstructionData::Unary { ref mut arg, .. } | - InstructionData::UnarySplit { ref mut arg, .. } | - InstructionData::StackStore { ref mut arg, .. } | - InstructionData::HeapLoad { ref mut arg, .. } | - InstructionData::Load { ref mut arg, .. } => { - self.map.rewrite_value(arg, loc)?; - } - - // `args: Value[2]` - InstructionData::Binary { ref mut args, .. } | - InstructionData::BinaryOverflow { ref mut args, .. } | - InstructionData::InsertLane { ref mut args, .. } | - InstructionData::IntCompare { ref mut args, .. } | - InstructionData::FloatCompare { ref mut args, .. } | - InstructionData::HeapStore { ref mut args, .. } | - InstructionData::Store { ref mut args, .. } => { - self.map.rewrite_values(args, loc)?; - } - - InstructionData::Ternary { ref mut args, .. } => { - self.map.rewrite_values(args, loc)?; - } - - InstructionData::MultiAry { ref mut args, .. } => { - self.map - .rewrite_values(args.as_mut_slice(value_lists), loc)?; - } - - InstructionData::Jump { - ref mut destination, - ref mut args, - .. - } | - InstructionData::Branch { - ref mut destination, - ref mut args, - .. - } | - InstructionData::BranchIcmp { - ref mut destination, - ref mut args, - .. - } => { - self.map.rewrite_ebb(destination, loc)?; - self.map - .rewrite_values(args.as_mut_slice(value_lists), loc)?; - } - - InstructionData::Call { ref mut args, .. } | - InstructionData::IndirectCall { ref mut args, .. } => { - self.map - .rewrite_values(args.as_mut_slice(value_lists), loc)?; - } + self.map + .rewrite_values(self.function.dfg.inst_args_mut(inst), loc)?; + if let Some(dest) = self.function.dfg[inst].branch_destination_mut() { + self.map.rewrite_ebb(dest, loc)?; } } } From 35d52872d9ac734638c27cdd0c8fd1e458d2cea9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 16:03:49 -0700 Subject: [PATCH 683/968] Add EntityList::first(). This is the same as slice::first(), except it returns the first element by value. The implementation can avoid checking the list length since empty lists already have a special representation. --- lib/cretonne/src/entity_list.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 7e11019529..3b7493094e 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -260,6 +260,15 @@ impl EntityList { self.as_slice(pool).get(index).cloned() } + /// Get the first element from the list. + pub fn first(&self, pool: &ListPool) -> Option { + if self.is_empty() { + None + } else { + Some(pool.data[self.index as usize]) + } + } + /// Get the list as a mutable slice. pub fn as_mut_slice<'a>(&'a mut self, pool: &'a mut ListPool) -> &'a mut [T] { let idx = self.index as usize; @@ -507,6 +516,7 @@ mod tests { assert!(list.is_empty()); assert_eq!(list.len(pool), 0); assert_eq!(list.as_slice(pool), &[]); + assert_eq!(list.first(pool), None); } #[test] @@ -523,6 +533,7 @@ mod tests { assert_eq!(list.len(pool), 1); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1]); + assert_eq!(list.first(pool), Some(i1)); assert_eq!(list.get(0, pool), Some(i1)); assert_eq!(list.get(1, pool), None); @@ -530,6 +541,7 @@ mod tests { assert_eq!(list.len(pool), 2); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1, i2]); + assert_eq!(list.first(pool), Some(i1)); assert_eq!(list.get(0, pool), Some(i1)); assert_eq!(list.get(1, pool), Some(i2)); assert_eq!(list.get(2, pool), None); @@ -538,6 +550,7 @@ mod tests { assert_eq!(list.len(pool), 3); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1, i2, i3]); + assert_eq!(list.first(pool), Some(i1)); assert_eq!(list.get(0, pool), Some(i1)); assert_eq!(list.get(1, pool), Some(i2)); assert_eq!(list.get(2, pool), Some(i3)); @@ -548,6 +561,7 @@ mod tests { assert_eq!(list.len(pool), 4); assert!(!list.is_empty()); assert_eq!(list.as_slice(pool), &[i1, i2, i3, i4]); + assert_eq!(list.first(pool), Some(i1)); assert_eq!(list.get(0, pool), Some(i1)); assert_eq!(list.get(1, pool), Some(i2)); assert_eq!(list.get(2, pool), Some(i3)); From 7d47053645f0e0399adcf795f31e343db732bf9b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 15:38:50 -0700 Subject: [PATCH 684/968] Maintain a ValueList with the results of each instruction. This is the first step of a larger refactoring to represent instruction results as value lists instead of using linked lists. The refactoring will also eliminate the special treatment of first results such that all result values can be detached and redefined. This change put us in a temporary state where results are represented both as linked lists and ValueList vectors. - Add a dfg.results table. - Add the first result in make_inst(). This behavior will change. - Recompute the result list in make_inst_results(). - Make dfg.first_result(inst) crash if the instruction has no results. --- lib/cretonne/src/ir/dfg.rs | 71 +++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 63ae3a5e97..16de65fb3b 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -28,14 +28,26 @@ pub struct DataFlowGraph { /// with the EBB containing each instruction. insts: EntityMap, - /// Memory pool of value lists referenced by instructions in `insts`. - pub value_lists: ValueListPool, + /// List of result values for each instruction. + /// + /// This map gets resized automatically by `make_inst()` so it is always in sync with the + /// primary `insts` map. + results: EntityMap, /// Extended basic blocks in the function and their arguments. /// This map is not in program order. That is handled by `Layout`, and so is the sequence of /// instructions contained in each EBB. ebbs: EntityMap, + /// Memory pool of value lists. + /// + /// The `ValueList` references into this pool appear in many places: + /// + /// - Instructions in `insts` that don't have room for their entire argument list inline. + /// - Instruction result values in `results`. + /// - EBB arguments in `ebbs`. + pub value_lists: ValueListPool, + /// Extended value table. Most `Value` references refer directly to their defining instruction. /// Others index into this table. /// @@ -61,8 +73,9 @@ impl DataFlowGraph { pub fn new() -> DataFlowGraph { DataFlowGraph { insts: EntityMap::new(), - value_lists: ValueListPool::new(), + results: EntityMap::new(), ebbs: EntityMap::new(), + value_lists: ValueListPool::new(), extended_values: Vec::new(), signatures: EntityMap::new(), ext_funcs: EntityMap::new(), @@ -140,8 +153,20 @@ impl DataFlowGraph { Direct(inst) => ValueDef::Res(inst, 0), Table(idx) => { match self.extended_values[idx] { - ValueData::Inst { inst, num, .. } => ValueDef::Res(inst, num as usize), - ValueData::Arg { ebb, num, .. } => ValueDef::Arg(ebb, num as usize), + ValueData::Inst { inst, num, .. } => { + assert_eq!(Some(v), + self.results[inst].get(num as usize, &self.value_lists), + "Dangling result value {}: {}", + v, + self.display_inst(inst)); + ValueDef::Res(inst, num as usize) + } + ValueData::Arg { ebb, num, .. } => { + assert_eq!(Some(v), + self.ebbs[ebb].args.get(num as usize, &self.value_lists), + "Dangling EBB argument value"); + ValueDef::Arg(ebb, num as usize) + } ValueData::Alias { original, .. } => { // Make sure we only recurse one level. `resolve_aliases` has safeguards to // detect alias loops without overrunning the stack. @@ -334,7 +359,13 @@ impl DataFlowGraph { /// The type of the first result is indicated by `data.ty`. If the instruction produces /// multiple results, also call `make_inst_results` to allocate value table entries. pub fn make_inst(&mut self, data: InstructionData) -> Inst { - self.insts.push(data) + let ty = data.first_type(); + let inst = self.insts.push(data); + let res = self.results.ensure(inst); + if !ty.is_void() { + res.push(Value::new_direct(inst), &mut self.value_lists); + } + inst } /// Get the instruction reference that will be assigned to the next instruction created by @@ -416,6 +447,8 @@ impl DataFlowGraph { let fixed_results = constraints.fixed_results(); let mut total_results = fixed_results; + self.results[inst].clear(&mut self.value_lists); + // Additional values form a linked list starting from the second result value. Generate // the list backwards so we don't have to modify value table entries in place. (This // causes additional result values to be numbered backwards which is not the aesthetic @@ -439,6 +472,7 @@ impl DataFlowGraph { inst: inst, next: head.into(), })); + self.results[inst].push(head.unwrap(), &mut self.value_lists); rev_num += 1; } first_type = Some(self.signatures[sig].return_types[res_idx].value_type); @@ -455,6 +489,7 @@ impl DataFlowGraph { next: head.into(), })); rev_num += 1; + self.results[inst].push(head.unwrap(), &mut self.value_lists); } first_type = Some(constraints.result_type(res_idx, ctrl_typevar)); } @@ -467,6 +502,14 @@ impl DataFlowGraph { } *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); + // Include the first result in the results vector. + if first_type.is_some() { + self.results[inst].push(Value::new_direct(inst), &mut self.value_lists); + } + self.results[inst] + .as_mut_slice(&mut self.value_lists) + .reverse(); + total_results } @@ -491,6 +534,10 @@ impl DataFlowGraph { /// Use this method to detach secondary values before using `replace(inst)` to provide an /// alternate instruction for computing the primary result value. pub fn detach_secondary_results(&mut self, inst: Inst) -> Option { + self.results[inst].clear(&mut self.value_lists); + if !self.insts[inst].first_type().is_void() { + self.results[inst].push(Value::new_direct(inst), &mut self.value_lists); + } self[inst].second_result_mut().and_then(|r| r.take()) } @@ -539,6 +586,8 @@ impl DataFlowGraph { } }; + self.results[res_inst].push(res, &mut self.value_lists); + // Now update `res` itself. if let ExpandedValue::Table(idx) = res.expand() { if let ValueData::Inst { @@ -597,8 +646,11 @@ impl DataFlowGraph { let data = self[orig].clone(); // After cloning, any secondary values are attached to both copies. Don't do that, we only // want them on the new clone. + let mut results = self.results[orig].take(); self.detach_secondary_results(orig); let new = self.make_inst(data); + results.as_mut_slice(&mut self.value_lists)[0] = Value::new_direct(new); + self.results[new] = results; pos.insert_inst(new); // Replace the original instruction with a copy of the new value. // This is likely to be immediately overwritten by something else, but this way we avoid @@ -611,9 +663,11 @@ impl DataFlowGraph { /// Get the first result of an instruction. /// - /// If `Inst` doesn't produce any results, this returns a `Value` with a `VOID` type. + /// This function panics if the instruction doesn't have any result. pub fn first_result(&self, inst: Inst) -> Value { - Value::new_direct(inst) + self.results[inst] + .first(&self.value_lists) + .expect("Instruction has no results") } /// Iterate through all the results of an instruction. @@ -854,6 +908,7 @@ mod tests { let mut res = dfg.inst_results(inst); let val = res.next().unwrap(); assert!(res.next().is_none()); + assert_eq!(val, dfg.first_result(inst)); assert_eq!(dfg.value_def(val), ValueDef::Res(inst, 0)); assert_eq!(dfg.value_type(val), types::I32); From 3c99dc0eb48bee1bc2bbbec53632eb7ba46225c2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Apr 2017 16:53:54 -0700 Subject: [PATCH 685/968] Change dfg.inst_results to return a slice. Now we can access instruction results and arguments as well as EBB arguments as slices. Delete the Values iterator which was traversing the linked lists of values. It is no longer needed. --- lib/cretonne/meta/gen_instr.py | 13 ++-- lib/cretonne/src/ir/builder.rs | 2 +- lib/cretonne/src/ir/dfg.rs | 70 +++---------------- lib/cretonne/src/legalizer/boundary.rs | 12 ++-- .../src/regalloc/live_value_tracker.rs | 2 +- lib/cretonne/src/regalloc/liveness.rs | 2 +- lib/cretonne/src/verifier.rs | 6 +- lib/cretonne/src/write.rs | 2 +- lib/reader/src/parser.rs | 5 +- 9 files changed, 33 insertions(+), 81 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 9ce279dc52..40456339ae 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -693,14 +693,17 @@ def gen_inst_builder(inst, fmt): fmt.line(fcall + '.0') return + fmt.line('let (inst, dfg) = {};'.format(fcall)) + if len(inst.value_results) == 1: - fmt.line('Value::new_direct({}.0)'.format(fcall)) + fmt.line('dfg.first_result(inst)') return - fmt.line('let (inst, dfg) = {};'.format(fcall)) - fmt.line('let mut results = dfg.inst_results(inst);') - fmt.line('({})'.format(', '.join( - len(inst.value_results) * ['results.next().unwrap()']))) + fmt.format( + 'let results = &dfg.inst_results(inst)[0..{}];', + len(inst.value_results)) + fmt.format('({})', ', '.join( + 'results[{}]'.format(i) for i in range(len(inst.value_results)))) def gen_builder(insts, fmt): diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index dcec1bbb91..7ce771de58 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -143,7 +143,7 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { assert_eq!(old_second_value, None, "Secondary result values {:?} would be left dangling by replacing {} with {}", - self.dfg.inst_results(self.inst).collect::>(), + self.dfg.inst_results(self.inst), self.dfg[self.inst].opcode(), data.opcode()); diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 16de65fb3b..1778453ee2 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -314,43 +314,6 @@ enum ValueData { Alias { ty: Type, original: Value }, } -/// Iterate through a list of related value references, such as: -/// -/// - All results defined by an instruction. See `DataFlowGraph::inst_results`. -/// - All arguments to an EBB. See `DataFlowGraph::ebb_args`. -/// -/// A value iterator borrows a `DataFlowGraph` reference. -pub struct Values<'a> { - dfg: &'a DataFlowGraph, - cur: Option, -} - -impl<'a> Iterator for Values<'a> { - type Item = Value; - - fn next(&mut self) -> Option { - let rval = self.cur; - if let Some(prev) = rval { - // Advance self.cur to the next value, or `None`. - self.cur = match prev.expand() { - ExpandedValue::Direct(inst) => self.dfg.insts[inst].second_result(), - ExpandedValue::Table(index) => { - match self.dfg.extended_values[index] { - ValueData::Inst { next, .. } => next.into(), - ValueData::Arg { .. } => { - panic!("EBB argument {} appeared in value list", prev) - } - ValueData::Alias { .. } => { - panic!("Alias value {} appeared in value list", prev) - } - } - } - }; - } - rval - } -} - /// Instructions. /// impl DataFlowGraph { @@ -671,15 +634,8 @@ impl DataFlowGraph { } /// Iterate through all the results of an instruction. - pub fn inst_results(&self, inst: Inst) -> Values { - Values { - dfg: self, - cur: if self.insts[inst].first_type().is_void() { - None - } else { - Some(Value::new_direct(inst)) - }, - } + pub fn inst_results(&self, inst: Inst) -> &[Value] { + self.results[inst].as_slice(&self.value_lists) } /// Get the call signature of a direct or indirect call instruction. @@ -858,10 +814,9 @@ impl<'a> fmt::Display for DisplayInst<'a> { let dfg = self.0; let inst = &dfg[self.1]; - let mut results = dfg.inst_results(self.1); - if let Some(first) = results.next() { + if let Some((first, rest)) = dfg.inst_results(self.1).split_first() { write!(f, "{}", first)?; - for v in results { + for v in rest { write!(f, ", {}", v)?; } write!(f, " = ")?; @@ -904,11 +859,9 @@ mod tests { assert_eq!(ins.first_type(), types::I32); } - // Result iterator. - let mut res = dfg.inst_results(inst); - let val = res.next().unwrap(); - assert!(res.next().is_none()); - assert_eq!(val, dfg.first_result(inst)); + // Results. + let val = dfg.first_result(inst); + assert_eq!(dfg.inst_results(inst), &[val]); assert_eq!(dfg.value_def(val), ValueDef::Res(inst, 0)); assert_eq!(dfg.value_type(val), types::I32); @@ -925,9 +878,8 @@ mod tests { let inst = dfg.make_inst(idata); assert_eq!(dfg.display_inst(inst).to_string(), "trap"); - // Result iterator should be empty. - let mut res = dfg.inst_results(inst); - assert_eq!(res.next(), None); + // Result slice should be empty. + assert_eq!(dfg.inst_results(inst), &[]); } #[test] @@ -1028,8 +980,8 @@ mod tests { let new_iadd = dfg.redefine_first_value(pos); let new_s = dfg.first_result(new_iadd); assert_eq!(dfg[iadd].opcode(), Opcode::Copy); - assert_eq!(dfg.inst_results(iadd).collect::>(), [s]); - assert_eq!(dfg.inst_results(new_iadd).collect::>(), [new_s, c]); + assert_eq!(dfg.inst_results(iadd), &[s]); + assert_eq!(dfg.inst_results(new_iadd), &[new_s, c]); assert_eq!(dfg.resolve_copies(s), new_s); pos.next_inst(); diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 42386cab83..f80b6574c4 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -302,11 +302,9 @@ fn convert_to_abi(dfg: &mut DataFlowGraph, } /// Check if a sequence of arguments match a desired sequence of argument types. -fn check_arg_types(dfg: &DataFlowGraph, args: Args, types: &[ArgumentType]) -> bool - where Args: IntoIterator -{ +fn check_arg_types(dfg: &DataFlowGraph, args: &[Value], types: &[ArgumentType]) -> bool { let mut n = 0; - for arg in args { + for &arg in args { match types.get(n) { Some(&ArgumentType { value_type, .. }) => { if dfg.value_type(arg) != value_type { @@ -335,7 +333,7 @@ fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Result<(), SigRef> { }; let sig = &dfg.signatures[sig_ref]; - if check_arg_types(dfg, args.iter().cloned(), &sig.argument_types[..]) && + if check_arg_types(dfg, args, &sig.argument_types[..]) && check_arg_types(dfg, dfg.inst_results(inst), &sig.return_types[..]) { // All types check out. Ok(()) @@ -347,9 +345,7 @@ fn check_call_signature(dfg: &DataFlowGraph, inst: Inst) -> Result<(), SigRef> { /// Check if the arguments of the return `inst` match the signature. fn check_return_signature(dfg: &DataFlowGraph, inst: Inst, sig: &Signature) -> bool { - check_arg_types(dfg, - dfg.inst_variable_args(inst).iter().cloned(), - &sig.return_types) + check_arg_types(dfg, dfg.inst_variable_args(inst), &sig.return_types) } /// Insert ABI conversion code for the arguments to the call or return instruction at `pos`. diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 458fe14e26..c9f5146245 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -233,7 +233,7 @@ impl LiveValueTracker { // Add the values defined by `inst`. let first_def = self.live.values.len(); - for value in dfg.inst_results(inst) { + for &value in dfg.inst_results(inst) { let lr = match liveness.get(value) { Some(lr) => lr, None => panic!("{} result {} has no live range", dfg[inst].opcode(), value), diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 90b817a825..cb937d67a2 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -308,7 +308,7 @@ impl Liveness { // Make sure we have created live ranges for dead defs. // TODO: When we implement DCE, we can use the absence of a live range to indicate // an unused value. - for def in func.dfg.inst_results(inst) { + for &def in func.dfg.inst_results(inst) { get_or_create(&mut self.ranges, def, func, &enc_info); } diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index ace83235df..d6018a8f5c 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -193,7 +193,7 @@ impl<'a> Verifier<'a> { } } else { // All result values for multi-valued instructions are created - let got_results = dfg.inst_results(inst).count(); + let got_results = dfg.inst_results(inst).len(); if got_results != total_results { return err!(inst, "expected {} result values, found {}", @@ -212,7 +212,7 @@ impl<'a> Verifier<'a> { self.verify_value(inst, arg)?; } - for res in self.func.dfg.inst_results(inst) { + for &res in self.func.dfg.inst_results(inst) { self.verify_value(inst, res)?; } @@ -448,7 +448,7 @@ impl<'a> Verifier<'a> { fn typecheck_results(&self, inst: Inst, ctrl_type: Type) -> Result<()> { let mut i = 0; - for result in self.func.dfg.inst_results(inst) { + for &result in self.func.dfg.inst_results(inst) { let result_type = self.func.dfg.value_type(result); let expected_type = self.func.dfg.compute_result_type(inst, i, ctrl_type); if let Some(expected_type) = expected_type { diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 09a4410f43..ae4a38284f 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -188,7 +188,7 @@ fn write_instruction(w: &mut Write, // Write value locations, if we have them. if !func.locations.is_empty() { let regs = isa.register_info(); - for r in func.dfg.inst_results(inst) { + for &r in func.dfg.inst_results(inst) { write!(s, ",{}", func.locations diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index a5c3b4244c..5f51b4c342 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1257,12 +1257,13 @@ impl<'a> Parser<'a> { // holds a reference to `ctx.function`. self.add_values(&mut ctx.map, results.into_iter(), - ctx.function.dfg.inst_results(inst))?; + ctx.function.dfg.inst_results(inst).iter().cloned())?; if let Some(result_locations) = result_locations { - for (value, loc) in ctx.function + for (&value, loc) in ctx.function .dfg .inst_results(inst) + .iter() .zip(result_locations) { *ctx.function.locations.ensure(value) = loc; } From 69180a2f84394ed7360529e888e4eb145780257d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 08:34:59 -0700 Subject: [PATCH 686/968] Simplify the back-end of InstBuilder. We don't want to distinguish between single-result and multiple-result instructions any longer. - Merge the simple_instruction() and complex_instruction() builder methods into a single build() that can handle all cases. - All format constructors now take a ctrl_type argument. Previously, some would take a result_type argument. - Instruction constructors no longer attempt to compute a single result type. Just pass a ctrl_type and let the backend decide. Fix one format constructor call in legalizer/split.rs which now takes a ctrl_type instead of a result type. --- lib/cretonne/meta/gen_instr.py | 56 +++++-------------- lib/cretonne/src/ir/builder.rs | 83 ++++++----------------------- lib/cretonne/src/ir/dfg.rs | 7 ++- lib/cretonne/src/legalizer/split.rs | 2 +- 4 files changed, 36 insertions(+), 112 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 40456339ae..ece67dfb2f 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -498,21 +498,12 @@ def gen_format_constructor(iform, fmt): Emit a method for creating and inserting inserting an `iform` instruction, where `iform` is an instruction format. - Instruction formats that can produce multiple results take a `ctrl_typevar` - argument for deducing the result types. Others take a `result_type` - argument. + All instruction formats take an `opcode` argument and a `ctrl_typevar` + argument for deducing the result types. """ # Construct method arguments. - args = ['self', 'opcode: Opcode'] - - if iform.multiple_results: - args.append('ctrl_typevar: Type') - # `dfg::make_inst_results` will compute the result type. - result_type = 'types::VOID' - else: - args.append('result_type: Type') - result_type = 'result_type' + args = ['self', 'opcode: Opcode', 'ctrl_typevar: Type'] # Normal operand arguments. Start with the immediate operands. for f in iform.imm_fields: @@ -537,16 +528,12 @@ def gen_format_constructor(iform, fmt): with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): fmt.line('opcode: opcode,') - fmt.line('ty: {},'.format(result_type)) + fmt.line('ty: types::VOID,') if iform.multiple_results: fmt.line('second_result: None.into(),') gen_member_inits(iform, fmt) - # Create result values if necessary. - if iform.multiple_results: - fmt.line('self.complex_instruction(data, ctrl_typevar)') - else: - fmt.line('self.simple_instruction(data)') + fmt.line('self.build(data, ctrl_typevar)') def gen_member_inits(iform, fmt): @@ -631,34 +618,19 @@ def gen_inst_builder(inst, fmt): if inst.is_polymorphic and not inst.use_typevar_operand: # This was an explicit method argument. args.append(inst.ctrl_typevar.name) - elif len(inst.value_results) == 0: + elif len(inst.value_results) == 0 or not inst.is_polymorphic: + # No controlling type variable needed. args.append('types::VOID') - elif inst.is_polymorphic: + else: + assert inst.is_polymorphic and inst.use_typevar_operand # Infer the controlling type variable from the input operands. opnum = inst.value_opnums[inst.format.typevar_operand] fmt.line( 'let ctrl_typevar = self.data_flow_graph().value_type({});' .format(inst.ins[opnum].name)) - if inst.format.multiple_results: - # The format constructor will resolve the result types from the - # type var. - args.append('ctrl_typevar') - elif inst.outs[inst.value_results[0]].typevar == inst.ctrl_typevar: - # The format constructor expects a simple result type. - # No type transformation needed from the controlling type - # variable. - args.append('ctrl_typevar') - else: - # The format constructor expects a simple result type. - # TODO: This formula could be resolved ahead of time. - args.append( - 'Opcode::{}.constraints().result_type(0, ctrl_typevar)' - .format(inst.camel_name)) - else: - # This non-polymorphic instruction has a fixed result type. - args.append( - inst.outs[inst.value_results[0]] - .typevar.singleton_type.rust_name()) + # The format constructor will resolve the result types from the + # type var. + args.append('ctrl_typevar') # Now add all of the immediate operands to the constructor arguments. for opnum in inst.imm_opnums: @@ -717,8 +689,8 @@ def gen_builder(insts, fmt): The `InstrBuilder` trait has one method per instruction opcode for conveniently constructing the instruction with minimum arguments. Polymorphic instructions infer their result types from the input - arguments when possible. In some cases, an explicit `result_type` - or `ctrl_typevar` argument is required. + arguments when possible. In some cases, an explicit `ctrl_typevar` + argument is required. The opcode methods return the new instruction's result values, or the `Inst` itself for instructions that don't have any results. diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 7ce771de58..c64ce79dc5 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -24,21 +24,11 @@ pub trait InstBuilderBase<'f>: Sized { fn data_flow_graph(&self) -> &DataFlowGraph; fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph; - /// Insert a simple instruction and return a reference to it. + /// Insert an instruction and return a reference to it, consuming the builder. /// - /// A 'simple' instruction has at most one result, and the `data.ty` field must contain the - /// result type or `VOID` for an instruction with no result values. - fn simple_instruction(self, data: InstructionData) -> (Inst, &'f mut DataFlowGraph); - - /// Insert a simple instruction and return a reference to it. - /// - /// A 'complex' instruction may produce multiple results, and the result types may depend on a - /// controlling type variable. For non-polymorphic instructions with multiple results, pass - /// `VOID` for the `ctrl_typevar` argument. - fn complex_instruction(self, - data: InstructionData, - ctrl_typevar: Type) - -> (Inst, &'f mut DataFlowGraph); + /// The result types may depend on a controlling type variable. For non-polymorphic + /// instructions with multiple results, pass `VOID` for the `ctrl_typevar` argument. + fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph); } // Include trait code generated by `lib/cretonne/meta/gen_instr.py`. @@ -79,16 +69,7 @@ impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { self.dfg } - fn simple_instruction(self, data: InstructionData) -> (Inst, &'fd mut DataFlowGraph) { - let inst = self.dfg.make_inst(data); - self.pos.insert_inst(inst); - (inst, self.dfg) - } - - fn complex_instruction(self, - data: InstructionData, - ctrl_typevar: Type) - -> (Inst, &'fd mut DataFlowGraph) { + fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'fd mut DataFlowGraph) { let inst = self.dfg.make_inst(data); self.dfg.make_inst_results(inst, ctrl_typevar); self.pos.insert_inst(inst); @@ -98,20 +79,12 @@ impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { /// Instruction builder that replaces an existing instruction. /// -/// The inserted instruction will have the same `Inst` number as the old one. This is the only way -/// of rewriting the first result value of an instruction since this is a `ExpandedValue::Direct` -/// variant which encodes the instruction number directly. +/// The inserted instruction will have the same `Inst` number as the old one. /// -/// If the old instruction produced a value, the same value number will refer to the new -/// instruction's first result, so if that value has any uses the type should stay the same. -/// -/// If the old instruction still has secondary result values attached, it is assumed that the new -/// instruction produces the same number and types of results. The old secondary values are -/// preserved. If the replacement instruction format does not support multiple results, the builder -/// panics. It is a bug to leave result values dangling. -/// -/// If the old instruction was capable of producing secondary results, but the values have been -/// detached, new result values are generated by calling `DataFlowGraph::make_inst_results()`. +/// If the old instruction still has result values attached, it is assumed that the new instruction +/// produces the same number and types of results. The old result values are preserved. If the +/// replacement instruction format does not support multiple results, the builder panics. It is a +/// bug to leave result values dangling. pub struct ReplaceBuilder<'f> { dfg: &'f mut DataFlowGraph, inst: Inst, @@ -136,46 +109,20 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { self.dfg } - fn simple_instruction(self, data: InstructionData) -> (Inst, &'f mut DataFlowGraph) { - // The replacement instruction cannot generate multiple results, so verify that the old - // instruction's secondary results have been detached. - let old_second_value = self.dfg[self.inst].second_result(); - assert_eq!(old_second_value, - None, - "Secondary result values {:?} would be left dangling by replacing {} with {}", - self.dfg.inst_results(self.inst), - self.dfg[self.inst].opcode(), - data.opcode()); - - // Splat the new instruction on top of the old one. - self.dfg[self.inst] = data; - (self.inst, self.dfg) - } - - fn complex_instruction(self, - data: InstructionData, - ctrl_typevar: Type) - -> (Inst, &'f mut DataFlowGraph) { - // If the old instruction still has secondary results attached, we'll keep them. - let old_second_value = self.dfg[self.inst].second_result(); - + fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) { // Splat the new instruction on top of the old one. self.dfg[self.inst] = data; - if old_second_value.is_none() { - // The old secondary values were either detached or non-existent. - // Construct new ones and set the first result type too. + if !self.dfg.has_results(self.inst) { + // The old result values were either detached or non-existent. + // Construct new ones. self.dfg.make_inst_results(self.inst, ctrl_typevar); } else { // Reattach the old secondary values. + let old_second_value = self.dfg.inst_results(self.inst).get(1).cloned(); if let Some(val_ref) = self.dfg[self.inst].second_result_mut() { // Don't check types here. Leave that to the verifier. *val_ref = old_second_value.into(); - } else { - // Actually, this instruction format should have called `simple_instruction()`, but - // we don't have a rule against calling `complex_instruction()` even when it is - // overkill. - panic!("Secondary result values left dangling"); } // Normally, make_inst_results() would also set the first result type, but we're not diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 1778453ee2..6fe3f91d57 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -633,7 +633,12 @@ impl DataFlowGraph { .expect("Instruction has no results") } - /// Iterate through all the results of an instruction. + /// Test if `inst` has any result values currently. + pub fn has_results(&self, inst: Inst) -> bool { + !self.results[inst].is_empty() + } + + /// Return all the results of an instruction. pub fn inst_results(&self, inst: Inst) -> &[Value] { self.results[inst].as_slice(&self.value_lists) } diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index d1f55a6266..3c03d1d77b 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -228,7 +228,7 @@ fn split_value(dfg: &mut DataFlowGraph, // need to insert a split instruction before returning. pos.goto_top(ebb); pos.next_inst(); - let concat_inst = dfg.ins(pos).Binary(concat, ty, lo, hi).0; + let concat_inst = dfg.ins(pos).Binary(concat, split_type, lo, hi).0; let concat_val = dfg.first_result(concat_inst); dfg.change_to_alias(value, concat_val); From 8b840e0a9ae7fb52b5d2e15d07e9edd32c36e0d8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 09:08:29 -0700 Subject: [PATCH 687/968] Remove the second_result field from instructions. Now that result values are stored in value lists in the DFG, these head-of-list pointers are no longer needed. --- lib/cretonne/meta/gen_instr.py | 40 ----------------------------- lib/cretonne/src/ir/builder.rs | 7 ----- lib/cretonne/src/ir/dfg.rs | 38 ++++++++++----------------- lib/cretonne/src/ir/instructions.rs | 6 ----- lib/reader/src/parser.rs | 5 ---- 5 files changed, 13 insertions(+), 83 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index ece67dfb2f..4d71e1e5b4 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -110,8 +110,6 @@ def gen_instruction_data_impl(fmt): - `pub fn opcode(&self) -> Opcode` - `pub fn first_type(&self) -> Type` - - `pub fn second_result(&self) -> Option` - - `pub fn second_result_mut(&mut self) -> Option<&mut PackedOption>` - `pub fn arguments(&self, &pool) -> &[Value]` - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]` - `pub fn take_value_list(&mut self) -> Option` @@ -147,42 +145,6 @@ def gen_instruction_data_impl(fmt): 'InstructionData::{} {{ ref mut ty, .. }} => ty,' .format(f.name)) - # Generate shared and mutable accessors for `second_result` which only - # applies to instruction formats that can produce multiple results. - # Everything else returns `None`. - fmt.doc_comment('Second result value, if any.') - with fmt.indented( - 'pub fn second_result(&self) -> Option {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - if f.multiple_results: - fmt.line( - 'InstructionData::' + f.name + - ' { second_result, .. }' + - ' => second_result.into(),') - else: - # Single or no results. - fmt.line( - 'InstructionData::{} {{ .. }} => None,' - .format(f.name)) - - fmt.doc_comment('Mutable reference to second result value, if any.') - with fmt.indented( - "pub fn second_result_mut(&mut self)" + - " -> Option<&mut PackedOption> {", '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - if f.multiple_results: - fmt.line( - 'InstructionData::' + f.name + - ' { ref mut second_result, .. }' + - ' => Some(second_result),') - else: - # Single or no results. - fmt.line( - 'InstructionData::{} {{ .. }} => None,' - .format(f.name)) - fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( 'pub fn typevar_operand(&self, pool: &ValueListPool) -> ' @@ -529,8 +491,6 @@ def gen_format_constructor(iform, fmt): 'let data = InstructionData::{} {{'.format(iform.name), '};'): fmt.line('opcode: opcode,') fmt.line('ty: types::VOID,') - if iform.multiple_results: - fmt.line('second_result: None.into(),') gen_member_inits(iform, fmt) fmt.line('self.build(data, ctrl_typevar)') diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index c64ce79dc5..312e5144da 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -118,13 +118,6 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { // Construct new ones. self.dfg.make_inst_results(self.inst, ctrl_typevar); } else { - // Reattach the old secondary values. - let old_second_value = self.dfg.inst_results(self.inst).get(1).cloned(); - if let Some(val_ref) = self.dfg[self.inst].second_result_mut() { - // Don't check types here. Leave that to the verifier. - *val_ref = old_second_value.into(); - } - // Normally, make_inst_results() would also set the first result type, but we're not // going to call that, so set it manually. *self.dfg[self.inst].first_type_mut() = self.dfg diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 6fe3f91d57..dc321030b3 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -457,12 +457,6 @@ impl DataFlowGraph { first_type = Some(constraints.result_type(res_idx, ctrl_typevar)); } - // Update the second_result pointer in `inst`. - if head.is_some() { - *self.insts[inst] - .second_result_mut() - .expect("instruction format doesn't allow multiple results") = head.into(); - } *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); // Include the first result in the results vector. @@ -497,11 +491,16 @@ impl DataFlowGraph { /// Use this method to detach secondary values before using `replace(inst)` to provide an /// alternate instruction for computing the primary result value. pub fn detach_secondary_results(&mut self, inst: Inst) -> Option { + if !self.has_results(inst) { + return None; + } + + let second = self.results[inst].get(1, &mut self.value_lists); self.results[inst].clear(&mut self.value_lists); if !self.insts[inst].first_type().is_void() { self.results[inst].push(Value::new_direct(inst), &mut self.value_lists); } - self[inst].second_result_mut().and_then(|r| r.take()) + second } /// Get the next secondary result after `value`. @@ -524,32 +523,21 @@ impl DataFlowGraph { /// created automatically. The `res` value must be a secondary instruction result detached from /// somewhere else. pub fn attach_secondary_result(&mut self, last_res: Value, res: Value) { - let (res_inst, res_num) = match last_res.expand() { - ExpandedValue::Direct(inst) => { - // We're adding the second value to `inst`. - let next = self[inst].second_result_mut().expect("bad inst format"); - assert!(next.is_none(), "last_res is not the last result"); - *next = res.into(); - (inst, 1) - } + let res_inst = match last_res.expand() { + ExpandedValue::Direct(inst) => inst, ExpandedValue::Table(idx) => { - if let ValueData::Inst { - num, - inst, - ref mut next, - .. - } = self.extended_values[idx] { + if let ValueData::Inst { inst, ref mut next, .. } = self.extended_values[idx] { assert!(next.is_none(), "last_res is not the last result"); *next = res.into(); - assert!(num < u16::MAX, "Too many arguments to EBB"); - (inst, num + 1) + inst } else { panic!("last_res is not an instruction result"); } } }; - self.results[res_inst].push(res, &mut self.value_lists); + let res_num = self.results[res_inst].push(res, &mut self.value_lists); + assert!(res_num <= u16::MAX as usize, "Too many result values"); // Now update `res` itself. if let ExpandedValue::Table(idx) = res.expand() { @@ -559,7 +547,7 @@ impl DataFlowGraph { ref mut next, .. } = self.extended_values[idx] { - *num = res_num; + *num = res_num as u16; *inst = res_inst; *next = None.into(); return; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 626c130740..29e3e6662f 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -17,7 +17,6 @@ use ir::types; use ir::DataFlowGraph; use entity_list; -use packed_option::PackedOption; use ref_slice::{ref_slice, ref_slice_mut}; /// Some instructions use an external list of argument values because there is not enough space in @@ -126,7 +125,6 @@ pub enum InstructionData { UnarySplit { opcode: Opcode, ty: Type, - second_result: PackedOption, arg: Value, }, Binary { @@ -143,7 +141,6 @@ pub enum InstructionData { BinaryOverflow { opcode: Opcode, ty: Type, - second_result: PackedOption, args: [Value; 2], }, Ternary { @@ -154,7 +151,6 @@ pub enum InstructionData { MultiAry { opcode: Opcode, ty: Type, - second_result: PackedOption, args: ValueList, }, InsertLane { @@ -216,14 +212,12 @@ pub enum InstructionData { Call { opcode: Opcode, ty: Type, - second_result: PackedOption, func_ref: FuncRef, args: ValueList, }, IndirectCall { opcode: Opcode, ty: Type, - second_result: PackedOption, sig_ref: SigRef, args: ValueList, }, diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 5f51b4c342..f948b1f586 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1442,7 +1442,6 @@ impl<'a> Parser<'a> { InstructionData::UnarySplit { opcode: opcode, ty: VOID, - second_result: None.into(), arg: self.match_value("expected SSA value operand")?, } } @@ -1474,7 +1473,6 @@ impl<'a> Parser<'a> { InstructionData::BinaryOverflow { opcode: opcode, ty: VOID, - second_result: None.into(), args: [lhs, rhs], } } @@ -1497,7 +1495,6 @@ impl<'a> Parser<'a> { InstructionData::MultiAry { opcode: opcode, ty: VOID, - second_result: None.into(), args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } @@ -1610,7 +1607,6 @@ impl<'a> Parser<'a> { InstructionData::Call { opcode: opcode, ty: VOID, - second_result: None.into(), func_ref: func_ref, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } @@ -1626,7 +1622,6 @@ impl<'a> Parser<'a> { InstructionData::IndirectCall { opcode: opcode, ty: VOID, - second_result: None.into(), sig_ref: sig_ref, args: args.into_value_list(&[callee], &mut ctx.function.dfg.value_lists), } From 08b4c5f52415b5dbd3dfedd4daa48d27b9139869 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 09:45:21 -0700 Subject: [PATCH 688/968] Don't attach a first result in make_inst(). That is now the job of make_inst_results(). --- lib/cretonne/src/ir/dfg.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index dc321030b3..63849e1a72 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -322,13 +322,9 @@ impl DataFlowGraph { /// The type of the first result is indicated by `data.ty`. If the instruction produces /// multiple results, also call `make_inst_results` to allocate value table entries. pub fn make_inst(&mut self, data: InstructionData) -> Inst { - let ty = data.first_type(); - let inst = self.insts.push(data); - let res = self.results.ensure(inst); - if !ty.is_void() { - res.push(Value::new_direct(inst), &mut self.value_lists); - } - inst + let n = self.num_insts() + 1; + self.results.resize(n); + self.insts.push(data) } /// Get the instruction reference that will be assigned to the next instruction created by @@ -392,9 +388,8 @@ impl DataFlowGraph { /// Create result values for an instruction that produces multiple results. /// - /// Instructions that produce 0 or 1 result values only need to be created with `make_inst`. If - /// the instruction may produce more than 1 result, call `make_inst_results` to allocate - /// value table entries for the additional results. + /// Instructions that produce no result values only need to be created with `make_inst`, + /// otherwise call `make_inst_results` to allocate value table entries for the results. /// /// The result value types are determined from the instruction's value type constraints and the /// provided `ctrl_typevar` type for polymorphic instructions. For non-polymorphic @@ -838,9 +833,10 @@ mod tests { let idata = InstructionData::Nullary { opcode: Opcode::Iconst, - ty: types::I32, + ty: types::VOID, }; let inst = dfg.make_inst(idata); + dfg.make_inst_results(inst, types::I32); assert_eq!(inst.to_string(), "inst0"); assert_eq!(dfg.display_inst(inst).to_string(), "v0 = iconst.i32"); From 96cc0242a6209d2c626e73619d1bca1d0589cb16 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 10:39:11 -0700 Subject: [PATCH 689/968] Add detach_results(), attach_result(), and append_result() methods. These low-level methods for manipulating instruction result value lists will replace the existing secondary_result methods. --- lib/cretonne/src/ir/dfg.rs | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 63849e1a72..583f7cc8a1 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -511,6 +511,67 @@ impl DataFlowGraph { panic!("{} is not a secondary result value", value); } + /// Detach the list of result values from `inst` and return it. + /// + /// This leaves `inst` without any result values. New result values can be created by calling + /// `make_inst_results` or by using a `replace(inst)` builder. + pub fn detach_results(&mut self, inst: Inst) -> ValueList { + self.results[inst].take() + } + + /// Attach an existing value to the result value list for `inst`. + /// + /// The `res` value is appended to the end of the result list. + /// + /// This is a very low-level operation. Usually, instruction results with the correct types are + /// created automatically. The `res` value must not be attached to anything else. + pub fn attach_result(&mut self, inst: Inst, res: Value) { + let res_inst = inst; + if let Some(last_res) = self.results[inst] + .as_slice(&mut self.value_lists) + .last() + .cloned() { + self.attach_secondary_result(last_res, res); + } else { + // This is the first result. + self.results[res_inst].push(res, &mut self.value_lists); + + // Now update `res` itself. + match res.expand() { + ExpandedValue::Table(idx) => { + if let ValueData::Inst { + ref mut num, + ref mut inst, + ref mut next, + .. + } = self.extended_values[idx] { + *num = 0; + *inst = res_inst; + *next = None.into(); + return; + } + } + ExpandedValue::Direct(inst) => { + assert_eq!(inst, res_inst); + return; + } + } + panic!("{} must be a result", res); + } + } + + /// Append a new instruction result value to `inst`. + pub fn append_result(&mut self, inst: Inst, ty: Type) -> Value { + let res = self.make_value(ValueData::Inst { + ty: ty, + inst: inst, + num: 0, + next: None.into(), + }); + self.attach_result(inst, res); + res + } + /// Attach an existing value as a secondary result after `last_res` which must be the last /// result of an instruction. /// From 0be78f970cfdca5844a0cada3a8960f9b39ac0a6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 10:47:49 -0700 Subject: [PATCH 690/968] Make tests less sensitive to specific value numbers. --- filetests/parser/branch.cton | 64 +++++++++++++++---------------- filetests/parser/call.cton | 6 +-- filetests/parser/rewrite.cton | 10 ++--- filetests/parser/tiny.cton | 72 +++++++++++++++++------------------ 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/filetests/parser/branch.cton b/filetests/parser/branch.cton index 1aa0ca185a..ac979bbb26 100644 --- a/filetests/parser/branch.cton +++ b/filetests/parser/branch.cton @@ -19,65 +19,65 @@ ebb1: ; Jumps with 1 arg. function onearg(i32) { -ebb0(vx0: i32): - jump ebb1(vx0) +ebb0(v90: i32): + jump ebb1(v90) -ebb1(vx1: i32): - jump ebb0(vx1) +ebb1(v91: i32): + jump ebb0(v91) } ; sameln: function onearg(i32) { -; nextln: ebb0(vx0: i32): -; nextln: jump ebb1(vx0) +; nextln: ebb0($v90: i32): +; nextln: jump ebb1($v90) ; nextln: -; nextln: ebb1(vx1: i32): -; nextln: jump ebb0(vx1) +; nextln: ebb1($v91: i32): +; nextln: jump ebb0($v91) ; nextln: } ; Jumps with 2 args. function twoargs(i32, f32) { -ebb0(vx0: i32, vx1: f32): - jump ebb1(vx0, vx1) +ebb0(v90: i32, v91: f32): + jump ebb1(v90, v91) -ebb1(vx2: i32, vx3: f32): - jump ebb0(vx2, vx3) +ebb1(v92: i32, v93: f32): + jump ebb0(v92, v93) } ; sameln: function twoargs(i32, f32) { -; nextln: ebb0(vx0: i32, vx1: f32): -; nextln: jump ebb1(vx0, vx1) +; nextln: ebb0($v90: i32, $v91: f32): +; nextln: jump ebb1($v90, $v91) ; nextln: -; nextln: ebb1(vx2: i32, vx3: f32): -; nextln: jump ebb0(vx2, vx3) +; nextln: ebb1($v92: i32, $v93: f32): +; nextln: jump ebb0($v92, $v93) ; nextln: } ; Branches with no arguments. The '()' empty argument list is optional. function minimal(i32) { -ebb0(vx0: i32): - brz vx0, ebb1 +ebb0(v90: i32): + brz v90, ebb1 ebb1: - brnz vx0, ebb1() + brnz v90, ebb1() } ; sameln: function minimal(i32) { -; nextln: ebb0(vx0: i32): -; nextln: brz vx0, ebb1 +; nextln: ebb0($v90: i32): +; nextln: brz $v90, ebb1 ; nextln: ; nextln: ebb1: -; nextln: brnz.i32 vx0, ebb1 +; nextln: brnz.i32 $v90, ebb1 ; nextln: } function twoargs(i32, f32) { -ebb0(vx0: i32, vx1: f32): - brz vx0, ebb1(vx0, vx1) +ebb0(v90: i32, v91: f32): + brz v90, ebb1(v90, v91) -ebb1(vx2: i32, vx3: f32): - brnz vx0, ebb0(vx2, vx3) +ebb1(v92: i32, v93: f32): + brnz v90, ebb0(v92, v93) } ; sameln: function twoargs(i32, f32) { -; nextln: ebb0(vx0: i32, vx1: f32): -; nextln: brz vx0, ebb1(vx0, vx1) +; nextln: ebb0($v90: i32, $v91: f32): +; nextln: brz $v90, ebb1($v90, $v91) ; nextln: -; nextln: ebb1(vx2: i32, vx3: f32): -; nextln: brnz.i32 vx0, ebb0(vx2, vx3) +; nextln: ebb1($v92: i32, $v93: f32): +; nextln: brnz.i32 $v90, ebb0($v92, $v93) ; nextln: } function jumptable(i32) { @@ -98,8 +98,8 @@ ebb40: ; nextln: jt0 = jump_table 0 ; nextln: jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 ; nextln: -; nextln: ebb0(vx0: i32): -; nextln: br_table vx0, jt1 +; nextln: ebb0($v3: i32): +; nextln: br_table $v3, jt1 ; nextln: trap ; nextln: ; nextln: ebb1: diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton index 72a61b6e21..027749f562 100644 --- a/filetests/parser/call.cton +++ b/filetests/parser/call.cton @@ -18,9 +18,9 @@ ebb1: } ; sameln: function r1() -> i32, f32 { ; nextln: ebb0: -; nextln: v0 = iconst.i32 3 -; nextln: v1 = f32const 0.0 -; nextln: return v0, v1 +; nextln: $v1 = iconst.i32 3 +; nextln: $v2 = f32const 0.0 +; nextln: return $v1, $v2 ; nextln: } function signatures() { diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton index 9f95cb93fa..ffd1851114 100644 --- a/filetests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -26,12 +26,12 @@ ebb100(v20: i32): function use_value() { ebb100(v20: i32): v1000 = iadd_imm v20, 5 - vx200 = iadd v20, v1000 + v200 = iadd v20, v1000 jump ebb100(v1000) } ; sameln: function use_value() { -; nextln: ebb0(vx0: i32): -; nextln: v0 = iadd_imm vx0, 5 -; nextln: v1 = iadd vx0, v0 -; nextln: jump ebb0(v0) +; nextln: ebb0($v20: i32): +; nextln: $v1000 = iadd_imm $v20, 5 +; nextln: $v200 = iadd $v20, $v1000 +; nextln: jump ebb0($v1000) ; nextln: } diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 868a7ba0b1..87140d9aad 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -20,19 +20,19 @@ ebb0: } ; sameln: function ivalues() { ; nextln: ebb0: -; nextln: v0 = iconst.i32 2 -; nextln: v1 = iconst.i8 6 -; nextln: v2 = ishl v0, v1 +; nextln: $v0 = iconst.i32 2 +; nextln: $v1 = iconst.i8 6 +; nextln: $v2 = ishl $v0, $v1 ; nextln: } ; Polymorphic istruction controlled by second operand. function select() { -ebb0(vx0: i32, vx1: i32, vx2: b1): - v0 = select vx2, vx0, vx1 +ebb0(v90: i32, v91: i32, v92: b1): + v0 = select v92, v90, v91 } ; sameln: function select() { -; nextln: ebb0(vx0: i32, vx1: i32, vx2: b1): -; nextln: v0 = select vx2, vx0, vx1 +; nextln: ebb0($v90: i32, $v91: i32, $v92: b1): +; nextln: $v0 = select $v92, $v90, $v91 ; nextln: } ; Lane indexes. @@ -44,54 +44,54 @@ ebb0: } ; sameln: function lanes() { ; nextln: ebb0: -; nextln: v0 = iconst.i32x4 2 -; nextln: v1 = extractlane v0, 3 -; nextln: v2 = insertlane v0, 1, v1 +; nextln: $v0 = iconst.i32x4 2 +; nextln: $v1 = extractlane $v0, 3 +; nextln: $v2 = insertlane $v0, 1, $v1 ; nextln: } ; Integer condition codes. function icmp(i32, i32) { -ebb0(vx0: i32, vx1: i32): - v0 = icmp eq vx0, vx1 - v1 = icmp ult vx0, vx1 - v2 = icmp_imm sge vx0, -12 - v3 = irsub_imm vx1, 45 - br_icmp eq vx0, vx1, ebb0(vx1, vx0) +ebb0(v90: i32, v91: i32): + v0 = icmp eq v90, v91 + v1 = icmp ult v90, v91 + v2 = icmp_imm sge v90, -12 + v3 = irsub_imm v91, 45 + br_icmp eq v90, v91, ebb0(v91, v90) } ; sameln: function icmp(i32, i32) { -; nextln: ebb0(vx0: i32, vx1: i32): -; nextln: v0 = icmp eq vx0, vx1 -; nextln: v1 = icmp ult vx0, vx1 -; nextln: v2 = icmp_imm sge vx0, -12 -; nextln: v3 = irsub_imm vx1, 45 -; nextln: br_icmp eq vx0, vx1, ebb0(vx1, vx0) +; nextln: ebb0($v90: i32, $v91: i32): +; nextln: $v0 = icmp eq $v90, $v91 +; nextln: $v1 = icmp ult $v90, $v91 +; nextln: $v2 = icmp_imm sge $v90, -12 +; nextln: $v3 = irsub_imm $v91, 45 +; nextln: br_icmp eq $v90, $v91, ebb0($v91, $v90) ; nextln: } ; Floating condition codes. function fcmp(f32, f32) { -ebb0(vx0: f32, vx1: f32): - v0 = fcmp eq vx0, vx1 - v1 = fcmp uno vx0, vx1 - v2 = fcmp lt vx0, vx1 +ebb0(v90: f32, v91: f32): + v0 = fcmp eq v90, v91 + v1 = fcmp uno v90, v91 + v2 = fcmp lt v90, v91 } ; sameln: function fcmp(f32, f32) { -; nextln: ebb0(vx0: f32, vx1: f32): -; nextln: v0 = fcmp eq vx0, vx1 -; nextln: v1 = fcmp uno vx0, vx1 -; nextln: v2 = fcmp lt vx0, vx1 +; nextln: ebb0($v90: f32, $v91: f32): +; nextln: $v0 = fcmp eq $v90, $v91 +; nextln: $v1 = fcmp uno $v90, $v91 +; nextln: $v2 = fcmp lt $v90, $v91 ; nextln: } ; The bitcast instruction has two type variables: The controlling type variable ; controls the outout type, and the input type is a free variable. function bitcast(i32, f32) { -ebb0(vx0: i32, vx1: f32): - v0 = bitcast.i8x4 vx0 - v1 = bitcast.i32 vx1 +ebb0(v90: i32, v91: f32): + v0 = bitcast.i8x4 v90 + v1 = bitcast.i32 v91 } ; sameln: function bitcast(i32, f32) { -; nextln: ebb0(vx0: i32, vx1: f32): -; nextln: v0 = bitcast.i8x4 vx0 -; nextln: v1 = bitcast.i32 vx1 +; nextln: ebb0($v90: i32, $v91: f32): +; nextln: $v0 = bitcast.i8x4 $v90 +; nextln: $v1 = bitcast.i32 $v91 ; nextln: } ; Stack slot references From 06b52744db657d1ce12900aeea9185dfb4f45eb8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 10:48:57 -0700 Subject: [PATCH 691/968] Don't assume all first results are direct values. We're about to change that. --- lib/cretonne/src/ir/dfg.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 583f7cc8a1..0b0ea7a8cc 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -207,13 +207,12 @@ impl DataFlowGraph { /// Find the original definition of a value, looking through value aliases as well as /// copy/spill/fill instructions. pub fn resolve_copies(&self, value: Value) -> Value { - use ir::entities::ExpandedValue::Direct; let mut v = value; for _ in 0..self.insts.len() { v = self.resolve_aliases(v); - v = match v.expand() { - Direct(inst) => { + v = match self.value_def(v) { + ValueDef::Res(inst, 0) => { match self[inst] { InstructionData::Unary { opcode, arg, .. } => { match opcode { @@ -1003,7 +1002,6 @@ mod tests { #[test] fn aliases() { use ir::InstBuilder; - use ir::entities::ExpandedValue::Direct; use ir::condcodes::IntCC; let mut func = Function::new(); @@ -1020,8 +1018,8 @@ mod tests { let arg0 = dfg.append_ebb_arg(ebb0, types::I32); let (s, c) = dfg.ins(pos).iadd_cout(v1, arg0); - let iadd = match s.expand() { - Direct(i) => i, + let iadd = match dfg.value_def(s) { + ValueDef::Res(i, 0) => i, _ => panic!(), }; From b9808bedc4e590961bd8b73ff9ab2692596c00cf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 11:02:59 -0700 Subject: [PATCH 692/968] Stop calling Value::new_direct. We only ever create table values now. Simplify legalizer::legalize_inst_results. Instead of calling detach_secondary_results, just detach all the results and don't treat the first result specially. --- filetests/isa/riscv/legalize-abi.cton | 6 +-- lib/cretonne/src/ir/dfg.rs | 48 +++++++++++++++------ lib/cretonne/src/legalizer/boundary.rs | 60 ++++++++------------------ lib/reader/src/parser.rs | 6 +-- lib/reader/src/sourcemap.rs | 2 +- 5 files changed, 59 insertions(+), 63 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 2e7da079bf..06d68768bd 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -34,9 +34,8 @@ ebb0: ; check: $ebb0: ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() ; check: $(v1new=$V) = iconcat $v1l, $v1h - ; check: $v1 = copy $v1new jump ebb1(v1) - ; The v1 copy gets resolved by split::simplify_branch_arguments(). + ; The v1 alias gets resolved by split::simplify_branch_arguments(). ; check: jump $ebb1($v1new) ebb1(v10: i64): @@ -77,9 +76,8 @@ ebb0: ; check: $ebb0: ; nextln: $(rv=$V) = call $fn1() ; check: $(v1new=$V) = ireduce.i8 $rv - ; check: $v1 = copy $v1new jump ebb1(v1) - ; The v1 copy gets resolved by split::simplify_branch_arguments(). + ; The v1 alias gets resolved by split::simplify_branch_arguments(). ; check: jump $ebb1($v1new) ebb1(v10: i8): diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 0b0ea7a8cc..359a6cd089 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -454,8 +454,14 @@ impl DataFlowGraph { *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); // Include the first result in the results vector. - if first_type.is_some() { - self.results[inst].push(Value::new_direct(inst), &mut self.value_lists); + if let Some(ty) = first_type { + let v = self.make_value(ValueData::Inst { + ty: ty, + num: 0, + inst: inst, + next: head.into(), + }); + self.results[inst].push(v, &mut self.value_lists); } self.results[inst] .as_mut_slice(&mut self.value_lists) @@ -489,11 +495,10 @@ impl DataFlowGraph { return None; } + let first = self.results[inst].first(&mut self.value_lists).unwrap(); let second = self.results[inst].get(1, &mut self.value_lists); self.results[inst].clear(&mut self.value_lists); - if !self.insts[inst].first_type().is_void() { - self.results[inst].push(Value::new_direct(inst), &mut self.value_lists); - } + self.results[inst].push(first, &mut self.value_lists); second } @@ -649,21 +654,36 @@ impl DataFlowGraph { pub fn redefine_first_value(&mut self, pos: &mut Cursor) -> Inst { let orig = pos.current_inst() .expect("Cursor must point at an instruction"); + let first_res = self.first_result(orig); + let first_type = self.value_type(first_res); let data = self[orig].clone(); - // After cloning, any secondary values are attached to both copies. Don't do that, we only - // want them on the new clone. - let mut results = self.results[orig].take(); - self.detach_secondary_results(orig); + let results = self.results[orig].take(); + self.results[orig].push(first_res, &mut self.value_lists); + + let new = self.make_inst(data); - results.as_mut_slice(&mut self.value_lists)[0] = Value::new_direct(new); - self.results[new] = results; + let new_first = self.make_value(ValueData::Inst { + ty: first_type, + num: 0, + inst: new, + next: None.into(), + }); + self.results[new].push(new_first, &mut self.value_lists); + + for i in 1.. { + if let Some(v) = results.get(i, &self.value_lists) { + self.attach_result(new, v); + } else { + break; + } + } + pos.insert_inst(new); // Replace the original instruction with a copy of the new value. // This is likely to be immediately overwritten by something else, but this way we avoid // leaving the DFG in a state with multiple references to secondary results and value // lists. It also means that this method doesn't change the semantics of the program. - let new_value = self.first_result(new); - self.replace(orig).copy(new_value); + self.replace(orig).copy(new_first); new } @@ -898,7 +918,7 @@ mod tests { let inst = dfg.make_inst(idata); dfg.make_inst_results(inst, types::I32); assert_eq!(inst.to_string(), "inst0"); - assert_eq!(dfg.display_inst(inst).to_string(), "v0 = iconst.i32"); + assert_eq!(dfg.display_inst(inst).to_string(), "vx0 = iconst.i32"); // Immutable reference resolution. { diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index f80b6574c4..272a19dfee 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -107,7 +107,7 @@ fn legalize_inst_results(dfg: &mut DataFlowGraph, -> Inst where ResType: FnMut(&DataFlowGraph, usize) -> ArgumentType { - let mut call = pos.current_inst() + let call = pos.current_inst() .expect("Cursor must point to a call instruction"); // We theoretically allow for call instructions that return a number of fixed results before @@ -115,58 +115,26 @@ fn legalize_inst_results(dfg: &mut DataFlowGraph, let fixed_results = dfg[call].opcode().constraints().fixed_results(); assert_eq!(fixed_results, 0, "Fixed results on calls not supported"); - let mut next_res = dfg.detach_secondary_results(call); - // The currently last result on the call instruction. - let mut last_res = dfg.first_result(call); + let results = dfg.detach_results(call); + let mut next_res = 0; let mut abi_res = 0; - // The first result requires special handling. - let first_ty = dfg.value_type(last_res); - if first_ty != get_abi_type(dfg, abi_res).value_type { - // Move the call out of the way, so we can redefine the first result. - let copy = call; - call = dfg.redefine_first_value(pos); - last_res = dfg.first_result(call); - // Set up a closure that can attach new results to `call`. - let mut get_res = |dfg: &mut DataFlowGraph, ty| { - let abi_type = get_abi_type(dfg, abi_res); - if ty == abi_type.value_type { - // Don't append the first result - it's not detachable. - if fixed_results + abi_res == 0 { - *dfg[call].first_type_mut() = ty; - debug_assert_eq!(last_res, dfg.first_result(call)); - } else { - last_res = dfg.append_secondary_result(last_res, ty); - } - abi_res += 1; - Ok(last_res) - } else { - Err(abi_type) - } - }; - - let v = convert_from_abi(dfg, pos, first_ty, &mut get_res); - dfg.replace(copy).copy(v); - } - - // Point immediately after the call and any instructions dealing with the first result. + // Point immediately after the call. pos.next_inst(); - // Now do the secondary results. - while let Some(res) = next_res { - next_res = dfg.next_secondary_result(res); + while let Some(res) = results.get(next_res, &dfg.value_lists) { + next_res += 1; let res_type = dfg.value_type(res); if res_type == get_abi_type(dfg, abi_res).value_type { // No value translation is necessary, this result matches the ABI type. - dfg.attach_secondary_result(last_res, res); - last_res = res; + dfg.attach_result(call, res); abi_res += 1; } else { let mut get_res = |dfg: &mut DataFlowGraph, ty| { let abi_type = get_abi_type(dfg, abi_res); if ty == abi_type.value_type { - last_res = dfg.append_secondary_result(last_res, ty); + let last_res = dfg.append_result(call, ty); abi_res += 1; Ok(last_res) } else { @@ -199,13 +167,18 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, { // Terminate the recursion when we get the desired type. let arg_type = match get_arg(dfg, ty) { - Ok(v) => return v, + Ok(v) => { + debug_assert_eq!(dfg.value_type(v), ty); + return v; + } Err(t) => t, }; // Reconstruct how `ty` was legalized into the `arg_type` argument. let conversion = legalize_abi_value(ty, &arg_type); + dbg!("convert_from_abi({}): {:?}", ty, conversion); + // The conversion describes value to ABI argument. We implement the reverse conversion here. match conversion { // Construct a `ty` by concatenating two ABI integers. @@ -213,6 +186,11 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, let abi_ty = ty.half_width().expect("Invalid type for conversion"); let lo = convert_from_abi(dfg, pos, abi_ty, get_arg); let hi = convert_from_abi(dfg, pos, abi_ty, get_arg); + dbg!("intsplit {}: {}, {}: {}", + lo, + dfg.value_type(lo), + hi, + dfg.value_type(hi)); dfg.ins(pos).iconcat(lo, hi) } // Construct a `ty` by concatenating two halves of a vector. diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f948b1f586..a501d6aaa3 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1750,12 +1750,12 @@ mod tests { .unwrap(); assert_eq!(func.name.to_string(), "qux"); let v4 = details.map.lookup_str("v4").unwrap(); - assert_eq!(v4.to_string(), "v0"); + assert_eq!(v4.to_string(), "vx0"); let vx3 = details.map.lookup_str("vx3").unwrap(); - assert_eq!(vx3.to_string(), "vx0"); + assert_eq!(vx3.to_string(), "vx2"); let aliased_to = func.dfg .resolve_aliases(Value::table_with_number(0).unwrap()); - assert_eq!(aliased_to.to_string(), "v0"); + assert_eq!(aliased_to.to_string(), "vx0"); } #[test] diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index cbea4b02a2..7f387ccee2 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -243,6 +243,6 @@ mod tests { assert_eq!(map.lookup_str("ebb0").unwrap().to_string(), "ebb0"); assert_eq!(map.lookup_str("v4").unwrap().to_string(), "vx0"); assert_eq!(map.lookup_str("vx7").unwrap().to_string(), "vx1"); - assert_eq!(map.lookup_str("v10").unwrap().to_string(), "v0"); + assert_eq!(map.lookup_str("v10").unwrap().to_string(), "vx2"); } } From 00ee850e33a7ea405867639bbe0fe239a5ee1dba Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 12:54:11 -0700 Subject: [PATCH 693/968] Stop linking result values together. Since results are in a value list, they don't need to form a linked list any longer. - Simplify make_inst_results() to create values in the natural order. - Eliminate the last use of next_secondary_value(). - Delete unused result manipulation methods. --- lib/cretonne/meta/gen_legalizer.py | 15 +- lib/cretonne/src/ir/dfg.rs | 277 +++-------------------------- 2 files changed, 33 insertions(+), 259 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 41cd9bab56..1a8d59d7fa 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -91,17 +91,10 @@ def unwrap_inst(iref, node, fmt): for d in node.defs[1:]: fmt.line('let src_{};'.format(d)) with fmt.indented('{', '}'): - fmt.line( - 'src_{} = dfg.detach_secondary_results(inst).unwrap();' - .format(node.defs[1])) - for i in range(2, len(node.defs)): - fmt.line( - 'src_{} = dfg.next_secondary_result(src_{})' - '.unwrap();' - .format(node.defs[i], node.defs[i - 1])) - fmt.line( - 'assert_eq!(dfg.next_secondary_result(src_{}), None);' - .format(node.defs[-1])) + fmt.line('let r = dfg.inst_results(inst);') + for i in range(1, len(node.defs)): + fmt.line('src_{} = r[{}];'.format(node.defs[i], i)) + fmt.line('dfg.detach_secondary_results(inst);') for d in node.defs[1:]: if d.has_free_typevar(): fmt.line( diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 359a6cd089..e00c1fe951 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -5,9 +5,8 @@ use ir::entities::ExpandedValue; use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; -use ir::builder::{InsertBuilder, ReplaceBuilder, InstBuilder}; +use ir::builder::{InsertBuilder, ReplaceBuilder}; use ir::layout::Cursor; -use packed_option::PackedOption; use write::write_operands; use std::fmt; @@ -292,20 +291,11 @@ pub enum ValueDef { // Internal table storage for extended values. #[derive(Clone, Debug)] enum ValueData { - // Value is defined by an instruction, but it is not the first result. - Inst { - ty: Type, - num: u16, // Result number starting from 0. - inst: Inst, - next: PackedOption, // Next result defined by `def`. - }, + // Value is defined by an instruction. + Inst { ty: Type, num: u16, inst: Inst }, // Value is an EBB argument. - Arg { - ty: Type, - num: u16, // Argument number, starting from 0. - ebb: Ebb, - }, + Arg { ty: Type, num: u16, ebb: Ebb }, // Value is an alias of another value. // An alias value can't be linked as an instruction result or EBB argument. It is used as a @@ -397,8 +387,6 @@ impl DataFlowGraph { /// The type of the first result value is also set, even if it was already set in the /// `InstructionData` passed to `make_inst`. If this function is called with a single-result /// instruction, that is the only effect. - /// - /// Returns the number of results produced by the instruction. pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { let constraints = self.insts[inst].opcode().constraints(); let fixed_results = constraints.fixed_results(); @@ -406,67 +394,27 @@ impl DataFlowGraph { self.results[inst].clear(&mut self.value_lists); - // Additional values form a linked list starting from the second result value. Generate - // the list backwards so we don't have to modify value table entries in place. (This - // causes additional result values to be numbered backwards which is not the aesthetic - // choice, but since it is only visible in extremely rare instructions with 3+ results, - // we don't care). - let mut head = None; - let mut first_type = None; - let mut rev_num = 1; + // The fixed results will appear at the front of the list. + for res_idx in 0..fixed_results { + self.append_result(inst, constraints.result_type(res_idx, ctrl_typevar)); + } // Get the call signature if this is a function call. if let Some(sig) = self.call_signature(inst) { // Create result values corresponding to the call return types. let var_results = self.signatures[sig].return_types.len(); total_results += var_results; - - for res_idx in (0..var_results).rev() { - if let Some(ty) = first_type { - head = Some(self.make_value(ValueData::Inst { - ty: ty, - num: (total_results - rev_num) as u16, - inst: inst, - next: head.into(), - })); - self.results[inst].push(head.unwrap(), &mut self.value_lists); - rev_num += 1; - } - first_type = Some(self.signatures[sig].return_types[res_idx].value_type); + for res_idx in 0..var_results { + let ty = self.signatures[sig].return_types[res_idx].value_type; + self.append_result(inst, ty); } } - // Then the fixed results which will appear at the front of the list. - for res_idx in (0..fixed_results).rev() { - if let Some(ty) = first_type { - head = Some(self.make_value(ValueData::Inst { - ty: ty, - num: (total_results - rev_num) as u16, - inst: inst, - next: head.into(), - })); - rev_num += 1; - self.results[inst].push(head.unwrap(), &mut self.value_lists); - } - first_type = Some(constraints.result_type(res_idx, ctrl_typevar)); + if let Some(v) = self.results[inst].first(&mut self.value_lists) { + let ty = self.value_type(v); + *self[inst].first_type_mut() = ty; } - *self.insts[inst].first_type_mut() = first_type.unwrap_or_default(); - - // Include the first result in the results vector. - if let Some(ty) = first_type { - let v = self.make_value(ValueData::Inst { - ty: ty, - num: 0, - inst: inst, - next: head.into(), - }); - self.results[inst].push(v, &mut self.value_lists); - } - self.results[inst] - .as_mut_slice(&mut self.value_lists) - .reverse(); - total_results } @@ -482,37 +430,18 @@ impl DataFlowGraph { ReplaceBuilder::new(self, inst) } - /// Detach secondary instruction results, and return the first of them. + /// Detach secondary instruction results. /// /// If `inst` produces two or more results, detach these secondary result values from `inst`. /// The first result value cannot be detached. - /// The full list of secondary results can be traversed with `next_secondary_result()`. /// /// Use this method to detach secondary values before using `replace(inst)` to provide an /// alternate instruction for computing the primary result value. - pub fn detach_secondary_results(&mut self, inst: Inst) -> Option { - if !self.has_results(inst) { - return None; + pub fn detach_secondary_results(&mut self, inst: Inst) { + if let Some(first) = self.results[inst].first(&mut self.value_lists) { + self.results[inst].clear(&mut self.value_lists); + self.results[inst].push(first, &mut self.value_lists); } - - let first = self.results[inst].first(&mut self.value_lists).unwrap(); - let second = self.results[inst].get(1, &mut self.value_lists); - self.results[inst].clear(&mut self.value_lists); - self.results[inst].push(first, &mut self.value_lists); - second - } - - /// Get the next secondary result after `value`. - /// - /// Use this function to traverse the full list of instruction results returned from - /// `detach_secondary_results()`. - pub fn next_secondary_result(&self, value: Value) -> Option { - if let ExpandedValue::Table(index) = value.expand() { - if let ValueData::Inst { next, .. } = self.extended_values[index] { - return next.into(); - } - } - panic!("{} is not a secondary result value", value); } /// Detach the list of result values from `inst` and return it. @@ -530,37 +459,17 @@ impl DataFlowGraph { /// This is a very low-level operation. Usually, instruction results with the correct types are /// created automatically. The `res` value must not be attached to anything else. pub fn attach_result(&mut self, inst: Inst, res: Value) { - let res_inst = inst; - if let Some(last_res) = self.results[inst] - .as_slice(&mut self.value_lists) - .last() - .cloned() { - self.attach_secondary_result(last_res, res); + let num = self.results[inst].push(res, &mut self.value_lists); + assert!(num <= u16::MAX as usize, "Too many result values"); + let ty = self.value_type(res); + if let ExpandedValue::Table(idx) = res.expand() { + self.extended_values[idx] = ValueData::Inst { + ty: ty, + num: num as u16, + inst: inst, + }; } else { - // This is the first result. - self.results[res_inst].push(res, &mut self.value_lists); - - // Now update `res` itself. - match res.expand() { - ExpandedValue::Table(idx) => { - if let ValueData::Inst { - ref mut num, - ref mut inst, - ref mut next, - .. - } = self.extended_values[idx] { - *num = 0; - *inst = res_inst; - *next = None.into(); - return; - } - } - ExpandedValue::Direct(inst) => { - assert_eq!(inst, res_inst); - return; - } - } - panic!("{} must be a result", res); + panic!("Unexpected direct value"); } } @@ -570,123 +479,11 @@ impl DataFlowGraph { ty: ty, inst: inst, num: 0, - next: None.into(), }); self.attach_result(inst, res); res } - /// Attach an existing value as a secondary result after `last_res` which must be the last - /// result of an instruction. - /// - /// This is a very low-level operation. Usually, instruction results with the correct types are - /// created automatically. The `res` value must be a secondary instruction result detached from - /// somewhere else. - pub fn attach_secondary_result(&mut self, last_res: Value, res: Value) { - let res_inst = match last_res.expand() { - ExpandedValue::Direct(inst) => inst, - ExpandedValue::Table(idx) => { - if let ValueData::Inst { inst, ref mut next, .. } = self.extended_values[idx] { - assert!(next.is_none(), "last_res is not the last result"); - *next = res.into(); - inst - } else { - panic!("last_res is not an instruction result"); - } - } - }; - - let res_num = self.results[res_inst].push(res, &mut self.value_lists); - assert!(res_num <= u16::MAX as usize, "Too many result values"); - - // Now update `res` itself. - if let ExpandedValue::Table(idx) = res.expand() { - if let ValueData::Inst { - ref mut num, - ref mut inst, - ref mut next, - .. - } = self.extended_values[idx] { - *num = res_num as u16; - *inst = res_inst; - *next = None.into(); - return; - } - } - panic!("{} must be a result", res); - } - - /// Append a new instruction result value after `last_res`. - /// - /// The `last_res` value must be the last value on an instruction. - pub fn append_secondary_result(&mut self, last_res: Value, ty: Type) -> Value { - // The only member that matters is `ty`. The rest is filled in by - // `attach_secondary_result`. - use entity_map::EntityRef; - let res = self.make_value(ValueData::Inst { - ty: ty, - inst: Inst::new(0), - num: 0, - next: None.into(), - }); - self.attach_secondary_result(last_res, res); - res - } - - /// Move the instruction at `pos` to a new `Inst` reference so its first result can be - /// redefined without overwriting the original instruction. - /// - /// The first result value of an instruction is intrinsically tied to the `Inst` reference, so - /// it is not possible to detach the value and attach it to something else. This function - /// copies the instruction pointed to by `pos` to a new `Inst` reference, making the original - /// `Inst` reference available to be redefined with `dfg.replace(inst)` above. - /// - /// Before: - /// - /// inst1: v1, vx2 = foo <-- pos - /// - /// After: - /// - /// inst7: v7, vx2 = foo - /// inst1: v1 = copy v7 <-- pos - /// - /// Returns the new `Inst` reference where the original instruction has been moved. - pub fn redefine_first_value(&mut self, pos: &mut Cursor) -> Inst { - let orig = pos.current_inst() - .expect("Cursor must point at an instruction"); - let first_res = self.first_result(orig); - let first_type = self.value_type(first_res); - let data = self[orig].clone(); - let results = self.results[orig].take(); - self.results[orig].push(first_res, &mut self.value_lists); - - - let new = self.make_inst(data); - let new_first = self.make_value(ValueData::Inst { - ty: first_type, - num: 0, - inst: new, - next: None.into(), - }); - self.results[new].push(new_first, &mut self.value_lists); - - for i in 1.. { - if let Some(v) = results.get(i, &self.value_lists) { - self.attach_result(new, v); - } else { - break; - } - } - - pos.insert_inst(new); - // Replace the original instruction with a copy of the new value. - // This is likely to be immediately overwritten by something else, but this way we avoid - // leaving the DFG in a state with multiple references to secondary results and value - // lists. It also means that this method doesn't change the semantics of the program. - self.replace(orig).copy(new_first); - new - } - /// Get the first result of an instruction. /// /// This function panics if the instruction doesn't have any result. @@ -1043,22 +840,6 @@ mod tests { _ => panic!(), }; - // Redefine the first value out of `iadd_cout`. - assert_eq!(pos.prev_inst(), Some(iadd)); - let new_iadd = dfg.redefine_first_value(pos); - let new_s = dfg.first_result(new_iadd); - assert_eq!(dfg[iadd].opcode(), Opcode::Copy); - assert_eq!(dfg.inst_results(iadd), &[s]); - assert_eq!(dfg.inst_results(new_iadd), &[new_s, c]); - assert_eq!(dfg.resolve_copies(s), new_s); - pos.next_inst(); - - // Detach the 'c' value from `iadd`. - { - assert_eq!(dfg.detach_secondary_results(new_iadd), Some(c)); - assert_eq!(dfg.next_secondary_result(c), None); - } - // Replace `iadd_cout` with a normal `iadd` and an `icmp`. dfg.replace(iadd).iadd(v1, arg0); let c2 = dfg.ins(pos).icmp(IntCC::UnsignedLessThan, s, v1); From 9c6a36d36d49cb10374c2ece53848ad0b157d51d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 13:14:03 -0700 Subject: [PATCH 694/968] Move the ctrl_typevar function into dfg. Soon, InstructionData won't have sufficient information to compute this. Give TargetIsa::encode() an explicit ctrl_typevar argument. This function does not require the instruction to be inserted in the DFG tables. --- lib/cretonne/src/ir/dfg.rs | 27 ++++++++++++++++++----- lib/cretonne/src/ir/instructions.rs | 22 ------------------- lib/cretonne/src/isa/arm32/mod.rs | 7 +++--- lib/cretonne/src/isa/arm64/mod.rs | 7 +++--- lib/cretonne/src/isa/intel/mod.rs | 7 +++--- lib/cretonne/src/isa/mod.rs | 8 +++++-- lib/cretonne/src/isa/riscv/mod.rs | 34 +++++++++++++++++++---------- lib/cretonne/src/legalizer/mod.rs | 2 +- lib/cretonne/src/verifier.rs | 2 +- lib/cretonne/src/write.rs | 2 +- src/filetest/binemit.rs | 4 +++- 11 files changed, 69 insertions(+), 53 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index e00c1fe951..1aebfbb03e 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,12 +1,13 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueListPool}; -use ir::entities::ExpandedValue; -use ir::instructions::{Opcode, InstructionData, CallInfo}; -use ir::extfunc::ExtFuncData; use entity_map::{EntityMap, PrimaryEntityData}; use ir::builder::{InsertBuilder, ReplaceBuilder}; +use ir::entities::ExpandedValue; +use ir::extfunc::ExtFuncData; +use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::layout::Cursor; +use ir::types; +use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueListPool}; use write::write_operands; use std::fmt; @@ -541,6 +542,22 @@ impl DataFlowGraph { .map(|&arg| arg.value_type) }) } + + /// Get the controlling type variable, or `VOID` if `inst` isn't polymorphic. + pub fn ctrl_typevar(&self, inst: Inst) -> Type { + let constraints = self[inst].opcode().constraints(); + + if !constraints.is_polymorphic() { + types::VOID + } else if constraints.requires_typevar_operand() { + // Not all instruction formats have a designated operand, but in that case + // `requires_typevar_operand()` should never be true. + self.value_type(self[inst].typevar_operand(&self.value_lists) + .expect("Instruction format doesn't have a designated operand, bad opcode.")) + } else { + self.value_type(self.first_result(inst)) + } + } } /// Allow immutable access to instructions via indexing. @@ -688,7 +705,7 @@ impl<'a> fmt::Display for DisplayInst<'a> { } - let typevar = inst.ctrl_typevar(dfg); + let typevar = dfg.ctrl_typevar(self.1); if typevar.is_void() { write!(f, "{}", inst.opcode())?; } else { diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 29e3e6662f..1e1262a507 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -14,7 +14,6 @@ use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::*; use ir::types; -use ir::DataFlowGraph; use entity_list; use ref_slice::{ref_slice, ref_slice_mut}; @@ -397,27 +396,6 @@ impl InstructionData { _ => CallInfo::NotACall, } } - - /// Get the controlling type variable, or `VOID` if this instruction isn't polymorphic. - /// - /// In most cases, the controlling type variable is the same as the first result type, but some - /// opcodes require us to read the type of the designated type variable operand from `dfg`. - pub fn ctrl_typevar(&self, dfg: &DataFlowGraph) -> Type { - let constraints = self.opcode().constraints(); - - if !constraints.is_polymorphic() { - types::VOID - } else if constraints.requires_typevar_operand() { - // Not all instruction formats have a designated operand, but in that case - // `requires_typevar_operand()` should never be true. - dfg.value_type(self.typevar_operand(&dfg.value_lists) - .expect("Instruction format doesn't have a designated operand, bad opcode.")) - } else { - // For locality of reference, we prefer to get the controlling type variable from - // `idata` itself, when possible. - self.first_type() - } - } } /// Information about branch and jump instructions. diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index c9c7f2af5c..b76ffa32cb 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -60,10 +60,11 @@ impl TargetIsa for Isa { } fn encode(&self, - dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData) + _dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData, + ctrl_typevar: ir::Type) -> Result { - lookup_enclist(inst.ctrl_typevar(dfg), + lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index a8cfe834b9..2499028919 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -53,10 +53,11 @@ impl TargetIsa for Isa { } fn encode(&self, - dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData) + _dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData, + ctrl_typevar: ir::Type) -> Result { - lookup_enclist(inst.ctrl_typevar(dfg), + lookup_enclist(ctrl_typevar, inst.opcode(), &enc_tables::LEVEL1_A64[..], &enc_tables::LEVEL2[..]) diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index b48a119632..60ce3a795d 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -60,10 +60,11 @@ impl TargetIsa for Isa { } fn encode(&self, - dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData) + _dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData, + ctrl_typevar: ir::Type) -> Result { - lookup_enclist(inst.ctrl_typevar(dfg), + lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 469142ab56..c40a839836 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -46,7 +46,7 @@ pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; use binemit::CodeSink; use settings; -use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature}; +use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature, Type}; pub mod riscv; pub mod intel; @@ -141,7 +141,11 @@ pub trait TargetIsa { /// Otherwise, return `None`. /// /// This is also the main entry point for determining if an instruction is legal. - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result; + fn encode(&self, + dfg: &DataFlowGraph, + inst: &InstructionData, + ctrl_typevar: Type) + -> Result; /// Get a data structure describing the instruction encodings in this ISA. fn encoding_info(&self) -> EncInfo; diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 7658db392e..b1f399665b 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -11,7 +11,7 @@ use binemit::CodeSink; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; -use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature}; +use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature, Type}; #[allow(dead_code)] struct Isa { @@ -60,8 +60,12 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result { - lookup_enclist(inst.ctrl_typevar(dfg), + fn encode(&self, + _dfg: &DataFlowGraph, + inst: &InstructionData, + ctrl_typevar: Type) + -> Result { + lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) @@ -120,7 +124,8 @@ mod tests { }; // ADDI is I/0b00100 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64).unwrap()), "I#04"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64, types::I64).unwrap()), + "I#04"); // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -131,7 +136,8 @@ mod tests { }; // Immediate is out of range for ADDI. - assert_eq!(isa.encode(&dfg, &inst64_large), Err(isa::Legalize::Expand)); + assert_eq!(isa.encode(&dfg, &inst64_large, types::I64), + Err(isa::Legalize::Expand)); // Create an iadd_imm.i32 which is encodable in RV64. let inst32 = InstructionData::BinaryImm { @@ -142,7 +148,8 @@ mod tests { }; // ADDIW is I/0b00110 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I#06"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32).unwrap()), + "I#06"); } // Same as above, but for RV32. @@ -167,7 +174,8 @@ mod tests { }; // In 32-bit mode, an i64 bit add should be narrowed. - assert_eq!(isa.encode(&dfg, &inst64), Err(isa::Legalize::Narrow)); + assert_eq!(isa.encode(&dfg, &inst64, types::I64), + Err(isa::Legalize::Narrow)); // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -178,7 +186,8 @@ mod tests { }; // In 32-bit mode, an i64 bit add should be narrowed. - assert_eq!(isa.encode(&dfg, &inst64_large), Err(isa::Legalize::Narrow)); + assert_eq!(isa.encode(&dfg, &inst64_large, types::I64), + Err(isa::Legalize::Narrow)); // Create an iadd_imm.i32 which is encodable in RV32. let inst32 = InstructionData::BinaryImm { @@ -189,7 +198,8 @@ mod tests { }; // ADDI is I/0b00100 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32).unwrap()), "I#04"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32).unwrap()), + "I#04"); // Create an imul.i32 which is encodable in RV32, but only when use_m is true. let mul32 = InstructionData::Binary { @@ -198,7 +208,8 @@ mod tests { args: [arg32, arg32], }; - assert_eq!(isa.encode(&dfg, &mul32), Err(isa::Legalize::Expand)); + assert_eq!(isa.encode(&dfg, &mul32, types::I32), + Err(isa::Legalize::Expand)); } #[test] @@ -224,6 +235,7 @@ mod tests { ty: types::I32, args: [arg32, arg32], }; - assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32).unwrap()), "R#10c"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32, types::I32).unwrap()), + "R#10c"); } } diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 00354822a1..7949aed311 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -64,7 +64,7 @@ pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: & split::simplify_branch_arguments(&mut func.dfg, inst); } - match isa.encode(&func.dfg, &func.dfg[inst]) { + match isa.encode(&func.dfg, &func.dfg[inst], func.dfg.ctrl_typevar(inst)) { Ok(encoding) => *func.encodings.ensure(inst) = encoding, Err(action) => { // We should transform the instruction into legal equivalents. diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index d6018a8f5c..0b84ad82bd 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -425,7 +425,7 @@ impl<'a> Verifier<'a> { let ctrl_type = if let Some(value_typeset) = constraints.ctrl_typeset() { // For polymorphic opcodes, determine the controlling type variable first. - let ctrl_type = inst_data.ctrl_typevar(&self.func.dfg); + let ctrl_type = self.func.dfg.ctrl_typevar(inst); if !value_typeset.contains(ctrl_type) { return err!(inst, "has an invalid controlling type {}", ctrl_type); diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index ae4a38284f..023eb4ee7a 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -152,7 +152,7 @@ fn type_suffix(func: &Function, inst: Inst) -> Option { } } - let rtype = inst_data.ctrl_typevar(&func.dfg); + let rtype = func.dfg.ctrl_typevar(inst); assert!(!rtype.is_void(), "Polymorphic instruction must produce a result"); Some(rtype) diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index f4ec6262c6..449e0887e6 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -110,7 +110,9 @@ impl SubTest for TestBinEmit { .get(inst) .map(|e| e.is_legal()) .unwrap_or(false) { - if let Ok(enc) = isa.encode(&func.dfg, &func.dfg[inst]) { + if let Ok(enc) = isa.encode(&func.dfg, + &func.dfg[inst], + func.dfg.ctrl_typevar(inst)) { *func.encodings.ensure(inst) = enc; } } From 7cac9dcb413eb449d109eab7fa798e7ffe2ecb93 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 13:43:24 -0700 Subject: [PATCH 695/968] Remove the first_type() methods from InstructionData. Also remove the type field from all the variants. The type of the first result value can be recovered from the value table now. --- lib/cretonne/meta/gen_instr.py | 24 +--------- lib/cretonne/src/ir/builder.rs | 6 --- lib/cretonne/src/ir/dfg.rs | 18 ++----- lib/cretonne/src/ir/instructions.rs | 74 ++++------------------------- lib/cretonne/src/isa/riscv/mod.rs | 8 ---- lib/cretonne/src/verifier.rs | 33 ++++--------- lib/reader/src/parser.rs | 34 +------------ 7 files changed, 26 insertions(+), 171 deletions(-) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 4d71e1e5b4..b1b32b7bf3 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -109,16 +109,14 @@ def gen_instruction_data_impl(fmt): the instruction formats: - `pub fn opcode(&self) -> Opcode` - - `pub fn first_type(&self) -> Type` - `pub fn arguments(&self, &pool) -> &[Value]` - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]` - `pub fn take_value_list(&mut self) -> Option` - `pub fn put_value_list(&mut self, args: ValueList>` """ - # The `opcode` and `first_type` methods simply read the `opcode` and `ty` - # members. This is really a workaround for Rust's enum types missing shared - # members. + # The `opcode` method simply reads the `opcode` members. This is really a + # workaround for Rust's enum types missing shared members. with fmt.indented('impl InstructionData {', '}'): fmt.doc_comment('Get the opcode of this instruction.') with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'): @@ -128,23 +126,6 @@ def gen_instruction_data_impl(fmt): 'InstructionData::{} {{ opcode, .. }} => opcode,' .format(f.name)) - fmt.doc_comment('Type of the first result, or `VOID`.') - with fmt.indented('pub fn first_type(&self) -> Type {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - fmt.line( - 'InstructionData::{} {{ ty, .. }} => ty,' - .format(f.name)) - - fmt.doc_comment('Mutable reference to the type of the first result.') - with fmt.indented( - 'pub fn first_type_mut(&mut self) -> &mut Type {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - fmt.line( - 'InstructionData::{} {{ ref mut ty, .. }} => ty,' - .format(f.name)) - fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( 'pub fn typevar_operand(&self, pool: &ValueListPool) -> ' @@ -490,7 +471,6 @@ def gen_format_constructor(iform, fmt): with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): fmt.line('opcode: opcode,') - fmt.line('ty: types::VOID,') gen_member_inits(iform, fmt) fmt.line('self.build(data, ctrl_typevar)') diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 312e5144da..0414f7c282 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -117,12 +117,6 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { // The old result values were either detached or non-existent. // Construct new ones. self.dfg.make_inst_results(self.inst, ctrl_typevar); - } else { - // Normally, make_inst_results() would also set the first result type, but we're not - // going to call that, so set it manually. - *self.dfg[self.inst].first_type_mut() = self.dfg - .compute_result_type(self.inst, 0, ctrl_typevar) - .unwrap_or_default(); } (self.inst, self.dfg) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 1aebfbb03e..49392d8bfb 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -132,7 +132,7 @@ impl DataFlowGraph { pub fn value_type(&self, v: Value) -> Type { use ir::entities::ExpandedValue::*; match v.expand() { - Direct(i) => self.insts[i].first_type(), + Direct(_) => panic!("Unexpected direct value"), Table(i) => { match self.extended_values[i] { ValueData::Inst { ty, .. } => ty, @@ -411,11 +411,6 @@ impl DataFlowGraph { } } - if let Some(v) = self.results[inst].first(&mut self.value_lists) { - let ty = self.value_type(v); - *self[inst].first_type_mut() = ty; - } - total_results } @@ -725,10 +720,7 @@ mod tests { fn make_inst() { let mut dfg = DataFlowGraph::new(); - let idata = InstructionData::Nullary { - opcode: Opcode::Iconst, - ty: types::VOID, - }; + let idata = InstructionData::Nullary { opcode: Opcode::Iconst }; let inst = dfg.make_inst(idata); dfg.make_inst_results(inst, types::I32); assert_eq!(inst.to_string(), "inst0"); @@ -739,7 +731,6 @@ mod tests { let immdfg = &dfg; let ins = &immdfg[inst]; assert_eq!(ins.opcode(), Opcode::Iconst); - assert_eq!(ins.first_type(), types::I32); } // Results. @@ -754,10 +745,7 @@ mod tests { fn no_results() { let mut dfg = DataFlowGraph::new(); - let idata = InstructionData::Nullary { - opcode: Opcode::Trap, - ty: types::VOID, - }; + let idata = InstructionData::Nullary { opcode: Opcode::Trap }; let inst = dfg.make_inst(idata); assert_eq!(dfg.display_inst(inst).to_string(), "trap"); diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 1e1262a507..61ed2f1eeb 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -100,161 +100,107 @@ impl FromStr for Opcode { #[derive(Clone, Debug)] #[allow(missing_docs)] pub enum InstructionData { - Nullary { opcode: Opcode, ty: Type }, - Unary { - opcode: Opcode, - ty: Type, - arg: Value, - }, - UnaryImm { - opcode: Opcode, - ty: Type, - imm: Imm64, - }, - UnaryIeee32 { - opcode: Opcode, - ty: Type, - imm: Ieee32, - }, - UnaryIeee64 { - opcode: Opcode, - ty: Type, - imm: Ieee64, - }, - UnarySplit { - opcode: Opcode, - ty: Type, - arg: Value, - }, - Binary { - opcode: Opcode, - ty: Type, - args: [Value; 2], - }, + Nullary { opcode: Opcode }, + Unary { opcode: Opcode, arg: Value }, + UnaryImm { opcode: Opcode, imm: Imm64 }, + UnaryIeee32 { opcode: Opcode, imm: Ieee32 }, + UnaryIeee64 { opcode: Opcode, imm: Ieee64 }, + UnarySplit { opcode: Opcode, arg: Value }, + Binary { opcode: Opcode, args: [Value; 2] }, BinaryImm { opcode: Opcode, - ty: Type, arg: Value, imm: Imm64, }, - BinaryOverflow { - opcode: Opcode, - ty: Type, - args: [Value; 2], - }, - Ternary { - opcode: Opcode, - ty: Type, - args: [Value; 3], - }, - MultiAry { - opcode: Opcode, - ty: Type, - args: ValueList, - }, + BinaryOverflow { opcode: Opcode, args: [Value; 2] }, + Ternary { opcode: Opcode, args: [Value; 3] }, + MultiAry { opcode: Opcode, args: ValueList }, InsertLane { opcode: Opcode, - ty: Type, lane: Uimm8, args: [Value; 2], }, ExtractLane { opcode: Opcode, - ty: Type, lane: Uimm8, arg: Value, }, IntCompare { opcode: Opcode, - ty: Type, cond: IntCC, args: [Value; 2], }, IntCompareImm { opcode: Opcode, - ty: Type, cond: IntCC, arg: Value, imm: Imm64, }, FloatCompare { opcode: Opcode, - ty: Type, cond: FloatCC, args: [Value; 2], }, Jump { opcode: Opcode, - ty: Type, destination: Ebb, args: ValueList, }, Branch { opcode: Opcode, - ty: Type, destination: Ebb, args: ValueList, }, BranchIcmp { opcode: Opcode, - ty: Type, cond: IntCC, destination: Ebb, args: ValueList, }, BranchTable { opcode: Opcode, - ty: Type, arg: Value, table: JumpTable, }, Call { opcode: Opcode, - ty: Type, func_ref: FuncRef, args: ValueList, }, IndirectCall { opcode: Opcode, - ty: Type, sig_ref: SigRef, args: ValueList, }, StackLoad { opcode: Opcode, - ty: Type, stack_slot: StackSlot, offset: Offset32, }, StackStore { opcode: Opcode, - ty: Type, arg: Value, stack_slot: StackSlot, offset: Offset32, }, HeapLoad { opcode: Opcode, - ty: Type, arg: Value, offset: Uoffset32, }, HeapStore { opcode: Opcode, - ty: Type, args: [Value; 2], offset: Uoffset32, }, Load { opcode: Opcode, - ty: Type, flags: MemFlags, arg: Value, offset: Offset32, }, Store { opcode: Opcode, - ty: Type, flags: MemFlags, args: [Value; 2], offset: Offset32, diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index b1f399665b..8bf9d7b732 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -118,7 +118,6 @@ mod tests { // Try to encode iadd_imm.i64 vx1, -10. let inst64 = InstructionData::BinaryImm { opcode: Opcode::IaddImm, - ty: types::I64, arg: arg64, imm: immediates::Imm64::new(-10), }; @@ -130,7 +129,6 @@ mod tests { // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { opcode: Opcode::IaddImm, - ty: types::I64, arg: arg64, imm: immediates::Imm64::new(-10000), }; @@ -142,7 +140,6 @@ mod tests { // Create an iadd_imm.i32 which is encodable in RV64. let inst32 = InstructionData::BinaryImm { opcode: Opcode::IaddImm, - ty: types::I32, arg: arg32, imm: immediates::Imm64::new(10), }; @@ -168,7 +165,6 @@ mod tests { // Try to encode iadd_imm.i64 vx1, -10. let inst64 = InstructionData::BinaryImm { opcode: Opcode::IaddImm, - ty: types::I64, arg: arg64, imm: immediates::Imm64::new(-10), }; @@ -180,7 +176,6 @@ mod tests { // Try to encode iadd_imm.i64 vx1, -10000. let inst64_large = InstructionData::BinaryImm { opcode: Opcode::IaddImm, - ty: types::I64, arg: arg64, imm: immediates::Imm64::new(-10000), }; @@ -192,7 +187,6 @@ mod tests { // Create an iadd_imm.i32 which is encodable in RV32. let inst32 = InstructionData::BinaryImm { opcode: Opcode::IaddImm, - ty: types::I32, arg: arg32, imm: immediates::Imm64::new(10), }; @@ -204,7 +198,6 @@ mod tests { // Create an imul.i32 which is encodable in RV32, but only when use_m is true. let mul32 = InstructionData::Binary { opcode: Opcode::Imul, - ty: types::I32, args: [arg32, arg32], }; @@ -232,7 +225,6 @@ mod tests { // Create an imul.i32 which is encodable in RV32M. let mul32 = InstructionData::Binary { opcode: Opcode::Imul, - ty: types::I32, args: [arg32, arg32], }; assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32, types::I32).unwrap()), diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 0b84ad82bd..7a287a64bd 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -183,23 +183,13 @@ impl<'a> Verifier<'a> { .unwrap_or(0); let total_results = fixed_results + var_results; - if total_results == 0 { - // Instructions with no results have a NULL `first_type()` - let ret_type = inst_data.first_type(); - if ret_type != types::VOID { - return err!(inst, - "instruction with no results expects NULL return type, found {}", - ret_type); - } - } else { - // All result values for multi-valued instructions are created - let got_results = dfg.inst_results(inst).len(); - if got_results != total_results { - return err!(inst, - "expected {} result values, found {}", - total_results, - got_results); - } + // All result values for multi-valued instructions are created + let got_results = dfg.inst_results(inst).len(); + if got_results != total_results { + return err!(inst, + "expected {} result values, found {}", + total_results, + got_results); } self.verify_entity_references(inst) @@ -671,7 +661,6 @@ mod tests { use super::{Verifier, Error}; use ir::Function; use ir::instructions::{InstructionData, Opcode}; - use ir::types; macro_rules! assert_err_with_msg { ($e:expr, $msg:expr) => ( @@ -698,11 +687,9 @@ mod tests { let mut func = Function::new(); let ebb0 = func.dfg.make_ebb(); func.layout.append_ebb(ebb0); - let nullary_with_bad_opcode = func.dfg - .make_inst(InstructionData::Nullary { - opcode: Opcode::Jump, - ty: types::VOID, - }); + let nullary_with_bad_opcode = + func.dfg + .make_inst(InstructionData::Nullary { opcode: Opcode::Jump }); func.layout.append_inst(nullary_with_bad_opcode, ebb0); let verifier = Verifier::new(&func); assert_err_with_msg!(verifier.run(), "instruction format"); diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index a501d6aaa3..dcc76b1b15 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1404,44 +1404,34 @@ impl<'a> Parser<'a> { opcode: Opcode) -> Result { let idata = match opcode.format() { - InstructionFormat::Nullary => { - InstructionData::Nullary { - opcode: opcode, - ty: VOID, - } - } + InstructionFormat::Nullary => InstructionData::Nullary { opcode: opcode }, InstructionFormat::Unary => { InstructionData::Unary { opcode: opcode, - ty: VOID, arg: self.match_value("expected SSA value operand")?, } } InstructionFormat::UnaryImm => { InstructionData::UnaryImm { opcode: opcode, - ty: VOID, imm: self.match_imm64("expected immediate integer operand")?, } } InstructionFormat::UnaryIeee32 => { InstructionData::UnaryIeee32 { opcode: opcode, - ty: VOID, imm: self.match_ieee32("expected immediate 32-bit float operand")?, } } InstructionFormat::UnaryIeee64 => { InstructionData::UnaryIeee64 { opcode: opcode, - ty: VOID, imm: self.match_ieee64("expected immediate 64-bit float operand")?, } } InstructionFormat::UnarySplit => { InstructionData::UnarySplit { opcode: opcode, - ty: VOID, arg: self.match_value("expected SSA value operand")?, } } @@ -1451,7 +1441,6 @@ impl<'a> Parser<'a> { let rhs = self.match_value("expected SSA value second operand")?; InstructionData::Binary { opcode: opcode, - ty: VOID, args: [lhs, rhs], } } @@ -1461,7 +1450,6 @@ impl<'a> Parser<'a> { let rhs = self.match_imm64("expected immediate integer second operand")?; InstructionData::BinaryImm { opcode: opcode, - ty: VOID, arg: lhs, imm: rhs, } @@ -1472,7 +1460,6 @@ impl<'a> Parser<'a> { let rhs = self.match_value("expected SSA value second operand")?; InstructionData::BinaryOverflow { opcode: opcode, - ty: VOID, args: [lhs, rhs], } } @@ -1486,7 +1473,6 @@ impl<'a> Parser<'a> { let false_arg = self.match_value("expected SSA value false operand")?; InstructionData::Ternary { opcode: opcode, - ty: VOID, args: [ctrl_arg, true_arg, false_arg], } } @@ -1494,7 +1480,6 @@ impl<'a> Parser<'a> { let args = self.parse_value_list()?; InstructionData::MultiAry { opcode: opcode, - ty: VOID, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } @@ -1504,7 +1489,6 @@ impl<'a> Parser<'a> { let args = self.parse_opt_value_list()?; InstructionData::Jump { opcode: opcode, - ty: VOID, destination: ebb_num, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } @@ -1516,7 +1500,6 @@ impl<'a> Parser<'a> { let args = self.parse_opt_value_list()?; InstructionData::Branch { opcode: opcode, - ty: VOID, destination: ebb_num, args: args.into_value_list(&[ctrl_arg], &mut ctx.function.dfg.value_lists), } @@ -1531,7 +1514,6 @@ impl<'a> Parser<'a> { let args = self.parse_opt_value_list()?; InstructionData::BranchIcmp { opcode: opcode, - ty: VOID, cond: cond, destination: ebb_num, args: args.into_value_list(&[lhs, rhs], &mut ctx.function.dfg.value_lists), @@ -1545,7 +1527,6 @@ impl<'a> Parser<'a> { let rhs = self.match_value("expected SSA value last operand")?; InstructionData::InsertLane { opcode: opcode, - ty: VOID, lane: lane, args: [lhs, rhs], } @@ -1556,7 +1537,6 @@ impl<'a> Parser<'a> { let lane = self.match_uimm8("expected lane number")?; InstructionData::ExtractLane { opcode: opcode, - ty: VOID, lane: lane, arg: arg, } @@ -1568,7 +1548,6 @@ impl<'a> Parser<'a> { let rhs = self.match_value("expected SSA value second operand")?; InstructionData::IntCompare { opcode: opcode, - ty: VOID, cond: cond, args: [lhs, rhs], } @@ -1580,7 +1559,6 @@ impl<'a> Parser<'a> { let rhs = self.match_imm64("expected immediate second operand")?; InstructionData::IntCompareImm { opcode: opcode, - ty: VOID, cond: cond, arg: lhs, imm: rhs, @@ -1593,7 +1571,6 @@ impl<'a> Parser<'a> { let rhs = self.match_value("expected SSA value second operand")?; InstructionData::FloatCompare { opcode: opcode, - ty: VOID, cond: cond, args: [lhs, rhs], } @@ -1606,7 +1583,6 @@ impl<'a> Parser<'a> { self.match_token(Token::RPar, "expected ')' after arguments")?; InstructionData::Call { opcode: opcode, - ty: VOID, func_ref: func_ref, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } @@ -1621,7 +1597,6 @@ impl<'a> Parser<'a> { self.match_token(Token::RPar, "expected ')' after arguments")?; InstructionData::IndirectCall { opcode: opcode, - ty: VOID, sig_ref: sig_ref, args: args.into_value_list(&[callee], &mut ctx.function.dfg.value_lists), } @@ -1633,7 +1608,6 @@ impl<'a> Parser<'a> { .and_then(|num| ctx.get_jt(num, &self.loc))?; InstructionData::BranchTable { opcode: opcode, - ty: VOID, arg: arg, table: table, } @@ -1644,7 +1618,6 @@ impl<'a> Parser<'a> { let offset = self.optional_offset32()?; InstructionData::StackLoad { opcode: opcode, - ty: VOID, stack_slot: ss, offset: offset, } @@ -1657,7 +1630,6 @@ impl<'a> Parser<'a> { let offset = self.optional_offset32()?; InstructionData::StackStore { opcode: opcode, - ty: VOID, arg: arg, stack_slot: ss, offset: offset, @@ -1668,7 +1640,6 @@ impl<'a> Parser<'a> { let offset = self.optional_uoffset32()?; InstructionData::HeapLoad { opcode: opcode, - ty: VOID, arg: addr, offset: offset, } @@ -1680,7 +1651,6 @@ impl<'a> Parser<'a> { let offset = self.optional_uoffset32()?; InstructionData::HeapStore { opcode: opcode, - ty: VOID, args: [arg, addr], offset: offset, } @@ -1691,7 +1661,6 @@ impl<'a> Parser<'a> { let offset = self.optional_offset32()?; InstructionData::Load { opcode: opcode, - ty: VOID, flags: flags, arg: addr, offset: offset, @@ -1705,7 +1674,6 @@ impl<'a> Parser<'a> { let offset = self.optional_offset32()?; InstructionData::Store { opcode: opcode, - ty: VOID, flags: flags, args: [arg, addr], offset: offset, From 23ae70cacf70f8419d26a11c69737e2db1fbd017 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 14:26:23 -0700 Subject: [PATCH 696/968] Flatten the Value reference representation. All values are now references into the value table, so drop the distinction between direct and table values. Direct values don't exist any more. Also remove the parser support for the 'vxNN' syntax. Only 'vNN' values can be parsed now. --- docs/cton_lexer.py | 3 +- filetests/isa/riscv/abi.cton | 1 - filetests/isa/riscv/expand-i32.cton | 2 +- filetests/isa/riscv/legalize-abi.cton | 2 +- filetests/isa/riscv/legalize-i64.cton | 2 +- filetests/isa/riscv/split-args.cton | 2 +- filetests/parser/rewrite.cton | 4 +- lib/cretonne/src/ir/dfg.rs | 155 +++++++++----------------- lib/cretonne/src/ir/entities.rs | 111 ++---------------- lib/cretonne/src/isa/riscv/mod.rs | 8 +- lib/cretonne/src/write.rs | 8 +- lib/reader/src/lexer.rs | 10 +- lib/reader/src/parser.rs | 50 +++++---- lib/reader/src/sourcemap.rs | 19 ++-- misc/vim/syntax/cton.vim | 2 +- 15 files changed, 115 insertions(+), 264 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index b40cb70e41..1c7224b593 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -47,10 +47,9 @@ class CretonneLexer(RegexLexer): # Well known value types. (r'\b(b\d+|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value - # vx = value # ss = stack slot # jt = jump table - (r'(vx?|ss|jt)\d+', Name.Variable), + (r'(v|ss|jt)\d+', Name.Variable), # ebb = extended basic block (r'(ebb)\d+', Name.Label), # Match instruction names in context. diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index d75813e220..d168fd768c 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -3,7 +3,6 @@ test legalizer isa riscv ; regex: V=v\d+ -; regex: VX=vx\d+ function f(i32) { sig0 = signature(i32) -> i32 diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index 0415c6026c..ce335fb551 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -7,7 +7,7 @@ isa riscv supports_m=1 set is_64bit=1 isa riscv supports_m=1 -; regex: V=vx?\d+ +; regex: V=v\d+ function carry_out(i32, i32) -> i32, b1 { ebb0(v1: i32, v2: i32): diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 06d68768bd..85b06b5755 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -2,7 +2,7 @@ test legalizer isa riscv -; regex: V=vx?\d+ +; regex: V=v\d+ function int_split_args(i64) -> i64 { ebb0(v0: i64): diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index dbe26f824c..dc8604f358 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -2,7 +2,7 @@ test legalizer isa riscv supports_m=1 -; regex: V=vx?\d+ +; regex: V=v\d+ function bitwise_and(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): diff --git a/filetests/isa/riscv/split-args.cton b/filetests/isa/riscv/split-args.cton index 8c69378f42..06b09bd6d1 100644 --- a/filetests/isa/riscv/split-args.cton +++ b/filetests/isa/riscv/split-args.cton @@ -2,7 +2,7 @@ test legalizer isa riscv -; regex: V=vx?\d+ +; regex: V=v\d+ function simple(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton index ffd1851114..e01391d156 100644 --- a/filetests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -12,13 +12,13 @@ test cat function defs() { ebb100(v20: i32): v1000 = iconst.i32x8 5 - vx200 = f64const 0x4.0p0 + v9200 = f64const 0x4.0p0 trap } ; sameln: function defs() { ; nextln: $ebb100($v20: i32): ; nextln: $v1000 = iconst.i32x8 5 -; nextln: $vx200 = f64const 0x1.0000000000000p2 +; nextln: $v9200 = f64const 0x1.0000000000000p2 ; nextln: trap ; nextln: } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 49392d8bfb..a6a85ba24c 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -2,7 +2,6 @@ use entity_map::{EntityMap, PrimaryEntityData}; use ir::builder::{InsertBuilder, ReplaceBuilder}; -use ir::entities::ExpandedValue; use ir::extfunc::ExtFuncData; use ir::instructions::{Opcode, InstructionData, CallInfo}; use ir::layout::Cursor; @@ -48,12 +47,8 @@ pub struct DataFlowGraph { /// - EBB arguments in `ebbs`. pub value_lists: ValueListPool, - /// Extended value table. Most `Value` references refer directly to their defining instruction. - /// Others index into this table. - /// - /// This is implemented directly with a `Vec` rather than an `EntityMap` because - /// the Value entity references can refer to two things -- an instruction or an extended value. - extended_values: Vec, + /// Primary value table with entries for all values. + values: EntityMap, /// Function signature table. These signatures are referenced by indirect call instructions as /// well as the external function references. @@ -65,6 +60,7 @@ pub struct DataFlowGraph { impl PrimaryEntityData for InstructionData {} impl PrimaryEntityData for EbbData {} +impl PrimaryEntityData for ValueData {} impl PrimaryEntityData for Signature {} impl PrimaryEntityData for ExtFuncData {} @@ -76,7 +72,7 @@ impl DataFlowGraph { results: EntityMap::new(), ebbs: EntityMap::new(), value_lists: ValueListPool::new(), - extended_values: Vec::new(), + values: EntityMap::new(), signatures: EntityMap::new(), ext_funcs: EntityMap::new(), } @@ -115,31 +111,20 @@ impl DataFlowGraph { impl DataFlowGraph { // Allocate an extended value entry. fn make_value(&mut self, data: ValueData) -> Value { - let vref = Value::new_table(self.extended_values.len()); - self.extended_values.push(data); - vref + self.values.push(data) } /// Check if a value reference is valid. pub fn value_is_valid(&self, v: Value) -> bool { - match v.expand() { - ExpandedValue::Direct(inst) => self.insts.is_valid(inst), - ExpandedValue::Table(index) => index < self.extended_values.len(), - } + self.values.is_valid(v) } /// Get the type of a value. pub fn value_type(&self, v: Value) -> Type { - use ir::entities::ExpandedValue::*; - match v.expand() { - Direct(_) => panic!("Unexpected direct value"), - Table(i) => { - match self.extended_values[i] { - ValueData::Inst { ty, .. } => ty, - ValueData::Arg { ty, .. } => ty, - ValueData::Alias { ty, .. } => ty, - } - } + match self.values[v] { + ValueData::Inst { ty, .. } | + ValueData::Arg { ty, .. } | + ValueData::Alias { ty, .. } => ty, } } @@ -148,31 +133,25 @@ impl DataFlowGraph { /// This is either the instruction that defined it or the Ebb that has the value as an /// argument. pub fn value_def(&self, v: Value) -> ValueDef { - use ir::entities::ExpandedValue::*; - match v.expand() { - Direct(inst) => ValueDef::Res(inst, 0), - Table(idx) => { - match self.extended_values[idx] { - ValueData::Inst { inst, num, .. } => { - assert_eq!(Some(v), - self.results[inst].get(num as usize, &self.value_lists), - "Dangling result value {}: {}", - v, - self.display_inst(inst)); - ValueDef::Res(inst, num as usize) - } - ValueData::Arg { ebb, num, .. } => { - assert_eq!(Some(v), - self.ebbs[ebb].args.get(num as usize, &self.value_lists), - "Dangling EBB argument value"); - ValueDef::Arg(ebb, num as usize) - } - ValueData::Alias { original, .. } => { - // Make sure we only recurse one level. `resolve_aliases` has safeguards to - // detect alias loops without overrunning the stack. - self.value_def(self.resolve_aliases(original)) - } - } + match self.values[v] { + ValueData::Inst { inst, num, .. } => { + assert_eq!(Some(v), + self.results[inst].get(num as usize, &self.value_lists), + "Dangling result value {}: {}", + v, + self.display_inst(inst)); + ValueDef::Res(inst, num as usize) + } + ValueData::Arg { ebb, num, .. } => { + assert_eq!(Some(v), + self.ebbs[ebb].args.get(num as usize, &self.value_lists), + "Dangling EBB argument value"); + ValueDef::Arg(ebb, num as usize) + } + ValueData::Alias { original, .. } => { + // Make sure we only recurse one level. `resolve_aliases` has safeguards to + // detect alias loops without overrunning the stack. + self.value_def(self.resolve_aliases(original)) } } } @@ -181,23 +160,15 @@ impl DataFlowGraph { /// /// Find the original SSA value that `value` aliases. pub fn resolve_aliases(&self, value: Value) -> Value { - use ir::entities::ExpandedValue::Table; let mut v = value; // Note that extended_values may be empty here. - for _ in 0..1 + self.extended_values.len() { - v = match v.expand() { - Table(idx) => { - match self.extended_values[idx] { - ValueData::Alias { original, .. } => { - // Follow alias values. - original - } - _ => return v, - } - } - _ => return v, - }; + for _ in 0..1 + self.values.len() { + if let ValueData::Alias { original, .. } = self.values[v] { + v = original; + } else { + return v; + } } panic!("Value alias loop detected for {}", value); } @@ -233,13 +204,7 @@ impl DataFlowGraph { /// /// Change the `dest` value to behave as an alias of `src`. This means that all uses of `dest` /// will behave as if they used that value `src`. - /// - /// The `dest` value cannot be a direct value defined as the first result of an instruction. To - /// replace a direct value with `src`, its defining instruction should be replaced with a - /// `copy src` instruction. See `replace()`. pub fn change_to_alias(&mut self, dest: Value, src: Value) { - use ir::entities::ExpandedValue::Table; - // Try to create short alias chains by finding the original source value. // This also avoids the creation of loops. let original = self.resolve_aliases(src); @@ -256,14 +221,10 @@ impl DataFlowGraph { self.value_type(dest), ty); - if let Table(idx) = dest.expand() { - self.extended_values[idx] = ValueData::Alias { - ty: ty, - original: original, - }; - } else { - panic!("Cannot change direct value {} into an alias", dest); - } + self.values[dest] = ValueData::Alias { + ty: ty, + original: original, + }; } /// Create a new value alias. @@ -458,15 +419,11 @@ impl DataFlowGraph { let num = self.results[inst].push(res, &mut self.value_lists); assert!(num <= u16::MAX as usize, "Too many result values"); let ty = self.value_type(res); - if let ExpandedValue::Table(idx) = res.expand() { - self.extended_values[idx] = ValueData::Inst { - ty: ty, - num: num as u16, - inst: inst, - }; - } else { - panic!("Unexpected direct value"); - } + self.values[res] = ValueData::Inst { + ty: ty, + num: num as u16, + inst: inst, + }; } /// Append a new instruction result value to `inst`. @@ -609,11 +566,7 @@ impl DataFlowGraph { /// /// Returns the new value. pub fn replace_ebb_arg(&mut self, old_arg: Value, new_type: Type) -> Value { - let old_data = if let ExpandedValue::Table(index) = old_arg.expand() { - self.extended_values[index].clone() - } else { - panic!("old_arg: {} must be an EBB argument", old_arg); - }; + let old_data = self.values[old_arg].clone(); // Create new value identical to the old one except for the type. let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = old_data { @@ -655,12 +608,10 @@ impl DataFlowGraph { // Now update `arg` itself. let arg_ebb = ebb; - if let ExpandedValue::Table(idx) = arg.expand() { - if let ValueData::Arg { ref mut num, ebb, .. } = self.extended_values[idx] { - *num = arg_num as u16; - assert_eq!(arg_ebb, ebb, "{} should already belong to EBB", arg); - return; - } + if let ValueData::Arg { ref mut num, ebb, .. } = self.values[arg] { + *num = arg_num as u16; + assert_eq!(arg_ebb, ebb, "{} should already belong to EBB", arg); + return; } panic!("{} must be an EBB argument value", arg); } @@ -724,7 +675,7 @@ mod tests { let inst = dfg.make_inst(idata); dfg.make_inst_results(inst, types::I32); assert_eq!(inst.to_string(), "inst0"); - assert_eq!(dfg.display_inst(inst).to_string(), "vx0 = iconst.i32"); + assert_eq!(dfg.display_inst(inst).to_string(), "v0 = iconst.i32"); // Immutable reference resolution. { @@ -766,12 +717,12 @@ mod tests { assert_eq!(dfg.ebb_args(ebb), &[]); let arg1 = dfg.append_ebb_arg(ebb, types::F32); - assert_eq!(arg1.to_string(), "vx0"); + assert_eq!(arg1.to_string(), "v0"); assert_eq!(dfg.num_ebb_args(ebb), 1); assert_eq!(dfg.ebb_args(ebb), &[arg1]); let arg2 = dfg.append_ebb_arg(ebb, types::I16); - assert_eq!(arg2.to_string(), "vx1"); + assert_eq!(arg2.to_string(), "v1"); assert_eq!(dfg.num_ebb_args(ebb), 2); assert_eq!(dfg.ebb_args(ebb), &[arg1, arg2]); @@ -835,7 +786,7 @@ mod tests { // Build a little test program. let v1 = dfg.ins(pos).iconst(types::I32, 42); - // Make sure we can resolve value aliases even when extended_values is empty. + // Make sure we can resolve value aliases even when values is empty. assert_eq!(dfg.resolve_aliases(v1), v1); let arg0 = dfg.append_ebb_arg(ebb0, types::I32); diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 876598d13a..813ec978c2 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -76,97 +76,20 @@ impl Ebb { /// An opaque reference to an SSA value. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Value(u32); -entity_impl!(Value); - -/// Value references can either reference an instruction directly, or they can refer to the -/// extended value table. -pub enum ExpandedValue { - /// This is the first value produced by the referenced instruction. - Direct(Inst), - - /// This value is described in the extended value table. - Table(usize), -} +entity_impl!(Value, "v"); impl Value { - /// Create a `Direct` value from its number representation. + /// Create a value from its number representation. /// This is the number in the `vNN` notation. /// /// This method is for use by the parser. - pub fn direct_with_number(n: u32) -> Option { + pub fn with_number(n: u32) -> Option { if n < u32::MAX / 2 { - let encoding = n * 2; - assert!(encoding < u32::MAX); - Some(Value(encoding)) + Some(Value(n)) } else { None } } - - /// Create a `Table` value from its number representation. - /// This is the number in the `vxNN` notation. - /// - /// This method is for use by the parser. - pub fn table_with_number(n: u32) -> Option { - if n < u32::MAX / 2 { - let encoding = n * 2 + 1; - assert!(encoding < u32::MAX); - Some(Value(encoding)) - } else { - None - } - } - - /// Create a `Direct` value corresponding to the first value produced by `i`. - pub fn new_direct(i: Inst) -> Value { - let encoding = i.index() * 2; - assert!(encoding < u32::MAX as usize); - Value(encoding as u32) - } - - /// Create a `Table` value referring to entry `i` in the `DataFlowGraph.extended_values` table. - /// This constructor should not be used directly. Use the public `DataFlowGraph` methods to - /// manipulate values. - pub fn new_table(index: usize) -> Value { - let encoding = index * 2 + 1; - assert!(encoding < u32::MAX as usize); - Value(encoding as u32) - } - - /// Expand the internal representation into something useful. - pub fn expand(&self) -> ExpandedValue { - use self::ExpandedValue::*; - let index = (self.0 / 2) as usize; - if self.0 % 2 == 0 { - Direct(Inst::new(index)) - } else { - Table(index) - } - } - - /// Assuming that this is a direct value, get the referenced instruction. - /// - /// # Panics - /// - /// If this is not a value created with `new_direct()`. - pub fn unwrap_direct(&self) -> Inst { - if let ExpandedValue::Direct(inst) = self.expand() { - inst - } else { - panic!("{} is not a direct value", self) - } - } -} - -/// Display a `Value` reference as "v7" or "v2x". -impl Display for Value { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - use self::ExpandedValue::*; - match self.expand() { - Direct(i) => write!(fmt, "v{}", i.0), - Table(i) => write!(fmt, "vx{}", i), - } - } } /// An opaque reference to an instruction in a function. @@ -276,32 +199,14 @@ impl From for AnyEntity { mod tests { use super::*; use std::u32; - use entity_map::EntityRef; #[test] fn value_with_number() { - assert_eq!(Value::direct_with_number(0).unwrap().to_string(), "v0"); - assert_eq!(Value::direct_with_number(1).unwrap().to_string(), "v1"); - assert_eq!(Value::table_with_number(0).unwrap().to_string(), "vx0"); - assert_eq!(Value::table_with_number(1).unwrap().to_string(), "vx1"); + assert_eq!(Value::with_number(0).unwrap().to_string(), "v0"); + assert_eq!(Value::with_number(1).unwrap().to_string(), "v1"); - assert_eq!(Value::direct_with_number(u32::MAX / 2), None); - assert_eq!(match Value::direct_with_number(u32::MAX / 2 - 1) - .unwrap() - .expand() { - ExpandedValue::Direct(i) => i.index() as u32, - _ => u32::MAX, - }, - u32::MAX / 2 - 1); - - assert_eq!(Value::table_with_number(u32::MAX / 2), None); - assert_eq!(match Value::table_with_number(u32::MAX / 2 - 1) - .unwrap() - .expand() { - ExpandedValue::Table(i) => i as u32, - _ => u32::MAX, - }, - u32::MAX / 2 - 1); + assert_eq!(Value::with_number(u32::MAX / 2), None); + assert!(Value::with_number(u32::MAX / 2 - 1).is_some()); } #[test] diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 8bf9d7b732..778e3b85aa 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -115,7 +115,7 @@ mod tests { let arg64 = dfg.append_ebb_arg(ebb, types::I64); let arg32 = dfg.append_ebb_arg(ebb, types::I32); - // Try to encode iadd_imm.i64 vx1, -10. + // Try to encode iadd_imm.i64 v1, -10. let inst64 = InstructionData::BinaryImm { opcode: Opcode::IaddImm, arg: arg64, @@ -126,7 +126,7 @@ mod tests { assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64, types::I64).unwrap()), "I#04"); - // Try to encode iadd_imm.i64 vx1, -10000. + // Try to encode iadd_imm.i64 v1, -10000. let inst64_large = InstructionData::BinaryImm { opcode: Opcode::IaddImm, arg: arg64, @@ -162,7 +162,7 @@ mod tests { let arg64 = dfg.append_ebb_arg(ebb, types::I64); let arg32 = dfg.append_ebb_arg(ebb, types::I32); - // Try to encode iadd_imm.i64 vx1, -10. + // Try to encode iadd_imm.i64 v1, -10. let inst64 = InstructionData::BinaryImm { opcode: Opcode::IaddImm, arg: arg64, @@ -173,7 +173,7 @@ mod tests { assert_eq!(isa.encode(&dfg, &inst64, types::I64), Err(isa::Legalize::Narrow)); - // Try to encode iadd_imm.i64 vx1, -10000. + // Try to encode iadd_imm.i64 v1, -10000. let inst64_large = InstructionData::BinaryImm { opcode: Opcode::IaddImm, arg: arg64, diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 023eb4ee7a..09e5ee6641 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -86,8 +86,8 @@ pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result { // Write out the basic block header, outdented: // // ebb1: - // ebb1(vx1: i32): - // ebb10(vx4: f64, vx5: b1): + // ebb1(v1: i32): + // ebb10(v4: f64, v5: b1): // // If we're writing encoding annotations, shift by 20. @@ -372,10 +372,10 @@ mod tests { f.dfg.append_ebb_arg(ebb, types::I8); assert_eq!(f.to_string(), - "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8):\n}\n"); + "function foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8):\n}\n"); f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(f.to_string(), - "function foo() {\n ss0 = stack_slot 4\n\nebb0(vx0: i8, vx1: f32x4):\n}\n"); + "function foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); } } diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 0e8eaccd0f..14a0fe45a4 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -34,7 +34,7 @@ pub enum Token<'a> { Float(&'a str), // Floating point immediate Integer(&'a str), // Integer immediate Type(types::Type), // i32, f32, b32x4, ... - Value(Value), // v12, vx7 + Value(Value), // v12, v7 Ebb(Ebb), // ebb3 StackSlot(u32), // ss3 JumpTable(u32), // jt2 @@ -306,8 +306,7 @@ impl<'a> Lexer<'a> { // decoded token. fn numbered_entity(prefix: &str, number: u32) -> Option> { match prefix { - "v" => Value::direct_with_number(number).map(|v| Token::Value(v)), - "vx" => Value::table_with_number(number).map(|v| Token::Value(v)), + "v" => Value::with_number(number).map(|v| Token::Value(v)), "ebb" => Ebb::with_number(number).map(|ebb| Token::Ebb(ebb)), "ss" => Some(Token::StackSlot(number)), "jt" => Some(Token::JumpTable(number)), @@ -531,15 +530,14 @@ mod tests { let mut lex = Lexer::new("v0 v00 vx01 ebb1234567890 ebb5234567890 v1x vx1 vxvx4 \ function0 function b1 i32x4 f32x5"); assert_eq!(lex.next(), - token(Token::Value(Value::direct_with_number(0).unwrap()), 1)); + token(Token::Value(Value::with_number(0).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("v00"), 1)); assert_eq!(lex.next(), token(Token::Identifier("vx01"), 1)); assert_eq!(lex.next(), token(Token::Ebb(Ebb::with_number(1234567890).unwrap()), 1)); assert_eq!(lex.next(), token(Token::Identifier("ebb5234567890"), 1)); assert_eq!(lex.next(), token(Token::Identifier("v1x"), 1)); - assert_eq!(lex.next(), - token(Token::Value(Value::table_with_number(1).unwrap()), 1)); + assert_eq!(lex.next(), token(Token::Identifier("vx1"), 1)); assert_eq!(lex.next(), token(Token::Identifier("vxvx4"), 1)); assert_eq!(lex.next(), token(Token::Identifier("function0"), 1)); assert_eq!(lex.next(), token(Token::Identifier("function"), 1)); diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index dcc76b1b15..ac2754a46c 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1011,7 +1011,7 @@ impl<'a> Parser<'a> { // We need to parse instruction results here because they are shared // between the parsing of value aliases and the parsing of instructions. // - // inst-results ::= Value(v) { "," Value(vx) } + // inst-results ::= Value(v) { "," Value(v) } let results = self.parse_inst_results()?; match self.token() { @@ -1032,7 +1032,7 @@ impl<'a> Parser<'a> { } // Parse parenthesized list of EBB arguments. Returns a vector of (u32, Type) pairs with the - // source vx numbers of the defined values and the defined types. + // source value numbers of the defined values and the defined types. // // ebb-args ::= * "(" ebb-arg { "," ebb-arg } ")" fn parse_ebb_args(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { @@ -1056,19 +1056,19 @@ impl<'a> Parser<'a> { // Parse a single EBB argument declaration, and append it to `ebb`. // - // ebb-arg ::= * Value(vx) ":" Type(t) + // ebb-arg ::= * Value(v) ":" Type(t) // fn parse_ebb_arg(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { - // ebb-arg ::= * Value(vx) ":" Type(t) - let vx = self.match_value("EBB argument must be a value")?; - let vx_location = self.loc; - // ebb-arg ::= Value(vx) * ":" Type(t) + // ebb-arg ::= * Value(v) ":" Type(t) + let v = self.match_value("EBB argument must be a value")?; + let v_location = self.loc; + // ebb-arg ::= Value(v) * ":" Type(t) self.match_token(Token::Colon, "expected ':' after EBB argument")?; - // ebb-arg ::= Value(vx) ":" * Type(t) + // ebb-arg ::= Value(v) ":" * Type(t) let t = self.match_type("expected EBB argument type")?; // Allocate the EBB argument and add the mapping. let value = ctx.function.dfg.append_ebb_arg(ebb, t); - ctx.map.def_value(vx, value, &vx_location) + ctx.map.def_value(v, value, &v_location) } fn parse_value_location(&mut self, ctx: &Context) -> Result { @@ -1147,21 +1147,21 @@ impl<'a> Parser<'a> { // Parse instruction results and return them. // - // inst-results ::= Value(v) { "," Value(vx) } + // inst-results ::= Value(v) { "," Value(v) } // fn parse_inst_results(&mut self) -> Result> { // Result value numbers. let mut results = Vec::new(); // instruction ::= * [inst-results "="] Opcode(opc) ["." Type] ... - // inst-results ::= * Value(v) { "," Value(vx) } + // inst-results ::= * Value(v) { "," Value(v) } if let Some(Token::Value(v)) = self.token() { self.consume(); results.push(v); - // inst-results ::= Value(v) * { "," Value(vx) } + // inst-results ::= Value(v) * { "," Value(v) } while self.optional(Token::Comma) { - // inst-results ::= Value(v) { "," * Value(vx) } + // inst-results ::= Value(v) { "," * Value(v) } results.push(self.match_value("expected result value")?); } } @@ -1171,7 +1171,7 @@ impl<'a> Parser<'a> { // Parse a value alias, and append it to `ebb`. // - // value_alias ::= [inst-results] "->" Value(vx) + // value_alias ::= [inst-results] "->" Value(v) // fn parse_value_alias(&mut self, results: Vec, ctx: &mut Context) -> Result<()> { if results.len() != 1 { @@ -1711,19 +1711,23 @@ mod tests { let (func, details) = Parser::new("function qux() { ebb0: v4 = iconst.i8 6 - vx3 -> v4 - v1 = iadd_imm vx3, 17 + v3 -> v4 + v1 = iadd_imm v3, 17 }") .parse_function(None) .unwrap(); assert_eq!(func.name.to_string(), "qux"); let v4 = details.map.lookup_str("v4").unwrap(); - assert_eq!(v4.to_string(), "vx0"); - let vx3 = details.map.lookup_str("vx3").unwrap(); - assert_eq!(vx3.to_string(), "vx2"); - let aliased_to = func.dfg - .resolve_aliases(Value::table_with_number(0).unwrap()); - assert_eq!(aliased_to.to_string(), "vx0"); + assert_eq!(v4.to_string(), "v0"); + let v3 = details.map.lookup_str("v3").unwrap(); + assert_eq!(v3.to_string(), "v2"); + match v3 { + AnyEntity::Value(v3) => { + let aliased_to = func.dfg.resolve_aliases(v3); + assert_eq!(aliased_to.to_string(), "v0"); + } + _ => panic!("expected value: {}", v3), + } } #[test] @@ -1789,7 +1793,7 @@ mod tests { fn ebb_header() { let (func, _) = Parser::new("function ebbs() { ebb0: - ebb4(vx3: i32): + ebb4(v3: i32): }") .parse_function(None) .unwrap(); diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 7f387ccee2..f84c792d89 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -16,7 +16,7 @@ use lexer::split_entity_name; /// Mapping from source entity names to entity references that are valid in the parsed function. #[derive(Debug)] pub struct SourceMap { - values: HashMap, // vNN, vxNN + values: HashMap, // vNN ebbs: HashMap, // ebbNN stack_slots: HashMap, // ssNN signatures: HashMap, // sigNN @@ -64,12 +64,7 @@ impl SourceMap { pub fn lookup_str(&self, name: &str) -> Option { split_entity_name(name).and_then(|(ent, num)| match ent { "v" => { - Value::direct_with_number(num) - .and_then(|v| self.get_value(v)) - .map(AnyEntity::Value) - } - "vx" => { - Value::table_with_number(num) + Value::with_number(num) .and_then(|v| self.get_value(v)) .map(AnyEntity::Value) } @@ -230,8 +225,8 @@ mod tests { let tf = parse_test("function detail() { ss10 = stack_slot 13 jt10 = jump_table ebb0 - ebb0(v4: i32, vx7: i32): - v10 = iadd v4, vx7 + ebb0(v4: i32, v7: i32): + v10 = iadd v4, v7 }") .unwrap(); let map = &tf.functions[0].1.map; @@ -241,8 +236,8 @@ mod tests { assert_eq!(map.lookup_str("ss10").unwrap().to_string(), "ss0"); assert_eq!(map.lookup_str("jt10").unwrap().to_string(), "jt0"); assert_eq!(map.lookup_str("ebb0").unwrap().to_string(), "ebb0"); - assert_eq!(map.lookup_str("v4").unwrap().to_string(), "vx0"); - assert_eq!(map.lookup_str("vx7").unwrap().to_string(), "vx1"); - assert_eq!(map.lookup_str("v10").unwrap().to_string(), "vx2"); + assert_eq!(map.lookup_str("v4").unwrap().to_string(), "v0"); + assert_eq!(map.lookup_str("v7").unwrap().to_string(), "v1"); + assert_eq!(map.lookup_str("v10").unwrap().to_string(), "v2"); } } diff --git a/misc/vim/syntax/cton.vim b/misc/vim/syntax/cton.vim index 7d6bdab08c..86162d8a50 100644 --- a/misc/vim/syntax/cton.vim +++ b/misc/vim/syntax/cton.vim @@ -18,7 +18,7 @@ syn keyword ctonDecl function stack_slot jump_table syn keyword ctonFilecheck check sameln nextln unordered not regex contained syn match ctonType /\<[bif]\d\+\(x\d\+\)\?\>/ -syn match ctonEntity /\<\(v\|vx\|ss\|jt\|fn\|sig\)\d\+\>/ +syn match ctonEntity /\<\(v\|ss\|jt\|fn\|sig\)\d\+\>/ syn match ctonLabel /\/ syn match ctonName /%\w\+\>/ From e2f8924c30529ac9301a09ec5b93d06f6c7fc843 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 15:12:58 -0700 Subject: [PATCH 697/968] Add some safety checks for detached values. These methods are used to reattach detached values: - change_to_alias - attach_result - attach_ebb_arg Add an assertion to all of them to ensure that the provided value is not already attached somewhere else. Use a new value_is_attached() method for the test. Also include a verifier check for uses of detached values. --- lib/cretonne/src/ir/dfg.rs | 56 +++++++++++++++++++++++++++--------- lib/cretonne/src/verifier.rs | 7 ++++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index a6a85ba24c..16af22cef6 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -156,6 +156,21 @@ impl DataFlowGraph { } } + /// Determine if `v` is an attached instruction result / EBB argument. + /// + /// An attached value can't be attached to something else without first being detached. + /// + /// Value aliases are not considered to be attached to anything. Use `resolve_aliases()` to + /// determine if the original aliased value is attached. + pub fn value_is_attached(&self, v: Value) -> bool { + use self::ValueData::*; + match self.values[v] { + Inst { inst, num, .. } => Some(&v) == self.inst_results(inst).get(num as usize), + Arg { ebb, num, .. } => Some(&v) == self.ebb_args(ebb).get(num as usize), + Alias { .. } => false, + } + } + /// Resolve value aliases. /// /// Find the original SSA value that `value` aliases. @@ -204,7 +219,10 @@ impl DataFlowGraph { /// /// Change the `dest` value to behave as an alias of `src`. This means that all uses of `dest` /// will behave as if they used that value `src`. + /// + /// The `dest` value can't be attached to an instruction or EBB. pub fn change_to_alias(&mut self, dest: Value, src: Value) { + assert!(!self.value_is_attached(dest)); // Try to create short alias chains by finding the original source value. // This also avoids the creation of loops. let original = self.resolve_aliases(src); @@ -409,6 +427,15 @@ impl DataFlowGraph { self.results[inst].take() } + /// Clear the list of result values from `inst`. + /// + /// This leaves `inst` without any result values. New result values can be created by calling + /// `make_inst_results` or by using a `replace(inst)` builder. + pub fn clear_results(&mut self, inst: Inst) { + self.results[inst].clear(&mut self.value_lists) + } + + /// Attach an existing value to the result value list for `inst`. /// /// The `res` value is appended to the end of the result list. @@ -416,6 +443,7 @@ impl DataFlowGraph { /// This is a very low-level operation. Usually, instruction results with the correct types are /// created automatically. The `res` value must not be attached to anything else. pub fn attach_result(&mut self, inst: Inst, res: Value) { + assert!(!self.value_is_attached(res)); let num = self.results[inst].push(res, &mut self.value_lists); assert!(num <= u16::MAX as usize, "Too many result values"); let ty = self.value_type(res); @@ -597,23 +625,19 @@ impl DataFlowGraph { /// Append an existing argument value to `ebb`. /// - /// The appended value should already be an EBB argument belonging to `ebb`, but it can't be - /// attached. In practice, this means that it should be one of the values returned from - /// `detach_ebb_args()`. + /// The appended value can't already be attached to something else. /// /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. pub fn attach_ebb_arg(&mut self, ebb: Ebb, arg: Value) { - let arg_num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); - assert!(arg_num <= u16::MAX as usize, "Too many arguments to EBB"); - - // Now update `arg` itself. - let arg_ebb = ebb; - if let ValueData::Arg { ref mut num, ebb, .. } = self.values[arg] { - *num = arg_num as u16; - assert_eq!(arg_ebb, ebb, "{} should already belong to EBB", arg); - return; - } - panic!("{} must be an EBB argument value", arg); + assert!(!self.value_is_attached(arg)); + let num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); + assert!(num <= u16::MAX as usize, "Too many arguments to EBB"); + let ty = self.value_type(arg); + self.values[arg] = ValueData::Arg { + ty: ty, + num: num as u16, + ebb: ebb, + }; } } @@ -796,6 +820,10 @@ mod tests { _ => panic!(), }; + // Remove `c` from the result list. + dfg.clear_results(iadd); + dfg.attach_result(iadd, s); + // Replace `iadd_cout` with a normal `iadd` and an `icmp`. dfg.replace(iadd).iadd(v1, arg0); let c2 = dfg.ins(pos).icmp(IntCC::UnsignedLessThan, s, v1); diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index 7a287a64bd..ba2f03ec9d 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -13,7 +13,6 @@ //! //! - The instruction format must match the opcode. //! - All result values must be created for multi-valued instructions. -//! - Instructions with no results must have a VOID `first_type()`. //! - All referenced entities must exist. (Values, EBBs, stack slots, ...) //! //! SSA form @@ -200,6 +199,12 @@ impl<'a> Verifier<'a> { for &arg in self.func.dfg.inst_args(inst) { self.verify_value(inst, arg)?; + + // All used values must be attached to something. + let original = self.func.dfg.resolve_aliases(arg); + if !self.func.dfg.value_is_attached(original) { + return err!(inst, "argument {} -> {} is not attached", arg, original); + } } for &res in self.func.dfg.inst_results(inst) { From bcb3882ef602baf54d6edf6ddaa84cddee9c71de Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 17:06:27 -0700 Subject: [PATCH 698/968] Add remove_inst() methods to Cursor and Layout. We didn't have a way of removing instructions again. --- lib/cretonne/src/ir/layout.rs | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 4f5d2355b2..aec53422f7 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -460,6 +460,32 @@ impl Layout { self.assign_inst_seq(inst); } + /// Remove `inst` from the layout. + pub fn remove_inst(&mut self, inst: Inst) { + let ebb = self.inst_ebb(inst) + .expect("Instruction already removed."); + // Clear the `inst` node and extract links. + let prev; + let next; + { + let n = &mut self.insts[inst]; + prev = n.prev; + next = n.next; + n.ebb = None.into(); + n.prev = None.into(); + n.next = None.into(); + } + // Fix up links to `inst`. + match prev.expand() { + None => self.ebbs[ebb].first_inst = next, + Some(p) => self.insts[p].next = next, + } + match next.expand() { + None => self.ebbs[ebb].last_inst = prev, + Some(n) => self.insts[n].prev = prev, + } + } + /// Iterate over the instructions in `ebb` in layout order. pub fn ebb_insts<'f>(&'f self, ebb: Ebb) -> Insts<'f> { Insts { @@ -881,6 +907,15 @@ impl<'f> Cursor<'f> { } } + /// Remove the instruction under the cursor. + /// + /// The cursor is left pointing at the position following the current instruction. + pub fn remove_inst(&mut self) { + let inst = self.current_inst().expect("No instruction to remove"); + self.next_inst(); + self.layout.remove_inst(inst); + } + /// Insert an EBB at the current position and switch to it. /// /// As far as possible, this method behaves as if the EBB header were an instruction inserted @@ -1142,6 +1177,19 @@ mod tests { assert_eq!(cur.prev_inst(), Some(i1)); assert_eq!(cur.prev_inst(), None); assert_eq!(cur.position(), CursorPosition::Before(e1)); + + // Test remove_inst. + cur.goto_inst(i2); + cur.remove_inst(); + verify(cur.layout, &[(e1, &[i1, i0])]); + assert_eq!(cur.layout.inst_ebb(i2), None); + cur.remove_inst(); + verify(cur.layout, &[(e1, &[i1])]); + assert_eq!(cur.layout.inst_ebb(i0), None); + assert_eq!(cur.position(), CursorPosition::After(e1)); + cur.layout.remove_inst(i1); + verify(cur.layout, &[(e1, &[])]); + assert_eq!(cur.layout.inst_ebb(i1), None); } #[test] From 2e64bb88baea633ea9b340b4d875246db40353a8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 08:18:16 -0700 Subject: [PATCH 699/968] Return the removed instruction from Cursor::remove_inst(). This is convenient for asserting that the right instruction was removed. --- lib/cretonne/src/ir/layout.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index aec53422f7..6e3e793125 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -910,10 +910,13 @@ impl<'f> Cursor<'f> { /// Remove the instruction under the cursor. /// /// The cursor is left pointing at the position following the current instruction. - pub fn remove_inst(&mut self) { + /// + /// Return the instruction that was removed. + pub fn remove_inst(&mut self) -> Inst { let inst = self.current_inst().expect("No instruction to remove"); self.next_inst(); self.layout.remove_inst(inst); + inst } /// Insert an EBB at the current position and switch to it. @@ -1180,10 +1183,10 @@ mod tests { // Test remove_inst. cur.goto_inst(i2); - cur.remove_inst(); + assert_eq!(cur.remove_inst(), i2); verify(cur.layout, &[(e1, &[i1, i0])]); assert_eq!(cur.layout.inst_ebb(i2), None); - cur.remove_inst(); + assert_eq!(cur.remove_inst(), i0); verify(cur.layout, &[(e1, &[i1])]); assert_eq!(cur.layout.inst_ebb(i0), None); assert_eq!(cur.position(), CursorPosition::After(e1)); From 8252b05b924488882522e3c15cc59523f21de59c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 08:15:44 -0700 Subject: [PATCH 700/968] Add a make_inst_results_reusing() generic method. This is a generalization of the existing make_inst_results() which lets you provide some or all of the result values instead of creating all new ones. --- lib/cretonne/src/ir/dfg.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 16af22cef6..ad22a03fa7 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -10,6 +10,7 @@ use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueLis use write::write_operands; use std::fmt; +use std::iter; use std::ops::{Index, IndexMut}; use std::u16; @@ -368,6 +369,22 @@ impl DataFlowGraph { /// `InstructionData` passed to `make_inst`. If this function is called with a single-result /// instruction, that is the only effect. pub fn make_inst_results(&mut self, inst: Inst, ctrl_typevar: Type) -> usize { + self.make_inst_results_reusing(inst, ctrl_typevar, iter::empty()) + } + + /// Create result values for `inst`, reusing the provided detached values. + /// + /// Create a new set of result values for `inst` using `ctrl_typevar` to determine the result + /// types. Any values provided by `reuse` will be reused. When `reuse` is exhausted or when it + /// produces `None`, a new value is created. + pub fn make_inst_results_reusing(&mut self, + inst: Inst, + ctrl_typevar: Type, + reuse: I) + -> usize + where I: Iterator> + { + let mut reuse = reuse.fuse(); let constraints = self.insts[inst].opcode().constraints(); let fixed_results = constraints.fixed_results(); let mut total_results = fixed_results; @@ -376,7 +393,13 @@ impl DataFlowGraph { // The fixed results will appear at the front of the list. for res_idx in 0..fixed_results { - self.append_result(inst, constraints.result_type(res_idx, ctrl_typevar)); + let ty = constraints.result_type(res_idx, ctrl_typevar); + if let Some(Some(v)) = reuse.next() { + debug_assert_eq!(self.value_type(v), ty, "Reused {} is wrong type", ty); + self.attach_result(inst, v); + } else { + self.append_result(inst, ty); + } } // Get the call signature if this is a function call. @@ -386,7 +409,12 @@ impl DataFlowGraph { total_results += var_results; for res_idx in 0..var_results { let ty = self.signatures[sig].return_types[res_idx].value_type; - self.append_result(inst, ty); + if let Some(Some(v)) = reuse.next() { + debug_assert_eq!(self.value_type(v), ty, "Reused {} is wrong type", ty); + self.attach_result(inst, v); + } else { + self.append_result(inst, ty); + } } } From e58218361977b95ab692817bc4a698f35ac529b0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 08:50:32 -0700 Subject: [PATCH 701/968] Add a with_results() method to the InsertBuilder. This makes it possible to reuse one or more result values in the instruction that is being inserted. Also add a with_result(v) method for the common case of reusing a single result value. This could be specialized in the future. --- lib/cretonne/src/ir/builder.rs | 87 +++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 0414f7c282..d9ef0faa25 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -58,6 +58,36 @@ impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { -> InsertBuilder<'c, 'fc, 'fd> { InsertBuilder { dfg: dfg, pos: pos } } + + /// Reuse result values in `reuse`. + /// + /// Convert this builder into one that will reuse the provided result values instead of + /// allocating new ones. The provided values for reuse must not be attached to anything. Any + /// missing result values will be allocated as normal. + /// + /// The `reuse` argument is expected to be an array of `Option`. + pub fn with_results(self, reuse: Array) -> InsertReuseBuilder<'c, 'fc, 'fd, Array> + where Array: AsRef<[Option]> + { + InsertReuseBuilder { + dfg: self.dfg, + pos: self.pos, + reuse: reuse, + } + } + + /// Reuse a single result value. + /// + /// Convert this into a builder that will reuse `v` as the single result value. The reused + /// result value `v` must not be attached to anything. + /// + /// This method should only be used when building an instruction with exactly one result. Use + /// `with_results()` for the more general case. + pub fn with_result(self, v: Value) -> InsertReuseBuilder<'c, 'fc, 'fd, [Option; 1]> { + // TODO: Specialize this to return a different builder that just attaches `v` instead of + // calling `make_inst_results_reusing()`. + self.with_results([Some(v)]) + } } impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { @@ -77,6 +107,37 @@ impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { } } +/// Builder that inserts a new instruction like `InsertBuilder`, but reusing result values. +pub struct InsertReuseBuilder<'c, 'fc: 'c, 'fd, Array> + where Array: AsRef<[Option]> +{ + pos: &'c mut Cursor<'fc>, + dfg: &'fd mut DataFlowGraph, + reuse: Array, +} + +impl<'c, 'fc, 'fd, Array> InstBuilderBase<'fd> for InsertReuseBuilder<'c, 'fc, 'fd, Array> + where Array: AsRef<[Option]> +{ + fn data_flow_graph(&self) -> &DataFlowGraph { + self.dfg + } + + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + self.dfg + } + + fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'fd mut DataFlowGraph) { + let inst = self.dfg.make_inst(data); + // Make an `Interator>`. + let ru = self.reuse.as_ref().iter().cloned(); + self.dfg + .make_inst_results_reusing(inst, ctrl_typevar, ru); + self.pos.insert_inst(inst); + (inst, self.dfg) + } +} + /// Instruction builder that replaces an existing instruction. /// /// The inserted instruction will have the same `Inst` number as the old one. @@ -125,7 +186,7 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { #[cfg(test)] mod tests { - use ir::{Function, Cursor, InstBuilder}; + use ir::{Function, Cursor, InstBuilder, ValueDef}; use ir::types::*; use ir::condcodes::*; @@ -150,4 +211,28 @@ mod tests { let cmp = dfg.ins(pos).icmp(IntCC::Equal, arg0, v0); assert_eq!(dfg.value_type(cmp), B1); } + + #[test] + fn reuse_results() { + let mut func = Function::new(); + let dfg = &mut func.dfg; + let ebb0 = dfg.make_ebb(); + let arg0 = dfg.append_ebb_arg(ebb0, I32); + let pos = &mut Cursor::new(&mut func.layout); + pos.insert_ebb(ebb0); + + let v0 = dfg.ins(pos).iadd_imm(arg0, 17); + assert_eq!(dfg.value_type(v0), I32); + let iadd = pos.prev_inst().unwrap(); + assert_eq!(dfg.value_def(v0), ValueDef::Res(iadd, 0)); + + // Detach v0 and reuse it for a different instruction. + dfg.clear_results(iadd); + let v0b = dfg.ins(pos).with_result(v0).iconst(I32, 3); + assert_eq!(v0, v0b); + assert_eq!(pos.current_inst(), Some(iadd)); + let iconst = pos.prev_inst().unwrap(); + assert!(iadd != iconst); + assert_eq!(dfg.value_def(v0), ValueDef::Res(iconst, 0)); + } } From 3ae0fe6e2bfa1fcc8d43a1b65e76f26ed2f27840 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Apr 2017 17:19:28 -0700 Subject: [PATCH 702/968] Avoid creating aliases when expanding legalizer patterns. Now that we can detach and reuse all values, there is no longer a need to create a lot of alias values during pattern expansion. Instead, reuse the values from the source pattern when emitting instructions in the destination pattern. If a destination instruction produces the exact same values as a source instruction, simply leave the values attached and replace the instruction it. Otherwise, detach the source values, reuse them in the expansion, and remove the source instruction afterwards. --- filetests/isa/riscv/expand-i32.cton | 4 +- lib/cretonne/meta/gen_legalizer.py | 86 ++++++++++++++--------------- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index ce335fb551..fe93521567 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -15,9 +15,7 @@ ebb0(v1: i32, v2: i32): return v3, v4 } ; check: $v3 = iadd $v1, $v2 -; check: $(cout=$V) = icmp ult $v3, $v1 -; It's possible the legalizer will rewrite these value aliases in the future. -; check: $v4 -> $cout +; check: $v4 = icmp ult $v3, $v1 ; check: return $v3, $v4 ; Expanding illegal immediate constants. diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 1a8d59d7fa..1376e3bc21 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -22,7 +22,7 @@ except ImportError: def unwrap_inst(iref, node, fmt): - # type: (str, Def, Formatter) -> None + # type: (str, Def, Formatter) -> bool """ Given a `Def` node, emit code that extracts all the instruction fields from `dfg[iref]`. @@ -31,7 +31,8 @@ def unwrap_inst(iref, node, fmt): :param iref: Name of the `Inst` reference to unwrap. :param node: `Def` node providing variable names. - + :returns: True if the instruction arguments were not detached, expecting a + replacement instruction to overwrite the original. """ fmt.comment('Unwrap {}'.format(node)) expr = node.expr @@ -76,31 +77,35 @@ def unwrap_inst(iref, node, fmt): if isinstance(v, Var) and v.has_free_typevar(): fmt.line('let typeof_{0} = dfg.value_type({0});'.format(v)) - # If the node has multiple results, detach the values. - # Place the secondary values in 'src_{}' locals. - if len(node.defs) > 1: + # If the node has results, detach the values. + # Place the values in locals. + replace_inst = False + if len(node.defs) > 0: if node.defs == node.defs[0].dst_def.defs: # Special case: The instruction replacing node defines the exact # same values. fmt.comment( - 'Multiple results handled by {}.' + 'Results handled by {}.' .format(node.defs[0].dst_def)) + replace_inst = True else: - fmt.comment('Detaching secondary results.') - # Boring case: Detach the secondary values, capture them in locals. - for d in node.defs[1:]: - fmt.line('let src_{};'.format(d)) + # Boring case: Detach the result values, capture them in locals. + fmt.comment('Detaching results.') + for d in node.defs: + fmt.line('let {};'.format(d)) with fmt.indented('{', '}'): fmt.line('let r = dfg.inst_results(inst);') - for i in range(1, len(node.defs)): - fmt.line('src_{} = r[{}];'.format(node.defs[i], i)) - fmt.line('dfg.detach_secondary_results(inst);') - for d in node.defs[1:]: + for i in range(len(node.defs)): + fmt.line('{} = r[{}];'.format(node.defs[i], i)) + fmt.line('dfg.clear_results(inst);') + for d in node.defs: if d.has_free_typevar(): fmt.line( - 'let typeof_{0} = dfg.value_type(src_{0});' + 'let typeof_{0} = dfg.value_type({0});' .format(d)) + return replace_inst + def wrap_tup(seq): # type: (Sequence[object]) -> str @@ -125,9 +130,7 @@ def is_value_split(node): def emit_dst_inst(node, fmt): # type: (Def, Formatter) -> None - exact_replace = False replaced_inst = None # type: str - fixup_first_result = False if is_value_split(node): # Split instructions are not emitted with the builder, but by calling @@ -146,21 +149,28 @@ def emit_dst_inst(node, fmt): builder = 'dfg.ins(pos)' else: src_def0 = node.defs[0].src_def - if src_def0 and node.defs[0] == src_def0.defs[0]: - # The primary result is replacing the primary result of the - # source pattern. + if src_def0 and node.defs == src_def0.defs: + # The replacement instruction defines the exact same values as + # the source pattern. Unwrapping would have left the results + # intact. # Replace the whole instruction. builder = 'let {} = dfg.replace(inst)'.format( wrap_tup(node.defs)) replaced_inst = 'inst' - # Secondary values weren't replaced if this is an exact - # replacement for all the source results. - exact_replace = (node.defs == src_def0.defs) else: - # Insert a new instruction since its primary def doesn't match - # the source. + # Insert a new instruction. builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) - fixup_first_result = node.defs[0].is_output() + # We may want to reuse some of the detached output values. + if len(node.defs) == 1 and node.defs[0].is_output(): + # Reuse the single source result value. + builder += '.with_result({})'.format(node.defs[0]) + elif any(d.is_output() for d in node.defs): + # We have some output values to be reused. + array = ', '.join( + ('Some({})'.format(d) if d.is_output() + else 'None') + for d in node.defs) + builder += '.with_results([{}])'.format(array) fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs))) @@ -172,23 +182,6 @@ def emit_dst_inst(node, fmt): .format(replaced_inst), '}'): fmt.line('pos.next_inst();') - # Fix up any output vars. - if fixup_first_result: - # The first result of the instruction just inserted is an output var, - # but it was not a primary result in the source pattern. - # We need to change the original value to an alias of the primary one - # we just inserted. - fmt.line('dfg.change_to_alias(src_{0}, {0});'.format(node.defs[0])) - - if not exact_replace: - # We don't support secondary values as outputs yet. Depending on the - # source value, we would need to : - # 1. For a primary source value, replace with a copy instruction. - # 2. For a secondary source value, request that the builder reuses the - # value when making secondary result nodes. - for d in node.defs[1:]: - assert not d.is_output() - def gen_xform(xform, fmt): # type: (XForm, Formatter) -> None @@ -202,7 +195,7 @@ def gen_xform(xform, fmt): """ # Unwrap the source instruction, create local variables for the input # variables. - unwrap_inst('inst', xform.src.rtl[0], fmt) + replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt) # We could support instruction predicates, but not yet. Should we just # return false if it fails? What about multiple patterns with different @@ -214,6 +207,11 @@ def gen_xform(xform, fmt): for dst in xform.dst.rtl: emit_dst_inst(dst, fmt) + # Delete the original instruction if we didn't have an opportunity to + # replace it. + if not replace_inst: + fmt.line('assert_eq!(pos.remove_inst(), inst);') + def gen_xform_group(xgrp, fmt): # type: (XFormGroup, Formatter) -> None From c716d86c8d887a3adbc52c369264fda50d961f84 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 09:45:36 -0700 Subject: [PATCH 703/968] Avoid creating value aliases in legalizer/split.rs. When we're splitting an EBB argument, we insert a iconcat/vconcat instruction that computes the original value from the new split arguments. The concat instruction can now define the original value directly, it is not necessary to define a new value and alias the old one. --- lib/cretonne/src/legalizer/split.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index 3c03d1d77b..764448b5bc 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -228,9 +228,9 @@ fn split_value(dfg: &mut DataFlowGraph, // need to insert a split instruction before returning. pos.goto_top(ebb); pos.next_inst(); - let concat_inst = dfg.ins(pos).Binary(concat, split_type, lo, hi).0; - let concat_val = dfg.first_result(concat_inst); - dfg.change_to_alias(value, concat_val); + dfg.ins(pos) + .with_result(value) + .Binary(concat, split_type, lo, hi); // Finally, splitting the EBB argument is not enough. We also have to repair all // of the predecessor instructions that branch here. From f9d3e654194c96d2dad9d7436794db443727a18a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 10:16:58 -0700 Subject: [PATCH 704/968] Don't create value aliases when legalizing ABI boundaries. When converting from ABI types to original program types, the final conversion instruction can place its result into the original value, so it doesn't need to be changed to an alias. --- filetests/isa/riscv/legalize-abi.cton | 25 +++++++------- lib/cretonne/src/legalizer/boundary.rs | 45 +++++++++++++++----------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 85b06b5755..88d35e699a 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -7,7 +7,7 @@ isa riscv function int_split_args(i64) -> i64 { ebb0(v0: i64): ; check: $ebb0($(v0l=$V): i32, $(v0h=$V): i32): - ; check: iconcat $v0l, $v0h + ; check: $v0 = iconcat $v0l, $v0h v1 = iadd_imm v0, 1 ; check: $(v1l=$V), $(v1h=$V) = isplit $v1 ; check: return $v1l, $v1h @@ -33,10 +33,9 @@ ebb0: v1 = call fn1() ; check: $ebb0: ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() - ; check: $(v1new=$V) = iconcat $v1l, $v1h + ; check: $v1 = iconcat $v1l, $v1h jump ebb1(v1) - ; The v1 alias gets resolved by split::simplify_branch_arguments(). - ; check: jump $ebb1($v1new) + ; check: jump $ebb1($v1) ebb1(v10: i64): jump ebb1(v10) @@ -49,10 +48,9 @@ ebb0: v1, v2 = call fn1() ; check: $ebb0: ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() - ; check: $(v2new=$V) = iconcat $v2l, $v2h + ; check: $v2 = iconcat $v2l, $v2h jump ebb1(v1, v2) - ; The v2 -> v2new alias is resolved by split::simplify_branch_arguments(). - ; check: jump $ebb1($v1, $v2new) + ; check: jump $ebb1($v1, $v2) ebb1(v9: i32, v10: i64): jump ebb1(v9, v10) @@ -61,8 +59,8 @@ ebb1(v9: i32, v10: i64): function int_ext(i8, i8 sext, i8 uext) -> i8 uext { ebb0(v1: i8, v2: i8, v3: i8): ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32): - ; check: ireduce.i8 $v2x - ; check: ireduce.i8 $v3x + ; check: $v2 = ireduce.i8 $v2x + ; check: $v3 = ireduce.i8 $v3x ; check: $(v1x=$V) = uextend.i32 $v1 ; check: return $v1x return v1 @@ -75,10 +73,9 @@ ebb0: v1 = call fn1() ; check: $ebb0: ; nextln: $(rv=$V) = call $fn1() - ; check: $(v1new=$V) = ireduce.i8 $rv + ; check: $v1 = ireduce.i8 $rv jump ebb1(v1) - ; The v1 alias gets resolved by split::simplify_branch_arguments(). - ; check: jump $ebb1($v1new) + ; check: jump $ebb1($v1) ebb1(v10: i8): jump ebb1(v10) @@ -93,9 +90,9 @@ ebb0(v0: i64x4): ; check: $(v0c=$V) = iconcat $v0cl, $v0ch ; check: $(v0d=$V) = iconcat $v0dl, $v0dh ; check: $(v0cd=$V) = vconcat $v0c, $v0d - ; check: $(v0abcd=$V) = vconcat $v0ab, $v0cd + ; check: $v0 = vconcat $v0ab, $v0cd v1 = bxor v0, v0 - ; check: $(v1ab=$V), $(v1cd=$V) = vsplit + ; check: $(v1ab=$V), $(v1cd=$V) = vsplit $v1 ; check: $(v1a=$V), $(v1b=$V) = vsplit $v1ab ; check: $(v1al=$V), $(v1ah=$V) = isplit $v1a ; check: $(v1bl=$V), $(v1bh=$V) = isplit $v1b diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 272a19dfee..cb2794a49d 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -85,10 +85,11 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { Err(abi_type) } }; - let converted = convert_from_abi(&mut func.dfg, &mut pos, arg_type, &mut get_arg); + let converted = + convert_from_abi(&mut func.dfg, &mut pos, arg_type, Some(arg), &mut get_arg); // The old `arg` is no longer an attached EBB argument, but there are probably still - // uses of the value. Make it an alias to the converted value. - func.dfg.change_to_alias(arg, converted); + // uses of the value. + assert_eq!(func.dfg.resolve_aliases(arg), converted); } } } @@ -141,9 +142,8 @@ fn legalize_inst_results(dfg: &mut DataFlowGraph, Err(abi_type) } }; - let v = convert_from_abi(dfg, pos, res_type, &mut get_res); - // The old `res` is no longer an attached result. - dfg.change_to_alias(res, v); + let v = convert_from_abi(dfg, pos, res_type, Some(res), &mut get_res); + assert_eq!(dfg.resolve_aliases(res), v); } } @@ -158,9 +158,11 @@ fn legalize_inst_results(dfg: &mut DataFlowGraph, /// - `Ok(arg)` if the requested type matches the next ABI argument. /// - `Err(arg_type)` if further conversions are needed from the ABI argument `arg_type`. /// +/// If the `into_result` value is provided, the converted result will be written into that value. fn convert_from_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor, ty: Type, + into_result: Option, get_arg: &mut GetArg) -> Value where GetArg: FnMut(&mut DataFlowGraph, Type) -> Result @@ -169,6 +171,7 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, let arg_type = match get_arg(dfg, ty) { Ok(v) => { debug_assert_eq!(dfg.value_type(v), ty); + assert_eq!(into_result, None); return v; } Err(t) => t, @@ -184,43 +187,49 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, // Construct a `ty` by concatenating two ABI integers. ValueConversion::IntSplit => { let abi_ty = ty.half_width().expect("Invalid type for conversion"); - let lo = convert_from_abi(dfg, pos, abi_ty, get_arg); - let hi = convert_from_abi(dfg, pos, abi_ty, get_arg); + let lo = convert_from_abi(dfg, pos, abi_ty, None, get_arg); + let hi = convert_from_abi(dfg, pos, abi_ty, None, get_arg); dbg!("intsplit {}: {}, {}: {}", lo, dfg.value_type(lo), hi, dfg.value_type(hi)); - dfg.ins(pos).iconcat(lo, hi) + dfg.ins(pos).with_results([into_result]).iconcat(lo, hi) } // Construct a `ty` by concatenating two halves of a vector. ValueConversion::VectorSplit => { let abi_ty = ty.half_vector().expect("Invalid type for conversion"); - let lo = convert_from_abi(dfg, pos, abi_ty, get_arg); - let hi = convert_from_abi(dfg, pos, abi_ty, get_arg); - dfg.ins(pos).vconcat(lo, hi) + let lo = convert_from_abi(dfg, pos, abi_ty, None, get_arg); + let hi = convert_from_abi(dfg, pos, abi_ty, None, get_arg); + dfg.ins(pos).with_results([into_result]).vconcat(lo, hi) } // Construct a `ty` by bit-casting from an integer type. ValueConversion::IntBits => { assert!(!ty.is_int()); let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); - let arg = convert_from_abi(dfg, pos, abi_ty, get_arg); - dfg.ins(pos).bitcast(ty, arg) + let arg = convert_from_abi(dfg, pos, abi_ty, None, get_arg); + dfg.ins(pos) + .with_results([into_result]) + .bitcast(ty, arg) } // ABI argument is a sign-extended version of the value we want. ValueConversion::Sext(abi_ty) => { - let arg = convert_from_abi(dfg, pos, abi_ty, get_arg); + let arg = convert_from_abi(dfg, pos, abi_ty, None, get_arg); // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. // We could insert an `assert_sreduce` which would fold with a following `sextend` of // this value. - dfg.ins(pos).ireduce(ty, arg) + dfg.ins(pos) + .with_results([into_result]) + .ireduce(ty, arg) } ValueConversion::Uext(abi_ty) => { - let arg = convert_from_abi(dfg, pos, abi_ty, get_arg); + let arg = convert_from_abi(dfg, pos, abi_ty, None, get_arg); // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. // We could insert an `assert_ureduce` which would fold with a following `uextend` of // this value. - dfg.ins(pos).ireduce(ty, arg) + dfg.ins(pos) + .with_results([into_result]) + .ireduce(ty, arg) } } } From bf74445eac9627ecafffea49c7ca42df35c26c89 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 10:37:58 -0700 Subject: [PATCH 705/968] Remove detach_secondary_results() and other cleanups. - The detach_secondary_results() is a leftover from the two-plane value representation. Use detach_results() instead to remove all instruction results. - Make the append_* DFG methods more direct. Don't depend on calling the corresponding attach_* methods. Just create a new value directly, using the values.next_key() trick. --- lib/cretonne/src/ir/dfg.rs | 94 ++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index ad22a03fa7..9f1a8b5299 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -433,20 +433,6 @@ impl DataFlowGraph { ReplaceBuilder::new(self, inst) } - /// Detach secondary instruction results. - /// - /// If `inst` produces two or more results, detach these secondary result values from `inst`. - /// The first result value cannot be detached. - /// - /// Use this method to detach secondary values before using `replace(inst)` to provide an - /// alternate instruction for computing the primary result value. - pub fn detach_secondary_results(&mut self, inst: Inst) { - if let Some(first) = self.results[inst].first(&mut self.value_lists) { - self.results[inst].clear(&mut self.value_lists); - self.results[inst].push(first, &mut self.value_lists); - } - } - /// Detach the list of result values from `inst` and return it. /// /// This leaves `inst` without any result values. New result values can be created by calling @@ -484,13 +470,14 @@ impl DataFlowGraph { /// Append a new instruction result value to `inst`. pub fn append_result(&mut self, inst: Inst, ty: Type) -> Value { - let res = self.make_value(ValueData::Inst { - ty: ty, - inst: inst, - num: 0, - }); - self.attach_result(inst, res); - res + let res = self.values.next_key(); + let num = self.results[inst].push(res, &mut self.value_lists); + assert!(num <= u16::MAX as usize, "Too many result values"); + self.make_value(ValueData::Inst { + ty: ty, + inst: inst, + num: num as u16, + }) } /// Get the first result of an instruction. @@ -596,22 +583,40 @@ impl DataFlowGraph { self.ebbs[ebb].args.len(&self.value_lists) } - /// Append an argument with type `ty` to `ebb`. - pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { - let val = self.make_value(ValueData::Arg { - ty: ty, - ebb: ebb, - num: 0, - }); - self.attach_ebb_arg(ebb, val); - val - } - /// Get the arguments to an EBB. pub fn ebb_args(&self, ebb: Ebb) -> &[Value] { self.ebbs[ebb].args.as_slice(&self.value_lists) } + /// Append an argument with type `ty` to `ebb`. + pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { + let arg = self.values.next_key(); + let num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); + assert!(num <= u16::MAX as usize, "Too many arguments to EBB"); + self.make_value(ValueData::Arg { + ty: ty, + num: num as u16, + ebb: ebb, + }) + } + + /// Append an existing argument value to `ebb`. + /// + /// The appended value can't already be attached to something else. + /// + /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. + pub fn attach_ebb_arg(&mut self, ebb: Ebb, arg: Value) { + assert!(!self.value_is_attached(arg)); + let num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); + assert!(num <= u16::MAX as usize, "Too many arguments to EBB"); + let ty = self.value_type(arg); + self.values[arg] = ValueData::Arg { + ty: ty, + num: num as u16, + ebb: ebb, + }; + } + /// Replace an EBB argument with a new value of type `ty`. /// /// The `old_value` must be an attached EBB argument. It is removed from its place in the list @@ -622,15 +627,11 @@ impl DataFlowGraph { /// /// Returns the new value. pub fn replace_ebb_arg(&mut self, old_arg: Value, new_type: Type) -> Value { - let old_data = self.values[old_arg].clone(); - // Create new value identical to the old one except for the type. - let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = old_data { + let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = self.values[old_arg] { (ebb, num) } else { - panic!("old_arg: {} must be an EBB argument: {:?}", - old_arg, - old_data); + panic!("{} must be an EBB argument", old_arg); }; let new_arg = self.make_value(ValueData::Arg { ty: new_type, @@ -650,23 +651,6 @@ impl DataFlowGraph { pub fn detach_ebb_args(&mut self, ebb: Ebb) -> ValueList { self.ebbs[ebb].args.take() } - - /// Append an existing argument value to `ebb`. - /// - /// The appended value can't already be attached to something else. - /// - /// In almost all cases, you should be using `append_ebb_arg()` instead of this method. - pub fn attach_ebb_arg(&mut self, ebb: Ebb, arg: Value) { - assert!(!self.value_is_attached(arg)); - let num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); - assert!(num <= u16::MAX as usize, "Too many arguments to EBB"); - let ty = self.value_type(arg); - self.values[arg] = ValueData::Arg { - ty: ty, - num: num as u16, - ebb: ebb, - }; - } } // Contents of an extended basic block. From d3235eb81ff29de8dd0cca7777b63e4b18778506 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Apr 2017 12:24:20 -0700 Subject: [PATCH 706/968] Stop tracking if instruction formats have multiple results. All instruction formats can represent multiple results now, so a few redundant formats can be removed: UnarySplit and BinaryOverflow. --- lib/cretonne/meta/base/formats.py | 13 +++------- lib/cretonne/meta/cdsl/formats.py | 39 ++++++----------------------- lib/cretonne/meta/cdsl/isa.py | 2 -- lib/cretonne/src/ir/instructions.rs | 2 -- lib/cretonne/src/verifier.rs | 2 -- lib/cretonne/src/write.rs | 2 -- lib/reader/src/parser.rs | 15 ----------- 7 files changed, 10 insertions(+), 65 deletions(-) diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index acb9ce0131..6e64e0e3f2 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -18,14 +18,10 @@ Unary = InstructionFormat(VALUE) UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) UnaryIeee64 = InstructionFormat(ieee64) -UnarySplit = InstructionFormat(VALUE, multiple_results=True) Binary = InstructionFormat(VALUE, VALUE) BinaryImm = InstructionFormat(VALUE, imm64) -# Generate result + overflow flag. -BinaryOverflow = InstructionFormat(VALUE, VALUE, multiple_results=True) - # The select instructions are controlled by the second VALUE operand. # The first VALUE operand is the controlling flag which has a derived type. # The fma instruction has the same constraint on all inputs. @@ -33,7 +29,7 @@ Ternary = InstructionFormat(VALUE, VALUE, VALUE, typevar_operand=1) # Catch-all for instructions with many outputs and inputs and no immediate # operands. -MultiAry = InstructionFormat(VARIABLE_ARGS, multiple_results=True) +MultiAry = InstructionFormat(VARIABLE_ARGS) InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE) ExtractLane = InstructionFormat(VALUE, ('lane', uimm8)) @@ -47,11 +43,8 @@ Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS) BranchIcmp = InstructionFormat(intcc, VALUE, VALUE, ebb, VARIABLE_ARGS) BranchTable = InstructionFormat(VALUE, jump_table) -Call = InstructionFormat( - func_ref, VARIABLE_ARGS, multiple_results=True) -IndirectCall = InstructionFormat( - sig_ref, VALUE, VARIABLE_ARGS, - multiple_results=True) +Call = InstructionFormat(func_ref, VARIABLE_ARGS) +IndirectCall = InstructionFormat(sig_ref, VALUE, VARIABLE_ARGS) Load = InstructionFormat(memflags, VALUE, offset32) Store = InstructionFormat(memflags, VALUE, VALUE, offset32) diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index f08d6fa119..0b7a72fe0a 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -18,13 +18,6 @@ class InstructionFormat(object): identified structurally, i.e., the format of an instruction is derived from the kinds of operands used in its declaration. - Most instruction formats produce a single result, or no result at all. If - an instruction can produce more than one result, the `multiple_results` - flag must be set on its format. All results are of the `value` kind, and - the instruction format does not keep track of how many results are - produced. Some instructions, like `call`, may have a variable number of - results. - The instruction format stores two separate lists of operands: Immediates and values. Immediate operands (including entity references) are represented as explicit members in the `InstructionData` variants. The @@ -40,16 +33,14 @@ class InstructionFormat(object): :param name: Instruction format name in CamelCase. This is used as a Rust variant name in both the `InstructionData` and `InstructionFormat` enums. - :param multiple_results: Set to `True` if this instruction format allows - more than one result to be produced. :param typevar_operand: Index of the value input operand that is used to infer the controlling type variable. By default, this is `0`, the first `value` operand. The index is relative to the values only, ignoring immediate operands. """ - # Map (multiple_results, imm_kinds, num_value_operands) -> format - _registry = dict() # type: Dict[Tuple[bool, Tuple[OperandKind, ...], int, bool], InstructionFormat] # noqa + # Map (imm_kinds, num_value_operands) -> format + _registry = dict() # type: Dict[Tuple[Tuple[OperandKind, ...], int, bool], InstructionFormat] # noqa # All existing formats. all_formats = list() # type: List[InstructionFormat] @@ -57,7 +48,6 @@ class InstructionFormat(object): def __init__(self, *kinds, **kwargs): # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa self.name = kwargs.get('name', None) # type: str - self.multiple_results = kwargs.get('multiple_results', False) # The number of value operands stored in the format, or `None` when # `has_value_list` is set. @@ -81,9 +71,7 @@ class InstructionFormat(object): # Compute a signature for the global registry. imm_kinds = tuple(f.kind for f in self.imm_fields) - sig = ( - self.multiple_results, imm_kinds, self.num_value_operands, - self.has_value_list) + sig = (imm_kinds, self.num_value_operands, self.has_value_list) if sig in InstructionFormat._registry: raise RuntimeError( "Format '{}' has the same signature as existing format '{}'" @@ -158,37 +146,24 @@ class InstructionFormat(object): :py:class:`Instruction` arguments of the same name, except they must be tuples of :py:`Operand` objects. """ - if len(outs) == 1: - multiple_results = outs[0].kind == VARIABLE_ARGS - else: - multiple_results = len(outs) > 1 - # Construct a signature. imm_kinds = tuple(op.kind for op in ins if op.is_immediate()) num_values = sum(1 for op in ins if op.is_value()) has_varargs = (VARIABLE_ARGS in tuple(op.kind for op in ins)) - sig = (multiple_results, imm_kinds, num_values, has_varargs) + sig = (imm_kinds, num_values, has_varargs) if sig in InstructionFormat._registry: return InstructionFormat._registry[sig] # Try another value list format as an alternative. - sig = (True, imm_kinds, num_values, has_varargs) - if sig in InstructionFormat._registry: - return InstructionFormat._registry[sig] - - sig = (multiple_results, imm_kinds, 0, True) - if sig in InstructionFormat._registry: - return InstructionFormat._registry[sig] - - sig = (True, imm_kinds, 0, True) + sig = (imm_kinds, 0, True) if sig in InstructionFormat._registry: return InstructionFormat._registry[sig] raise RuntimeError( - 'No instruction format matches multiple_results={},' + 'No instruction format matches ' 'imms={}, vals={}, varargs={}'.format( - multiple_results, imm_kinds, num_values, has_varargs)) + imm_kinds, num_values, has_varargs)) @staticmethod def extract_names(globs): diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 1ead583c5a..a44a1e6333 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -207,8 +207,6 @@ class EncRecipe(object): if not format.has_value_list: assert len(self.ins) == format.num_value_operands self.outs = self._verify_constraints(outs) - if len(self.outs) > 1: - assert format.multiple_results def __str__(self): # type: () -> str diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 61ed2f1eeb..9484110d83 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -105,14 +105,12 @@ pub enum InstructionData { UnaryImm { opcode: Opcode, imm: Imm64 }, UnaryIeee32 { opcode: Opcode, imm: Ieee32 }, UnaryIeee64 { opcode: Opcode, imm: Ieee64 }, - UnarySplit { opcode: Opcode, arg: Value }, Binary { opcode: Opcode, args: [Value; 2] }, BinaryImm { opcode: Opcode, arg: Value, imm: Imm64, }, - BinaryOverflow { opcode: Opcode, args: [Value; 2] }, Ternary { opcode: Opcode, args: [Value; 3] }, MultiAry { opcode: Opcode, args: ValueList }, InsertLane { diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index ba2f03ec9d..ee78a07501 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -255,10 +255,8 @@ impl<'a> Verifier<'a> { &UnaryImm { .. } | &UnaryIeee32 { .. } | &UnaryIeee64 { .. } | - &UnarySplit { .. } | &Binary { .. } | &BinaryImm { .. } | - &BinaryOverflow { .. } | &Ternary { .. } | &InsertLane { .. } | &ExtractLane { .. } | diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 09e5ee6641..fcfb843c0c 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -245,10 +245,8 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result UnaryImm { imm, .. } => write!(w, " {}", imm), UnaryIeee32 { imm, .. } => write!(w, " {}", imm), UnaryIeee64 { imm, .. } => write!(w, " {}", imm), - UnarySplit { arg, .. } => write!(w, " {}", arg), Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), BinaryImm { arg, imm, .. } => write!(w, " {}, {}", arg, imm), - BinaryOverflow { args, .. } => write!(w, " {}, {}", args[0], args[1]), Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), MultiAry { ref args, .. } => { if args.is_empty() { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index ac2754a46c..f133732e8e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1429,12 +1429,6 @@ impl<'a> Parser<'a> { imm: self.match_ieee64("expected immediate 64-bit float operand")?, } } - InstructionFormat::UnarySplit => { - InstructionData::UnarySplit { - opcode: opcode, - arg: self.match_value("expected SSA value operand")?, - } - } InstructionFormat::Binary => { let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; @@ -1454,15 +1448,6 @@ impl<'a> Parser<'a> { imm: rhs, } } - InstructionFormat::BinaryOverflow => { - let lhs = self.match_value("expected SSA value first operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let rhs = self.match_value("expected SSA value second operand")?; - InstructionData::BinaryOverflow { - opcode: opcode, - args: [lhs, rhs], - } - } InstructionFormat::Ternary => { // Names here refer to the `select` instruction. // This format is also use by `fma`. From 7e9bdcf059fb52d67f604cc8dfc64ed2bad3999a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Apr 2017 15:04:00 -0700 Subject: [PATCH 707/968] Allow for special purpose function arguments and return values. Enumerate a set of special purposes for function arguments that general purpose code needs to know about. Some of these argument purposes will only appear in the signature of the current function, representing things the prologue and epilogues need to know about like the link register and callee-saved registers. Get rid of the 'inreg' argument flag. Arguments can be pre-assigned to a specific register instead. --- docs/cton_lexer.py | 3 +- docs/langref.rst | 5 +- filetests/parser/call.cton | 10 ++++ lib/cretonne/src/ir/extfunc.rs | 97 +++++++++++++++++++++++++++++++--- lib/cretonne/src/ir/mod.rs | 2 +- lib/reader/src/parser.rs | 17 +++--- 6 files changed, 116 insertions(+), 18 deletions(-) diff --git a/docs/cton_lexer.py b/docs/cton_lexer.py index 1c7224b593..1024765cbe 100644 --- a/docs/cton_lexer.py +++ b/docs/cton_lexer.py @@ -42,8 +42,7 @@ class CretonneLexer(RegexLexer): (r'[-+]?(\d+\.\d+([eE]\d+)?|s?NaN|Inf)', Number.Float), (r'[-+]?\d+', Number.Integer), # Known attributes. - (keywords('align', 'aligntrap', 'uext', 'sext', 'inreg'), - Name.Attribute), + (keywords('uext', 'sext'), Name.Attribute), # Well known value types. (r'\b(b\d+|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), # v = value diff --git a/docs/langref.rst b/docs/langref.rst index d4b30780b4..fce84d0cdf 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -364,8 +364,9 @@ calling convention: signature : "(" [arglist] ")" ["->" retlist] [call_conv] arglist : arg { "," arg } retlist : arglist - arg : type { flag } - flag : "uext" | "sext" | "inreg" + arg : type [argext] [argspecial] + argext : "uext" | "sext" + argspecial: "sret" | "link" | "fp" | "csr" callconv : `string` Arguments and return values have flags whose meaning is mostly target diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton index 027749f562..7626524110 100644 --- a/filetests/parser/call.cton +++ b/filetests/parser/call.cton @@ -68,3 +68,13 @@ ebb0(v0: i64): ; check: call_indirect $sig0, $v1($v0) ; check: $v3, $v4 = call_indirect $sig2, $v1() ; check: return + +; Special purpose function arguments +function special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { +ebb0(v1: i32, v2: i32, v3: i32): + return v4, v2, v3, v1 +} +; check: function special1(i32 sret, i32 fp, i32 link) -> i32 link, i32 fp, i32 sret { +; check: ebb0($v1: i32, $v2: i32, $v3: i32): +; check: return $v3, $v2, $v1 +; check: } diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 43daaed192..a21b5911a9 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -9,6 +9,7 @@ use ir::{Type, FunctionName, SigRef, ArgumentLoc}; use isa::RegInfo; use std::cmp; use std::fmt; +use std::str::FromStr; /// Function signature. /// @@ -111,10 +112,10 @@ impl fmt::Display for Signature { pub struct ArgumentType { /// Type of the argument value. pub value_type: Type, + /// Special purpose of argument, or `Normal`. + pub purpose: ArgumentPurpose, /// Method for extending argument to a full register. pub extension: ArgumentExtension, - /// Place this argument in a register if possible. - pub inreg: bool, /// ABI-specific location of this argument, or `Unassigned` for arguments that have not yet /// been legalized. @@ -127,7 +128,7 @@ impl ArgumentType { ArgumentType { value_type: vt, extension: ArgumentExtension::None, - inreg: false, + purpose: ArgumentPurpose::Normal, location: Default::default(), } } @@ -149,8 +150,8 @@ impl<'a> fmt::Display for DisplayArgumentType<'a> { ArgumentExtension::Uext => write!(f, " uext")?, ArgumentExtension::Sext => write!(f, " sext")?, } - if self.0.inreg { - write!(f, " inreg")?; + if self.0.purpose != ArgumentPurpose::Normal { + write!(f, " {}", self.0.purpose)?; } if self.0.location.is_assigned() { @@ -181,6 +182,75 @@ pub enum ArgumentExtension { Sext, } +/// The special purpose of a function argument. +/// +/// Function arguments and return values are used to pass user program values between functions, +/// but they are also used to represent special registers with significance to the ABI such as +/// frame pointers and callee-saved registers. +/// +/// The argument purpose is used to indicate any special meaning of an argument or return value. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ArgumentPurpose { + /// A normal user program value passed to or from a function. + Normal, + + /// Struct return pointer. + /// + /// When a function needs to return more data than will fit in registers, the caller passes a + /// pointer to a memory location where the return value can be written. In some ABIs, this + /// struct return pointer is passed in a specific register. + /// + /// This argument kind can also appear as a return value for ABIs that require a function with + /// a `StructReturn` pointer argument to also return that pointer in a register. + StructReturn, + + /// The link register. + /// + /// Most RISC architectures implement calls by saving the return address in a designated + /// register rather than pushing it on the stack. This is represented with a `Link` argument. + /// + /// Similarly, some return instructions expect the return address in a register represented as + /// a `Link` return value. + Link, + + /// The frame pointer. + /// + /// This indicates the frame pointer register which has a special meaning in some ABIs. + /// + /// The frame pointer appears as an argument and as a return value since it is a callee-saved + /// register. + FramePointer, + + /// A callee-saved register. + /// + /// Some calling conventions have registers that must be saved by the callee. These registers + /// are represented as `CalleeSaved` arguments and return values. + CalleeSaved, +} + +/// Text format names of the `ArgumentPurpose` variants. +static PURPOSE_NAMES: [&'static str; 5] = ["normal", "sret", "link", "fp", "csr"]; + +impl fmt::Display for ArgumentPurpose { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(PURPOSE_NAMES[*self as usize]) + } +} + +impl FromStr for ArgumentPurpose { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "normal" => Ok(ArgumentPurpose::Normal), + "sret" => Ok(ArgumentPurpose::StructReturn), + "link" => Ok(ArgumentPurpose::Link), + "fp" => Ok(ArgumentPurpose::FramePointer), + "csr" => Ok(ArgumentPurpose::CalleeSaved), + _ => Err(()), + } + } +} + /// An external function. /// /// Information about a function that can be called directly with a direct `call` instruction. @@ -209,8 +279,21 @@ mod tests { assert_eq!(t.to_string(), "i32"); t.extension = ArgumentExtension::Uext; assert_eq!(t.to_string(), "i32 uext"); - t.inreg = true; - assert_eq!(t.to_string(), "i32 uext inreg"); + t.purpose = ArgumentPurpose::StructReturn; + assert_eq!(t.to_string(), "i32 uext sret"); + } + + #[test] + fn argument_purpose() { + let all_purpose = [ArgumentPurpose::Normal, + ArgumentPurpose::StructReturn, + ArgumentPurpose::Link, + ArgumentPurpose::FramePointer, + ArgumentPurpose::CalleeSaved]; + for (&e, &n) in all_purpose.iter().zip(PURPOSE_NAMES.iter()) { + assert_eq!(e.to_string(), n); + assert_eq!(Ok(e), n.parse()); + } } #[test] diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 488b4dab32..49617165e9 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -18,7 +18,7 @@ mod progpoint; mod valueloc; pub use ir::funcname::FunctionName; -pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; +pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ArgumentPurpose, ExtFuncData}; pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f133732e8e..f96d60518a 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -767,8 +767,13 @@ impl<'a> Parser<'a> { match s { "uext" => arg.extension = ArgumentExtension::Uext, "sext" => arg.extension = ArgumentExtension::Sext, - "inreg" => arg.inreg = true, - _ => break, + _ => { + if let Ok(purpose) = s.parse() { + arg.purpose = purpose; + } else { + break; + } + } } self.consume(); } @@ -1672,7 +1677,7 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::ir::ArgumentExtension; + use cretonne::ir::{ArgumentExtension, ArgumentPurpose}; use cretonne::ir::types; use cretonne::ir::entities::AnyEntity; use testfile::{Details, Comment}; @@ -1685,7 +1690,7 @@ mod tests { let arg = p.parse_argument_type(None).unwrap(); assert_eq!(arg.value_type, types::I32); assert_eq!(arg.extension, ArgumentExtension::Sext); - assert_eq!(arg.inreg, false); + assert_eq!(arg.purpose, ArgumentPurpose::Normal); let Error { location, message } = p.parse_argument_type(None).unwrap_err(); assert_eq!(location.line_number, 1); assert_eq!(message, "expected argument type"); @@ -1721,11 +1726,11 @@ mod tests { assert_eq!(sig.argument_types.len(), 0); assert_eq!(sig.return_types.len(), 0); - let sig2 = Parser::new("(i8 inreg uext, f32, f64) -> i32 sext, f64") + let sig2 = Parser::new("(i8 uext, f32, f64, i32 sret) -> i32 sext, f64") .parse_signature(None) .unwrap(); assert_eq!(sig2.to_string(), - "(i8 uext inreg, f32, f64) -> i32 sext, f64"); + "(i8 uext, f32, f64, i32 sret) -> i32 sext, f64"); // `void` is not recognized as a type by the lexer. It should not appear in files. assert_eq!(Parser::new("() -> void") From 74595c66234d6ff63fe53187d2b03a54e7dd506f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Apr 2017 15:45:55 -0700 Subject: [PATCH 708/968] Fix broken test. --- filetests/parser/call.cton | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton index 7626524110..67472f7efc 100644 --- a/filetests/parser/call.cton +++ b/filetests/parser/call.cton @@ -71,10 +71,10 @@ ebb0(v0: i64): ; Special purpose function arguments function special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { -ebb0(v1: i32, v2: i32, v3: i32): +ebb0(v1: i32, v2: i32, v3: i32, v4: i32): return v4, v2, v3, v1 } -; check: function special1(i32 sret, i32 fp, i32 link) -> i32 link, i32 fp, i32 sret { -; check: ebb0($v1: i32, $v2: i32, $v3: i32): -; check: return $v3, $v2, $v1 +; check: function special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { +; check: ebb0($v1: i32, $v2: i32, $v3: i32, $v4: i32): +; check: return $v4, $v2, $v3, $v1 ; check: } From c040a495c0dfe9e62e3042704007c7139494b5be Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Apr 2017 14:53:14 -0700 Subject: [PATCH 709/968] Simplify check_arg_types(). Iterator tricks. --- lib/cretonne/src/legalizer/boundary.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index cb2794a49d..537d6cb9de 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -290,21 +290,9 @@ fn convert_to_abi(dfg: &mut DataFlowGraph, /// Check if a sequence of arguments match a desired sequence of argument types. fn check_arg_types(dfg: &DataFlowGraph, args: &[Value], types: &[ArgumentType]) -> bool { - let mut n = 0; - for &arg in args { - match types.get(n) { - Some(&ArgumentType { value_type, .. }) => { - if dfg.value_type(arg) != value_type { - return false; - } - } - None => return false, - } - n += 1 - } - - // Also verify that the number of arguments matches. - n == types.len() + let arg_types = args.iter().map(|&v| dfg.value_type(v)); + let sig_types = types.iter().map(|&at| at.value_type); + arg_types.eq(sig_types) } /// Check if the arguments of the call `inst` match the signature. From 315c858b480096f6769590daa5d3386fc2085ea0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Apr 2017 15:44:17 -0700 Subject: [PATCH 710/968] Append link and sret arguments in legalize_signature. These special-purpose arguments and return values are only relevant for the function being compiled, so add a `current` flag to legalize_signature(). - Add the necessary argument values to the entry block to represent the special-purpose arguments. - Propagate the link and sret arguments to return instructions if the legalized signature asks for it. --- filetests/isa/riscv/legalize-abi.cton | 18 ++--- filetests/isa/riscv/legalize-i64.cton | 16 ++-- filetests/isa/riscv/parse-encoding.cton | 2 +- filetests/isa/riscv/split-args.cton | 10 +-- lib/cretonne/src/ir/extfunc.rs | 12 ++- lib/cretonne/src/isa/mod.rs | 19 ++++- lib/cretonne/src/isa/riscv/abi.rs | 17 ++++- lib/cretonne/src/isa/riscv/mod.rs | 4 +- lib/cretonne/src/legalizer/boundary.rs | 98 +++++++++++++++++++++++-- 9 files changed, 161 insertions(+), 35 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 88d35e699a..a3632edca3 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -6,11 +6,11 @@ isa riscv function int_split_args(i64) -> i64 { ebb0(v0: i64): - ; check: $ebb0($(v0l=$V): i32, $(v0h=$V): i32): + ; check: $ebb0($(v0l=$V): i32, $(v0h=$V): i32, $(link=$V): i32): ; check: $v0 = iconcat $v0l, $v0h v1 = iadd_imm v0, 1 ; check: $(v1l=$V), $(v1h=$V) = isplit $v1 - ; check: return $v1l, $v1h + ; check: return $v1l, $v1h, $link return v1 } @@ -31,7 +31,7 @@ function split_ret_val() { fn1 = function foo() -> i64 ebb0: v1 = call fn1() - ; check: $ebb0: + ; check: $ebb0($(link=$V): i32): ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() ; check: $v1 = iconcat $v1l, $v1h jump ebb1(v1) @@ -46,7 +46,7 @@ function split_ret_val2() { fn1 = function foo() -> i32, i64 ebb0: v1, v2 = call fn1() - ; check: $ebb0: + ; check: $ebb0($(link=$V): i32): ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() ; check: $v2 = iconcat $v2l, $v2h jump ebb1(v1, v2) @@ -58,11 +58,11 @@ ebb1(v9: i32, v10: i64): function int_ext(i8, i8 sext, i8 uext) -> i8 uext { ebb0(v1: i8, v2: i8, v3: i8): - ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32): + ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32, $(link=$V): i32): ; check: $v2 = ireduce.i8 $v2x ; check: $v3 = ireduce.i8 $v3x ; check: $(v1x=$V) = uextend.i32 $v1 - ; check: return $v1x + ; check: return $v1x, $link return v1 } @@ -71,7 +71,7 @@ function ext_ret_val() { fn1 = function foo() -> i8 sext ebb0: v1 = call fn1() - ; check: $ebb0: + ; check: $ebb0($V: i32): ; nextln: $(rv=$V) = call $fn1() ; check: $v1 = ireduce.i8 $rv jump ebb1(v1) @@ -83,7 +83,7 @@ ebb1(v10: i8): function vector_split_args(i64x4) -> i64x4 { ebb0(v0: i64x4): - ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32): + ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32, $(link=$V): i32): ; check: $(v0a=$V) = iconcat $v0al, $v0ah ; check: $(v0b=$V) = iconcat $v0bl, $v0bh ; check: $(v0ab=$V) = vconcat $v0a, $v0b @@ -99,7 +99,7 @@ ebb0(v0: i64x4): ; check: $(v1c=$V), $(v1d=$V) = vsplit $v1cd ; check: $(v1cl=$V), $(v1ch=$V) = isplit $v1c ; check: $(v1dl=$V), $(v1dh=$V) = isplit $v1d - ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh + ; check: return $v1al, $v1ah, $v1bl, $v1bh, $v1cl, $v1ch, $v1dl, $v1dh, $link return v1 } diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index dc8604f358..516502c4d4 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -9,39 +9,39 @@ ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 } -; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): ; check: [R#ec ; sameln: $(v3l=$V) = band $v1l, $v2l ; check: [R#ec ; sameln: $(v3h=$V) = band $v1h, $v2h ; check: $v3 = iconcat $v3l, $v3h -; check: return $v3l, $v3h +; check: return $v3l, $v3h, $link function bitwise_or(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 } -; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): ; check: [R#cc ; sameln: $(v3l=$V) = bor $v1l, $v2l ; check: [R#cc ; sameln: $(v3h=$V) = bor $v1h, $v2h ; check: $v3 = iconcat $v3l, $v3h -; check: return $v3l, $v3h +; check: return $v3l, $v3h, $link function bitwise_xor(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 } -; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): ; check: [R#8c ; sameln: $(v3l=$V) = bxor $v1l, $v2l ; check: [R#8c ; sameln: $(v3h=$V) = bxor $v1h, $v2h ; check: $v3 = iconcat $v3l, $v3h -; check: return $v3l, $v3h +; check: return $v3l, $v3h, $link function arith_add(i64, i64) -> i64 { ; Legalizing iadd.i64 requires two steps: @@ -51,7 +51,7 @@ ebb0(v1: i64, v2: i64): v3 = iadd v1, v2 return v3 } -; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): ; check: [R#0c ; sameln: $(v3l=$V) = iadd $v1l, $v2l ; check: $(c=$V) = icmp ult $v3l, $v1l @@ -61,4 +61,4 @@ ebb0(v1: i64, v2: i64): ; check: [R#0c ; sameln: $(v3h=$V) = iadd $v3h1, $c_int ; check: $v3 = iconcat $v3l, $v3h -; check: return $v3l, $v3h +; check: return $v3l, $v3h, $link diff --git a/filetests/isa/riscv/parse-encoding.cton b/filetests/isa/riscv/parse-encoding.cton index d3cc6eee4b..69a25ce744 100644 --- a/filetests/isa/riscv/parse-encoding.cton +++ b/filetests/isa/riscv/parse-encoding.cton @@ -3,7 +3,7 @@ test legalizer isa riscv function parse_encoding(i32 [%x5]) -> i32 [%x10] { - ; check: function parse_encoding(i32 [%x5]) -> i32 [%x10] { + ; check: function parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] { sig0 = signature(i32 [%x10]) -> i32 [%x10] ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] diff --git a/filetests/isa/riscv/split-args.cton b/filetests/isa/riscv/split-args.cton index 06b09bd6d1..a568204658 100644 --- a/filetests/isa/riscv/split-args.cton +++ b/filetests/isa/riscv/split-args.cton @@ -6,7 +6,7 @@ isa riscv function simple(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): -; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): jump ebb1(v1) ; check: jump $ebb1($v1l, $v1h) @@ -16,12 +16,12 @@ ebb1(v3: i64): ; check: $(v4l=$V) = band $v3l, $v2l ; check: $(v4h=$V) = band $v3h, $v2h return v4 - ; check: return $v4l, $v4h + ; check: return $v4l, $v4h, $link } function multi(i64) -> i64 { ebb1(v1: i64): -; check: $ebb1($(v1l=$V): i32, $(v1h=$V): i32): +; check: $ebb1($(v1l=$V): i32, $(v1h=$V): i32, $(link=$V): i32): jump ebb2(v1, v1) ; check: jump $ebb2($v1l, $v1l, $v1h, $v1h) @@ -36,12 +36,12 @@ ebb3(v4: i64): ; check: $(v5l=$V) = band $v4l, $v3l ; check: $(v5h=$V) = band $v4h, $v3h return v5 - ; check: return $v5l, $v5h + ; check: return $v5l, $v5h, $link } function loop(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): -; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32): +; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): jump ebb1(v1) ; check: jump $ebb1($v1l, $v1h) diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index a21b5911a9..0971015f62 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -6,7 +6,7 @@ //! This module declares the data types used to represent external functions and call signatures. use ir::{Type, FunctionName, SigRef, ArgumentLoc}; -use isa::RegInfo; +use isa::{RegInfo, RegUnit}; use std::cmp; use std::fmt; use std::str::FromStr; @@ -133,6 +133,16 @@ impl ArgumentType { } } + /// Create an argument type for a special-purpose register. + pub fn special_reg(vt: Type, purpose: ArgumentPurpose, regunit: RegUnit) -> ArgumentType { + ArgumentType { + value_type: vt, + extension: ArgumentExtension::None, + purpose: purpose, + location: ArgumentLoc::Reg(regunit), + } + } + /// Return an object that can display `self` with correct register names. pub fn display<'a, R: Into>>(&'a self, regs: R) -> DisplayArgumentType<'a> { DisplayArgumentType(self, regs.into()) diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index c40a839836..e951ad31de 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -166,7 +166,24 @@ pub trait TargetIsa { /// - Vector types can be bit-cast and broken down into smaller vectors or scalars. /// /// The legalizer will adapt argument and return values as necessary at all ABI boundaries. - fn legalize_signature(&self, _sig: &mut Signature) { + /// + /// When this function is called to legalize the signature of the function currently begin + /// compiler, `_current` is true. The legalized signature can then also contain special purpose + /// arguments and return values such as: + /// + /// - A `link` argument representing the link registers on RISC architectures that don't push + /// the return address on the stack. + /// - A `link` return value which will receive the value that was passed to the `link` + /// argument. + /// - An `sret` argument can be added if one wasn't present already. This is necessary if the + /// signature returns more values than registers are available for returning values. + /// - An `sret` return value can be added if the ABI requires a function to return its `sret` + /// argument in a register. + /// + /// Arguments and return values for the caller's frame pointer and other callee-saved registers + /// should not be added by this function. These arguments are not added until after register + /// allocation. + fn legalize_signature(&self, _sig: &mut Signature, _current: bool) { unimplemented!() } diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index fa158dceb5..453a9c7be8 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -6,7 +6,7 @@ //! This doesn't support the soft-float ABI at the moment. use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; -use ir::{Signature, Type, ArgumentType, ArgumentLoc, ArgumentExtension}; +use ir::{Signature, Type, ArgumentType, ArgumentLoc, ArgumentExtension, ArgumentPurpose}; use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; @@ -80,7 +80,7 @@ impl ArgAssigner for Args { } /// Legalize `sig` for RISC-V. -pub fn legalize_signature(sig: &mut Signature, flags: &shared_settings::Flags) { +pub fn legalize_signature(sig: &mut Signature, flags: &shared_settings::Flags, current: bool) { let bits = if flags.is_64bit() { 64 } else { 32 }; let mut args = Args::new(bits); @@ -88,4 +88,17 @@ pub fn legalize_signature(sig: &mut Signature, flags: &shared_settings::Flags) { let mut rets = Args::new(bits); legalize_args(&mut sig.return_types, &mut rets); + + if current { + let ptr = Type::int(bits).unwrap(); + + // Add the link register as an argument and return value. + // + // The `jalr` instruction implementing a return can technically accept the return address + // in any register, but a micro-architecture with a return address predictor will only + // recognize it as a return if the address is in `x1`. + let link = ArgumentType::special_reg(ptr, ArgumentPurpose::Link, GPR.unit(1)); + sig.argument_types.push(link); + sig.return_types.push(link); + } } diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 778e3b85aa..ac5057103c 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -78,9 +78,9 @@ impl TargetIsa for Isa { }) } - fn legalize_signature(&self, sig: &mut Signature) { + fn legalize_signature(&self, sig: &mut Signature, current: bool) { // We can pass in `self.isa_flags` too, if we need it. - abi::legalize_signature(sig, &self.shared_flags) + abi::legalize_signature(sig, &self.shared_flags, current) } fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink) { diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 537d6cb9de..e0cf9a2f21 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -20,7 +20,7 @@ use abi::{legalize_abi_value, ValueConversion}; use flowgraph::ControlFlowGraph; use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef, - ArgumentType}; + ArgumentType, ArgumentPurpose}; use ir::instructions::CallInfo; use isa::TargetIsa; use legalizer::split::{isplit, vsplit}; @@ -31,9 +31,9 @@ use legalizer::split::{isplit, vsplit}; /// change the entry block arguments, calls, or return instructions, so this can leave the function /// in a state with type discrepancies. pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { - isa.legalize_signature(&mut func.signature); + isa.legalize_signature(&mut func.signature, true); for sig in func.dfg.signatures.keys() { - isa.legalize_signature(&mut func.dfg.signatures[sig]); + isa.legalize_signature(&mut func.dfg.signatures[sig], false); } if let Some(entry) = func.layout.entry_block() { @@ -50,6 +50,9 @@ pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { /// The original entry EBB arguments are computed from the new ABI arguments by code inserted at /// the top of the entry block. fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { + let mut has_sret = false; + let mut has_link = false; + // Insert position for argument conversion code. // We want to insert instructions before the first instruction in the entry block. // If the entry block is empty, append instructions to it instead. @@ -73,11 +76,22 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { // No value translation is necessary, this argument matches the ABI type. // Just use the original EBB argument value. This is the most common case. func.dfg.attach_ebb_arg(entry, arg); + match abi_types[abi_arg].purpose { + ArgumentPurpose::Normal => {} + ArgumentPurpose::StructReturn => { + assert!(!has_sret, "Multiple sret arguments found"); + has_sret = true; + } + _ => panic!("Unexpected special-purpose arg {}", abi_types[abi_arg]), + } abi_arg += 1; } else { // Compute the value we want for `arg` from the legalized ABI arguments. let mut get_arg = |dfg: &mut DataFlowGraph, ty| { let abi_type = abi_types[abi_arg]; + assert_eq!(abi_type.purpose, + ArgumentPurpose::Normal, + "Can't legalize special-purpose argument"); if ty == abi_type.value_type { abi_arg += 1; Ok(dfg.append_ebb_arg(entry, ty)) @@ -92,6 +106,35 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { assert_eq!(func.dfg.resolve_aliases(arg), converted); } } + + // The legalized signature may contain additional arguments representing special-purpose + // registers. + for &arg in &abi_types[abi_arg..] { + match arg.purpose { + // Any normal arguments should have been processed above. + ArgumentPurpose::Normal => { + panic!("Leftover arg: {}", arg); + } + // The callee-save arguments should not appear until after register allocation is + // done. + ArgumentPurpose::FramePointer | + ArgumentPurpose::CalleeSaved => { + panic!("Premature callee-saved arg {}", arg); + } + // These can be meaningfully added by `legalize_signature()`. + ArgumentPurpose::Link => { + assert!(!has_link, "Multiple link arguments found"); + has_link = true; + } + ArgumentPurpose::StructReturn => { + assert!(!has_sret, "Multiple sret arguments found"); + has_sret = true; + } + } + // Just create entry block values to match here. We will use them in `handle_return_abi()` + // below. + func.dfg.append_ebb_arg(entry, arg.value_type); + } } /// Legalize the results returned from a call instruction to match the ABI signature. @@ -445,7 +488,7 @@ pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mu true } -/// Insert ABI conversion code before and after the call instruction at `pos`. +/// Insert ABI conversion code before and after the return instruction at `pos`. /// /// Return `true` if any instructions were inserted. pub fn handle_return_abi(dfg: &mut DataFlowGraph, @@ -461,15 +504,58 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph, return false; } - let abi_args = sig.return_types.len(); + // Count the special-purpose return values (`link` and `sret`) that were appended to the + // legalized signature. + let special_args = sig.return_types + .iter() + .rev() + .take_while(|&rt| { + rt.purpose == ArgumentPurpose::Link || + rt.purpose == ArgumentPurpose::StructReturn + }) + .count(); + + let abi_args = sig.return_types.len() - special_args; legalize_inst_arguments(dfg, cfg, pos, abi_args, |_, abi_arg| sig.return_types[abi_arg]); + assert_eq!(dfg.inst_variable_args(inst).len(), abi_args); + + // Append special return arguments for any `sret` and `link` return values added to the + // legalized signature. These values should simply be propagated from the entry block + // arguments. + if special_args > 0 { + dbg!("Adding {} special-purpose arguments to {}", + special_args, + dfg.display_inst(inst)); + let mut vlist = dfg[inst].take_value_list().unwrap(); + for arg in &sig.return_types[abi_args..] { + match arg.purpose { + ArgumentPurpose::Link | + ArgumentPurpose::StructReturn => {} + ArgumentPurpose::Normal => panic!("unexpected return value {}", arg), + _ => panic!("Unsupported special purpose return value {}", arg), + } + // A `link` or `sret` return value can only appear in a signature that has a unique + // matching argument. They are appended at the end, so search the signature from the + // end. + let idx = sig.argument_types + .iter() + .rposition(|t| t.purpose == arg.purpose) + .expect("No matching special purpose argument."); + // Get the corresponding entry block value and add it to the return instruction's + // arguments. + let val = dfg.ebb_args(pos.layout.entry_block().unwrap())[idx]; + debug_assert_eq!(dfg.value_type(val), arg.value_type); + vlist.push(val, &mut dfg.value_lists); + } + dfg[inst].put_value_list(vlist); + } debug_assert!(check_return_signature(dfg, inst, sig), - "Signature still wrong: {}, sig{}", + "Signature still wrong: {} / signature {}", dfg.display_inst(inst), sig); From 832247019b7144b82a2fb8c0ecbc4906149e0cf7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Apr 2017 16:08:16 -0700 Subject: [PATCH 711/968] Remove the return_reg instruction. RISC architectures that take a return address in a register can use a special-purpose `link` return value to do so. --- docs/langref.rst | 1 - filetests/isa/riscv/abi.cton | 6 +++--- filetests/isa/riscv/encoding.cton | 4 ++-- filetests/regalloc/basic.cton | 2 +- lib/cretonne/meta/base/instructions.py | 19 ------------------- lib/cretonne/meta/isa/riscv/encodings.py | 11 +++++------ 6 files changed, 11 insertions(+), 32 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index fce84d0cdf..008f2a74b1 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -386,7 +386,6 @@ preamble`: .. autoinst:: call .. autoinst:: x_return -.. autoinst:: return_reg This simple example illustrates direct function calls and signatures:: diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index d168fd768c..eba5609f3e 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -4,7 +4,7 @@ isa riscv ; regex: V=v\d+ -function f(i32) { +function f() { sig0 = signature(i32) -> i32 ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] @@ -27,6 +27,6 @@ function f(i32) { sig5 = signature(i64x4) ; check: sig5 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) -ebb0(v0: i32): - return_reg v0 +ebb0: + return } diff --git a/filetests/isa/riscv/encoding.cton b/filetests/isa/riscv/encoding.cton index f6defe27bf..fdd3ee4329 100644 --- a/filetests/isa/riscv/encoding.cton +++ b/filetests/isa/riscv/encoding.cton @@ -15,7 +15,7 @@ ebb0(v1: i32, v2: i32): ; check: [R#10c] ; sameln: $v12 = imul - return_reg v1 + return ; check: [Iret#19] - ; sameln: return_reg + ; sameln: return } diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index 00e204f9b1..cbff589431 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -8,5 +8,5 @@ ebb0(v1: i32, v2: i32): v3 = iadd v1, v2 ; check: [R#0c,%x0] ; sameln: iadd - return_reg v3 + return } diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index a7c549ce93..3035f2b98e 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -155,25 +155,6 @@ x_return = Instruction( """, ins=rvals, is_return=True, is_terminator=True) -raddr = Operand('raddr', iAddr, doc='Return address') - -return_reg = Instruction( - 'return_reg', r""" - Return from the function to a return address held in a register. - - Unconditionally transfer control to the calling function, passing the - provided return values. The list of return values must match the - function signature's return types. - - This instruction should only be used by ISA-specific epilogue lowering - code. It is equivalent to :inst:`return`, but the return address is - provided explicitly in a register. This style of return instruction is - used by RISC architectures such as ARM and RISC-V. A normal - :inst:`return` will be legalized into this instruction on these - architectures. - """, - ins=(raddr, rvals), is_return=True, is_terminator=True) - FN = Operand( 'FN', entities.func_ref, diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 294015b4ea..ffc848ef06 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -109,9 +109,8 @@ for inst, f3 in [ RV32.enc(inst.b1, SBzero, BRANCH(f3)) RV64.enc(inst.b1, SBzero, BRANCH(f3)) -# Returns are a special case of JALR. -# Note: Return stack predictors will only recognize this as a return when the -# return address is provided in `x1`. We may want a special encoding to enforce -# that. -RV32.enc(base.return_reg.i32, Iret, JALR()) -RV64.enc(base.return_reg.i64, Iret, JALR()) +# Returns are a special case of JALR using %x1 to hold the return address. +# The return address is provided by a special-purpose `link` return value that +# is added by legalize_signature(). +RV32.enc(base.x_return, Iret, JALR()) +RV64.enc(base.x_return, Iret, JALR()) From 9c23196049bf0f48022e048f1b6f05724df27a32 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Apr 2017 16:26:04 -0700 Subject: [PATCH 712/968] Implement binary emission of RISC-V return instructions. The return address is now always supplied in %x1, so the return address predictor will recognize the jalr as a return and not some indirect branch. --- filetests/isa/riscv/binary32.cton | 9 ++++++--- lib/cretonne/meta/isa/riscv/recipes.py | 2 +- lib/cretonne/src/isa/riscv/binemit.rs | 10 ++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 984b7b8a80..f62a6187e0 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -2,10 +2,10 @@ test binemit isa riscv -function RV32I() { +function RV32I(i32 link [%x1]) -> i32 link [%x1] { fn0 = function foo() -ebb0: +ebb0(v9999: i32): [-,%x10] v1 = iconst.i32 1 [-,%x21] v2 = iconst.i32 2 @@ -83,7 +83,10 @@ ebb0: call fn0() ; bin: Call(fn0) 000000ef brz v1, ebb3 - fallthrough ebb1 + brnz v1, ebb1 + + ; jalr %x0, %x1, 0 + return v9999 ; bin: 00008067 ebb1: ; beq 0x000 diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index ad682fa74b..b5aa56bc85 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -110,7 +110,7 @@ Iicmp = EncRecipe( # I-type encoding for `jalr` as a return instruction. We won't use the # immediate offset. # The variable return values are not encoded. -Iret = EncRecipe('Iret', MultiAry, size=4, ins=GPR, outs=()) +Iret = EncRecipe('Iret', MultiAry, size=4, ins=(), outs=()) # U-type instructions have a 20-bit immediate that targets bits 12-31. U = EncRecipe( diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 94ba8b53bf..12a3eb462f 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -172,8 +172,14 @@ fn recipe_iicmp(func: &Function, inst: Inst, sink: &mut C } } -fn recipe_iret(_func: &Function, _inst: Inst, _sink: &mut CS) { - unimplemented!() +fn recipe_iret(func: &Function, inst: Inst, sink: &mut CS) { + // Return instructions are always a jalr to %x1. + // The return address is provided as a special-purpose link argument. + put_i(func.encodings[inst].bits(), + 1, // rs1 = %x1 + 0, // no offset. + 0, // rd = %x0: no address written. + sink); } /// U-type instructions. From c621770507d86edf00efa437a319722c7da2cfb3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Apr 2017 14:38:03 -0700 Subject: [PATCH 713/968] Move the verifier into a verifier/mod.rs file. Make room for verifier sub-modules in separate files. --- lib/cretonne/src/{verifier.rs => verifier/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/cretonne/src/{verifier.rs => verifier/mod.rs} (100%) diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier/mod.rs similarity index 100% rename from lib/cretonne/src/verifier.rs rename to lib/cretonne/src/verifier/mod.rs From 4cf3babda0144807ef5b228c4220f0a668825d2d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 09:49:03 -0700 Subject: [PATCH 714/968] Add an enable_verifier setting. This is off by default, but enabled by the parser when reading a textual IL file. Test files can still override the default to turn off verification. The setting enables IL verifier passes at critical points of the compilation pipeline. --- lib/cretonne/meta/base/settings.py | 8 ++++++++ lib/cretonne/src/settings.rs | 1 + lib/reader/src/parser.rs | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/base/settings.py b/lib/cretonne/meta/base/settings.py index 1f7e21668b..186c808524 100644 --- a/lib/cretonne/meta/base/settings.py +++ b/lib/cretonne/meta/base/settings.py @@ -18,6 +18,14 @@ opt_level = EnumSetting( """, 'default', 'best', 'fastest') +enable_verifier = BoolSetting( + """ + Run the Cretonne IL verifier at strategic times during compilation. + + This makes compilation slower but catches many bugs. The verifier is + disabled by default, except when reading Cretonne IL from a text file. + """) + is_64bit = BoolSetting("Enable 64-bit code generation") is_compressed = BoolSetting("Enable compressed instructions") diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 23ff7d688d..204588c7c1 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -271,6 +271,7 @@ mod tests { assert_eq!(f.to_string(), "[shared]\n\ opt_level = \"default\"\n\ + enable_verifier = false\n\ is_64bit = false\n\ is_compressed = false\n\ enable_float = true\n\ diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f96d60518a..eae8c925b0 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -17,7 +17,7 @@ use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; use cretonne::isa::{self, TargetIsa, Encoding}; -use cretonne::settings; +use cretonne::settings::{self, Configurable}; use testfile::{TestFile, Details, Comment}; use error::{Location, Error, Result}; use lexer::{self, Lexer, Token}; @@ -577,6 +577,13 @@ impl<'a> Parser<'a> { let mut isas = Vec::new(); let mut flag_builder = settings::builder(); + // Change the default for `enable_verifier` to `true`. It defaults to `false` because it + // would slow down normal compilation, but when we're reading IL from a text file we're + // either testing or debugging Cretonne, and verification makes sense. + flag_builder + .set_bool("enable_verifier", true) + .expect("Missing enable_verifier setting"); + while let Some(Token::Identifier(command)) = self.token() { match command { "set" => { @@ -1849,7 +1856,10 @@ mod tests { assert_eq!(tf.commands[0].command, "cfg"); assert_eq!(tf.commands[1].command, "verify"); match tf.isa_spec { - IsaSpec::None(s) => assert!(!s.enable_float()), + IsaSpec::None(s) => { + assert!(s.enable_verifier()); + assert!(!s.enable_float()); + } _ => panic!("unexpected ISAs"), } assert_eq!(tf.preamble_comments.len(), 2); From 83381dadc5188de41e69d14baafafcaf680a64fb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 10:55:12 -0700 Subject: [PATCH 715/968] Add global CtonResult and CtonError types. These are for reporting the overall result of compiling a function. --- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/result.rs | 68 ++++++++++++++++++++++++++++++++ lib/cretonne/src/verifier/mod.rs | 7 ++++ 3 files changed, 76 insertions(+) create mode 100644 lib/cretonne/src/result.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index e44899f8c2..986c45355b 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -21,6 +21,7 @@ pub mod flowgraph; pub mod ir; pub mod isa; pub mod regalloc; +pub mod result; pub mod settings; pub mod sparse_map; pub mod verifier; diff --git a/lib/cretonne/src/result.rs b/lib/cretonne/src/result.rs new file mode 100644 index 0000000000..6605f1daff --- /dev/null +++ b/lib/cretonne/src/result.rs @@ -0,0 +1,68 @@ +//! Result and error types representing the outcome of compiling a function. + +use verifier; +use std::error::Error as StdError; +use std::fmt; +use std::result; + +/// A compilation error. +/// +/// When Cretonne fails to compile a function, it will return one of these error codes. +#[derive(Debug)] +pub enum CtonError { + /// An IL verifier error. + /// + /// This always represents a bug, either in the code that generated IL for Cretonne, or a bug + /// in Cretonne itself. + Verifier(verifier::Error), + + /// An implementation limit was exceeded. + /// + /// Cretonne can compile very large and complicated functions, but the implementation has + /// limits that cause compilation to fail when they are exceeded. + /// + /// See http://cretonne.readthedocs.io/en/latest/langref.html#implementation-limits + ImplLimitExceeded, + + /// The code size for the function is too large. + /// + /// Different target ISAs may impose a limit on the size of a compiled function. If that limit + /// is exceeded, compilation fails. + CodeTooLarge, +} + +/// A Cretonne compilation result. +pub type CtonResult = result::Result<(), CtonError>; + +impl fmt::Display for CtonError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CtonError::Verifier(ref e) => write!(f, "Verifier error: {}", e), + CtonError::ImplLimitExceeded | + CtonError::CodeTooLarge => f.write_str(self.description()), + } + } +} + +impl StdError for CtonError { + fn description(&self) -> &str { + match *self { + CtonError::Verifier(ref e) => &e.message, + CtonError::ImplLimitExceeded => "Implementation limit exceeded", + CtonError::CodeTooLarge => "Code for function is too large", + } + } + fn cause(&self) -> Option<&StdError> { + match *self { + CtonError::Verifier(ref e) => Some(e), + CtonError::ImplLimitExceeded | + CtonError::CodeTooLarge => None, + } + } +} + +impl From for CtonError { + fn from(e: verifier::Error) -> CtonError { + CtonError::Verifier(e) + } +} diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index ee78a07501..c6c2fe792e 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -59,6 +59,7 @@ use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallIn use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, Value, Type}; use Context; +use std::error as std_error; use std::fmt::{self, Display, Formatter}; use std::result; use std::collections::BTreeSet; @@ -78,6 +79,12 @@ impl Display for Error { } } +impl std_error::Error for Error { + fn description(&self) -> &str { + &self.message + } +} + /// Verifier result. pub type Result = result::Result; From b6105ab79bf07a16e709b941aab32300a5fc6b6e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 12:03:05 -0700 Subject: [PATCH 716/968] Verifier results are always void. No need for a type parameter. --- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/verifier/mod.rs | 46 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 051adc47aa..680ddbffeb 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -52,7 +52,7 @@ impl Context { /// /// The `TargetIsa` argument is currently unused, but the verifier will soon be able to also /// check ISA-dependent constraints. - pub fn verify<'a, ISA: Into>>(&self, _isa: ISA) -> verifier::Result<()> { + pub fn verify<'a, ISA: Into>>(&self, _isa: ISA) -> verifier::Result { verifier::verify_context(self) } diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index c6c2fe792e..b6f8ba8710 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -86,7 +86,7 @@ impl std_error::Error for Error { } /// Verifier result. -pub type Result = result::Result; +pub type Result = result::Result<(), Error>; // Create an `Err` variant of `Result` from a location and `format!` arguments. macro_rules! err { @@ -106,12 +106,12 @@ macro_rules! err { } /// Verify `func`. -pub fn verify_function(func: &Function) -> Result<()> { +pub fn verify_function(func: &Function) -> Result { Verifier::new(func).run() } /// Verify `ctx`. -pub fn verify_context(ctx: &Context) -> Result<()> { +pub fn verify_context(ctx: &Context) -> Result { let verifier = Verifier::new(&ctx.func); verifier.domtree_integrity(&ctx.domtree)?; verifier.cfg_integrity(&ctx.cfg)?; @@ -135,7 +135,7 @@ impl<'a> Verifier<'a> { } } - fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result<()> { + fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result { let is_terminator = self.func.dfg[inst].opcode().is_terminator(); let is_last_inst = self.func.layout.last_inst(ebb) == Some(inst); @@ -173,7 +173,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn instruction_integrity(&self, inst: Inst) -> Result<()> { + fn instruction_integrity(&self, inst: Inst) -> Result { let inst_data = &self.func.dfg[inst]; let dfg = &self.func.dfg; @@ -201,7 +201,7 @@ impl<'a> Verifier<'a> { self.verify_entity_references(inst) } - fn verify_entity_references(&self, inst: Inst) -> Result<()> { + fn verify_entity_references(&self, inst: Inst) -> Result { use ir::instructions::InstructionData::*; for &arg in self.func.dfg.inst_args(inst) { @@ -279,7 +279,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn verify_ebb(&self, inst: Inst, e: Ebb) -> Result<()> { + fn verify_ebb(&self, inst: Inst, e: Ebb) -> Result { if !self.func.dfg.ebb_is_valid(e) { err!(inst, "invalid ebb reference {}", e) } else { @@ -287,7 +287,7 @@ impl<'a> Verifier<'a> { } } - fn verify_sig_ref(&self, inst: Inst, s: SigRef) -> Result<()> { + fn verify_sig_ref(&self, inst: Inst, s: SigRef) -> Result { if !self.func.dfg.signatures.is_valid(s) { err!(inst, "invalid signature reference {}", s) } else { @@ -295,7 +295,7 @@ impl<'a> Verifier<'a> { } } - fn verify_func_ref(&self, inst: Inst, f: FuncRef) -> Result<()> { + fn verify_func_ref(&self, inst: Inst, f: FuncRef) -> Result { if !self.func.dfg.ext_funcs.is_valid(f) { err!(inst, "invalid function reference {}", f) } else { @@ -303,7 +303,7 @@ impl<'a> Verifier<'a> { } } - fn verify_stack_slot(&self, inst: Inst, ss: StackSlot) -> Result<()> { + fn verify_stack_slot(&self, inst: Inst, ss: StackSlot) -> Result { if !self.func.stack_slots.is_valid(ss) { err!(inst, "invalid stack slot {}", ss) } else { @@ -311,7 +311,7 @@ impl<'a> Verifier<'a> { } } - fn verify_value_list(&self, inst: Inst, l: &ValueList) -> Result<()> { + fn verify_value_list(&self, inst: Inst, l: &ValueList) -> Result { if !l.is_valid(&self.func.dfg.value_lists) { err!(inst, "invalid value list reference {:?}", l) } else { @@ -319,7 +319,7 @@ impl<'a> Verifier<'a> { } } - fn verify_jump_table(&self, inst: Inst, j: JumpTable) -> Result<()> { + fn verify_jump_table(&self, inst: Inst, j: JumpTable) -> Result { if !self.func.jump_tables.is_valid(j) { err!(inst, "invalid jump table reference {}", j) } else { @@ -327,7 +327,7 @@ impl<'a> Verifier<'a> { } } - fn verify_value(&self, loc_inst: Inst, v: Value) -> Result<()> { + fn verify_value(&self, loc_inst: Inst, v: Value) -> Result { let dfg = &self.func.dfg; if !dfg.value_is_valid(v) { return err!(loc_inst, "invalid value reference {}", v); @@ -378,7 +378,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn domtree_integrity(&self, domtree: &DominatorTree) -> Result<()> { + fn domtree_integrity(&self, domtree: &DominatorTree) -> Result { // We consider two `DominatorTree`s to be equal if they return the same immediate // dominator for each EBB. Therefore the current domtree is valid if it matches the freshly // computed one. @@ -396,7 +396,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn typecheck_entry_block_arguments(&self) -> Result<()> { + fn typecheck_entry_block_arguments(&self) -> Result { if let Some(ebb) = self.func.layout.entry_block() { let expected_types = &self.func.signature.argument_types; let ebb_arg_count = self.func.dfg.num_ebb_args(ebb); @@ -419,7 +419,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn typecheck(&self, inst: Inst) -> Result<()> { + fn typecheck(&self, inst: Inst) -> Result { let inst_data = &self.func.dfg[inst]; let constraints = inst_data.opcode().constraints(); @@ -446,7 +446,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn typecheck_results(&self, inst: Inst, ctrl_type: Type) -> Result<()> { + fn typecheck_results(&self, inst: Inst, ctrl_type: Type) -> Result { let mut i = 0; for &result in self.func.dfg.inst_results(inst) { let result_type = self.func.dfg.value_type(result); @@ -473,7 +473,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn typecheck_fixed_args(&self, inst: Inst, ctrl_type: Type) -> Result<()> { + fn typecheck_fixed_args(&self, inst: Inst, ctrl_type: Type) -> Result { let constraints = self.func.dfg[inst].opcode().constraints(); for (i, &arg) in self.func.dfg.inst_fixed_args(inst).iter().enumerate() { @@ -504,7 +504,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn typecheck_variable_args(&self, inst: Inst) -> Result<()> { + fn typecheck_variable_args(&self, inst: Inst) -> Result { match self.func.dfg[inst].analyze_branch(&self.func.dfg.value_lists) { BranchInfo::SingleDest(ebb, _) => { let iter = self.func @@ -552,7 +552,7 @@ impl<'a> Verifier<'a> { fn typecheck_variable_args_iterator>(&self, inst: Inst, iter: I) - -> Result<()> { + -> Result { let variable_args = self.func.dfg.inst_variable_args(inst); let mut i = 0; @@ -583,7 +583,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn typecheck_return(&self, inst: Inst) -> Result<()> { + fn typecheck_return(&self, inst: Inst) -> Result { if self.func.dfg[inst].opcode().is_return() { let args = self.func.dfg.inst_variable_args(inst); let expected_types = &self.func.signature.return_types; @@ -605,7 +605,7 @@ impl<'a> Verifier<'a> { Ok(()) } - fn cfg_integrity(&self, cfg: &ControlFlowGraph) -> Result<()> { + fn cfg_integrity(&self, cfg: &ControlFlowGraph) -> Result { let mut expected_succs = BTreeSet::::new(); let mut got_succs = BTreeSet::::new(); let mut expected_preds = BTreeSet::::new(); @@ -653,7 +653,7 @@ impl<'a> Verifier<'a> { Ok(()) } - pub fn run(&self) -> Result<()> { + pub fn run(&self) -> Result { self.typecheck_entry_block_arguments()?; for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { From 225ed39fbdabdd9cad63e37852e1f7e85231aaa8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 12:36:35 -0700 Subject: [PATCH 717/968] Run the verifier in the Context methods when it is enabled. The test drivers can stop calling comp_ctx.verify because legalize() and regalloc() do it themselves now. This also makes it possible for those two passes to return other CtonError codes in the future, not just verifier errors. --- lib/cretonne/src/context.rs | 16 ++++++++++++++-- src/filetest/legalizer.rs | 7 +++---- src/filetest/regalloc.rs | 10 +++++----- src/utils.rs | 10 ++++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 680ddbffeb..71dfa4539b 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -15,6 +15,7 @@ use ir::Function; use isa::TargetIsa; use legalize_function; use regalloc; +use result::CtonResult; use verifier; /// Persistent data structures and compilation pipeline. @@ -56,9 +57,19 @@ impl Context { verifier::verify_context(self) } + /// Run the verifier only if the `enable_verifier` setting is true. + pub fn verify_if(&self, isa: &TargetIsa) -> CtonResult { + if isa.flags().enable_verifier() { + self.verify(isa).map_err(Into::into) + } else { + Ok(()) + } + } + /// Run the legalizer for `isa` on the function. - pub fn legalize(&mut self, isa: &TargetIsa) { + pub fn legalize(&mut self, isa: &TargetIsa) -> CtonResult { legalize_function(&mut self.func, &mut self.cfg, isa); + self.verify_if(isa) } /// Recompute the control flow graph and dominator tree. @@ -68,8 +79,9 @@ impl Context { } /// Run the register allocator. - pub fn regalloc(&mut self, isa: &TargetIsa) { + pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc .run(isa, &mut self.func, &self.cfg, &self.domtree); + self.verify_if(isa) } } diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index 1a5f774664..c6e08ffe29 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -8,7 +8,7 @@ use cretonne::{self, write_function}; use cretonne::ir::Function; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; -use utils::pretty_verifier_error; +use utils::pretty_error; struct TestLegalizer; @@ -40,9 +40,8 @@ impl SubTest for TestLegalizer { let isa = context.isa.expect("legalizer needs an ISA"); comp_ctx.flowgraph(); - comp_ctx.legalize(isa); - comp_ctx.verify(isa) - .map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; + comp_ctx.legalize(isa) + .map_err(|e| pretty_error(&comp_ctx.func, e))?; let mut text = String::new(); write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index daf7adcb07..87045eb764 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -10,7 +10,7 @@ use cretonne::{self, write_function}; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; -use utils::pretty_verifier_error; +use utils::pretty_error; struct TestRegalloc; @@ -45,10 +45,10 @@ impl SubTest for TestRegalloc { comp_ctx.flowgraph(); // TODO: Should we have an option to skip legalization? - comp_ctx.legalize(isa); - comp_ctx.regalloc(isa); - comp_ctx.verify(isa) - .map_err(|e| pretty_verifier_error(&comp_ctx.func, e))?; + comp_ctx.legalize(isa) + .map_err(|e| pretty_error(&comp_ctx.func, e))?; + comp_ctx.regalloc(isa) + .map_err(|e| pretty_error(&comp_ctx.func, e))?; let mut text = String::new(); write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; diff --git a/src/utils.rs b/src/utils.rs index d2a6fae3c7..3519c55627 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ use cretonne::ir::entities::AnyEntity; use cretonne::{ir, verifier, write_function}; +use cretonne::result::CtonError; use std::fmt::Write; use std::fs::File; use std::io::{Result, Read}; @@ -45,6 +46,15 @@ pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> Strin msg } +/// Pretty-print a Cretonne error. +pub fn pretty_error(func: &ir::Function, err: CtonError) -> String { + if let CtonError::Verifier(e) = err { + pretty_verifier_error(func, e) + } else { + err.to_string() + } +} + #[test] fn test_match_directive() { assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar")); From 85f277a2fbf0c5143804d2012f8a2158846f551a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 13:35:20 -0700 Subject: [PATCH 718/968] Add a liveness verifier. The liveness verifier will check that the live ranges are consistent with the function. It runs as part of the register allocation pipeline when enable_verifier is set. The initial implementation checks the live ranges, but not the ISA-specific constraints and affinities. --- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/regalloc/context.rs | 11 +- lib/cretonne/src/regalloc/liverange.rs | 11 +- lib/cretonne/src/verifier/liveness.rs | 179 +++++++++++++++++++++++++ lib/cretonne/src/verifier/mod.rs | 38 +++--- src/utils.rs | 2 +- 6 files changed, 220 insertions(+), 23 deletions(-) create mode 100644 lib/cretonne/src/verifier/liveness.rs diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 71dfa4539b..f8a207c6fc 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -81,7 +81,7 @@ impl Context { /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc - .run(isa, &mut self.func, &self.cfg, &self.domtree); + .run(isa, &mut self.func, &self.cfg, &self.domtree)?; self.verify_if(isa) } } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 2c59ab9797..5d91a258fa 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -11,6 +11,8 @@ use isa::TargetIsa; use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; +use result::CtonResult; +use verifier::verify_liveness; /// Persistent memory allocations for register allocation. pub struct Context { @@ -40,7 +42,8 @@ impl Context { isa: &TargetIsa, func: &mut Function, cfg: &ControlFlowGraph, - domtree: &DominatorTree) { + domtree: &DominatorTree) + -> CtonResult { // `Liveness` and `Coloring` are self-clearing. // Tracker state (dominator live sets) is actually reused between the spilling and coloring // phases. @@ -49,10 +52,16 @@ impl Context { // First pass: Liveness analysis. self.liveness.compute(isa, func, cfg); + if isa.flags().enable_verifier() { + verify_liveness(isa, func, cfg, &self.liveness)?; + } + // TODO: Second pass: Spilling. // Third pass: Reload and coloring. self.coloring .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + + Ok(()) } } diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 94c16cb9ae..0594af19ed 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -174,12 +174,12 @@ pub struct LiveRange { /// for contiguous EBBs where all but the last live-in interval covers the whole EBB. /// #[derive(Copy, Clone)] -struct Interval { +pub struct Interval { /// Interval starting point. /// /// Since this interval does not represent the def of the value, it must begin at an EBB header /// where the value is live-in. - begin: Ebb, + pub begin: Ebb, /// Interval end point. /// @@ -190,7 +190,7 @@ struct Interval { /// When this represents multiple contiguous live-in intervals, this is the end point of the /// last interval. The other intervals end at the terminator instructions of their respective /// EBB. - end: Inst, + pub end: Inst, } impl Interval { @@ -368,6 +368,11 @@ impl LiveRange { .ok() .map(|n| self.liveins[n].end) } + + /// Get all the live-in intervals. + pub fn liveins(&self) -> &[Interval] { + &self.liveins + } } /// Allow a `LiveRange` to be stored in a `SparseMap` indexed by values. diff --git a/lib/cretonne/src/verifier/liveness.rs b/lib/cretonne/src/verifier/liveness.rs new file mode 100644 index 0000000000..d80f18a8b3 --- /dev/null +++ b/lib/cretonne/src/verifier/liveness.rs @@ -0,0 +1,179 @@ +//! Liveness verifier. + +use flowgraph::ControlFlowGraph; +use ir::{Function, Inst, Value, ProgramOrder, ProgramPoint, ExpandedProgramPoint}; +use ir::entities::AnyEntity; +use isa::TargetIsa; +use regalloc::liveness::Liveness; +use regalloc::liverange::LiveRange; +use std::cmp::Ordering; +use verifier::Result; + +/// Verify liveness information for `func`. +/// +/// The provided control flow graph is assumed to be sound. +/// +/// - All values in the program must have a live range. +/// - The live range def point must match where the value is defined. +/// - The live range must reach all uses. +/// - When a live range is live-in to an EBB, it must be live at all the predecessors. +/// - The live range affinity must be compatible with encoding constraints. +/// +/// We don't verify that live ranges are minimal. This would require recomputing live ranges for +/// all values. +pub fn verify_liveness(_isa: &TargetIsa, + func: &Function, + cfg: &ControlFlowGraph, + liveness: &Liveness) + -> Result { + let verifier = LivenessVerifier { + func: func, + cfg: cfg, + liveness: liveness, + }; + verifier.check_ebbs()?; + verifier.check_insts()?; + Ok(()) +} + +struct LivenessVerifier<'a> { + func: &'a Function, + cfg: &'a ControlFlowGraph, + liveness: &'a Liveness, +} + +impl<'a> LivenessVerifier<'a> { + /// Check all EBB arguments. + fn check_ebbs(&self) -> Result { + for ebb in self.func.layout.ebbs() { + for &val in self.func.dfg.ebb_args(ebb) { + let lr = match self.liveness.get(val) { + Some(lr) => lr, + None => return err!(ebb, "EBB arg {} has no live range", val), + }; + self.check_lr(ebb.into(), val, lr)?; + } + } + Ok(()) + } + + /// Check all instructions. + fn check_insts(&self) -> Result { + for ebb in self.func.layout.ebbs() { + for inst in self.func.layout.ebb_insts(ebb) { + // Check the defs. + for &val in self.func.dfg.inst_results(inst) { + let lr = match self.liveness.get(val) { + Some(lr) => lr, + None => return err!(inst, "{} has no live range", val), + }; + self.check_lr(inst.into(), val, lr)?; + } + + // Check the uses. + for &val in self.func.dfg.inst_args(inst) { + let lr = match self.liveness.get(val) { + Some(lr) => lr, + None => return err!(inst, "{} has no live range", val), + }; + if !self.live_at_use(lr, inst) { + return err!(inst, "{} is not live at this use", val); + } + } + } + } + Ok(()) + } + + /// Is `lr` live at the use `inst`? + fn live_at_use(&self, lr: &LiveRange, inst: Inst) -> bool { + let l = &self.func.layout; + + // Check if `inst` is in the def range, not including the def itself. + if l.cmp(lr.def(), inst) == Ordering::Less && + l.cmp(inst, lr.def_local_end()) != Ordering::Greater { + return true; + } + + // Otherwise see if `inst` is in one of the live-in ranges. + match lr.livein_local_end(l.inst_ebb(inst).unwrap(), l) { + Some(end) => l.cmp(inst, end) != Ordering::Greater, + None => false, + } + } + + /// Check the integrity of the live range `lr`. + fn check_lr(&self, def: ProgramPoint, val: Value, lr: &LiveRange) -> Result { + let l = &self.func.layout; + + let loc: AnyEntity = match def.into() { + ExpandedProgramPoint::Ebb(e) => e.into(), + ExpandedProgramPoint::Inst(i) => i.into(), + }; + if lr.def() != def { + return err!(loc, "Wrong live range def ({}) for {}", lr.def(), val); + } + if lr.is_dead() { + if !lr.is_local() { + return err!(loc, "Dead live range {} should be local", val); + } else { + return Ok(()); + } + } + let def_ebb = match def.into() { + ExpandedProgramPoint::Ebb(e) => e, + ExpandedProgramPoint::Inst(i) => l.inst_ebb(i).unwrap(), + }; + match lr.def_local_end().into() { + ExpandedProgramPoint::Ebb(e) => { + return err!(loc, "Def local range for {} can't end at {}", val, e) + } + ExpandedProgramPoint::Inst(i) => { + if self.func.layout.inst_ebb(i) != Some(def_ebb) { + return err!(loc, "Def local end for {} in wrong ebb", val); + } + } + } + + // Now check the live-in intervals against the CFG. + for &livein in lr.liveins() { + let mut ebb = livein.begin; + if !l.is_ebb_inserted(ebb) { + return err!(loc, "{} livein at {} which is not in the layout", val, ebb); + } + let end_ebb = match l.inst_ebb(livein.end) { + Some(e) => e, + None => { + return err!(loc, + "{} livein for {} ends at {} which is not in the layout", + val, + ebb, + livein.end) + } + }; + + // Check all the EBBs in the interval independently. + loop { + // If `val` is live-in at `ebb`, it must be live at all the predecessors. + for &(_, pred) in self.cfg.get_predecessors(ebb) { + if !self.live_at_use(lr, pred) { + return err!(pred, + "{} is live in to {} but not live at predecessor", + val, + ebb); + } + } + + if ebb == end_ebb { + break; + } + ebb = match l.next_ebb(ebb) { + Some(e) => e, + None => return err!(loc, "end of {} livein ({}) never reached", val, end_ebb), + }; + } + } + + Ok(()) + } +} diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index b6f8ba8710..f2e8a7fc50 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -64,6 +64,27 @@ use std::fmt::{self, Display, Formatter}; use std::result; use std::collections::BTreeSet; +pub use self::liveness::verify_liveness; + +// Create an `Err` variant of `Result` from a location and `format!` arguments. +macro_rules! err { + ( $loc:expr, $msg:expr ) => { + Err(::verifier::Error { + location: $loc.into(), + message: String::from($msg), + }) + }; + + ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => { + Err(::verifier::Error { + location: $loc.into(), + message: format!( $fmt, $( $arg ),+ ), + }) + }; +} + +mod liveness; + /// A verifier error. #[derive(Debug, PartialEq, Eq)] pub struct Error { @@ -88,23 +109,6 @@ impl std_error::Error for Error { /// Verifier result. pub type Result = result::Result<(), Error>; -// Create an `Err` variant of `Result` from a location and `format!` arguments. -macro_rules! err { - ( $loc:expr, $msg:expr ) => { - Err(Error { - location: $loc.into(), - message: String::from($msg), - }) - }; - - ( $loc:expr, $fmt:expr, $( $arg:expr ),+ ) => { - Err(Error { - location: $loc.into(), - message: format!( $fmt, $( $arg ),+ ), - }) - }; -} - /// Verify `func`. pub fn verify_function(func: &Function) -> Result { Verifier::new(func).run() diff --git a/src/utils.rs b/src/utils.rs index 3519c55627..1bcba4891a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -40,7 +40,7 @@ pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> Strin AnyEntity::Inst(inst) => { write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst)).unwrap() } - _ => {} + _ => msg.push('\n'), } write_function(&mut msg, func, None).unwrap(); msg From 5d9e703e4f6cebed96ef16c77a5004d167a170eb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Apr 2017 16:23:33 -0700 Subject: [PATCH 719/968] Run the post-regalloc verification inside the regalloc context. This means that we can verify the basics with verify_context before moving on to verifying the liveness information. Live ranges are now verified immediately after computing them and after register allocation is complete. --- lib/cretonne/src/context.rs | 5 ++--- lib/cretonne/src/regalloc/context.rs | 6 +++++- lib/cretonne/src/verifier/mod.rs | 12 ++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index f8a207c6fc..e4ba8a0667 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -54,7 +54,7 @@ impl Context { /// The `TargetIsa` argument is currently unused, but the verifier will soon be able to also /// check ISA-dependent constraints. pub fn verify<'a, ISA: Into>>(&self, _isa: ISA) -> verifier::Result { - verifier::verify_context(self) + verifier::verify_context(&self.func, &self.cfg, &self.domtree) } /// Run the verifier only if the `enable_verifier` setting is true. @@ -81,7 +81,6 @@ impl Context { /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc - .run(isa, &mut self.func, &self.cfg, &self.domtree)?; - self.verify_if(isa) + .run(isa, &mut self.func, &self.cfg, &self.domtree) } } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 5d91a258fa..8f399c65de 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -12,7 +12,7 @@ use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; use result::CtonResult; -use verifier::verify_liveness; +use verifier::{verify_context, verify_liveness}; /// Persistent memory allocations for register allocation. pub struct Context { @@ -62,6 +62,10 @@ impl Context { self.coloring .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + if isa.flags().enable_verifier() { + verify_context(func, cfg, domtree)?; + verify_liveness(isa, func, cfg, &self.liveness)?; + } Ok(()) } } diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index f2e8a7fc50..1cc8f740dd 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -58,7 +58,6 @@ use ir::entities::AnyEntity; use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, Value, Type}; -use Context; use std::error as std_error; use std::fmt::{self, Display, Formatter}; use std::result; @@ -114,11 +113,12 @@ pub fn verify_function(func: &Function) -> Result { Verifier::new(func).run() } -/// Verify `ctx`. -pub fn verify_context(ctx: &Context) -> Result { - let verifier = Verifier::new(&ctx.func); - verifier.domtree_integrity(&ctx.domtree)?; - verifier.cfg_integrity(&ctx.cfg)?; +/// Verify `func` after checking the integrity of associated context data structures `cfg` and +/// `domtree`. +pub fn verify_context(func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) -> Result { + let verifier = Verifier::new(func); + verifier.cfg_integrity(cfg)?; + verifier.domtree_integrity(domtree)?; verifier.run() } From 100666e3004d6c3c6fbc3bdecac19bda27d31ea2 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Sat, 22 Apr 2017 16:58:29 -0700 Subject: [PATCH 720/968] Verify that the instruction encoding matches what the ISA would encode. Fixes #69 --- filetests/isa/riscv/verify-encoding.cton | 21 +++++++++ lib/cretonne/src/context.rs | 4 +- lib/cretonne/src/regalloc/context.rs | 2 +- lib/cretonne/src/verifier/mod.rs | 59 +++++++++++++++++++++--- src/filetest/runone.rs | 2 +- src/filetest/verifier.rs | 2 +- 6 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 filetests/isa/riscv/verify-encoding.cton diff --git a/filetests/isa/riscv/verify-encoding.cton b/filetests/isa/riscv/verify-encoding.cton new file mode 100644 index 0000000000..73d28c842a --- /dev/null +++ b/filetests/isa/riscv/verify-encoding.cton @@ -0,0 +1,21 @@ +test verifier +isa riscv + +function RV32I(i32 link [%x1]) -> i32 link [%x1] { + fn0 = function foo() + +ebb0(v9999: i32): + ; iconst.i32 needs legalizing, so it should throw a + [R#0,-] v1 = iconst.i32 1 ; error: Instruction failed to re-encode + return v9999 +} + +function RV32I(i32 link [%x1]) -> i32 link [%x1] { + fn0 = function foo() + +ebb0(v9999: i32): + v1 = iconst.i32 1 + v2 = iconst.i32 2 + [R#0,-] v3 = iadd v1, v2 ; error: Instruction re-encoding + return v9999 +} diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index e4ba8a0667..d8b6af4d0b 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -53,8 +53,8 @@ impl Context { /// /// The `TargetIsa` argument is currently unused, but the verifier will soon be able to also /// check ISA-dependent constraints. - pub fn verify<'a, ISA: Into>>(&self, _isa: ISA) -> verifier::Result { - verifier::verify_context(&self.func, &self.cfg, &self.domtree) + pub fn verify<'a, ISA: Into>>(&self, isa: ISA) -> verifier::Result { + verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa.into()) } /// Run the verifier only if the `enable_verifier` setting is true. diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 8f399c65de..b57edebb5f 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -63,7 +63,7 @@ impl Context { .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); if isa.flags().enable_verifier() { - verify_context(func, cfg, domtree)?; + verify_context(func, cfg, domtree, Some(isa))?; verify_liveness(isa, func, cfg, &self.liveness)?; } Ok(()) diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 1cc8f740dd..6bf9b155c6 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -58,6 +58,7 @@ use ir::entities::AnyEntity; use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, Value, Type}; +use isa::TargetIsa; use std::error as std_error; use std::fmt::{self, Display, Formatter}; use std::result; @@ -109,14 +110,18 @@ impl std_error::Error for Error { pub type Result = result::Result<(), Error>; /// Verify `func`. -pub fn verify_function(func: &Function) -> Result { - Verifier::new(func).run() +pub fn verify_function(func: &Function, isa: Option<&TargetIsa>) -> Result { + Verifier::new(func, isa).run() } /// Verify `func` after checking the integrity of associated context data structures `cfg` and /// `domtree`. -pub fn verify_context(func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) -> Result { - let verifier = Verifier::new(func); +pub fn verify_context(func: &Function, + cfg: &ControlFlowGraph, + domtree: &DominatorTree, + isa: Option<&TargetIsa>) + -> Result { + let verifier = Verifier::new(func, isa); verifier.cfg_integrity(cfg)?; verifier.domtree_integrity(domtree)?; verifier.run() @@ -126,16 +131,18 @@ struct Verifier<'a> { func: &'a Function, cfg: ControlFlowGraph, domtree: DominatorTree, + isa: Option<&'a TargetIsa>, } impl<'a> Verifier<'a> { - pub fn new(func: &'a Function) -> Verifier { + pub fn new(func: &'a Function, isa: Option<&'a TargetIsa>) -> Verifier<'a> { let cfg = ControlFlowGraph::with_function(func); let domtree = DominatorTree::with_function(func, &cfg); Verifier { func: func, cfg: cfg, domtree: domtree, + isa: isa, } } @@ -657,6 +664,42 @@ impl<'a> Verifier<'a> { Ok(()) } + /// If the verifier has been set up with an ISA, make sure that the recorded encoding for the + /// instruction (if any) matches how the ISA would encode it. + fn verify_encoding(&self, inst: Inst) -> Result { + if let Some(isa) = self.isa { + let encoding = self.func + .encodings + .get(inst) + .cloned() + .unwrap_or_default(); + if encoding.is_legal() { + let verify_encoding = + isa.encode(&self.func.dfg, + &self.func.dfg[inst], + self.func.dfg.ctrl_typevar(inst)); + match verify_encoding { + Ok(verify_encoding) => { + if verify_encoding != encoding { + return err!(inst, + "Instruction re-encoding {} doesn't match {}", + isa.encoding_info().display(verify_encoding), + isa.encoding_info().display(encoding)); + } + } + Err(e) => { + return err!(inst, + "Instruction failed to re-encode {}: {:?}", + isa.encoding_info().display(encoding), + e) + } + } + } + } + + Ok(()) + } + pub fn run(&self) -> Result { self.typecheck_entry_block_arguments()?; for ebb in self.func.layout.ebbs() { @@ -664,8 +707,10 @@ impl<'a> Verifier<'a> { self.ebb_integrity(ebb, inst)?; self.instruction_integrity(inst)?; self.typecheck(inst)?; + self.verify_encoding(inst)?; } } + Ok(()) } } @@ -692,7 +737,7 @@ mod tests { #[test] fn empty() { let func = Function::new(); - let verifier = Verifier::new(&func); + let verifier = Verifier::new(&func, None); assert_eq!(verifier.run(), Ok(())); } @@ -705,7 +750,7 @@ mod tests { func.dfg .make_inst(InstructionData::Nullary { opcode: Opcode::Jump }); func.layout.append_inst(nullary_with_bad_opcode, ebb0); - let verifier = Verifier::new(&func); + let verifier = Verifier::new(&func, None); assert_err_with_msg!(verifier.run(), "instruction format"); } } diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index bc16725d74..60231d22f4 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -116,7 +116,7 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { - verify_function(&func).map_err(|e| pretty_verifier_error(&func, e))?; + verify_function(&func, isa).map_err(|e| pretty_verifier_error(&func, e))?; context.verified = true; } diff --git a/src/filetest/verifier.rs b/src/filetest/verifier.rs index 5812dc9db0..1dd5cc88b4 100644 --- a/src/filetest/verifier.rs +++ b/src/filetest/verifier.rs @@ -51,7 +51,7 @@ impl SubTest for TestVerifier { } } - match verify_function(func) { + match verify_function(func, context.isa) { Ok(_) => { match expected { None => Ok(()), From 551a91178acc515d321cd802885e834185d44e47 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Sat, 22 Apr 2017 17:39:43 -0700 Subject: [PATCH 721/968] Update a comment for the move of display_enc(). --- lib/cretonne/src/isa/encoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index c5151978aa..3d866727a4 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -60,7 +60,7 @@ impl fmt::Display for Encoding { } /// Temporary object that holds enough context to properly display an encoding. -/// This is meant to be created by `TargetIsa::display_enc()`. +/// This is meant to be created by `EncInfo::display()`. pub struct DisplayEncoding { pub encoding: Encoding, pub recipe_names: &'static [&'static str], From b13cd2321c56483a793211cac7da644802784fc0 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Sun, 23 Apr 2017 11:45:12 -0700 Subject: [PATCH 722/968] Make sure we double back after legalizing an instruction. The other legalizer cases have a continue after setting the position to double back, while this one didn't. Make sure that we do, in case another legalizer block is added after this one. --- lib/cretonne/src/legalizer/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 7949aed311..50300027a2 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -91,6 +91,7 @@ pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: & // unsound. Should we attempt to detect that? if changed { pos.set_position(prev_pos); + continue; } } } From a332c3d024c02b961ea975dcebcb99335583bc52 Mon Sep 17 00:00:00 2001 From: Eric Anholt Date: Sun, 23 Apr 2017 11:55:14 -0700 Subject: [PATCH 723/968] Make sure that encodings has entries for all instructions after legalize(). If we generated new instructions as part of legalize, and the new instructions failed to legalize, we'd be left with a func.encodings[] that would panic when you dereferenced the inst. --- lib/cretonne/src/legalizer/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 50300027a2..edd233ccee 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -100,6 +100,7 @@ pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: & prev_pos = pos.position(); } } + func.encodings.resize(func.dfg.num_insts()); } // Include legalization patterns that were generated by `gen_legalizer.py` from the `XForms` in From c36aedfd03a5c5491364d814512302acf1569e2d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 25 Apr 2017 15:50:59 -0700 Subject: [PATCH 724/968] Add an EntityMap::get_or_default() method. This covers a common pattern for secondary entity maps: Get the value in the map or the default value for out-of-range keys. --- lib/cretonne/src/binemit/relaxation.rs | 2 +- lib/cretonne/src/entity_map.rs | 5 +++++ lib/cretonne/src/verifier/mod.rs | 6 +----- lib/cretonne/src/write.rs | 8 +------- src/filetest/binemit.rs | 7 ++----- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index 179540dad0..74158a61e4 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -64,7 +64,7 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { } while let Some(inst) = pos.next_inst() { - let enc = func.encodings.get(inst).cloned().unwrap_or_default(); + let enc = func.encodings.get_or_default(inst); let size = encinfo.bytes(enc); // See if this might be a branch that is out of range. diff --git a/lib/cretonne/src/entity_map.rs b/lib/cretonne/src/entity_map.rs index f9ac258f92..cee4215a4e 100644 --- a/lib/cretonne/src/entity_map.rs +++ b/lib/cretonne/src/entity_map.rs @@ -148,6 +148,11 @@ impl EntityMap } &mut self.elems[k.index()] } + + /// Get the element at `k` or the default value if `k` is out of range. + pub fn get_or_default(&self, k: K) -> V { + self.elems.get(k.index()).cloned().unwrap_or_default() + } } /// Immutable indexing into an `EntityMap`. diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 6bf9b155c6..e3d5607c99 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -668,11 +668,7 @@ impl<'a> Verifier<'a> { /// instruction (if any) matches how the ISA would encode it. fn verify_encoding(&self, inst: Inst) -> Result { if let Some(isa) = self.isa { - let encoding = self.func - .encodings - .get(inst) - .cloned() - .unwrap_or_default(); + let encoding = self.func.encodings.get_or_default(inst); if encoding.is_legal() { let verify_encoding = isa.encode(&self.func.dfg, diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index fcfb843c0c..5d46ccb624 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -189,13 +189,7 @@ fn write_instruction(w: &mut Write, if !func.locations.is_empty() { let regs = isa.register_info(); for &r in func.dfg.inst_results(inst) { - write!(s, - ",{}", - func.locations - .get(r) - .cloned() - .unwrap_or_default() - .display(®s))? + write!(s, ",{}", func.locations.get_or_default(r).display(®s))? } } write!(s, "]")?; diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 449e0887e6..2f695bff1e 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -106,10 +106,7 @@ impl SubTest for TestBinEmit { // Give an encoding to any instruction that doesn't already have one. for ebb in func.layout.ebbs() { for inst in func.layout.ebb_insts(ebb) { - if !func.encodings - .get(inst) - .map(|e| e.is_legal()) - .unwrap_or(false) { + if !func.encodings.get_or_default(inst).is_legal() { if let Ok(enc) = isa.encode(&func.dfg, &func.dfg[inst], func.dfg.ctrl_typevar(inst)) { @@ -157,7 +154,7 @@ impl SubTest for TestBinEmit { ebb); for inst in func.layout.ebb_insts(ebb) { sink.text.clear(); - let enc = func.encodings.get(inst).cloned().unwrap_or_default(); + let enc = func.encodings.get_or_default(inst); // Send legal encodings into the emitter. if enc.is_legal() { From cd99eee86b850ce2595908aaba77ac07f7a9016f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Apr 2017 10:11:50 -0700 Subject: [PATCH 725/968] Add a TargetIsa::regclass_for_abi_type() function. The legalize_signature() function will return ArgumentLoc::Reg arguments that contain a register unit. However, the register also needs to be able to associate a register class with the argument values to fully track the used registers. When values are defined by instructions, the register class is part for the operand constraints for the instruction. For values defined on ABI boundaries like function arguments and return values from a call, the register class is provided by the new regclass_for_abi_type() function. Provide implementations of this function in abi modules of all the targets, even those that don't have a legalize_signature() implementation yet. Since we're adding abi modules to all targets, move the legalize_signature() stubs in there and make the function mandatory in TargetIsa. All targets will eventually need this function. --- lib/cretonne/src/isa/arm32/abi.rs | 27 +++++++++++++++++++++++++++ lib/cretonne/src/isa/arm32/mod.rs | 11 ++++++++++- lib/cretonne/src/isa/arm64/abi.rs | 18 ++++++++++++++++++ lib/cretonne/src/isa/arm64/mod.rs | 11 ++++++++++- lib/cretonne/src/isa/intel/abi.rs | 18 ++++++++++++++++++ lib/cretonne/src/isa/intel/mod.rs | 11 ++++++++++- lib/cretonne/src/isa/mod.rs | 14 ++++++++++---- lib/cretonne/src/isa/riscv/abi.rs | 6 ++++++ lib/cretonne/src/isa/riscv/mod.rs | 6 +++++- 9 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 lib/cretonne/src/isa/arm32/abi.rs create mode 100644 lib/cretonne/src/isa/arm64/abi.rs create mode 100644 lib/cretonne/src/isa/intel/abi.rs diff --git a/lib/cretonne/src/isa/arm32/abi.rs b/lib/cretonne/src/isa/arm32/abi.rs new file mode 100644 index 0000000000..c4e0ffc2ee --- /dev/null +++ b/lib/cretonne/src/isa/arm32/abi.rs @@ -0,0 +1,27 @@ +//! ARM ABI implementation. + +use ir; +use isa::RegClass; +use settings as shared_settings; +use super::registers::{S, D, Q, GPR}; + +/// Legalize `sig`. +pub fn legalize_signature(_sig: &mut ir::Signature, + _flags: &shared_settings::Flags, + _current: bool) { + unimplemented!() +} + +/// Get register class for a type appearing in a legalized signature. +pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { + if ty.is_int() { + GPR + } else { + match ty.bits() { + 32 => S, + 64 => D, + 128 => Q, + _ => panic!("Unexpected {} ABI type for arm32", ty), + } + } +} diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index b76ffa32cb..735fcd4b2c 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -1,6 +1,7 @@ //! ARM 32-bit Instruction Set Architecture. pub mod settings; +mod abi; mod binemit; mod enc_tables; mod registers; @@ -9,7 +10,7 @@ use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir; #[allow(dead_code)] @@ -77,6 +78,14 @@ impl TargetIsa for Isa { }) } + fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { + abi::legalize_signature(sig, &self.shared_flags, current) + } + + fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass { + abi::regclass_for_abi_type(ty) + } + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/arm64/abi.rs b/lib/cretonne/src/isa/arm64/abi.rs new file mode 100644 index 0000000000..8c2e9ed7d6 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/abi.rs @@ -0,0 +1,18 @@ +//! ARM 64 ABI implementation. + +use ir; +use isa::RegClass; +use settings as shared_settings; +use super::registers::{GPR, FPR}; + +/// Legalize `sig`. +pub fn legalize_signature(_sig: &mut ir::Signature, + _flags: &shared_settings::Flags, + _current: bool) { + unimplemented!() +} + +/// Get register class for a type appearing in a legalized signature. +pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { + if ty.is_int() { GPR } else { FPR } +} diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 2499028919..91a8a50f4e 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -1,6 +1,7 @@ //! ARM 64-bit Instruction Set Architecture. pub mod settings; +mod abi; mod binemit; mod enc_tables; mod registers; @@ -9,7 +10,7 @@ use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir; #[allow(dead_code)] @@ -70,6 +71,14 @@ impl TargetIsa for Isa { }) } + fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { + abi::legalize_signature(sig, &self.shared_flags, current) + } + + fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass { + abi::regclass_for_abi_type(ty) + } + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/intel/abi.rs b/lib/cretonne/src/isa/intel/abi.rs new file mode 100644 index 0000000000..437925cd40 --- /dev/null +++ b/lib/cretonne/src/isa/intel/abi.rs @@ -0,0 +1,18 @@ +//! Intel ABI implementation. + +use ir; +use isa::RegClass; +use settings as shared_settings; +use super::registers::{GPR, FPR}; + +/// Legalize `sig`. +pub fn legalize_signature(_sig: &mut ir::Signature, + _flags: &shared_settings::Flags, + _current: bool) { + unimplemented!() +} + +/// Get register class for a type appearing in a legalized signature. +pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { + if ty.is_int() { GPR } else { FPR } +} diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 60ce3a795d..768150eb50 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -1,6 +1,7 @@ //! Intel Instruction Set Architectures. pub mod settings; +mod abi; mod binemit; mod enc_tables; mod registers; @@ -9,7 +10,7 @@ use binemit::CodeSink; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir; #[allow(dead_code)] @@ -77,6 +78,14 @@ impl TargetIsa for Isa { }) } + fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { + abi::legalize_signature(sig, &self.shared_flags, current) + } + + fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass { + abi::regclass_for_abi_type(ty) + } + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index e951ad31de..bd9e0caf9a 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -168,7 +168,7 @@ pub trait TargetIsa { /// The legalizer will adapt argument and return values as necessary at all ABI boundaries. /// /// When this function is called to legalize the signature of the function currently begin - /// compiler, `_current` is true. The legalized signature can then also contain special purpose + /// compiler, `current` is true. The legalized signature can then also contain special purpose /// arguments and return values such as: /// /// - A `link` argument representing the link registers on RISC architectures that don't push @@ -183,9 +183,15 @@ pub trait TargetIsa { /// Arguments and return values for the caller's frame pointer and other callee-saved registers /// should not be added by this function. These arguments are not added until after register /// allocation. - fn legalize_signature(&self, _sig: &mut Signature, _current: bool) { - unimplemented!() - } + fn legalize_signature(&self, sig: &mut Signature, current: bool); + + /// Get the register class that should be used to represent an ABI argument or return value of + /// type `ty`. This should be the top-level register class that contains the argument + /// registers. + /// + /// This function can assume that it will only be asked to provide register classes for types + /// that `legalize_signature()` produces in `ArgumentLoc::Reg` entries. + fn regclass_for_abi_type(&self, ty: Type) -> RegClass; /// Emit binary machine code for a single instruction into the `sink` trait object. /// diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 453a9c7be8..f13e6d2801 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -7,6 +7,7 @@ use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; use ir::{Signature, Type, ArgumentType, ArgumentLoc, ArgumentExtension, ArgumentPurpose}; +use isa::RegClass; use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; @@ -102,3 +103,8 @@ pub fn legalize_signature(sig: &mut Signature, flags: &shared_settings::Flags, c sig.return_types.push(link); } } + +/// Get register class for a type appearing in a legalized signature. +pub fn regclass_for_abi_type(ty: Type) -> RegClass { + if ty.is_float() { FPR } else { GPR } +} diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index ac5057103c..60b795156a 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -10,7 +10,7 @@ use super::super::settings as shared_settings; use binemit::CodeSink; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature, Type}; #[allow(dead_code)] @@ -83,6 +83,10 @@ impl TargetIsa for Isa { abi::legalize_signature(sig, &self.shared_flags, current) } + fn regclass_for_abi_type(&self, ty: Type) -> RegClass { + abi::regclass_for_abi_type(ty) + } + fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } From 93db01d38aa2029cafd5e5ccda109cc05eca4282 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Apr 2017 10:47:43 -0700 Subject: [PATCH 726/968] Assign an affinity to function argument values. Use the argument types from the function signature to initialize the affinity of register and stack arguments. --- lib/cretonne/src/regalloc/affinity.rs | 12 +++++++++++- lib/cretonne/src/regalloc/liveness.rs | 19 +++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs index 529828759e..f56b96d434 100644 --- a/lib/cretonne/src/regalloc/affinity.rs +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -8,7 +8,8 @@ //! subclass. This is just a hint, and the register allocator is allowed to pick a register from a //! larger register class instead. -use isa::{RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; +use ir::{ArgumentType, ArgumentLoc}; +use isa::{TargetIsa, RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; /// Preferred register allocation for an SSA value. #[derive(Clone, Copy)] @@ -42,6 +43,15 @@ impl Affinity { } } + /// Create an affinity that matches an ABI argument for `isa`. + pub fn abi(arg: &ArgumentType, isa: &TargetIsa) -> Affinity { + match arg.location { + ArgumentLoc::Unassigned => Affinity::Any, + ArgumentLoc::Reg(_) => Affinity::Reg(isa.regclass_for_abi_type(arg.value_type).into()), + ArgumentLoc::Stack(_) => Affinity::Stack, + } + } + /// Merge an operand constraint into this affinity. /// /// Note that this does not guarantee that the register allocator will pick a register that diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index cb937d67a2..737470b2d1 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -190,6 +190,7 @@ type LiveRangeSet = SparseMap; /// Create it if necessary. fn get_or_create<'a>(lrset: &'a mut LiveRangeSet, value: Value, + isa: &TargetIsa, func: &Function, enc_info: &EncInfo) -> &'a mut LiveRange { @@ -211,11 +212,17 @@ fn get_or_create<'a>(lrset: &'a mut LiveRangeSet, .map(Affinity::new) .unwrap_or_default(); } - ValueDef::Arg(ebb, _) => { + ValueDef::Arg(ebb, num) => { def = ebb.into(); - // Don't apply any affinity to EBB arguments. - // They could be in a register or on the stack. - affinity = Default::default(); + if func.layout.entry_block() == Some(ebb) { + // The affinity for entry block arguments can be inferred from the function + // signature. + affinity = Affinity::abi(&func.signature.argument_types[num], isa); + } else { + // Don't apply any affinity to normal EBB arguments. + // They could be in a register or on the stack. + affinity = Default::default(); + } } }; lrset.insert(LiveRange::new(value, def, affinity)); @@ -309,7 +316,7 @@ impl Liveness { // TODO: When we implement DCE, we can use the absence of a live range to indicate // an unused value. for &def in func.dfg.inst_results(inst) { - get_or_create(&mut self.ranges, def, func, &enc_info); + get_or_create(&mut self.ranges, def, isa, func, &enc_info); } // Iterator of constraints, one per value operand. @@ -322,7 +329,7 @@ impl Liveness { for &arg in func.dfg.inst_args(inst) { // Get the live range, create it as a dead range if necessary. - let lr = get_or_create(&mut self.ranges, arg, func, &enc_info); + let lr = get_or_create(&mut self.ranges, arg, isa, func, &enc_info); // Extend the live range to reach this use. extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg); From 2f866171ca7720ae9ac801652c82808d86c6c4f7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Apr 2017 11:15:46 -0700 Subject: [PATCH 727/968] Add an Affinity::display() method. Make it possible to display affinities given a RegInfo reference. --- lib/cretonne/src/isa/registers.rs | 12 ++++++++++++ lib/cretonne/src/regalloc/affinity.rs | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 864b950f63..3c4f1b411a 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -153,6 +153,12 @@ impl RegClassData { } } +impl fmt::Display for RegClassData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name) + } +} + /// A small reference to a register class. /// /// Use this when storing register classes in compact data structures. The `RegInfo::rc()` method @@ -176,6 +182,12 @@ impl From for RegClassIndex { } } +impl fmt::Display for RegClassIndex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "rci{}", self.0) + } +} + /// Information about the registers in an ISA. /// /// The `RegUnit` data structure collects all relevant static information about the registers in an diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs index f56b96d434..f6ca587485 100644 --- a/lib/cretonne/src/regalloc/affinity.rs +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -8,6 +8,7 @@ //! subclass. This is just a hint, and the register allocator is allowed to pick a register from a //! larger register class instead. +use std::fmt; use ir::{ArgumentType, ArgumentLoc}; use isa::{TargetIsa, RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; @@ -75,4 +76,28 @@ impl Affinity { Affinity::Stack => {} } } + + /// Return an object that can display this value affinity, using the register info from the + /// target ISA. + pub fn display<'a, R: Into>>(self, regs: R) -> DisplayAffinity<'a> { + DisplayAffinity(self, regs.into()) + } +} + +/// Displaying an `Affinity` correctly requires the associated `RegInfo` from the target ISA. +pub struct DisplayAffinity<'a>(Affinity, Option<&'a RegInfo>); + +impl<'a> fmt::Display for DisplayAffinity<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Affinity::None => write!(f, "none"), + Affinity::Stack => write!(f, "stack"), + Affinity::Reg(rci) => { + match self.1 { + Some(regs) => write!(f, "{}", regs.rc(rci)), + None => write!(f, "{}", rci), + } + } + } + } } From 1e51f97108e8fd7723ad59d37e7327c913c1f1e7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 25 Apr 2017 15:40:06 -0700 Subject: [PATCH 728/968] Rename Affinity::Any to Affinity::None. This value affinity doesn't mean "whatever", it means that the value does not interact with any real instructions, where "real" means instructions that have a legal encoding. Update the liveness verifier to check this property: - Encoded instructions can only use real values. - Encoded instructions define real values. - Ghost instructions define ghost values. --- lib/cretonne/src/regalloc/affinity.rs | 23 +++++++++++++----- lib/cretonne/src/regalloc/coloring.rs | 4 +++- lib/cretonne/src/verifier/liveness.rs | 34 ++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs index f6ca587485..64d2b640df 100644 --- a/lib/cretonne/src/regalloc/affinity.rs +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -15,8 +15,11 @@ use isa::{TargetIsa, RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; /// Preferred register allocation for an SSA value. #[derive(Clone, Copy)] pub enum Affinity { - /// Don't care. This value can go anywhere. - Any, + /// No affinity. + /// + /// This indicates a value that is not defined or used by any real instructions. It is a ghost + /// value that won't appear in the final program. + None, /// This value should be placed in a spill slot on the stack. Stack, @@ -27,14 +30,14 @@ pub enum Affinity { impl Default for Affinity { fn default() -> Self { - Affinity::Any + Affinity::None } } impl Affinity { /// Create an affinity that satisfies a single constraint. /// - /// This will never create the indifferent `Affinity::Any`. + /// This will never create an `Affinity::None`. /// Use the `Default` implementation for that. pub fn new(constraint: &OperandConstraint) -> Affinity { if constraint.kind == ConstraintKind::Stack { @@ -47,19 +50,27 @@ impl Affinity { /// Create an affinity that matches an ABI argument for `isa`. pub fn abi(arg: &ArgumentType, isa: &TargetIsa) -> Affinity { match arg.location { - ArgumentLoc::Unassigned => Affinity::Any, + ArgumentLoc::Unassigned => Affinity::None, ArgumentLoc::Reg(_) => Affinity::Reg(isa.regclass_for_abi_type(arg.value_type).into()), ArgumentLoc::Stack(_) => Affinity::Stack, } } + /// Is this the `None` affinity? + pub fn is_none(self) -> bool { + match self { + Affinity::None => true, + _ => false, + } + } + /// Merge an operand constraint into this affinity. /// /// Note that this does not guarantee that the register allocator will pick a register that /// satisfies the constraint. pub fn merge(&mut self, constraint: &OperandConstraint, reg_info: &RegInfo) { match *self { - Affinity::Any => *self = Affinity::new(constraint), + Affinity::None => *self = Affinity::new(constraint), Affinity::Reg(rc) => { // If the preferred register class is a subclass of the constraint, there's no need // to change anything. diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index b9e5fe7148..84e81660d7 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -327,7 +327,9 @@ impl<'a> Context<'a> { } } Affinity::Stack => unimplemented!(), - Affinity::Any => unimplemented!(), + Affinity::None => { + panic!("Encoded instruction defines {} with no affinity", lv.value) + } } } diff --git a/lib/cretonne/src/verifier/liveness.rs b/lib/cretonne/src/verifier/liveness.rs index d80f18a8b3..d3d841b68e 100644 --- a/lib/cretonne/src/verifier/liveness.rs +++ b/lib/cretonne/src/verifier/liveness.rs @@ -21,12 +21,13 @@ use verifier::Result; /// /// We don't verify that live ranges are minimal. This would require recomputing live ranges for /// all values. -pub fn verify_liveness(_isa: &TargetIsa, +pub fn verify_liveness(isa: &TargetIsa, func: &Function, cfg: &ControlFlowGraph, liveness: &Liveness) -> Result { let verifier = LivenessVerifier { + isa: isa, func: func, cfg: cfg, liveness: liveness, @@ -37,6 +38,7 @@ pub fn verify_liveness(_isa: &TargetIsa, } struct LivenessVerifier<'a> { + isa: &'a TargetIsa, func: &'a Function, cfg: &'a ControlFlowGraph, liveness: &'a Liveness, @@ -61,6 +63,8 @@ impl<'a> LivenessVerifier<'a> { fn check_insts(&self) -> Result { for ebb in self.func.layout.ebbs() { for inst in self.func.layout.ebb_insts(ebb) { + let encoding = self.func.encodings.get_or_default(inst); + // Check the defs. for &val in self.func.dfg.inst_results(inst) { let lr = match self.liveness.get(val) { @@ -68,6 +72,24 @@ impl<'a> LivenessVerifier<'a> { None => return err!(inst, "{} has no live range", val), }; self.check_lr(inst.into(), val, lr)?; + + if encoding.is_legal() { + // A legal instruction is not allowed to define ghost values. + if lr.affinity.is_none() { + return err!(inst, + "{} is a ghost value defined by a real [{}] instruction", + val, + self.isa.encoding_info().display(encoding)); + } + } else { + // A non-encoded instruction can only define ghost values. + if !lr.affinity.is_none() { + return err!(inst, + "{} is a real {} value defined by a ghost instruction", + val, + lr.affinity.display(&self.isa.register_info())); + } + } } // Check the uses. @@ -79,6 +101,16 @@ impl<'a> LivenessVerifier<'a> { if !self.live_at_use(lr, inst) { return err!(inst, "{} is not live at this use", val); } + + if encoding.is_legal() { + // A legal instruction is not allowed to depend on ghost values. + if lr.affinity.is_none() { + return err!(inst, + "{} is a ghost value used by a real [{}] instruction", + val, + self.isa.encoding_info().display(encoding)); + } + } } } } From 305de3e73bbd5de17e4ce66a930b3f86f3bfc4e4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Apr 2017 13:13:15 -0700 Subject: [PATCH 729/968] Add an enable_e setting for the RV32E instruction set. This limited RISC-V version only has registers %x0 - %x15. Make sure the ABI lowering code doesn't use the banned registers for arguments. --- filetests/isa/riscv/abi-e.cton | 14 ++++++++++++++ lib/cretonne/meta/isa/riscv/settings.py | 3 +++ lib/cretonne/src/isa/riscv/abi.rs | 18 ++++++++++++------ lib/cretonne/src/isa/riscv/mod.rs | 3 +-- lib/cretonne/src/isa/riscv/settings.rs | 3 ++- 5 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 filetests/isa/riscv/abi-e.cton diff --git a/filetests/isa/riscv/abi-e.cton b/filetests/isa/riscv/abi-e.cton new file mode 100644 index 0000000000..f770a3558f --- /dev/null +++ b/filetests/isa/riscv/abi-e.cton @@ -0,0 +1,14 @@ +; Test the legalization of function signatures for RV32E. +test legalizer +isa riscv enable_e + +; regex: V=v\d+ + +function f() { + ; Spilling into the stack args after %x15 since %16 and up are not + ; available in RV32E. + sig0 = signature(i64, i64, i64, i64) -> i64 + ; check: sig0 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [0], i32 [4]) -> i32 [%x10], i32 [%x11] +ebb0: + return +} diff --git a/lib/cretonne/meta/isa/riscv/settings.py b/lib/cretonne/meta/isa/riscv/settings.py index e6fe230f89..c8b88db55e 100644 --- a/lib/cretonne/meta/isa/riscv/settings.py +++ b/lib/cretonne/meta/isa/riscv/settings.py @@ -18,6 +18,9 @@ enable_m = BoolSetting( "Enable the use of 'M' instructions if available", default=True) +enable_e = BoolSetting( + "Enable the 'RV32E' instruction set with only 16 registers") + use_m = And(supports_m, enable_m) use_a = And(supports_a, shared.enable_atomics) use_f = And(supports_f, shared.enable_float) diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index f13e6d2801..0df3806529 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -8,24 +8,27 @@ use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; use ir::{Signature, Type, ArgumentType, ArgumentLoc, ArgumentExtension, ArgumentPurpose}; use isa::RegClass; -use isa::riscv::registers::{GPR, FPR}; use settings as shared_settings; +use super::registers::{GPR, FPR}; +use super::settings; struct Args { pointer_bits: u16, pointer_bytes: u32, pointer_type: Type, regs: u32, + reg_limit: u32, offset: u32, } impl Args { - fn new(bits: u16) -> Args { + fn new(bits: u16, enable_e: bool) -> Args { Args { pointer_bits: bits, pointer_bytes: bits as u32 / 8, pointer_type: Type::int(bits).unwrap(), regs: 0, + reg_limit: if enable_e { 6 } else { 8 }, offset: 0, } } @@ -62,7 +65,7 @@ impl ArgAssigner for Args { } } - if self.regs < 8 { + if self.regs < self.reg_limit { // Assign to a register. let reg = if ty.is_float() { FPR.unit(10 + self.regs as usize) @@ -81,13 +84,16 @@ impl ArgAssigner for Args { } /// Legalize `sig` for RISC-V. -pub fn legalize_signature(sig: &mut Signature, flags: &shared_settings::Flags, current: bool) { +pub fn legalize_signature(sig: &mut Signature, + flags: &shared_settings::Flags, + isa_flags: &settings::Flags, + current: bool) { let bits = if flags.is_64bit() { 64 } else { 32 }; - let mut args = Args::new(bits); + let mut args = Args::new(bits, isa_flags.enable_e()); legalize_args(&mut sig.argument_types, &mut args); - let mut rets = Args::new(bits); + let mut rets = Args::new(bits, isa_flags.enable_e()); legalize_args(&mut sig.return_types, &mut rets); if current { diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 60b795156a..fe267ae870 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -79,8 +79,7 @@ impl TargetIsa for Isa { } fn legalize_signature(&self, sig: &mut Signature, current: bool) { - // We can pass in `self.isa_flags` too, if we need it. - abi::legalize_signature(sig, &self.shared_flags, current) + abi::legalize_signature(sig, &self.shared_flags, &self.isa_flags, current) } fn regclass_for_abi_type(&self, ty: Type) -> RegClass { diff --git a/lib/cretonne/src/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs index c070612d08..aa66d75fd9 100644 --- a/lib/cretonne/src/isa/riscv/settings.rs +++ b/lib/cretonne/src/isa/riscv/settings.rs @@ -24,7 +24,8 @@ mod tests { supports_a = false\n\ supports_f = false\n\ supports_d = false\n\ - enable_m = true\n"); + enable_m = true\n\ + enable_e = false\n"); // Predicates are not part of the Display output. assert_eq!(f.full_float(), false); } From bb8ae9a4fb9d4dc49e651966eccb2033bfbded86 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Apr 2017 13:54:40 -0700 Subject: [PATCH 730/968] Add a TargetIsa::allocatable_registers() method. This gives the target ISA a chance to reserve registers like the stack pointer or hard-wired 0 registers like %x0 on RISC-V. --- filetests/regalloc/basic.cton | 3 ++- lib/cretonne/src/isa/arm32/abi.rs | 6 ++++++ lib/cretonne/src/isa/arm32/mod.rs | 5 +++++ lib/cretonne/src/isa/arm64/abi.rs | 6 ++++++ lib/cretonne/src/isa/arm64/mod.rs | 5 +++++ lib/cretonne/src/isa/intel/abi.rs | 6 ++++++ lib/cretonne/src/isa/intel/mod.rs | 5 +++++ lib/cretonne/src/isa/mod.rs | 21 ++++++++++++++------- lib/cretonne/src/isa/riscv/abi.rs | 24 ++++++++++++++++++++++-- lib/cretonne/src/isa/riscv/mod.rs | 19 ++++++++++++------- lib/cretonne/src/regalloc/coloring.rs | 3 +-- lib/cretonne/src/regalloc/mod.rs | 1 + 12 files changed, 85 insertions(+), 19 deletions(-) diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index cbff589431..3fc58dabd9 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -6,7 +6,8 @@ isa riscv function add(i32, i32) { ebb0(v1: i32, v2: i32): v3 = iadd v1, v2 -; check: [R#0c,%x0] +; TODO: This shouldn't clobber the link register. +; check: [R#0c,%x1] ; sameln: iadd return } diff --git a/lib/cretonne/src/isa/arm32/abi.rs b/lib/cretonne/src/isa/arm32/abi.rs index c4e0ffc2ee..93ae0e7e61 100644 --- a/lib/cretonne/src/isa/arm32/abi.rs +++ b/lib/cretonne/src/isa/arm32/abi.rs @@ -2,6 +2,7 @@ use ir; use isa::RegClass; +use regalloc::AllocatableSet; use settings as shared_settings; use super::registers::{S, D, Q, GPR}; @@ -25,3 +26,8 @@ pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { } } } + +/// Get the set of allocatable registers for `func`. +pub fn allocatable_registers(_func: &ir::Function) -> AllocatableSet { + unimplemented!() +} diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 735fcd4b2c..ec9de51917 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -12,6 +12,7 @@ use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encodin use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir; +use regalloc; #[allow(dead_code)] struct Isa { @@ -86,6 +87,10 @@ impl TargetIsa for Isa { abi::regclass_for_abi_type(ty) } + fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func) + } + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/arm64/abi.rs b/lib/cretonne/src/isa/arm64/abi.rs index 8c2e9ed7d6..f5a2dc91c7 100644 --- a/lib/cretonne/src/isa/arm64/abi.rs +++ b/lib/cretonne/src/isa/arm64/abi.rs @@ -2,6 +2,7 @@ use ir; use isa::RegClass; +use regalloc::AllocatableSet; use settings as shared_settings; use super::registers::{GPR, FPR}; @@ -16,3 +17,8 @@ pub fn legalize_signature(_sig: &mut ir::Signature, pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { if ty.is_int() { GPR } else { FPR } } + +/// Get the set of allocatable registers for `func`. +pub fn allocatable_registers(_func: &ir::Function) -> AllocatableSet { + unimplemented!() +} diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 91a8a50f4e..4067142f57 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -12,6 +12,7 @@ use isa::enc_tables::{lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir; +use regalloc; #[allow(dead_code)] struct Isa { @@ -79,6 +80,10 @@ impl TargetIsa for Isa { abi::regclass_for_abi_type(ty) } + fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func) + } + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/intel/abi.rs b/lib/cretonne/src/isa/intel/abi.rs index 437925cd40..e6c5edd5bf 100644 --- a/lib/cretonne/src/isa/intel/abi.rs +++ b/lib/cretonne/src/isa/intel/abi.rs @@ -2,6 +2,7 @@ use ir; use isa::RegClass; +use regalloc::AllocatableSet; use settings as shared_settings; use super::registers::{GPR, FPR}; @@ -16,3 +17,8 @@ pub fn legalize_signature(_sig: &mut ir::Signature, pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { if ty.is_int() { GPR } else { FPR } } + +/// Get the set of allocatable registers for `func`. +pub fn allocatable_registers(_func: &ir::Function) -> AllocatableSet { + unimplemented!() +} diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 768150eb50..1812a41354 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -12,6 +12,7 @@ use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encodin use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; use ir; +use regalloc; #[allow(dead_code)] struct Isa { @@ -86,6 +87,10 @@ impl TargetIsa for Isa { abi::regclass_for_abi_type(ty) } + fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func) + } + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index bd9e0caf9a..4b618e0798 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -46,7 +46,8 @@ pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; use binemit::CodeSink; use settings; -use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature, Type}; +use ir; +use regalloc; pub mod riscv; pub mod intel; @@ -142,9 +143,9 @@ pub trait TargetIsa { /// /// This is also the main entry point for determining if an instruction is legal. fn encode(&self, - dfg: &DataFlowGraph, - inst: &InstructionData, - ctrl_typevar: Type) + dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData, + ctrl_typevar: ir::Type) -> Result; /// Get a data structure describing the instruction encodings in this ISA. @@ -183,7 +184,7 @@ pub trait TargetIsa { /// Arguments and return values for the caller's frame pointer and other callee-saved registers /// should not be added by this function. These arguments are not added until after register /// allocation. - fn legalize_signature(&self, sig: &mut Signature, current: bool); + fn legalize_signature(&self, sig: &mut ir::Signature, current: bool); /// Get the register class that should be used to represent an ABI argument or return value of /// type `ty`. This should be the top-level register class that contains the argument @@ -191,13 +192,19 @@ pub trait TargetIsa { /// /// This function can assume that it will only be asked to provide register classes for types /// that `legalize_signature()` produces in `ArgumentLoc::Reg` entries. - fn regclass_for_abi_type(&self, ty: Type) -> RegClass; + fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass; + + /// Get the set of allocatable registers that can be used when compiling `func`. + /// + /// This set excludes reserved registers like the stack pointer and other special-purpose + /// registers. + fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet; /// Emit binary machine code for a single instruction into the `sink` trait object. /// /// Note that this will call `put*` methods on the trait object via its vtable which is not the /// fastest way of emitting code. - fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink); + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink); /// Get a static array of names associated with relocations in this ISA. /// diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 0df3806529..ab9e485ed5 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -6,8 +6,9 @@ //! This doesn't support the soft-float ABI at the moment. use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; -use ir::{Signature, Type, ArgumentType, ArgumentLoc, ArgumentExtension, ArgumentPurpose}; +use ir::{self, Type, ArgumentType, ArgumentLoc, ArgumentExtension, ArgumentPurpose}; use isa::RegClass; +use regalloc::AllocatableSet; use settings as shared_settings; use super::registers::{GPR, FPR}; use super::settings; @@ -84,7 +85,7 @@ impl ArgAssigner for Args { } /// Legalize `sig` for RISC-V. -pub fn legalize_signature(sig: &mut Signature, +pub fn legalize_signature(sig: &mut ir::Signature, flags: &shared_settings::Flags, isa_flags: &settings::Flags, current: bool) { @@ -114,3 +115,22 @@ pub fn legalize_signature(sig: &mut Signature, pub fn regclass_for_abi_type(ty: Type) -> RegClass { if ty.is_float() { FPR } else { GPR } } + +pub fn allocatable_registers(_func: &ir::Function, isa_flags: &settings::Flags) -> AllocatableSet { + let mut regs = AllocatableSet::new(); + regs.take(GPR, GPR.unit(0)); // Hard-wired 0. + // %x1 is the link register which is available for allocation. + regs.take(GPR, GPR.unit(2)); // Stack pointer. + regs.take(GPR, GPR.unit(3)); // Global pointer. + regs.take(GPR, GPR.unit(4)); // Thread pointer. + // TODO: %x8 is the frame pointer. Reserve it? + + // Remove %x16 and up for RV32E. + if isa_flags.enable_e() { + for u in 16..32 { + regs.take(GPR, GPR.unit(u)); + } + } + + regs +} diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index fe267ae870..cf5aafc7db 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -11,7 +11,8 @@ use binemit::CodeSink; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; -use ir::{Function, Inst, InstructionData, DataFlowGraph, Signature, Type}; +use ir; +use regalloc; #[allow(dead_code)] struct Isa { @@ -61,9 +62,9 @@ impl TargetIsa for Isa { } fn encode(&self, - _dfg: &DataFlowGraph, - inst: &InstructionData, - ctrl_typevar: Type) + _dfg: &ir::DataFlowGraph, + inst: &ir::InstructionData, + ctrl_typevar: ir::Type) -> Result { lookup_enclist(ctrl_typevar, inst.opcode(), @@ -78,15 +79,19 @@ impl TargetIsa for Isa { }) } - fn legalize_signature(&self, sig: &mut Signature, current: bool) { + fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { abi::legalize_signature(sig, &self.shared_flags, &self.isa_flags, current) } - fn regclass_for_abi_type(&self, ty: Type) -> RegClass { + fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass { abi::regclass_for_abi_type(ty) } - fn emit_inst(&self, func: &Function, inst: Inst, sink: &mut CodeSink) { + fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func, &self.isa_flags) + } + + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 84e81660d7..f1fc2348ad 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -101,8 +101,7 @@ impl Coloring { encinfo: isa.encoding_info(), domtree: domtree, liveness: liveness, - // TODO: Ask the target ISA about reserved registers etc. - usable_regs: AllocatableSet::new(), + usable_regs: isa.allocatable_registers(func), }; ctx.run(self, func, tracker) } diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index fb19286670..0682be2fd6 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -11,4 +11,5 @@ pub mod coloring; mod affinity; mod context; +pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; From 4c8eb85f3917539a08f6f16470c83e75c9605e4a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Apr 2017 14:38:16 -0700 Subject: [PATCH 731/968] Color entry block arguments using the function signature. The arguments to the entry block arrive in registers determined by the ABI. This information is stored in the signature. Use a separate function for coloring entry block arguments using the signature information. We can't handle stack arguments yet. --- filetests/regalloc/basic.cton | 3 +- lib/cretonne/src/regalloc/coloring.rs | 79 +++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index 3fc58dabd9..14fa263ac0 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -6,8 +6,7 @@ isa riscv function add(i32, i32) { ebb0(v1: i32, v2: i32): v3 = iadd v1, v2 -; TODO: This shouldn't clobber the link register. -; check: [R#0c,%x1] +; check: [R#0c,%x5] ; sameln: iadd return } diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index f1fc2348ad..0a5e9e803a 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -37,7 +37,7 @@ use entity_map::EntityMap; use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph}; +use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Signature, ArgumentLoc}; use isa::{TargetIsa, RegInfo, Encoding, EncInfo, ConstraintKind}; use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; @@ -180,14 +180,15 @@ impl<'a> Context<'a> { let (liveins, args) = tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); - // The live-ins have already been assigned a register. Reconstruct the allocatable set. - let mut regs = self.livein_regs(liveins, func); - - // TODO: Arguments to the entry block are pre-colored by the ABI. We should probably call - // a whole other function for that case. - self.color_args(args, &mut regs, &mut func.locations); - - regs + // Arguments to the entry block have ABI constraints. + if func.layout.entry_block() == Some(ebb) { + assert_eq!(liveins.len(), 0); + self.color_entry_args(&func.signature, args, &mut func.locations) + } else { + // The live-ins have already been assigned a register. Reconstruct the allocatable set. + let regs = self.livein_regs(liveins, func); + self.color_args(args, regs, &mut func.locations) + } } /// Initialize a set of allocatable registers from the values that are live-in to a block. @@ -218,14 +219,68 @@ impl<'a> Context<'a> { regs } + /// Color the arguments to the entry block. + /// + /// These are function arguments that should already have assigned register units in the + /// function signature. + /// + /// Return the set of remaining allocatable registers. + fn color_entry_args(&self, + sig: &Signature, + args: &[LiveValue], + locations: &mut EntityMap) + -> AllocatableSet { + assert_eq!(sig.argument_types.len(), args.len()); + + let mut regs = self.usable_regs.clone(); + + for (lv, abi) in args.iter().zip(&sig.argument_types) { + match lv.affinity { + Affinity::Reg(rc_index) => { + let regclass = self.reginfo.rc(rc_index); + if let ArgumentLoc::Reg(regunit) = abi.location { + regs.take(regclass, regunit); + *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + } else { + // This should have been fixed by the reload pass. + panic!("Entry arg {} has {} affinity, but ABI {}", + lv.value, + lv.affinity.display(&self.reginfo), + abi.display(&self.reginfo)); + } + + } + Affinity::Stack => { + if let ArgumentLoc::Stack(_offset) = abi.location { + // TODO: Allocate a stack slot at incoming offset and assign it. + panic!("Unimplemented {}: {} stack allocation", + lv.value, + abi.display(&self.reginfo)); + } else { + // This should have been fixed by the reload pass. + panic!("Entry arg {} has stack affinity, but ABI {}", + lv.value, + abi.display(&self.reginfo)); + } + } + // This is a ghost value, unused in the function. Don't assign it to a location + // either. + Affinity::None => {} + } + } + + regs + } + /// Color the live arguments to the current block. /// /// It is assumed that any live-in register values have already been taken out of the register /// set. fn color_args(&self, args: &[LiveValue], - regs: &mut AllocatableSet, - locations: &mut EntityMap) { + mut regs: AllocatableSet, + locations: &mut EntityMap) + -> AllocatableSet { for lv in args { // Only look at the register arguments. if let Affinity::Reg(rc_index) = lv.affinity { @@ -238,6 +293,8 @@ impl<'a> Context<'a> { *locations.ensure(lv.value) = ValueLoc::Reg(regunit); } } + + regs } /// Color the values defined by `inst` and insert any necessary shuffle code to satisfy From 962a3a6a5e56c6a152fd3895a950daa94ea333a3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Apr 2017 12:28:18 -0700 Subject: [PATCH 732/968] Upgrade to Rust 1.17. - Remove some uses of 'static in const and static globals that are no longer needed. - Use the new struct initialization shorthand. --- lib/cretonne/meta/gen_encoding.py | 2 +- lib/cretonne/meta/gen_instr.py | 4 +- lib/cretonne/meta/gen_settings.py | 2 +- lib/cretonne/src/ir/builder.rs | 9 +- lib/cretonne/src/ir/dfg.rs | 30 ++--- lib/cretonne/src/ir/extfunc.rs | 4 +- lib/cretonne/src/ir/function.rs | 2 +- lib/cretonne/src/ir/layout.rs | 2 +- lib/cretonne/src/ir/memflags.rs | 2 +- lib/cretonne/src/isa/arm32/mod.rs | 2 +- lib/cretonne/src/isa/arm64/mod.rs | 2 +- lib/cretonne/src/isa/encoding.rs | 5 +- lib/cretonne/src/isa/intel/mod.rs | 2 +- lib/cretonne/src/isa/registers.rs | 2 +- lib/cretonne/src/isa/riscv/mod.rs | 2 +- lib/cretonne/src/iterators.rs | 5 +- lib/cretonne/src/legalizer/split.rs | 10 +- lib/cretonne/src/regalloc/coloring.rs | 4 +- .../src/regalloc/live_value_tracker.rs | 6 +- lib/cretonne/src/regalloc/liverange.rs | 4 +- lib/cretonne/src/verifier/liveness.rs | 8 +- lib/cretonne/src/verifier/mod.rs | 8 +- lib/filecheck/src/checker.rs | 8 +- lib/filecheck/src/explain.rs | 2 +- lib/reader/src/lexer.rs | 4 +- lib/reader/src/parser.rs | 114 ++++++++---------- src/cton-util.rs | 2 +- src/filetest/concurrent.rs | 16 +-- src/filetest/runner.rs | 2 +- src/filetest/runone.rs | 4 +- src/print_cfg.rs | 2 +- 31 files changed, 118 insertions(+), 153 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 706ad46780..bbe1df7591 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -451,7 +451,7 @@ def emit_recipe_names(isa, fmt): This is used for pretty-printing encodings. """ with fmt.indented( - 'static RECIPE_NAMES: [&\'static str; {}] = [' + 'static RECIPE_NAMES: [&str; {}] = [' .format(len(isa.all_recipes)), '];'): for r in isa.all_recipes: fmt.line('"{}",'.format(r.name)) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index b1b32b7bf3..f3a4343ab2 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -470,7 +470,7 @@ def gen_format_constructor(iform, fmt): # Generate the instruction data. with fmt.indented( 'let data = InstructionData::{} {{'.format(iform.name), '};'): - fmt.line('opcode: opcode,') + fmt.line('opcode,') gen_member_inits(iform, fmt) fmt.line('self.build(data, ctrl_typevar)') @@ -489,7 +489,7 @@ def gen_member_inits(iform, fmt): # Value operands. if iform.has_value_list: - fmt.line('args: args,') + fmt.line('args,') elif iform.num_value_operands == 1: fmt.line('arg: arg0,') elif iform.num_value_operands > 1: diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 847a72399d..23e9a5c7a9 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -132,7 +132,7 @@ def gen_descriptors(sgrp, fmt): raise AssertionError("Unknown setting kind") with fmt.indented( - 'static ENUMERATORS: [&\'static str; {}] = [' + 'static ENUMERATORS: [&str; {}] = [' .format(len(enums.table)), '];'): for txt in enums.table: diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index d9ef0faa25..ae71f754c7 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -56,7 +56,7 @@ impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { pub fn new(dfg: &'fd mut DataFlowGraph, pos: &'c mut Cursor<'fc>) -> InsertBuilder<'c, 'fc, 'fd> { - InsertBuilder { dfg: dfg, pos: pos } + InsertBuilder { dfg, pos } } /// Reuse result values in `reuse`. @@ -72,7 +72,7 @@ impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { InsertReuseBuilder { dfg: self.dfg, pos: self.pos, - reuse: reuse, + reuse, } } @@ -154,10 +154,7 @@ pub struct ReplaceBuilder<'f> { impl<'f> ReplaceBuilder<'f> { /// Create a `ReplaceBuilder` that will overwrite `inst`. pub fn new(dfg: &'f mut DataFlowGraph, inst: Inst) -> ReplaceBuilder { - ReplaceBuilder { - dfg: dfg, - inst: inst, - } + ReplaceBuilder { dfg, inst } } } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 9f1a8b5299..063d7cbbd7 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -240,10 +240,7 @@ impl DataFlowGraph { self.value_type(dest), ty); - self.values[dest] = ValueData::Alias { - ty: ty, - original: original, - }; + self.values[dest] = ValueData::Alias { ty, original }; } /// Create a new value alias. @@ -252,10 +249,7 @@ impl DataFlowGraph { pub fn make_value_alias(&mut self, src: Value) -> Value { let ty = self.value_type(src); - let data = ValueData::Alias { - ty: ty, - original: src, - }; + let data = ValueData::Alias { ty, original: src }; self.make_value(data) } } @@ -462,9 +456,9 @@ impl DataFlowGraph { assert!(num <= u16::MAX as usize, "Too many result values"); let ty = self.value_type(res); self.values[res] = ValueData::Inst { - ty: ty, + ty, num: num as u16, - inst: inst, + inst, }; } @@ -474,8 +468,8 @@ impl DataFlowGraph { let num = self.results[inst].push(res, &mut self.value_lists); assert!(num <= u16::MAX as usize, "Too many result values"); self.make_value(ValueData::Inst { - ty: ty, - inst: inst, + ty, + inst, num: num as u16, }) } @@ -594,9 +588,9 @@ impl DataFlowGraph { let num = self.ebbs[ebb].args.push(arg, &mut self.value_lists); assert!(num <= u16::MAX as usize, "Too many arguments to EBB"); self.make_value(ValueData::Arg { - ty: ty, + ty, num: num as u16, - ebb: ebb, + ebb, }) } @@ -611,9 +605,9 @@ impl DataFlowGraph { assert!(num <= u16::MAX as usize, "Too many arguments to EBB"); let ty = self.value_type(arg); self.values[arg] = ValueData::Arg { - ty: ty, + ty, num: num as u16, - ebb: ebb, + ebb, }; } @@ -635,8 +629,8 @@ impl DataFlowGraph { }; let new_arg = self.make_value(ValueData::Arg { ty: new_type, - num: num, - ebb: ebb, + num, + ebb, }); self.ebbs[ebb].args.as_mut_slice(&mut self.value_lists)[num as usize] = new_arg; diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 0971015f62..e0863f8d1d 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -138,7 +138,7 @@ impl ArgumentType { ArgumentType { value_type: vt, extension: ArgumentExtension::None, - purpose: purpose, + purpose, location: ArgumentLoc::Reg(regunit), } } @@ -239,7 +239,7 @@ pub enum ArgumentPurpose { } /// Text format names of the `ArgumentPurpose` variants. -static PURPOSE_NAMES: [&'static str; 5] = ["normal", "sret", "link", "fp", "csr"]; +static PURPOSE_NAMES: [&str; 5] = ["normal", "sret", "link", "fp", "csr"]; impl fmt::Display for ArgumentPurpose { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 177ca0b5f1..075ee7c6fd 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -57,7 +57,7 @@ impl Function { /// Create a function with the given name and signature. pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function { Function { - name: name, + name, signature: sig, stack_slots: EntityMap::new(), jump_tables: EntityMap::new(), diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 6e3e793125..b8ef83e75b 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -647,7 +647,7 @@ impl<'f> Cursor<'f> { /// The cursor holds a mutable reference to `layout` for its entire lifetime. pub fn new(layout: &'f mut Layout) -> Cursor { Cursor { - layout: layout, + layout, pos: CursorPosition::Nowhere, } } diff --git a/lib/cretonne/src/ir/memflags.rs b/lib/cretonne/src/ir/memflags.rs index 8cef887da2..bd2f2bab19 100644 --- a/lib/cretonne/src/ir/memflags.rs +++ b/lib/cretonne/src/ir/memflags.rs @@ -7,7 +7,7 @@ enum FlagBit { Aligned, } -const NAMES: [&'static str; 2] = ["notrap", "aligned"]; +const NAMES: [&str; 2] = ["notrap", "aligned"]; /// Flags for memory operations like load/store. /// diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index ec9de51917..e3128fdc0e 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -39,7 +39,7 @@ fn isa_constructor(shared_flags: shared_settings::Flags, }; Box::new(Isa { isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, + shared_flags, cpumode: level1, }) } diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 4067142f57..f6fd70fb7a 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -33,7 +33,7 @@ fn isa_constructor(shared_flags: shared_settings::Flags, -> Box { Box::new(Isa { isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, + shared_flags, }) } diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index 3d866727a4..93888b109e 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -19,10 +19,7 @@ pub struct Encoding { impl Encoding { /// Create a new `Encoding` containing `(recipe, bits)`. pub fn new(recipe: u16, bits: u16) -> Encoding { - Encoding { - recipe: recipe, - bits: bits, - } + Encoding { recipe, bits } } /// Get the recipe number in this encoding. diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 1812a41354..5871c8766a 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -39,7 +39,7 @@ fn isa_constructor(shared_flags: shared_settings::Flags, }; Box::new(Isa { isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, + shared_flags, cpumode: level1, }) } diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 3c4f1b411a..15a089db17 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -220,7 +220,7 @@ impl RegInfo { /// Make a temporary object that can display a register unit. pub fn display_regunit(&self, regunit: RegUnit) -> DisplayRegUnit { DisplayRegUnit { - regunit: regunit, + regunit, reginfo: self, } } diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index cf5aafc7db..ad64e4fd29 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -39,7 +39,7 @@ fn isa_constructor(shared_flags: shared_settings::Flags, }; Box::new(Isa { isa_flags: settings::Flags::new(&shared_flags, builder), - shared_flags: shared_flags, + shared_flags, cpumode: level1, }) } diff --git a/lib/cretonne/src/iterators.rs b/lib/cretonne/src/iterators.rs index 3bdc3cc1cb..bca8cea2e4 100644 --- a/lib/cretonne/src/iterators.rs +++ b/lib/cretonne/src/iterators.rs @@ -8,10 +8,7 @@ pub trait IteratorExtras: Iterator { Self::Item: Clone { let elem = self.next(); - AdjacentPairs { - iter: self, - elem: elem, - } + AdjacentPairs { iter: self, elem } } } diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index 764448b5bc..cbca25c9c2 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -261,11 +261,11 @@ fn add_repair(concat: Opcode, hi_num: usize, repairs: &mut Vec) { repairs.push(Repair { - concat: concat, - split_type: split_type, - ebb: ebb, - num: num, - hi_num: hi_num, + concat, + split_type, + ebb, + num, + hi_num, }); } diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 0a5e9e803a..544c5b5da0 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -99,8 +99,8 @@ impl Coloring { let mut ctx = Context { reginfo: isa.register_info(), encinfo: isa.encoding_info(), - domtree: domtree, - liveness: liveness, + domtree, + liveness, usable_regs: isa.allocatable_registers(func), }; ctx.run(self, func, tracker) diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index c9f5146245..78085564f7 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -70,9 +70,9 @@ impl LiveValueVec { fn push(&mut self, value: Value, endpoint: Inst, affinity: Affinity) { self.values .push(LiveValue { - value: value, - endpoint: endpoint, - affinity: affinity, + value, + endpoint, + affinity, }); } diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 0594af19ed..a854cfc9ea 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -208,8 +208,8 @@ impl LiveRange { /// The live range will be created as dead, but it can be extended with `extend_in_ebb()`. pub fn new(value: Value, def: ProgramPoint, affinity: Affinity) -> LiveRange { LiveRange { - value: value, - affinity: affinity, + value, + affinity, def_begin: def, def_end: def, liveins: Vec::new(), diff --git a/lib/cretonne/src/verifier/liveness.rs b/lib/cretonne/src/verifier/liveness.rs index d3d841b68e..51911b3f38 100644 --- a/lib/cretonne/src/verifier/liveness.rs +++ b/lib/cretonne/src/verifier/liveness.rs @@ -27,10 +27,10 @@ pub fn verify_liveness(isa: &TargetIsa, liveness: &Liveness) -> Result { let verifier = LivenessVerifier { - isa: isa, - func: func, - cfg: cfg, - liveness: liveness, + isa, + func, + cfg, + liveness, }; verifier.check_ebbs()?; verifier.check_insts()?; diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index e3d5607c99..82e95d7f1f 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -139,10 +139,10 @@ impl<'a> Verifier<'a> { let cfg = ControlFlowGraph::with_function(func); let domtree = DominatorTree::with_function(func, &cfg); Verifier { - func: func, - cfg: cfg, - domtree: domtree, - isa: isa, + func, + cfg, + domtree, + isa, } } diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index 031a50d9e3..e70768e32c 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -25,7 +25,7 @@ enum Directive { // 1. Keyword. // 2. Rest of line / pattern. // -const DIRECTIVE_RX: &'static str = r"\b(check|sameln|nextln|unordered|not|regex):\s+(.*)"; +const DIRECTIVE_RX: &str = r"\b(check|sameln|nextln|unordered|not|regex):\s+(.*)"; impl Directive { /// Create a new directive from a `DIRECTIVE_RX` match. @@ -264,9 +264,9 @@ struct State<'a> { impl<'a> State<'a> { fn new(text: &'a str, env_vars: &'a VariableMap, recorder: &'a mut Recorder) -> State<'a> { State { - text: text, - env_vars: env_vars, - recorder: recorder, + text, + env_vars, + recorder, vars: HashMap::new(), last_ordered: 0, max_match: 0, diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs index 49780321b5..f268620d11 100644 --- a/lib/filecheck/src/explain.rs +++ b/lib/filecheck/src/explain.rs @@ -60,7 +60,7 @@ pub struct Explainer<'a> { impl<'a> Explainer<'a> { pub fn new(text: &'a str) -> Explainer { Explainer { - text: text, + text, directive: 0, matches: Vec::new(), vardefs: Vec::new(), diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 14a0fe45a4..1262b63888 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -55,7 +55,7 @@ pub struct LocatedToken<'a> { /// Wrap up a `Token` with the given location. fn token<'a>(token: Token<'a>, loc: Location) -> Result, LocatedError> { Ok(LocatedToken { - token: token, + token, location: loc, }) } @@ -76,7 +76,7 @@ pub struct LocatedError { /// Wrap up an `Error` with the given location. fn error<'a>(error: Error, loc: Location) -> Result, LocatedError> { Err(LocatedError { - error: error, + error, location: loc, }) } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index eae8c925b0..02ec88196e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -51,10 +51,10 @@ pub fn parse_test<'a>(text: &'a str) -> Result> { let functions = parser.parse_function_list(isa_spec.unique_isa())?; Ok(TestFile { - commands: commands, - isa_spec: isa_spec, - preamble_comments: preamble_comments, - functions: functions, + commands, + isa_spec, + preamble_comments, + functions, }) } @@ -100,7 +100,7 @@ impl<'a> Context<'a> { function: f, map: SourceMap::new(), aliases: HashMap::new(), - unique_isa: unique_isa, + unique_isa, } } @@ -272,11 +272,7 @@ impl<'a> Parser<'a> { Token::Comment(text) => { // Gather comments, associate them with `comment_entity`. if let Some(entity) = self.comment_entity { - self.comments - .push(Comment { - entity: entity, - text: text, - }); + self.comments.push(Comment { entity, text }); } } _ => self.lookahead = Some(token), @@ -676,7 +672,7 @@ impl<'a> Parser<'a> { ctx.rewrite_references()?; let details = Details { - location: location, + location, comments: self.take_comments(), map: ctx.map, }; @@ -924,7 +920,7 @@ impl<'a> Parser<'a> { .def_entity(sigref.into(), &loc) .expect("duplicate SigRef entities created"); ExtFuncData { - name: name, + name, signature: sigref, } } @@ -933,7 +929,7 @@ impl<'a> Parser<'a> { self.consume(); let name = self.parse_function_name()?; ExtFuncData { - name: name, + name, signature: sig, } } @@ -1416,28 +1412,28 @@ impl<'a> Parser<'a> { opcode: Opcode) -> Result { let idata = match opcode.format() { - InstructionFormat::Nullary => InstructionData::Nullary { opcode: opcode }, + InstructionFormat::Nullary => InstructionData::Nullary { opcode }, InstructionFormat::Unary => { InstructionData::Unary { - opcode: opcode, + opcode, arg: self.match_value("expected SSA value operand")?, } } InstructionFormat::UnaryImm => { InstructionData::UnaryImm { - opcode: opcode, + opcode, imm: self.match_imm64("expected immediate integer operand")?, } } InstructionFormat::UnaryIeee32 => { InstructionData::UnaryIeee32 { - opcode: opcode, + opcode, imm: self.match_ieee32("expected immediate 32-bit float operand")?, } } InstructionFormat::UnaryIeee64 => { InstructionData::UnaryIeee64 { - opcode: opcode, + opcode, imm: self.match_ieee64("expected immediate 64-bit float operand")?, } } @@ -1446,7 +1442,7 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value second operand")?; InstructionData::Binary { - opcode: opcode, + opcode, args: [lhs, rhs], } } @@ -1455,7 +1451,7 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_imm64("expected immediate integer second operand")?; InstructionData::BinaryImm { - opcode: opcode, + opcode, arg: lhs, imm: rhs, } @@ -1469,14 +1465,14 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let false_arg = self.match_value("expected SSA value false operand")?; InstructionData::Ternary { - opcode: opcode, + opcode, args: [ctrl_arg, true_arg, false_arg], } } InstructionFormat::MultiAry => { let args = self.parse_value_list()?; InstructionData::MultiAry { - opcode: opcode, + opcode, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } @@ -1485,7 +1481,7 @@ impl<'a> Parser<'a> { let ebb_num = self.match_ebb("expected jump destination EBB")?; let args = self.parse_opt_value_list()?; InstructionData::Jump { - opcode: opcode, + opcode, destination: ebb_num, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } @@ -1496,7 +1492,7 @@ impl<'a> Parser<'a> { let ebb_num = self.match_ebb("expected branch destination EBB")?; let args = self.parse_opt_value_list()?; InstructionData::Branch { - opcode: opcode, + opcode, destination: ebb_num, args: args.into_value_list(&[ctrl_arg], &mut ctx.function.dfg.value_lists), } @@ -1510,8 +1506,8 @@ impl<'a> Parser<'a> { let ebb_num = self.match_ebb("expected branch destination EBB")?; let args = self.parse_opt_value_list()?; InstructionData::BranchIcmp { - opcode: opcode, - cond: cond, + opcode, + cond, destination: ebb_num, args: args.into_value_list(&[lhs, rhs], &mut ctx.function.dfg.value_lists), } @@ -1523,8 +1519,8 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value last operand")?; InstructionData::InsertLane { - opcode: opcode, - lane: lane, + opcode, + lane, args: [lhs, rhs], } } @@ -1532,11 +1528,7 @@ impl<'a> Parser<'a> { let arg = self.match_value("expected SSA value last operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; let lane = self.match_uimm8("expected lane number")?; - InstructionData::ExtractLane { - opcode: opcode, - lane: lane, - arg: arg, - } + InstructionData::ExtractLane { opcode, lane, arg } } InstructionFormat::IntCompare => { let cond = self.match_enum("expected intcc condition code")?; @@ -1544,8 +1536,8 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value second operand")?; InstructionData::IntCompare { - opcode: opcode, - cond: cond, + opcode, + cond, args: [lhs, rhs], } } @@ -1555,8 +1547,8 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_imm64("expected immediate second operand")?; InstructionData::IntCompareImm { - opcode: opcode, - cond: cond, + opcode, + cond, arg: lhs, imm: rhs, } @@ -1567,8 +1559,8 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let rhs = self.match_value("expected SSA value second operand")?; InstructionData::FloatCompare { - opcode: opcode, - cond: cond, + opcode, + cond, args: [lhs, rhs], } } @@ -1579,8 +1571,8 @@ impl<'a> Parser<'a> { let args = self.parse_value_list()?; self.match_token(Token::RPar, "expected ')' after arguments")?; InstructionData::Call { - opcode: opcode, - func_ref: func_ref, + opcode, + func_ref, args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), } } @@ -1593,8 +1585,8 @@ impl<'a> Parser<'a> { let args = self.parse_value_list()?; self.match_token(Token::RPar, "expected ')' after arguments")?; InstructionData::IndirectCall { - opcode: opcode, - sig_ref: sig_ref, + opcode, + sig_ref, args: args.into_value_list(&[callee], &mut ctx.function.dfg.value_lists), } } @@ -1603,20 +1595,16 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let table = self.match_jt() .and_then(|num| ctx.get_jt(num, &self.loc))?; - InstructionData::BranchTable { - opcode: opcode, - arg: arg, - table: table, - } + InstructionData::BranchTable { opcode, arg, table } } InstructionFormat::StackLoad => { let ss = self.match_ss("expected stack slot number: ss«n»") .and_then(|num| ctx.get_ss(num, &self.loc))?; let offset = self.optional_offset32()?; InstructionData::StackLoad { - opcode: opcode, + opcode, stack_slot: ss, - offset: offset, + offset, } } InstructionFormat::StackStore => { @@ -1626,19 +1614,19 @@ impl<'a> Parser<'a> { .and_then(|num| ctx.get_ss(num, &self.loc))?; let offset = self.optional_offset32()?; InstructionData::StackStore { - opcode: opcode, - arg: arg, + opcode, + arg, stack_slot: ss, - offset: offset, + offset, } } InstructionFormat::HeapLoad => { let addr = self.match_value("expected SSA value address")?; let offset = self.optional_uoffset32()?; InstructionData::HeapLoad { - opcode: opcode, + opcode, arg: addr, - offset: offset, + offset, } } InstructionFormat::HeapStore => { @@ -1647,9 +1635,9 @@ impl<'a> Parser<'a> { let addr = self.match_value("expected SSA value address")?; let offset = self.optional_uoffset32()?; InstructionData::HeapStore { - opcode: opcode, + opcode, args: [arg, addr], - offset: offset, + offset, } } InstructionFormat::Load => { @@ -1657,10 +1645,10 @@ impl<'a> Parser<'a> { let addr = self.match_value("expected SSA value address")?; let offset = self.optional_offset32()?; InstructionData::Load { - opcode: opcode, - flags: flags, + opcode, + flags, arg: addr, - offset: offset, + offset, } } InstructionFormat::Store => { @@ -1670,10 +1658,10 @@ impl<'a> Parser<'a> { let addr = self.match_value("expected SSA value address")?; let offset = self.optional_offset32()?; InstructionData::Store { - opcode: opcode, - flags: flags, + opcode, + flags, args: [arg, addr], - offset: offset, + offset, } } }; diff --git a/src/cton-util.rs b/src/cton-util.rs index fea4d879be..70fcbc3c84 100644 --- a/src/cton-util.rs +++ b/src/cton-util.rs @@ -17,7 +17,7 @@ mod cat; mod print_cfg; mod rsfilecheck; -const USAGE: &'static str = " +const USAGE: &str = " Cretonne code generator utility Usage: diff --git a/src/filetest/concurrent.rs b/src/filetest/concurrent.rs index 9ea1114000..dbdd76f35c 100644 --- a/src/filetest/concurrent.rs +++ b/src/filetest/concurrent.rs @@ -51,8 +51,8 @@ impl ConcurrentRunner { ConcurrentRunner { request_tx: Some(request_tx), - reply_rx: reply_rx, - handles: handles, + reply_rx, + handles, } } @@ -120,10 +120,7 @@ fn worker_thread(thread_num: usize, // Tell them we're starting this job. // The receiver should always be present for this as long as we have jobs. replies - .send(Reply::Starting { - jobid: jobid, - thread_num: thread_num, - }) + .send(Reply::Starting { jobid, thread_num }) .unwrap(); let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| { @@ -142,12 +139,7 @@ fn worker_thread(thread_num: usize, dbg!("FAIL: {}", msg); } - replies - .send(Reply::Done { - jobid: jobid, - result: result, - }) - .unwrap(); + replies.send(Reply::Done { jobid, result }).unwrap(); } }) .unwrap() diff --git a/src/filetest/runner.rs b/src/filetest/runner.rs index 1993ca5877..23f94fe78f 100644 --- a/src/filetest/runner.rs +++ b/src/filetest/runner.rs @@ -81,7 +81,7 @@ impl TestRunner { /// Create a new blank TrstRunner. pub fn new(verbose: bool) -> TestRunner { TestRunner { - verbose: verbose, + verbose, dir_stack: Vec::new(), tests: Vec::new(), new_tests: 0, diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index 60231d22f4..621f2bfd6a 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -55,9 +55,9 @@ pub fn run(path: &Path) -> TestResult { for (func, details) in testfile.functions { let mut context = Context { preamble_comments: &testfile.preamble_comments, - details: details, + details, verified: false, - flags: flags, + flags, isa: None, }; diff --git a/src/print_cfg.rs b/src/print_cfg.rs index d5b87d806b..1d5deff68c 100644 --- a/src/print_cfg.rs +++ b/src/print_cfg.rs @@ -32,7 +32,7 @@ struct CFGPrinter<'a> { impl<'a> CFGPrinter<'a> { pub fn new(func: &'a Function) -> CFGPrinter<'a> { CFGPrinter { - func: func, + func, cfg: ControlFlowGraph::with_function(func), } } From 15606fa7354d2ba806fa255873ff946c7ed9a31a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Apr 2017 12:52:41 -0700 Subject: [PATCH 733/968] Upgrade to rustfmt 0.8.3. --- src/cat.rs | 6 ++++-- src/filetest/legalizer.rs | 6 ++++-- src/filetest/regalloc.rs | 9 ++++++--- src/filetest/runone.rs | 6 ++++-- src/filetest/subtest.rs | 12 ++++++++---- src/print_cfg.rs | 6 ++++-- src/rsfilecheck.rs | 18 ++++++++++++------ test-all.sh | 2 +- 8 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/cat.rs b/src/cat.rs index fd49722ec0..a53d3b4294 100644 --- a/src/cat.rs +++ b/src/cat.rs @@ -21,8 +21,10 @@ pub fn run(files: Vec) -> CommandResult { } fn cat_one(filename: String) -> CommandResult { - let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; - let items = parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))?; + let buffer = read_to_string(&filename) + .map_err(|e| format!("{}: {}", filename, e))?; + let items = parse_functions(&buffer) + .map_err(|e| format!("{}: {}", filename, e))?; for (idx, func) in items.into_iter().enumerate() { if idx != 0 { diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index c6e08ffe29..8fd7f45b16 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -40,11 +40,13 @@ impl SubTest for TestLegalizer { let isa = context.isa.expect("legalizer needs an ISA"); comp_ctx.flowgraph(); - comp_ctx.legalize(isa) + comp_ctx + .legalize(isa) .map_err(|e| pretty_error(&comp_ctx.func, e))?; let mut text = String::new(); - write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; + write_function(&mut text, &comp_ctx.func, Some(isa)) + .map_err(|e| e.to_string())?; run_filecheck(&text, context) } } diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index 87045eb764..79e7ad8e0f 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -45,13 +45,16 @@ impl SubTest for TestRegalloc { comp_ctx.flowgraph(); // TODO: Should we have an option to skip legalization? - comp_ctx.legalize(isa) + comp_ctx + .legalize(isa) .map_err(|e| pretty_error(&comp_ctx.func, e))?; - comp_ctx.regalloc(isa) + comp_ctx + .regalloc(isa) .map_err(|e| pretty_error(&comp_ctx.func, e))?; let mut text = String::new(); - write_function(&mut text, &comp_ctx.func, Some(isa)).map_err(|e| e.to_string())?; + write_function(&mut text, &comp_ctx.func, Some(isa)) + .map_err(|e| e.to_string())?; run_filecheck(&text, context) } } diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index 621f2bfd6a..b43d926f0e 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -26,7 +26,8 @@ pub fn run(path: &Path) -> TestResult { } // Parse the test commands. - let mut tests = testfile.commands + let mut tests = testfile + .commands .iter() .map(new_subtest) .collect::>>()?; @@ -116,7 +117,8 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { - verify_function(&func, isa).map_err(|e| pretty_verifier_error(&func, e))?; + verify_function(&func, isa) + .map_err(|e| pretty_verifier_error(&func, e))?; context.verified = true; } diff --git a/src/filetest/subtest.rs b/src/filetest/subtest.rs index 923439d490..9c7159dbb5 100644 --- a/src/filetest/subtest.rs +++ b/src/filetest/subtest.rs @@ -76,12 +76,14 @@ impl<'a> filecheck::VariableMap for Context<'a> { /// Run filecheck on `text`, using directives extracted from `context`. pub fn run_filecheck(text: &str, context: &Context) -> Result<()> { let checker = build_filechecker(context)?; - if checker.check(&text, context) + if checker + .check(&text, context) .map_err(|e| format!("filecheck: {}", e))? { Ok(()) } else { // Filecheck mismatch. Emit an explanation as output. - let (_, explain) = checker.explain(&text, context) + let (_, explain) = checker + .explain(&text, context) .map_err(|e| format!("explain: {}", e))?; Err(format!("filecheck failed:\n{}{}", checker, explain)) } @@ -92,11 +94,13 @@ pub fn build_filechecker(context: &Context) -> Result { let mut builder = CheckerBuilder::new(); // Preamble comments apply to all functions. for comment in context.preamble_comments { - builder.directive(comment.text) + builder + .directive(comment.text) .map_err(|e| format!("filecheck: {}", e))?; } for comment in &context.details.comments { - builder.directive(comment.text) + builder + .directive(comment.text) .map_err(|e| format!("filecheck: {}", e))?; } let checker = builder.finish(); diff --git a/src/print_cfg.rs b/src/print_cfg.rs index 1d5deff68c..ec479e3c92 100644 --- a/src/print_cfg.rs +++ b/src/print_cfg.rs @@ -91,8 +91,10 @@ impl<'a> Display for CFGPrinter<'a> { } fn print_cfg(filename: String) -> CommandResult { - let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; - let items = parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))?; + let buffer = read_to_string(&filename) + .map_err(|e| format!("{}: {}", filename, e))?; + let items = parse_functions(&buffer) + .map_err(|e| format!("{}: {}", filename, e))?; for (idx, func) in items.into_iter().enumerate() { if idx != 0 { diff --git a/src/rsfilecheck.rs b/src/rsfilecheck.rs index 79d913bce9..5ee464d20a 100644 --- a/src/rsfilecheck.rs +++ b/src/rsfilecheck.rs @@ -18,11 +18,13 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } let mut buffer = String::new(); - io::stdin().read_to_string(&mut buffer) + io::stdin() + .read_to_string(&mut buffer) .map_err(|e| format!("stdin: {}", e))?; if verbose { - let (success, explain) = checker.explain(&buffer, NO_VARIABLES) + let (success, explain) = checker + .explain(&buffer, NO_VARIABLES) .map_err(|e| e.to_string())?; print!("{}", explain); if success { @@ -31,11 +33,13 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } else { Err("Check failed".to_string()) } - } else if checker.check(&buffer, NO_VARIABLES) + } else if checker + .check(&buffer, NO_VARIABLES) .map_err(|e| e.to_string())? { Ok(()) } else { - let (_, explain) = checker.explain(&buffer, NO_VARIABLES) + let (_, explain) = checker + .explain(&buffer, NO_VARIABLES) .map_err(|e| e.to_string())?; print!("{}", explain); Err("Check failed".to_string()) @@ -43,9 +47,11 @@ pub fn run(files: Vec, verbose: bool) -> CommandResult { } fn read_checkfile(filename: &str) -> Result { - let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; + let buffer = read_to_string(&filename) + .map_err(|e| format!("{}: {}", filename, e))?; let mut builder = CheckerBuilder::new(); - builder.text(&buffer) + builder + .text(&buffer) .map_err(|e| format!("{}: {}", filename, e))?; Ok(builder.finish()) } diff --git a/test-all.sh b/test-all.sh index 5b718d8bc4..d387cae46d 100755 --- a/test-all.sh +++ b/test-all.sh @@ -30,7 +30,7 @@ function banner() { # rustfmt is installed. # # This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.8.1" +RUSTFMT_VERSION="0.8.3" if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then banner "Rust formatting" From a29ea664e2a706b2486da672094572844edf3890 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Apr 2017 13:39:05 -0700 Subject: [PATCH 734/968] Install rustfmt as a separate Travis install step. - Add a check-rustfmt.sh script which checks if the right version of rustfmt is installed. - Run check-rustfmt.sh --install as an install step under travis_wait. This is to work around the issue where cargo takes forever to build rustfmt, causing Travis to terminate the build because it hasn't produced any output for 10 minutes. --- .travis.yml | 1 + check-rustfmt.sh | 35 +++++++++++++++++++++++++++++++++++ test-all.sh | 21 +-------------------- 3 files changed, 37 insertions(+), 20 deletions(-) create mode 100755 check-rustfmt.sh diff --git a/.travis.yml b/.travis.yml index 1986553bf1..03dd6f76e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ addons: - python3-pip install: - pip3 install --user --upgrade mypy flake8 + - travis_wait ./check-rustfmt.sh --install script: ./test-all.sh cache: cargo: true diff --git a/check-rustfmt.sh b/check-rustfmt.sh new file mode 100755 index 0000000000..c0c99c9a91 --- /dev/null +++ b/check-rustfmt.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Usage: check-rustfmt.sh [--install] +# +# Check that the desired version of rustfmt is installed. +# +# Rustfmt is still immature enough that its formatting decisions can change +# between versions. This makes it difficult to enforce a certain style in a +# test script since not all developers will upgrade rustfmt at the same time. +# To work around this, we only verify formatting when a specific version of +# rustfmt is installed. +# +# Exits 0 if the right version of rustfmt is installed, 1 otherwise. +# +# With the --install option, also tries to install the right version. + +# This version should always be bumped to the newest version available. +VERS="0.8.3" + +if cargo install --list | grep -q "^rustfmt v$VERS"; then + exit 0 +fi + +if [ "$1" != "--install" ]; then + echo "********************************************************************" + echo "* Please install rustfmt v$VERS to verify formatting. *" + echo "* If a newer version of rustfmt is available, update this script. *" + echo "********************************************************************" + echo "$0 --install" + sleep 1 + exit 1 +fi + +echo "Installing rustfmt v$VERS." +cargo install --force --vers="$VERS" rustfmt diff --git a/test-all.sh b/test-all.sh index d387cae46d..07d6fa2bdd 100755 --- a/test-all.sh +++ b/test-all.sh @@ -22,28 +22,9 @@ function banner() { } # Run rustfmt if we have it. -# -# Rustfmt is still immature enough that its formatting decisions can change -# between versions. This makes it difficult to enforce a certain style in a -# test script since not all developers will upgrade rustfmt at the same time. -# To work around this, we only verify formatting when a specific version of -# rustfmt is installed. -# -# This version should always be bumped to the newest version available. -RUSTFMT_VERSION="0.8.3" - -if cargo install --list | grep -q "^rustfmt v$RUSTFMT_VERSION"; then +if $topdir/check-rustfmt.sh; then banner "Rust formatting" $topdir/format-all.sh --write-mode=diff -elif [ -n "$TRAVIS" ]; then - # We're running under Travis CI. - # Install rustfmt, it will be cached for the next build. - echo "Installing rustfmt v$RUSTFMT_VERSION." - cargo install --force --vers="$RUSTFMT_VERSION" rustfmt - $topdir/format-all.sh --write-mode=diff -else - echo "Please install rustfmt v$RUSTFMT_VERSION to verify formatting." - echo "If a newer version of rustfmt is available, update this script." fi # Check if any Python files have changed since we last checked them. From 6fe4aa2f8d627693a5046ce026fd997e9fa3bd8e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Apr 2017 17:39:58 -0700 Subject: [PATCH 735/968] Extract the topological ordering into a module. Multiple passes will need to iterate over EBBs in a dominator-topological order. Move that functionality into a separate module. --- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/regalloc/coloring.rs | 54 ++--------- lib/cretonne/src/regalloc/context.rs | 10 ++- lib/cretonne/src/topo_order.rs | 125 ++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 46 deletions(-) create mode 100644 lib/cretonne/src/topo_order.rs diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 986c45355b..1f51b38d3f 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -35,4 +35,5 @@ mod packed_option; mod partition_slice; mod predicates; mod ref_slice; +mod topo_order; mod write; diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 544c5b5da0..a1b3899577 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -43,19 +43,13 @@ use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; -use sparse_map::SparseSet; +use topo_order::TopoOrder; /// Data structures for the coloring pass. /// /// These are scratch space data structures that can be reused between invocations. -pub struct Coloring { - /// Set of visited EBBs. - visited: SparseSet, - - /// Stack of EBBs to be visited next. - stack: Vec, -} +pub struct Coloring {} /// Bundle of references that the coloring algorithm needs. /// @@ -83,10 +77,7 @@ struct Context<'a> { impl Coloring { /// Allocate scratch space data structures for the coloring pass. pub fn new() -> Coloring { - Coloring { - visited: SparseSet::new(), - stack: Vec::new(), - } + Coloring {} } /// Run the coloring algorithm over `func`. @@ -95,6 +86,7 @@ impl Coloring { func: &mut Function, domtree: &DominatorTree, liveness: &mut Liveness, + topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { let mut ctx = Context { reginfo: isa.register_info(), @@ -103,45 +95,17 @@ impl Coloring { liveness, usable_regs: isa.allocatable_registers(func), }; - ctx.run(self, func, tracker) + ctx.run(func, topo, tracker) } } impl<'a> Context<'a> { /// Run the coloring algorithm. - fn run(&mut self, data: &mut Coloring, func: &mut Function, tracker: &mut LiveValueTracker) { - // Just visit blocks in layout order, letting `process_ebb` enforce a topological ordering. + fn run(&mut self, func: &mut Function, topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { + // Just visit blocks in layout order, letting `topo` enforce a topological ordering. // TODO: Once we have a loop tree, we could visit hot blocks first. - let mut next = func.layout.entry_block(); - while let Some(ebb) = next { - self.process_ebb(ebb, data, func, tracker); - next = func.layout.next_ebb(ebb); - } - } - - /// Process `ebb`, but only after ensuring that the immediate dominator has been processed. - /// - /// This method can be called with the most desired order of visiting the EBBs. It will convert - /// that order into a valid topological order by visiting dominators first. - fn process_ebb(&mut self, - mut ebb: Ebb, - data: &mut Coloring, - func: &mut Function, - tracker: &mut LiveValueTracker) { - // The stack is just a scratch space for this algorithm. We leave it empty when returning. - assert!(data.stack.is_empty()); - - // Trace up the dominator tree until we reach a dominator that has already been visited. - while data.visited.insert(ebb).is_none() { - data.stack.push(ebb); - match self.domtree.idom(ebb) { - Some(idom) => ebb = func.layout.inst_ebb(idom).expect("idom not in layout"), - None => break, - } - } - - // Pop off blocks in topological order. - while let Some(ebb) = data.stack.pop() { + topo.reset(func.layout.ebbs()); + while let Some(ebb) = topo.next(&func.layout, self.domtree) { self.visit_ebb(ebb, func, tracker); } } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index b57edebb5f..b8cebd1bcc 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -12,11 +12,13 @@ use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; use result::CtonResult; +use topo_order::TopoOrder; use verifier::{verify_context, verify_liveness}; /// Persistent memory allocations for register allocation. pub struct Context { liveness: Liveness, + topo: TopoOrder, tracker: LiveValueTracker, coloring: Coloring, } @@ -29,6 +31,7 @@ impl Context { pub fn new() -> Context { Context { liveness: Liveness::new(), + topo: TopoOrder::new(), tracker: LiveValueTracker::new(), coloring: Coloring::new(), } @@ -60,7 +63,12 @@ impl Context { // Third pass: Reload and coloring. self.coloring - .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + .run(isa, + func, + domtree, + &mut self.liveness, + &mut self.topo, + &mut self.tracker); if isa.flags().enable_verifier() { verify_context(func, cfg, domtree, Some(isa))?; diff --git a/lib/cretonne/src/topo_order.rs b/lib/cretonne/src/topo_order.rs new file mode 100644 index 0000000000..55cece2c2f --- /dev/null +++ b/lib/cretonne/src/topo_order.rs @@ -0,0 +1,125 @@ +//! Topological order of EBBs, according to the dominator tree. + +use dominator_tree::DominatorTree; +use ir::{Ebb, Layout}; +use sparse_map::SparseSet; + +/// Present EBBs in a topological order such that all dominating EBBs are guaranteed to be visited +/// before the current EBB. +/// +/// There are many topological orders of the EBBs in a function, so it is possible to provide a +/// preferred order, and the `TopoOrder` will present EBBs in an order that is as close as possible +/// to the preferred order. +pub struct TopoOrder { + /// Preferred order of EBBs to visit. + preferred: Vec, + + /// Next entry to get from `preferred`. + next: usize, + + /// Set of visited EBBs. + visited: SparseSet, + + /// Stack of EBBs to be visited next, already in `visited`. + stack: Vec, +} + +impl TopoOrder { + /// Create a new empty topological order. + pub fn new() -> TopoOrder { + TopoOrder { + preferred: Vec::new(), + next: 0, + visited: SparseSet::new(), + stack: Vec::new(), + } + } + + /// Reset and initialize with a preferred sequence of EBBs. The resulting topological order is + /// guaranteed to contain all of the EBBs in `preferred` as well as any dominators. + pub fn reset(&mut self, preferred: Ebbs) + where Ebbs: IntoIterator + { + self.preferred.clear(); + self.preferred.extend(preferred); + self.next = 0; + self.visited.clear(); + self.stack.clear(); + } + + /// Get the next EBB in the topological order. + /// + /// Two things are guaranteed about the EBBs returned by this function: + /// + /// - All EBBs in the `preferred` iterator given to `reset` will be returned. + /// - All dominators are visited before the EBB returned. + pub fn next(&mut self, layout: &Layout, domtree: &DominatorTree) -> Option { + // Any entries in `stack` should be returned immediately. They have already been added to + // `visited`. + while self.stack.is_empty() { + match self.preferred.get(self.next).cloned() { + None => return None, + Some(mut ebb) => { + // We have the next EBB in the preferred order. + self.next += 1; + // Push it along with any non-visited dominators. + while self.visited.insert(ebb).is_none() { + self.stack.push(ebb); + match domtree.idom(ebb) { + Some(idom) => ebb = layout.inst_ebb(idom).expect("idom not in layout"), + None => break, + } + } + } + } + } + return self.stack.pop(); + } +} + +#[cfg(test)] +mod test { + use flowgraph::ControlFlowGraph; + use dominator_tree::DominatorTree; + use ir::{Function, InstBuilder, Cursor}; + use std::iter; + use super::*; + + #[test] + fn empty() { + let func = Function::new(); + let cfg = ControlFlowGraph::with_function(&func); + let domtree = DominatorTree::with_function(&func, &cfg); + let mut topo = TopoOrder::new(); + + assert_eq!(topo.next(&func.layout, &domtree), None); + topo.reset(func.layout.ebbs()); + assert_eq!(topo.next(&func.layout, &domtree), None); + } + + #[test] + fn simple() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + + { + let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); + + cur.insert_ebb(ebb0); + dfg.ins(cur).jump(ebb1, &[]); + cur.insert_ebb(ebb1); + dfg.ins(cur).jump(ebb1, &[]); + } + + let cfg = ControlFlowGraph::with_function(&func); + let domtree = DominatorTree::with_function(&func, &cfg); + let mut topo = TopoOrder::new(); + + topo.reset(iter::once(ebb1)); + assert_eq!(topo.next(&func.layout, &domtree), Some(ebb0)); + assert_eq!(topo.next(&func.layout, &domtree), Some(ebb1)); + assert_eq!(topo.next(&func.layout, &domtree), None); + } +} From 8cd67f08a94079cebb9d749c8dbe4b3021fff6bd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 2 May 2017 11:32:12 -0700 Subject: [PATCH 736/968] Add a regmove instruction. This will be used to locally change the register locations of values in order to satisfy instruction constraints. --- docs/langref.rst | 5 ++++ filetests/parser/tiny.cton | 15 ++++++++++ lib/cretonne/meta/base/formats.py | 4 ++- lib/cretonne/meta/base/immediates.py | 6 ++++ lib/cretonne/meta/base/instructions.py | 19 ++++++++++++- lib/cretonne/src/ir/builder.rs | 1 + lib/cretonne/src/ir/dfg.rs | 2 +- lib/cretonne/src/ir/instructions.rs | 7 +++++ lib/cretonne/src/verifier/mod.rs | 3 +- lib/cretonne/src/write.rs | 21 ++++++++++++-- lib/reader/src/parser.rs | 38 +++++++++++++++++++++++++- 11 files changed, 114 insertions(+), 7 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 008f2a74b1..e6a0e2b3d5 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -583,6 +583,11 @@ allocation pass and beyond. .. autoinst:: spill .. autoinst:: fill +Register values can be temporarily diverted to other registers by the +:inst:`regmove` instruction. + +.. autoinst:: regmove + Vector operations ----------------- diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 87140d9aad..5ad2f7a39d 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -155,3 +155,18 @@ ebb0(v1: i32): ; nextln: store $v2, $v1 ; nextln: store aligned $v3, $v1+12 ; nextln: store notrap aligned $v3, $v1-12 + +; Register diversions. +; This test file has no ISA, so we can unly use register unit numbers. +function diversion(i32) { +ebb0(v1: i32): + regmove v1, %10 -> %20 + regmove v1, %20 -> %10 + return +} +; sameln: function diversion(i32) { +; nextln: ebb0($v1: i32): +; nextln: regmove $v1, %10 -> %20 +; nextln: regmove $v1, %20 -> %10 +; nextln: return +; nextln: } diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 6e64e0e3f2..368fb13078 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS from .immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from .immediates import intcc, floatcc, memflags +from .immediates import intcc, floatcc, memflags, regunit from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot Nullary = InstructionFormat() @@ -57,5 +57,7 @@ StackStore = InstructionFormat(VALUE, stack_slot, offset32) HeapLoad = InstructionFormat(VALUE, uoffset32) HeapStore = InstructionFormat(VALUE, VALUE, uoffset32) +RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit)) + # Finally extract the names of global variables in this module. InstructionFormat.extract_names(globals()) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 8e643bb9ee..2458b76e70 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -96,3 +96,9 @@ memflags = ImmediateKind( 'memflags', 'Memory operation flags', default_member='flags', rust_type='MemFlags') + +#: A register unit in the current target ISA. +regunit = ImmediateKind( + 'regunit', + 'A register unit in the target ISA', + rust_type='RegUnit') diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 3035f2b98e..b9d2d5a5af 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -10,7 +10,7 @@ from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from base.immediates import intcc, floatcc, memflags +from base.immediates import intcc, floatcc, memflags, regunit from base import entities import base.formats # noqa @@ -467,6 +467,23 @@ fill = Instruction( """, ins=x, outs=a) +src = Operand('src', regunit) +dst = Operand('dst', regunit) + +regmove = Instruction( + 'regmove', r""" + Temporarily divert ``x`` from ``src`` to ``dst``. + + This instruction moves the location of a value from one register to + another without creating a new SSA value. It is used by the register + allocator to temporarily rearrange register assignments in order to + satisfy instruction constraints. + + The register diversions created by this instruction must be undone + before the value leaves the EBB. At the entry to a new EBB, all live + values must be in their originally assigned registers. + """, + ins=(x, src, dst)) # # Vector operations diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index ae71f754c7..a5a3719299 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -9,6 +9,7 @@ use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::{IntCC, FloatCC}; +use isa::RegUnit; /// Base trait for instruction builders. /// diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 063d7cbbd7..6e33c9d592 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -687,7 +687,7 @@ impl<'a> fmt::Display for DisplayInst<'a> { } else { write!(f, "{}.{}", inst.opcode(), typevar)?; } - write_operands(f, dfg, self.1) + write_operands(f, dfg, None, self.1) } } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 9484110d83..0b32929af6 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -14,6 +14,7 @@ use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::*; use ir::types; +use isa::RegUnit; use entity_list; use ref_slice::{ref_slice, ref_slice_mut}; @@ -203,6 +204,12 @@ pub enum InstructionData { args: [Value; 2], offset: Offset32, }, + RegMove { + opcode: Opcode, + arg: Value, + src: RegUnit, + dst: RegUnit, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 82e95d7f1f..e1568d5bab 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -284,7 +284,8 @@ impl<'a> Verifier<'a> { &HeapLoad { .. } | &HeapStore { .. } | &Load { .. } | - &Store { .. } => {} + &Store { .. } | + &RegMove { .. } => {} } Ok(()) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 5d46ccb624..af66cc8868 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -225,12 +225,16 @@ fn write_instruction(w: &mut Write, None => write!(w, "{}", opcode)?, } - write_operands(w, &func.dfg, inst)?; + write_operands(w, &func.dfg, isa, inst)?; writeln!(w, "") } /// Write the operands of `inst` to `w` with a prepended space. -pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result { +pub fn write_operands(w: &mut Write, + dfg: &DataFlowGraph, + isa: Option<&TargetIsa>, + inst: Inst) + -> Result { let pool = &dfg.value_lists; use ir::instructions::InstructionData::*; match dfg[inst] { @@ -321,6 +325,19 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result offset, .. } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), + RegMove { arg, src, dst, .. } => { + if let Some(isa) = isa { + let regs = isa.register_info(); + write!(w, + " {}, {} -> {}", + arg, + regs.display_regunit(src), + regs.display_regunit(dst)) + } else { + write!(w, " {}, %{} -> %{}", arg, src, dst) + } + } + } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 02ec88196e..b907ee426f 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -16,7 +16,7 @@ use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; -use cretonne::isa::{self, TargetIsa, Encoding}; +use cretonne::isa::{self, TargetIsa, Encoding, RegUnit}; use cretonne::settings::{self, Configurable}; use testfile::{TestFile, Details, Comment}; use error::{Location, Error, Result}; @@ -551,6 +551,29 @@ impl<'a> Parser<'a> { } } + // Match and consume a register unit either by number `%15` or by name `%rax`. + fn match_regunit(&mut self, isa: Option<&TargetIsa>) -> Result { + if let Some(Token::Name(name)) = self.token() { + self.consume(); + match isa { + Some(isa) => { + isa.register_info() + .parse_regunit(name) + .ok_or_else(|| self.error("invalid register name")) + } + None => { + name.parse() + .map_err(|_| self.error("invalid register number")) + } + } + } else { + match isa { + Some(isa) => err!(self.loc, "Expected {} register unit", isa.name()), + None => err!(self.loc, "Expected register unit number"), + } + } + } + /// Parse a list of test commands. pub fn parse_test_commands(&mut self) -> Vec> { let mut list = Vec::new(); @@ -1664,6 +1687,19 @@ impl<'a> Parser<'a> { offset, } } + InstructionFormat::RegMove => { + let arg = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let src = self.match_regunit(ctx.unique_isa)?; + self.match_token(Token::Arrow, "expected '->' between register units")?; + let dst = self.match_regunit(ctx.unique_isa)?; + InstructionData::RegMove { + opcode, + arg, + src, + dst, + } + } }; Ok(idata) } From 7a1ba057e42d045a39a156b364620472d9830fa2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 12:24:32 -0700 Subject: [PATCH 737/968] Ignore .mypy_cache A recent mypy update started writing the .mypy_cache directory which we don't want under version control. The cache is only used by the experimental "mypy --incremental" mode which we don't use, but it is always written anyway. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5f3b8f2899..a230556d47 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target Cargo.lock .*.rustfmt cretonne.dbg* +.mypy_cache From 673279068fb7b0e94d840fcc8bd003e5fd06a997 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 12:54:09 -0700 Subject: [PATCH 738/968] Run mypy in python 3 mode. This still picks up the 2.7 type annotations in comments. Fix the compute_quadratic signature to allow for the ValuewView of an OrderedDict in python 3. --- lib/cretonne/meta/constant_hash.py | 4 ++-- lib/cretonne/meta/mypy.ini | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/constant_hash.py b/lib/cretonne/meta/constant_hash.py index 96560ffc87..47f5eebe33 100644 --- a/lib/cretonne/meta/constant_hash.py +++ b/lib/cretonne/meta/constant_hash.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from cdsl import next_power_of_two try: - from typing import Any, List, Sequence, Callable # noqa + from typing import Any, List, Iterable, Callable # noqa except ImportError: pass @@ -32,7 +32,7 @@ def simple_hash(s): def compute_quadratic(items, hash_function): - # type: (Sequence[Any], Callable[[Any], int]) -> List[Any] + # type: (Iterable[Any], Callable[[Any], int]) -> List[Any] """ Compute an open addressed, quadratically probed hash table containing `items`. The returned table is a list containing the elements of the diff --git a/lib/cretonne/meta/mypy.ini b/lib/cretonne/meta/mypy.ini index ca7f5e4c00..7046100b4c 100644 --- a/lib/cretonne/meta/mypy.ini +++ b/lib/cretonne/meta/mypy.ini @@ -1,5 +1,4 @@ [mypy] -python_version = 2.7 disallow_untyped_defs = True warn_unused_ignores = True warn_return_any = True From 9bbeaeda24aeba323da1170606c4f4296030ff7b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 12:20:35 -0700 Subject: [PATCH 739/968] Add a regs_overlap function to the isa module. Test it with the arm32 register banks which have the most interesting properties. Most other registers have a single register unit. --- lib/cretonne/src/isa/arm32/registers.rs | 37 ++++++++++++++++++++++++- lib/cretonne/src/isa/mod.rs | 2 +- lib/cretonne/src/isa/registers.rs | 10 +++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/isa/arm32/registers.rs b/lib/cretonne/src/isa/arm32/registers.rs index 69571976cb..283f113d1e 100644 --- a/lib/cretonne/src/isa/arm32/registers.rs +++ b/lib/cretonne/src/isa/arm32/registers.rs @@ -6,7 +6,7 @@ include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); #[cfg(test)] mod tests { - use super::INFO; + use super::{INFO, GPR, S, D}; use isa::RegUnit; #[test] @@ -29,4 +29,39 @@ mod tests { assert_eq!(uname(31), "%s31"); assert_eq!(uname(64), "%r0"); } + + #[test] + fn overlaps() { + // arm32 has the most interesting register geometries, so test `regs_overlap()` here. + use isa::regs_overlap; + + let r0 = GPR.unit(0); + let r1 = GPR.unit(1); + let r2 = GPR.unit(2); + + assert!(regs_overlap(GPR, r0, GPR, r0)); + assert!(regs_overlap(GPR, r2, GPR, r2)); + assert!(!regs_overlap(GPR, r0, GPR, r1)); + assert!(!regs_overlap(GPR, r1, GPR, r0)); + assert!(!regs_overlap(GPR, r2, GPR, r1)); + assert!(!regs_overlap(GPR, r1, GPR, r2)); + + let s0 = S.unit(0); + let s1 = S.unit(1); + let s2 = S.unit(2); + let s3 = S.unit(3); + let d0 = D.unit(0); + let d1 = D.unit(1); + + assert!(regs_overlap(S, s0, D, d0)); + assert!(regs_overlap(S, s1, D, d0)); + assert!(!regs_overlap(S, s0, D, d1)); + assert!(!regs_overlap(S, s1, D, d1)); + assert!(regs_overlap(S, s2, D, d1)); + assert!(regs_overlap(S, s3, D, d1)); + assert!(!regs_overlap(D, d1, S, s1)); + assert!(regs_overlap(D, d1, S, s2)); + assert!(!regs_overlap(D, d0, D, d1)); + assert!(regs_overlap(D, d1, D, d1)); + } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 4b618e0798..34d3e21ef6 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -42,7 +42,7 @@ pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, BranchRange}; pub use isa::encoding::{Encoding, EncInfo}; -pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex}; +pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; use binemit::CodeSink; use settings; diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 15a089db17..76fd39685a 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -188,6 +188,16 @@ impl fmt::Display for RegClassIndex { } } +/// Test of two registers overlap. +/// +/// A register is identified as a `(RegClass, RegUnit)` pair. The register class is needed to +/// determine the width (in regunits) of the register. +pub fn regs_overlap(rc1: RegClass, reg1: RegUnit, rc2: RegClass, reg2: RegUnit) -> bool { + let end1 = reg1 + rc1.width as RegUnit; + let end2 = reg2 + rc2.width as RegUnit; + !(end1 <= reg2 || end2 <= reg1) +} + /// Information about the registers in an ISA. /// /// The `RegUnit` data structure collects all relevant static information about the registers in an From d1d6c626d9cfd3444166271e3de339dc1e7943ab Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 2 May 2017 09:59:58 -0700 Subject: [PATCH 740/968] Add a few register utilities. --- lib/cretonne/src/isa/registers.rs | 5 +++ lib/cretonne/src/isa/riscv/registers.rs | 14 ++++++++- lib/cretonne/src/regalloc/allocatable_set.rs | 33 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 76fd39685a..122dec8e71 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -151,6 +151,11 @@ impl RegClassData { let uoffset = offset * self.width as usize; self.first + uoffset as RegUnit } + + /// Does this register class contain `regunit`? + pub fn contains(&self, regunit: RegUnit) -> bool { + self.mask[(regunit / 32) as usize] & (1u32 << (regunit % 32)) != 0 + } } impl fmt::Display for RegClassData { diff --git a/lib/cretonne/src/isa/riscv/registers.rs b/lib/cretonne/src/isa/riscv/registers.rs index 7deef9251a..9447e5ea29 100644 --- a/lib/cretonne/src/isa/riscv/registers.rs +++ b/lib/cretonne/src/isa/riscv/registers.rs @@ -6,7 +6,7 @@ include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs")); #[cfg(test)] mod tests { - use super::INFO; + use super::{INFO, GPR, FPR}; use isa::RegUnit; #[test] @@ -34,4 +34,16 @@ mod tests { assert_eq!(uname(63), "%f31"); assert_eq!(uname(64), "%INVALID64"); } + + #[test] + fn classes() { + assert!(GPR.contains(GPR.unit(0))); + assert!(GPR.contains(GPR.unit(31))); + assert!(!FPR.contains(GPR.unit(0))); + assert!(!FPR.contains(GPR.unit(31))); + assert!(!GPR.contains(FPR.unit(0))); + assert!(!GPR.contains(FPR.unit(31))); + assert!(FPR.contains(FPR.unit(0))); + assert!(FPR.contains(FPR.unit(31))); + } } diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 29a7e26964..714a82aab7 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -78,6 +78,25 @@ impl AllocatableSet { } rsi } + + /// Check if any register units allocated out of this set interferes with units allocated out + /// of `other`. + /// + /// This assumes that unused bits are 1. + pub fn interferes_with(&self, other: &AllocatableSet) -> bool { + self.avail + .iter() + .zip(&other.avail) + .any(|(&x, &y)| (x | y) != !0) + } + + /// Intersect this set of allocatable registers with `other`. This has the effect of removing + /// any register units from this set that are not in `other`. + pub fn intersect(&mut self, other: &AllocatableSet) { + for (x, &y) in self.avail.iter_mut().zip(&other.avail) { + *x &= y; + } + } } /// Iterator over available registers in a register class. @@ -179,4 +198,18 @@ mod tests { assert_eq!(regs.iter(GPR).count(), 7); assert_eq!(regs.iter(DPR).collect::>(), [30, 33, 35]); } + + #[test] + fn interference() { + let mut regs1 = AllocatableSet::new(); + let mut regs2 = AllocatableSet::new(); + + assert!(!regs1.interferes_with(®s2)); + regs1.take(&GPR, 32); + assert!(!regs1.interferes_with(®s2)); + regs2.take(&GPR, 31); + assert!(!regs1.interferes_with(®s2)); + regs1.intersect(®s2); + assert!(regs1.interferes_with(®s2)); + } } From fedc834ecdd5262b28c917f761c16974d8456eb9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 13:57:39 -0700 Subject: [PATCH 741/968] Also return live-through values from process_inst(). The coloring algorithm will need to look at the live-through values to check if they interfere with fixed-register outputs for calls etc. --- lib/cretonne/src/regalloc/coloring.rs | 2 +- .../src/regalloc/live_value_tracker.rs | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index a1b3899577..f664c43c48 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -276,7 +276,7 @@ impl<'a> Context<'a> { locations: &mut EntityMap) { // First update the live value tracker with this instruction. // Get lists of values that are killed and defined by `inst`. - let (kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + let (_throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); // Get the operand constraints for `inst` that we are trying to satisfy. let constraints = self.encinfo diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 78085564f7..4ef7df8d3a 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -139,7 +139,7 @@ impl LiveValueTracker { /// This depends on the stored live value set at `ebb`'s immediate dominator, so that must have /// been visited first. /// - /// Returns `(liveins, args)` as a pair or slices. The first slice is the set of live-in values + /// Returns `(liveins, args)` as a pair of slices. The first slice is the set of live-in values /// from the immediate dominator. The second slice is the set of `ebb` arguments that are live. /// Dead arguments with no uses are ignored and not added to the set. pub fn ebb_top(&mut self, @@ -209,9 +209,16 @@ impl LiveValueTracker { /// Determine the set of already live values that are killed by `inst`, and add the new defined /// values to the tracked set. /// - /// Returns `(kills, defs)` as a pair of slices. The `defs` slice is guaranteed to be in the - /// same order as `inst`'s results, and includes dead defines. The order of `kills` is - /// arbitrary. + /// Returns `(throughs, kills, defs)` as a tuple of slices: + /// + /// 1. The `throughs` slice is the set of live-through values that are neither defined nor + /// killed by the instruction. + /// 2. The `kills` slice is the set of values that were live before the instruction and are + /// killed at the instruction. This does not include dead defs. + /// 3. The `defs` slice is guaranteed to be in the same order as `inst`'s results, and includes + /// dead defines. + /// + /// The order of `throughs` and `kills` is arbitrary. /// /// The `drop_dead()` method must be called next to actually remove the dead values from the /// tracked set after the two returned slices are no longer needed. @@ -219,7 +226,7 @@ impl LiveValueTracker { inst: Inst, dfg: &DataFlowGraph, liveness: &Liveness) - -> (&[LiveValue], &[LiveValue]) { + -> (&[LiveValue], &[LiveValue], &[LiveValue]) { // Save a copy of the live values before any branches or jumps that could be somebody's // immediate dominator. match dfg[inst].analyze_branch(&dfg.value_lists) { @@ -249,7 +256,9 @@ impl LiveValueTracker { } } - (&self.live.values[first_kill..first_def], &self.live.values[first_def..]) + (&self.live.values[0..first_kill], + &self.live.values[first_kill..first_def], + &self.live.values[first_def..]) } /// Drop the values that are now dead after moving past `inst`. From b3b15f9c321acd111f345d4522ee80203fa3260a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 15:21:00 -0700 Subject: [PATCH 742/968] Add support for tied operand constraints. The register constraint for an output operand can be specified as an integer indicating the input operand number to tie. The tied operands must use the same register. Generate operand constraints using ConstraintKind::Tied(n) for both the tied operands. The n index refers to the opposite array. The input operand refers to the outs array and vice versa. --- lib/cretonne/meta/cdsl/isa.py | 22 ++++++++++++--- lib/cretonne/meta/gen_encoding.py | 42 ++++++++++++++++++++--------- lib/cretonne/src/isa/constraints.rs | 11 ++++---- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index a44a1e6333..f1125f59da 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -7,7 +7,7 @@ from .ast import Apply # The typing module is only required by mypy, and we don't use these imports # outside type comments. try: - from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, TYPE_CHECKING # noqa + from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, Dict, TYPE_CHECKING # noqa if TYPE_CHECKING: from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa from .predicates import PredNode # noqa @@ -220,13 +220,27 @@ class EncRecipe(object): if isinstance(c, int): # An integer constraint is bound to a value operand. # Check that it is in range. - assert c >= 0 - if not self.format.has_value_list: - assert c < self.format.num_value_operands + assert c >= 0 and c < len(self.ins) else: assert isinstance(c, RegClass) or isinstance(c, Register) return seq + def ties(self): + # type: () -> Tuple[Dict[int, int], Dict[int, int]] + """ + Return two dictionaries representing the tied operands. + + The first maps input number to tied output number, the second maps + output number to tied input number. + """ + i2o = dict() # type: Dict[int, int] + o2i = dict() # type: Dict[int, int] + for o, i in enumerate(self.outs): + if isinstance(i, int): + i2o[i] = o + o2i[o] = i + return (i2o, o2i) + class Encoding(object): """ diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index bbe1df7591..f19377b8bd 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -60,9 +60,9 @@ from cdsl.registers import RegClass, Register from cdsl.predicates import FieldPredicate try: - from typing import Sequence, Set, Tuple, List, Iterable, DefaultDict, TYPE_CHECKING # noqa + from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa if TYPE_CHECKING: - from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode # noqa + from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode, EncRecipe # noqa from cdsl.predicates import PredNode, PredLeaf # noqa from cdsl.types import ValueType # noqa from cdsl.instructions import Instruction # noqa @@ -470,13 +470,20 @@ def emit_recipe_constraints(isa, fmt): .format(len(isa.all_recipes)), '];'): for r in isa.all_recipes: fmt.comment(r.name) + tied_i2o, tied_o2i = r.ties() with fmt.indented('RecipeConstraints {', '},'): - emit_operand_constraints(r.ins, 'ins', fmt) - emit_operand_constraints(r.outs, 'outs', fmt) + emit_operand_constraints(r, r.ins, 'ins', tied_i2o, fmt) + emit_operand_constraints(r, r.outs, 'outs', tied_o2i, fmt) -def emit_operand_constraints(seq, field, fmt): - # type: (Sequence[OperandConstraint], str, srcgen.Formatter) -> None +def emit_operand_constraints( + recipe, # type: EncRecipe + seq, # type: Sequence[OperandConstraint] + field, # type: str + tied, # type: Dict[int, int] + fmt # type: srcgen.Formatter + ): + # type: (...) -> None """ Emit a struct field initializer for an array of operand constraints. """ @@ -484,16 +491,25 @@ def emit_operand_constraints(seq, field, fmt): fmt.line('{}: &[],'.format(field)) return with fmt.indented('{}: &['.format(field), '],'): - for cons in seq: + for n, cons in enumerate(seq): with fmt.indented('OperandConstraint {', '},'): if isinstance(cons, RegClass): - fmt.line('kind: ConstraintKind::Reg,') - fmt.line('regclass: {},'.format(cons)) + if n in tied: + fmt.format('kind: ConstraintKind::Tied({}),', tied[n]) + else: + fmt.line('kind: ConstraintKind::Reg,') + fmt.format('regclass: {},', cons) elif isinstance(cons, Register): - fmt.line( - 'kind: ConstraintKind::FixedReg({}),' - .format(cons.unit)) - fmt.line('regclass: {},'.format(cons.regclass)) + assert n not in tied, "Can't tie fixed register operand" + fmt.format( + 'kind: ConstraintKind::FixedReg({}),', cons.unit) + fmt.format('regclass: {},', cons.regclass) + elif isinstance(cons, int): + # This is a tied output constraint. It should never happen + # for input constraints. + assert cons == tied[n], "Invalid tied constraint" + fmt.format('kind: ConstraintKind::Tied({}),', cons) + fmt.format('regclass: {},', recipe.ins[cons]) else: raise AssertionError( 'Unsupported constraint {}'.format(cons)) diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index 23cd923f93..fa30fd24fe 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -33,13 +33,14 @@ pub enum ConstraintKind { /// register. FixedReg(RegUnit), - /// This result value must use the same register as an input value operand. Input operands - /// can't be tied. + /// This result value must use the same register as an input value operand. /// - /// The associated number is the index of the input value operand this result is tied to. + /// The associated number is the index of the input value operand this result is tied to. The + /// constraint's `regclass` field is the same as the tied operand's register class. /// - /// The constraint's `regclass` field is the top-level register class containing the tied - /// operand's register class. + /// When an (in, out) operand pair is tied, this constraint kind appears in both the `ins` and + /// the `outs` arrays. The constraint for the in operand is `Tied(out)`, and the constraint for + /// the out operand is `Tied(in)`. Tied(u8), /// This operand must be a value in a stack slot. From 3d2fdec1afe44999136c6eedcec566a3923765c4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 12:44:22 -0700 Subject: [PATCH 743/968] Add constraint summaries to RecipeConstraints. Most instructions don't have any fixed register constraints. Add boolean summaries that can be used to check if it is worthwhile to scan the constraint lists when looking for a fixed register constraint. Also add a tied_ops summary bool which indicates that the instruction has tied operand constraints. --- lib/cretonne/meta/gen_encoding.py | 9 +++++++++ lib/cretonne/src/isa/constraints.rs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index f19377b8bd..c56db8fc79 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -474,6 +474,15 @@ def emit_recipe_constraints(isa, fmt): with fmt.indented('RecipeConstraints {', '},'): emit_operand_constraints(r, r.ins, 'ins', tied_i2o, fmt) emit_operand_constraints(r, r.outs, 'outs', tied_o2i, fmt) + fmt.format( + 'fixed_ins: {},', + str(any(isinstance(c, Register) + for c in r.ins)).lower()) + fmt.format( + 'fixed_outs: {},', + str(any(isinstance(c, Register) + for c in r.outs)).lower()) + fmt.format('tied_ops: {},', str(bool(tied_i2o)).lower()) def emit_operand_constraints( diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index fa30fd24fe..e38b044535 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -67,6 +67,15 @@ pub struct RecipeConstraints { /// If the instruction produces a variable number of results, it's probably a call and the /// constraints must be derived from the calling convention ABI. pub outs: &'static [OperandConstraint], + + /// Are any of the input constraints `FixedReg`? + pub fixed_ins: bool, + + /// Are any of the output constraints `FixedReg`? + pub fixed_outs: bool, + + /// Are there any tied operands? + pub tied_ops: bool, } /// Constraints on the range of a branch instruction. From 5bdb61a5f1f1996daedb81965b9069970ef10743 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sun, 30 Apr 2017 13:34:51 -0700 Subject: [PATCH 744/968] Add the very basics of Intel 32-bit instruction encodings. Tabulate the Intel opcode representations and implement an OP() function which computes the encoding bits. Implement the single-byte opcode with a reg-reg ModR/M byte. --- filetests/isa/intel/binary32.cton | 23 ++++++++ lib/cretonne/meta/isa/intel/__init__.py | 2 +- lib/cretonne/meta/isa/intel/encodings.py | 10 ++++ lib/cretonne/meta/isa/intel/recipes.py | 67 ++++++++++++++++++++++++ lib/cretonne/src/isa/intel/binemit.rs | 30 ++++++++++- lib/cretonne/src/isa/intel/enc_tables.rs | 3 +- lib/cretonne/src/isa/intel/mod.rs | 4 ++ 7 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 filetests/isa/intel/binary32.cton create mode 100644 lib/cretonne/meta/isa/intel/encodings.py create mode 100644 lib/cretonne/meta/isa/intel/recipes.py diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton new file mode 100644 index 0000000000..c8440440b9 --- /dev/null +++ b/filetests/isa/intel/binary32.cton @@ -0,0 +1,23 @@ +; binary emission of 32-bit code. +test binemit +isa intel + +; The binary encodings can be verified with the command: +; +; sed -ne 's/^ *; asm: *//p' filetests/isa/intel/binary32.cton | llvm-mc -show-encoding -triple=i386 +; + +function I32() { +ebb0: + [-,%rcx] v1 = iconst.i32 1 + [-,%rsi] v2 = iconst.i32 2 + + ; Integer Register-Register Operations. + + ; asm: addl %esi, %ecx + [-,%rcx] v10 = iadd v1, v2 ; bin: 01 f1 + ; asm: addl %ecx, %esi + [-,%rsi] v11 = iadd v2, v1 ; bin: 01 ce + + return +} diff --git a/lib/cretonne/meta/isa/intel/__init__.py b/lib/cretonne/meta/isa/intel/__init__.py index 6aea0fd288..0828d790aa 100644 --- a/lib/cretonne/meta/isa/intel/__init__.py +++ b/lib/cretonne/meta/isa/intel/__init__.py @@ -17,7 +17,7 @@ is no x87 floating point support. from __future__ import absolute_import from . import defs -from . import settings, registers # noqa +from . import encodings, settings, registers # noqa # Re-export the primary target ISA definition. ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py new file mode 100644 index 0000000000..5d1ffdb4ec --- /dev/null +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -0,0 +1,10 @@ +""" +Intel Encodings. +""" +from __future__ import absolute_import +from base import instructions as base +from .defs import I32 +from .recipes import Op1rr +from .recipes import OP + +I32.enc(base.iadd.i32, Op1rr, OP(0x01)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py new file mode 100644 index 0000000000..9535598917 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -0,0 +1,67 @@ +""" +Intel Encoding recipes. +""" +from __future__ import absolute_import +from cdsl.isa import EncRecipe +# from cdsl.predicates import IsSignedInt +from base.formats import Binary +from .registers import GPR + +# Opcode representation. +# +# Cretonne requires each recipe to have a single encoding size in bytes, and +# Intel opcodes are variable length, so we use separate recipes for different +# styles of opcodes and prefixes. The opcode format is indicated by the recipe +# name prefix: +# +# Op1* OP(op) +# 0F Op2* OP(op) +# 0F 38 Op3* OP38(op) +# 0F 3A Op3* OP3A(op) +# 66 Mp1* MP66(op) +# 66 0F Mp2* MP66(op) +# 66 0F 38 Mp3* MP6638(op) +# 66 0F 3A Mp3* MP663A(op) +# F2 Mp1* MPF2(op) +# F2 0F Mp2* MPF2(op) +# F2 0F 38 Mp3* MPF238(op) +# F2 0F 3A Mp3* MPF23A(op) +# F3 Mp1* MPF3(op) +# F3 0F Mp2* MPF3(op) +# F3 0F 38 Mp3* MPF338(op) +# F3 0F 3A Mp3* MPF33A(op) +# +# VEX/XOP and EVEX prefixes are not yet supported. +# +# The encoding bits are: +# +# 0-7: The opcode byte . +# 8-9: pp, mandatory prefix: +# 00 none (Op*) +# 01 66 (Mp*) +# 10 F3 (Mp*) +# 11 F2 (Mp*) +# 10-11: mm, opcode map: +# 00 (Op1/Mp1) +# 01 0F (Op2/Mp2) +# 10 0F 38 (Op3/Mp3) +# 11 0F 3A (Op3/Mp3) +# 12-14 rrr, opcode bits for the ModR/M byte for certain opcodes. +# 15: REX.W bit (or VEX.W/E) +# +# There is some redundancy between bits 8-11 and the recipe names, but we have +# enough bits, and the pp+mm format is ready for supporting VEX prefixes. + + +def OP(op, pp=0, mm=0, rrr=0, w=0): + # type: (int, int, int, int, int) -> int + assert op <= 0xff + assert pp <= 0b11 + assert mm <= 0b11 + assert rrr <= 0b111 + assert w <= 1 + return op | (pp << 8) | (mm << 10) | (rrr << 12) | (w << 15) + + +# XX /r +Op1rr = EncRecipe('Op1rr', Binary, size=2, ins=(GPR, GPR), outs=0) diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 8870abd8f1..4b322c6969 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -1,6 +1,34 @@ //! Emitting binary Intel machine code. use binemit::{CodeSink, bad_encoding}; -use ir::{Function, Inst}; +use ir::{Function, Inst, InstructionData}; +use isa::RegUnit; include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); + +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; + +fn put_op1(bits: u16, sink: &mut CS) { + debug_assert!(bits & 0x0f00 == 0, "Invalid encoding bits for Op1*"); + sink.put1(bits as u8); +} + +fn modrm_rr(rm: RegUnit, reg: RegUnit, sink: &mut CS) { + let reg = reg as u8 & 7; + let rm = rm as u8 & 7; + let mut b = 0b11000000; + b |= reg << 3; + b |= rm; + sink.put1(b); +} + +fn recipe_op1rr(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Binary { args, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_rr(func.locations[args[0]].unwrap_reg(), + func.locations[args[1]].unwrap_reg(), + sink); + } else { + panic!("Expected Binary format: {:?}", func.dfg[inst]); + } +} diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 1ee1fbd4b3..1ec93f00a9 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,10 +1,11 @@ //! Encoding tables for Intel ISAs. -use ir::InstructionData; +use ir::{Opcode, InstructionData}; use ir::types; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::encoding::RecipeSizing; +use super::registers::*; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 5871c8766a..1711be3166 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -94,4 +94,8 @@ impl TargetIsa for Isa { fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } + + fn reloc_names(&self) -> &'static [&'static str] { + &binemit::RELOC_NAMES + } } From db9f64d2f3ddfdced770f6a29fe145c289b68dab Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 9 May 2017 11:43:58 -0700 Subject: [PATCH 745/968] Make `srem` have the sign of the dividend. This is how remainder is defined in C (as of C99), C++ (as of C++11), Rust, and WebAssembly, for example. --- lib/cretonne/meta/base/instructions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index b9d2d5a5af..5777ed76ea 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -683,14 +683,13 @@ urem = Instruction( srem = Instruction( 'srem', """ - Signed integer remainder. + Signed integer remainder. The result has the sign of the dividend. This operation traps if the divisor is zero. .. todo:: Integer remainder vs modulus. - Clarify whether the result has the sign of the divisor or the - dividend. Should we add a ``smod`` instruction for the case where + Should we add a ``smod`` instruction for the case where the result has the same sign as the divisor? """, ins=(x, y), outs=a, can_trap=True) From cdb3a71dd11eed2f533d52408046ff5af5c625f7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 8 May 2017 20:56:08 -0700 Subject: [PATCH 746/968] Add encodings for Intel dynamic shift instructions. These instructions have a fixed register constraint; the shift amount is passed in CL. Add meta language syntax so a fixed register can be specified as "GPR.rcx". --- filetests/isa/intel/binary32.cton | 15 ++++++++++++ lib/cretonne/meta/cdsl/registers.py | 29 ++++++++++++++++++++---- lib/cretonne/meta/isa/intel/encodings.py | 9 +++++++- lib/cretonne/meta/isa/intel/recipes.py | 3 +++ lib/cretonne/src/isa/intel/binemit.rs | 21 +++++++++++++++++ 5 files changed, 72 insertions(+), 5 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index c8440440b9..d609a0c030 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -19,5 +19,20 @@ ebb0: ; asm: addl %ecx, %esi [-,%rsi] v11 = iadd v2, v1 ; bin: 01 ce + ; Dynamic shifts take the shift amount in %rcx. + + ; asm: shll %cl, %esi + [-,%rsi] v12 = ishl v2, v1 ; bin: d3 e6 + ; asm: shll %cl, %ecx + [-,%rcx] v13 = ishl v1, v1 ; bin: d3 e1 + ; asm: shrl %cl, %esi + [-,%rsi] v14 = ushr v2, v1 ; bin: d3 ee + ; asm: shrl %cl, %ecx + [-,%rcx] v15 = ushr v1, v1 ; bin: d3 e9 + ; asm: sarl %cl, %esi + [-,%rsi] v16 = sshr v2, v1 ; bin: d3 fe + ; asm: sarl %cl, %ecx + [-,%rcx] v17 = sshr v1, v1 ; bin: d3 f9 + return } diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index b21d2348a6..1f6bfa682f 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -12,12 +12,12 @@ register bank. A register bank consists of a number of *register units* which are the smallest indivisible units of allocation and interference. A register unit doesn't -necesarily correspond to a particular number of bits in a register, it is more +necessarily correspond to a particular number of bits in a register, it is more like a placeholder that can be used to determine of a register is taken or not. The register allocator works with *register classes* which can allocate one or more register units at a time. A register class allocates more than one -register unit at a time when its registers are composed of smaller alocatable +register unit at a time when its registers are composed of smaller allocatable units. For example, the ARM double precision floating point registers are composed of two single precision registers. """ @@ -151,6 +151,18 @@ class RegBank(object): # sub-class. rc2.subclasses.append(rc1) + def unit_by_name(self, name): + # type: (str) -> int + """ + Get a register unit in this bank by name. + """ + if name in self.names: + r = self.names.index(name) + elif name.startswith(self.prefix): + r = int(name[len(self.prefix):]) + assert r < self.units, 'Invalid register name: ' + name + return self.first_unit + r + class RegClass(object): """ @@ -242,6 +254,15 @@ class RegClass(object): return RegClass(self.bank, count=c, width=w, start=s) + def __getattr__(self, attr): + # type: (str) -> Register + """ + Get a specific register in the class by name. + + For example: `GPR.r5`. + """ + return Register(self, self.bank.unit_by_name(attr)) + def mask(self): # type: () -> List[int] """ @@ -298,8 +319,8 @@ class Register(object): Specific registers are used to describe constraints on instructions where some operands must use a fixed register. - Register objects should be created using the indexing syntax on the - register class. + Register instances can be created with the constructor, or accessed as + attributes on the register class: `GPR.rcx`. """ def __init__(self, rc, unit): # type: (RegClass, int) -> None diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 5d1ffdb4ec..3896532c85 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -4,7 +4,14 @@ Intel Encodings. from __future__ import absolute_import from base import instructions as base from .defs import I32 -from .recipes import Op1rr +from .recipes import Op1rr, Op1rc from .recipes import OP I32.enc(base.iadd.i32, Op1rr, OP(0x01)) + +# 32-bit shifts and rotates. +# Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit +# and 16-bit shifts would need explicit masking. +I32.enc(base.ishl.i32.i32, Op1rc, OP(0xd3, rrr=4)) +I32.enc(base.ushr.i32.i32, Op1rc, OP(0xd3, rrr=5)) +I32.enc(base.sshr.i32.i32, Op1rc, OP(0xd3, rrr=7)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 9535598917..0ffd7e5c50 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -65,3 +65,6 @@ def OP(op, pp=0, mm=0, rrr=0, w=0): # XX /r Op1rr = EncRecipe('Op1rr', Binary, size=2, ins=(GPR, GPR), outs=0) + +# XX /n with one arg in %rcx, for shifts. +Op1rc = EncRecipe('Op1rc', Binary, size=2, ins=(GPR, GPR.rcx), outs=0) diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 4b322c6969..0644d583d3 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -13,6 +13,7 @@ fn put_op1(bits: u16, sink: &mut CS) { sink.put1(bits as u8); } +/// Emit a ModR/M byte for reg-reg operands. fn modrm_rr(rm: RegUnit, reg: RegUnit, sink: &mut CS) { let reg = reg as u8 & 7; let rm = rm as u8 & 7; @@ -22,6 +23,16 @@ fn modrm_rr(rm: RegUnit, reg: RegUnit, sink: &mut CS) { sink.put1(b); } +/// Emit a ModR/M byte where the reg bits are part of the opcode. +fn modrm_r_bits(rm: RegUnit, bits: u16, sink: &mut CS) { + let reg = (bits >> 12) as u8 & 7; + let rm = rm as u8 & 7; + let mut b = 0b11000000; + b |= reg << 3; + b |= rm; + sink.put1(b); +} + fn recipe_op1rr(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::Binary { args, .. } = func.dfg[inst] { put_op1(func.encodings[inst].bits(), sink); @@ -32,3 +43,13 @@ fn recipe_op1rr(func: &Function, inst: Inst, sink: &mut C panic!("Expected Binary format: {:?}", func.dfg[inst]); } } + +fn recipe_op1rc(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Binary { args, .. } = func.dfg[inst] { + let bits = func.encodings[inst].bits(); + put_op1(bits, sink); + modrm_r_bits(func.locations[args[0]].unwrap_reg(), bits, sink); + } else { + panic!("Expected Binary format: {:?}", func.dfg[inst]); + } +} From 0dbdd90af7c7f1e485fc0dd3858dec7340172124 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 2 May 2017 13:48:19 -0700 Subject: [PATCH 747/968] Add a register diversion tracker. Keep track of the current location of register values as regmove instructions are encountered throughout an EBB. --- lib/cretonne/src/regalloc/diversion.rs | 114 +++++++++++++++++++++++++ lib/cretonne/src/regalloc/mod.rs | 2 + 2 files changed, 116 insertions(+) create mode 100644 lib/cretonne/src/regalloc/diversion.rs diff --git a/lib/cretonne/src/regalloc/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs new file mode 100644 index 0000000000..2096af8722 --- /dev/null +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -0,0 +1,114 @@ +//! Register diversions. +//! +//! Normally, a value is assigned to a single register or stack location by the register allocator. +//! Sometimes, it is necessary to move register values to a different register in order to satisfy +//! instruction constraints. +//! +//! These register diversions are local to an EBB. No values can be diverted when entering a new +//! EBB. + +use entity_map::EntityMap; +use ir::{Value, ValueLoc}; +use isa::RegUnit; + +/// A diversion of a value from its original register location to a new register. +/// +/// In IL, a diversion is represented by a `regmove` instruction, possibly a chain of them for the +/// same value. +/// +/// When tracking diversions, the `from` field is the original assigned value location, and `to` is +/// the current one. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Diversion { + /// The value that is diverted. + pub value: Value, + /// The original register value location. + pub from: RegUnit, + /// The current register value location. + pub to: RegUnit, +} + +impl Diversion { + /// Make a new register diversion. + pub fn new(value: Value, from: RegUnit, to: RegUnit) -> Diversion { + Diversion { value, from, to } + } +} + +/// Keep track of register diversions in an EBB. +pub struct RegDiversions { + current: Vec, +} + +impl RegDiversions { + /// Create a new empty diversion tracker. + pub fn new() -> RegDiversions { + RegDiversions { current: Vec::new() } + } + + /// Clear the tracker, preparing for a new EBB. + pub fn clear(&mut self) { + self.current.clear() + } + + /// Get the current diversion of `value`, if any. + pub fn diversion(&self, value: Value) -> Option<&Diversion> { + self.current.iter().find(|d| d.value == value) + } + + /// Get all current diversion. + pub fn all(&self) -> &[Diversion] { + self.current.as_slice() + } + + /// Get the current register location for `value`. Fall back to the assignment map for + /// non-diverted values. + pub fn reg(&self, value: Value, locations: &EntityMap) -> RegUnit { + match self.diversion(value) { + Some(d) => d.to, + None => locations[value].unwrap_reg(), + } + } + + /// Record a register move. + pub fn regmove(&mut self, value: Value, from: RegUnit, to: RegUnit) { + if let Some(i) = self.current.iter().position(|d| d.value == value) { + debug_assert_eq!(self.current[i].to, from, "Bad regmove chain for {}", value); + if self.current[i].from != to { + self.current[i].to = to; + } else { + self.current.swap_remove(i); + } + } else { + self.current.push(Diversion::new(value, from, to)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::Value; + use entity_map::EntityRef; + + #[test] + fn inserts() { + let mut divs = RegDiversions::new(); + let v1 = Value::new(1); + let v2 = Value::new(2); + + divs.regmove(v1, 10, 12); + assert_eq!(divs.diversion(v1), + Some(&Diversion { + value: v1, + from: 10, + to: 12, + })); + assert_eq!(divs.diversion(v2), None); + + divs.regmove(v1, 12, 11); + assert_eq!(divs.diversion(v1).unwrap().to, 11); + divs.regmove(v1, 11, 10); + assert_eq!(divs.diversion(v1), None); + } +} diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 0682be2fd6..9803c3af3f 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -10,6 +10,8 @@ pub mod coloring; mod affinity; mod context; +mod diversion; pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; +pub use self::diversion::RegDiversions; From be047ba5c4e39756814cb9b403c70ec911efd372 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Apr 2017 15:41:28 -0700 Subject: [PATCH 748/968] Use a constraint solver for register coloring. Most of the time, register coloring is almost trivial: just pick available registers for the values defined by the current instruction. However, some instructions have register operand constraints, and it may be necessary to move live registers around to satisfy the constraints. Sometimes the instruction's own operands can interfere with each other in a way that you can't just pick a register assignment for each output in order. This is complicated enough that it is worthwhile to represent as a constraint satisfaction problem in a separate solver module. The representation is chosen to be very fast in the common case where the constraints are trivial to solve. The current implementation is still incomplete, but as functional as the code it's replacing. Missing features: - Handle tied operand constraints. - Handle ABI constraints on calls and return instructions. - Execute a constraint solution by emitting regmove instructions. - Handling register diversions before leaving the EBB. --- lib/cretonne/src/isa/encoding.rs | 2 +- lib/cretonne/src/regalloc/coloring.rs | 329 +++++++++++----- lib/cretonne/src/regalloc/mod.rs | 1 + lib/cretonne/src/regalloc/solver.rs | 536 ++++++++++++++++++++++++++ 4 files changed, 763 insertions(+), 105 deletions(-) create mode 100644 lib/cretonne/src/regalloc/solver.rs diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index 93888b109e..b5062d988c 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -104,7 +104,7 @@ pub struct EncInfo { impl EncInfo { /// Get the value operand constraints for `enc` if it is a legal encoding. - pub fn operand_constraints(&self, enc: Encoding) -> Option<&RecipeConstraints> { + pub fn operand_constraints(&self, enc: Encoding) -> Option<&'static RecipeConstraints> { self.constraints.get(enc.recipe()) } diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index f664c43c48..871d86e757 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -15,7 +15,11 @@ //! values used by the tied operands must be killed by the instruction. This can be achieved by //! inserting a `copy` to a new value immediately before the two-address instruction. //! -//! 3. The register pressure must be lowered sufficiently by inserting spill code. Register +//! 3. If a value is bound to more than one operand on the same instruction, the operand +//! constraints must be compatible. This can also be achieved by inserting copies so the +//! incompatible operands get different values. +//! +//! 4. The register pressure must be lowered sufficiently by inserting spill code. Register //! operands are allowed to read spilled values, but each such instance must be counted as using //! a register. //! @@ -26,30 +30,28 @@ //! a topological order relative to the dominance relation, we can assign colors to the values //! defined by the instruction and only consider the colors of other values that are live at the //! instruction. -//! -//! The topological order of instructions inside an EBB is simply the layout order, starting from -//! the EBB header. A topological order of the EBBs can only visit an EBB once its immediate -//! dominator has been visited. -//! -//! There are many valid topological orders of the EBBs, and the specific order can affect which -//! coloring hints are satisfied and which are broken. -//! use entity_map::EntityMap; use dominator_tree::DominatorTree; use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Signature, ArgumentLoc}; -use isa::{TargetIsa, RegInfo, Encoding, EncInfo, ConstraintKind}; +use isa::{TargetIsa, Encoding, EncInfo, OperandConstraint, ConstraintKind}; +use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; +use regalloc::solver::Solver; +use regalloc::RegDiversions; use topo_order::TopoOrder; /// Data structures for the coloring pass. /// /// These are scratch space data structures that can be reused between invocations. -pub struct Coloring {} +pub struct Coloring { + divert: RegDiversions, + solver: Solver, +} /// Bundle of references that the coloring algorithm needs. /// @@ -69,6 +71,13 @@ struct Context<'a> { domtree: &'a DominatorTree, liveness: &'a mut Liveness, + // References to working set data structures. + // If we need to borrow out of a data structure across a method call, it must be passed as a + // function argument instead, see the `LiveValueTracker` arguments. + topo: &'a mut TopoOrder, + divert: &'a mut RegDiversions, + solver: &'a mut Solver, + // Pristine set of registers that the allocator can use. // This set remains immutable, we make clones. usable_regs: AllocatableSet, @@ -77,7 +86,10 @@ struct Context<'a> { impl Coloring { /// Allocate scratch space data structures for the coloring pass. pub fn new() -> Coloring { - Coloring {} + Coloring { + divert: RegDiversions::new(), + solver: Solver::new(), + } } /// Run the coloring algorithm over `func`. @@ -93,26 +105,31 @@ impl Coloring { encinfo: isa.encoding_info(), domtree, liveness, + topo, + divert: &mut self.divert, + solver: &mut self.solver, usable_regs: isa.allocatable_registers(func), }; - ctx.run(func, topo, tracker) + ctx.run(func, tracker) } } impl<'a> Context<'a> { /// Run the coloring algorithm. - fn run(&mut self, func: &mut Function, topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { - // Just visit blocks in layout order, letting `topo` enforce a topological ordering. + fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { + // Just visit blocks in layout order, letting `self.topo` enforce a topological ordering. // TODO: Once we have a loop tree, we could visit hot blocks first. - topo.reset(func.layout.ebbs()); - while let Some(ebb) = topo.next(&func.layout, self.domtree) { + self.topo.reset(func.layout.ebbs()); + while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { self.visit_ebb(ebb, func, tracker); } } /// Visit `ebb`, assuming that the immediate dominator has already been visited. fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + dbg!("Coloring {}:", ebb); let mut regs = self.visit_ebb_header(ebb, func, tracker); + self.divert.clear(); // Now go through the instructions in `ebb` and color the values they define. let mut pos = Cursor::new(&mut func.layout); @@ -168,10 +185,15 @@ impl<'a> Context<'a> { .get(value) .expect("No live range for live-in") .affinity; - if let Affinity::Reg(rc_index) = affinity { - let regclass = self.reginfo.rc(rc_index); - match func.locations[value] { - ValueLoc::Reg(regunit) => regs.take(regclass, regunit), + if let Affinity::Reg(rci) = affinity { + let rc = self.reginfo.rc(rci); + let loc = func.locations[value]; + dbg!("Live-in: {}:{} in {}", + lv.value, + rc, + loc.display(&self.reginfo)); + match loc { + ValueLoc::Reg(reg) => regs.take(rc, reg), ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value), ValueLoc::Stack(ss) => { panic!("Live-in {} is in {}, should be register", value, ss) @@ -200,11 +222,11 @@ impl<'a> Context<'a> { for (lv, abi) in args.iter().zip(&sig.argument_types) { match lv.affinity { - Affinity::Reg(rc_index) => { - let regclass = self.reginfo.rc(rc_index); - if let ArgumentLoc::Reg(regunit) = abi.location { - regs.take(regclass, regunit); - *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + Affinity::Reg(rci) => { + let rc = self.reginfo.rc(rci); + if let ArgumentLoc::Reg(reg) = abi.location { + regs.take(rc, reg); + *locations.ensure(lv.value) = ValueLoc::Reg(reg); } else { // This should have been fixed by the reload pass. panic!("Entry arg {} has {} affinity, but ABI {}", @@ -247,14 +269,14 @@ impl<'a> Context<'a> { -> AllocatableSet { for lv in args { // Only look at the register arguments. - if let Affinity::Reg(rc_index) = lv.affinity { - let regclass = self.reginfo.rc(rc_index); + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); // TODO: Fall back to a top-level super-class. Sub-classes are only hints. - let regunit = regs.iter(regclass) + let reg = regs.iter(rc) .next() .expect("Out of registers for arguments"); - regs.take(regclass, regunit); - *locations.ensure(lv.value) = ValueLoc::Reg(regunit); + regs.take(rc, reg); + *locations.ensure(lv.value) = ValueLoc::Reg(reg); } } @@ -266,7 +288,7 @@ impl<'a> Context<'a> { /// /// Update `regs` to reflect the allocated registers after `inst`, including removing any dead /// or killed values from the set. - fn visit_inst(&self, + fn visit_inst(&mut self, inst: Inst, encoding: Encoding, _pos: &mut Cursor, @@ -274,95 +296,194 @@ impl<'a> Context<'a> { tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, locations: &mut EntityMap) { - // First update the live value tracker with this instruction. - // Get lists of values that are killed and defined by `inst`. - let (_throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + dbg!("Coloring [{}] {}", + self.encinfo.display(encoding), + dfg.display_inst(inst)); // Get the operand constraints for `inst` that we are trying to satisfy. let constraints = self.encinfo .operand_constraints(encoding) - .expect("Missing instruction encoding") - .clone(); + .expect("Missing instruction encoding"); + + // Program the solver with register constraints for the input side. + self.solver.reset(regs); + self.program_input_constraints(inst, constraints.ins, dfg, locations); + if self.solver.has_fixed_input_conflicts() { + self.divert_fixed_input_conflicts(tracker.live(), locations); + } + self.solver.inputs_done(); + + // Update the live value tracker with this instruction. + let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); // Get rid of the killed values. for lv in kills { - if let Affinity::Reg(rc_index) = lv.affinity { - let regclass = self.reginfo.rc(rc_index); - if let ValueLoc::Reg(regunit) = locations[lv.value] { - regs.free(regclass, regunit); - } + if let Affinity::Reg(rci) = lv.affinity { + self.solver + .add_kill(lv.value, + self.reginfo.rc(rci), + self.divert.reg(lv.value, locations)); } } - // Process the defined values with fixed constraints. - // TODO: Handle constraints on call return values. - assert_eq!(defs.len(), - constraints.outs.len(), - "Can't handle variable results"); - for (lv, opcst) in defs.iter().zip(constraints.outs) { - match lv.affinity { - // This value should go in a register. - Affinity::Reg(rc_index) => { - // The preferred register class is not a requirement. - let pref_rc = self.reginfo.rc(rc_index); - match opcst.kind { - ConstraintKind::Reg => { - // This is a standard register constraint. The preferred register class - // should have been computed as a subclass of the hard constraint of - // the def. - assert!(opcst.regclass.has_subclass(rc_index), - "{} preference {} is not compatible with the definition \ - constraint {}", - lv.value, - pref_rc.name, - opcst.regclass.name); - // Try to grab a register from the preferred class, but fall back to - // the actual constraint if we have to. - let regunit = regs.iter(pref_rc) - .next() - .or_else(|| regs.iter(opcst.regclass).next()) - .expect("Ran out of registers"); - regs.take(opcst.regclass, regunit); - *locations.ensure(lv.value) = ValueLoc::Reg(regunit); - } - ConstraintKind::Tied(arg_index) => { - // This def must use the same register as a fixed instruction argument. - let arg = dfg.inst_args(inst)[arg_index as usize]; - let loc = locations[arg]; - *locations.ensure(lv.value) = loc; - // Mark the reused register. It's not really clear if we support tied - // stack operands. We could do that for some Intel read-modify-write - // encodings. - if let ValueLoc::Reg(regunit) = loc { - // This is going to assert out unless the incoming value at - // `arg_index` was killed. Tied operands must be fixed to - // ensure that before running the coloring pass. - regs.take(opcst.regclass, regunit); - } - } - ConstraintKind::FixedReg(_regunit) => unimplemented!(), - ConstraintKind::Stack => { - panic!("{}:{} should be a stack value", lv.value, pref_rc.name) - } - } - } - Affinity::Stack => unimplemented!(), - Affinity::None => { - panic!("Encoded instruction defines {} with no affinity", lv.value) - } - } + // Program the fixed output constraints before the general defines. This allows us to + // detect conflicts between fixed outputs and tied operands where the input value hasn't + // been converted to a solver variable. + if constraints.fixed_outs { + self.program_fixed_output_constraints(inst, + constraints.outs, + defs, + throughs, + dfg, + locations); + } + self.program_output_constraints(inst, constraints.outs, defs, dfg, locations); + + // Finally, we've fully programmed the constraint solver. + // We expect a quick solution in most cases. + let mut output_regs = self.solver + .quick_solve() + .unwrap_or_else(|_| self.iterate_solution()); + + // Apply the solution to the defs. + for v in self.solver.vars().iter().filter(|&v| v.is_define) { + *locations.ensure(v.value) = ValueLoc::Reg(v.solution); } - // Get rid of the dead defs. + // Update `regs` for the next instruction, remove the dead defs. for lv in defs { if lv.endpoint == inst { - if let Affinity::Reg(rc_index) = lv.affinity { - let regclass = self.reginfo.rc(rc_index); - if let ValueLoc::Reg(regunit) = locations[lv.value] { - regs.free(regclass, regunit); + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + let reg = self.divert.reg(lv.value, locations); + output_regs.free(rc, reg); + } + } + } + *regs = output_regs; + } + + /// Program the input-side constraints for `inst` into the constraint solver. + fn program_input_constraints(&mut self, + inst: Inst, + constraints: &[OperandConstraint], + dfg: &mut DataFlowGraph, + locations: &mut EntityMap) { + for (op, &value) in constraints + .iter() + .zip(dfg.inst_args(inst)) + .filter(|&(op, _)| op.kind != ConstraintKind::Stack) { + // Reload pass is supposed to ensure that all arguments to register operands are + // already in a register. + let cur_reg = self.divert.reg(value, locations); + match op.kind { + ConstraintKind::FixedReg(regunit) => { + if regunit != cur_reg { + self.solver + .reassign_in(value, op.regclass, cur_reg, regunit); } } + ConstraintKind::Reg | + ConstraintKind::Tied(_) => { + if !op.regclass.contains(cur_reg) { + self.solver + .add_var(value, op.regclass, cur_reg, &self.reginfo); + } + } + ConstraintKind::Stack => unreachable!(), + } + } + } + + // Find existing live values that conflict with the fixed input register constraints programmed + // into the constraint solver. Convert them to solver variables so they can be diverted. + fn divert_fixed_input_conflicts(&mut self, + live: &[LiveValue], + locations: &mut EntityMap) { + for lv in live { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + let reg = self.divert.reg(lv.value, locations); + if self.solver.is_fixed_input_conflict(rc, reg) { + self.solver.add_var(lv.value, rc, reg, &self.reginfo); + } } } } + + /// Program any fixed-register output constraints into the solver. This may also detect + /// conflicts between live-through registers and fixed output registers. These live-through + /// values need to be turned into solver variables so they can be reassigned. + fn program_fixed_output_constraints(&mut self, + _inst: Inst, + constraints: &[OperandConstraint], + defs: &[LiveValue], + throughs: &[LiveValue], + _dfg: &mut DataFlowGraph, + locations: &mut EntityMap) { + for (op, lv) in constraints.iter().zip(defs) { + if let ConstraintKind::FixedReg(reg) = op.kind { + self.add_fixed_output(lv.value, op.regclass, reg, throughs, locations); + } + } + } + + /// Add a single fixed output value to the solver. + fn add_fixed_output(&mut self, + value: Value, + rc: RegClass, + reg: RegUnit, + throughs: &[LiveValue], + locations: &mut EntityMap) { + if !self.solver.add_fixed_output(rc, reg) { + // The fixed output conflicts with some of the live-through registers. + for lv in throughs { + if let Affinity::Reg(rci) = lv.affinity { + let rc2 = self.reginfo.rc(rci); + let reg2 = self.divert.reg(lv.value, locations); + if regs_overlap(rc, reg, rc2, reg2) { + // This live-through value is interfering with the fixed output assignment. + // Convert it to a solver variable. + // TODO: Use a looser constraint than the affinity hint. Any allocatable + // register in the top-level register class would be OK. Maybe `add_var` + // should take both a preferred class and a required constraint class. + self.solver.add_var(lv.value, rc2, reg2, &self.reginfo); + } + } + } + + let ok = self.solver.add_fixed_output(rc, reg); + assert!(ok, "Couldn't clear fixed output interference for {}", value); + } + *locations.ensure(value) = ValueLoc::Reg(reg); + } + + /// Program the output-side constraints for `inst` into the constraint solver. + /// + /// It is assumed that all fixed outputs have already been handled. + fn program_output_constraints(&mut self, + _inst: Inst, + constraints: &[OperandConstraint], + defs: &[LiveValue], + _dfg: &mut DataFlowGraph, + _locations: &mut EntityMap) { + for (op, lv) in constraints.iter().zip(defs) { + match op.kind { + ConstraintKind::FixedReg(_) | + ConstraintKind::Stack => continue, + ConstraintKind::Reg => { + self.solver.add_def(lv.value, op.regclass); + } + ConstraintKind::Tied(_) => unimplemented!(), + } + } + } + + /// Try harder to find a solution to the constraint problem since `quick_solve()` failed. + /// + /// We may need to move more registers around before a solution is possible. Use an iterative + /// algorithm that adds one more variable until a solution can be found. + fn iterate_solution(&self) -> AllocatableSet { + unimplemented!(); + } } diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 9803c3af3f..fd2e78e9c3 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -11,6 +11,7 @@ pub mod coloring; mod affinity; mod context; mod diversion; +mod solver; pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs new file mode 100644 index 0000000000..05751c5155 --- /dev/null +++ b/lib/cretonne/src/regalloc/solver.rs @@ -0,0 +1,536 @@ +//! Constraint solver for register coloring. +//! +//! The coloring phase of SSA-based register allocation is very simple in theory, but in practice +//! it is complicated by the various constraints imposed by individual instructions: +//! +//! - Call and return instructions have to satisfy ABI requirements for arguments and return +//! values. +//! - Values live across a call must be in a callee-saved register. +//! - Some instructions have operand constraints such as register sub-classes, fixed registers, or +//! tied operands. +//! +//! # The instruction register coloring problem +//! +//! The constraint solver addresses the problem of satisfying the constraints of a single +//! instruction. We have: +//! +//! - A set of values that are live in registers before the instruction, with current register +//! assignments. Some are used by the instruction, some are not. +//! - A subset of the live register values that are killed by the instruction. +//! - A set of new register values that are defined by the instruction. +//! The constraint solver addresses the problem of satisfying the constraints of a single +//! instruction. We have: +//! +//! - A set of values that are live in registers before the instruction, with current register +//! assignments. Some are used by the instruction, some are not. +//! - A subset of the live register values that are killed by the instruction. +//! - A set of new register values that are defined by the instruction. +//! +//! We are not concerned with stack values at all. The reload pass ensures that all values required +//! to be in a register by the instruction are already in a register. +//! +//! A solution to the register coloring problem consists of: +//! +//! - Register reassignment prescriptions for a subset of the live register values. +//! - Register assignments for the defined values. +//! +//! The solution ensures that when live registers are reassigned as prescribed before the +//! instruction, all its operand constraints are satisfied, and the definition assignments won't +//! conflict. +//! +//! # Register diversions and global interference +//! +//! We can divert register values temporarily to satisfy constraints, but we need to put the +//! values back into their originally assigned register locations before leaving the EBB. +//! Otherwise, values won't be in the right register at the entry point of other EBBs. +//! +//! Some values are *local*, and we don't need to worry about putting those values back since they +//! are not used in any other EBBs. +//! +//! When we assign register locations to defines, we are assigning both the register used locally +//! immediately after the instruction and the register used globally when the defined value is used +//! in a different EBB. We need to avoid interference both locally at the instruction and globally. +//! +//! We have multiple mappings of values to registers: +//! +//! 1. The initial local mapping before the instruction. This includes any diversions from previous +//! instructions in the EBB, but not diversions for the current instruction. +//! 2. The local mapping after applying the additional reassignments required to satisfy the +//! constraints of the current instruction. +//! 3. The local mapping after the instruction. This excludes values killed by the instruction and +//! includes values defined by the instruction. +//! 4. The global mapping after the instruction. This mapping only contains values with global live +//! ranges, and it does not include any diversions. +//! +//! All four mappings must be kept free of interference. +//! +//! # Problems handled by previous passes. +//! +//! The constraint solver can only reassign registers, it can't create spill code, so some +//! constraints are handled by earlier passes: +//! +//! - There will be enough free registers available for the defines. Ensuring this is the primary +//! purpose of the spilling phase. +//! - When the same value is used for multiple operands, the intersection of operand constraints is +//! non-empty. The spilling phase will insert copies to handle mutually incompatible constraints, +//! such as when the same value is bound to two different function arguments. +//! - Values bound to tied operands must be killed by the instruction. Also enforced by the +//! spiller. +//! - Values used by register operands are in registers, and values used by stack operands are in +//! stack slots. This is enforced by the reload pass. +//! +//! # Solver algorithm +//! +//! The goal of the solver is to satisfy the instruction constraints with a minimal number of +//! register assignments before the instruction. +//! +//! 1. Compute the set of values used by operands with a fixed register constraint that isn't +//! already satisfied. These are mandatory predetermined reassignments. +//! 2. Compute the set of values that don't satisfy their register class constraint. These are +//! mandatory reassignments that we need to solve. +//! 3. Add the set of defines to the set of variables computed in 2. Exclude defines tied to an +//! input operand since their value is pre-determined. +//! +//! The set of values computed in 2. and 3. are the *variables* for the solver. Given a set of +//! variables, we can also compute a set of allocatable registers by removing the variables from +//! the set of assigned registers before the instruction. +//! +//! 1. For each variable, compute its domain as the intersection of the allocatable registers and +//! its register class constraint. +//! 2. Sort the variables in order of increasing domain size. +//! 3. Search for a solution that assigns each variable a register from its domain without +//! interference between variables. +//! +//! If the search fails to find a solution, we may need to reassign more registers. Find an +//! appropriate candidate among the set of live register values, add it as a variable and start +//! over. + +use ir::Value; +use isa::{RegInfo, RegClass, RegUnit}; +use regalloc::allocatable_set::RegSetIter; +use sparse_map::{SparseMap, SparseMapValue}; +use std::fmt; +use super::AllocatableSet; + +/// A variable in the constraint problem. +/// +/// Variables represent register values that can be assigned to any register unit within the +/// constraint register class. This includes live register values that can be reassigned to a new +/// register and values defined by the instruction which must be assigned to a register. +/// +/// Besides satisfying the register class constraint, variables must also be mutually +/// non-interfering in up to three contexts: +/// +/// 1. Input side live registers, after applying all the reassignments. +/// 2. Output side live registers, considering all the local register diversions. +/// 3. Global live register, not considering any local diversions. +/// +pub struct Variable { + /// The value whose register assignment we're looking for. + pub value: Value, + + /// Avoid interference on the input side. + is_input: bool, + + /// Avoid interference on the output side. + is_output: bool, + + /// Avoid interference with the global registers. + is_global: bool, + + /// The value is defined by the current instruction. + pub is_define: bool, + + /// Number of registers available in the domain of this variable. + domain: u16, + + /// The assigned register unit after a full solution was found. + pub solution: RegUnit, + + /// Any solution must belong to the constraint register class. + constraint: RegClass, +} + +impl Variable { + /// Get an iterator over possible register choices, given the available registers on the input + /// and output sides respectively. + fn iter(&self, iregs: &AllocatableSet, oregs: &AllocatableSet) -> RegSetIter { + if self.is_input && self.is_output { + let mut r = iregs.clone(); + r.intersect(oregs); + r.iter(self.constraint) + } else if self.is_input { + iregs.iter(self.constraint) + } else { + oregs.iter(self.constraint) + } + } +} + +impl fmt::Display for Variable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}({}", self.value, self.constraint)?; + if self.is_input { + write!(f, ", in")?; + } + if self.is_output { + write!(f, ", out")?; + } + if self.is_global { + write!(f, ", global")?; + } + if self.is_define { + write!(f, ", def")?; + } + if self.domain > 0 { + write!(f, ", {}", self.domain)?; + } + write!(f, ")") + } +} + + +struct Assignment { + value: Value, + from: RegUnit, + to: RegUnit, + rc: RegClass, +} + +impl SparseMapValue for Assignment { + fn key(&self) -> Value { + self.value + } +} + +/// Constraint solver for register allocation around a single instruction. +/// +/// Start by programming in the instruction constraints. +/// +/// 1. Initialize the solver by calling `reset()` with the set of allocatable registers before the +/// instruction. +/// 2. Program the input side constraints: Call `reassign_in()` for all fixed register constraints, +/// and `add_var()` for any input operands whose constraints are not already satisfied. +/// 3. Check for conflicts between fixed input assignments and existing live values by calling +/// `has_fixed_input_conflicts()`. Resolve any conflicts by calling `add_var()` with the +/// conflicting values. +/// 4. Prepare for adding output side constraints by calling `inputs_done()`. +/// 5. Add any killed register values that no longer cause interference on the output side by +/// calling `add_kill()`. +/// 6. Program the output side constraints: Call `add_fixed_output()` for all fixed register +/// constraints and `add_def()` for free defines. Resolve fixed output conflicts by calling +/// `add_var()`. +/// +pub struct Solver { + /// Register reassignments that are required or decided as part of a full solution. + assignments: SparseMap, + + /// Variables are the values that should be reassigned as part of a solution. + /// Values with a fixed register constraints are not considered variables. They are represented + /// in the `assignments` vector if necessary. + vars: Vec, + + /// Are we finished adding input-side constraints? This changes the meaning of the `regs_in` + /// and `regs_out` register sets. + inputs_done: bool, + + /// Available registers on the input side of the instruction. + /// + /// While we're adding input constraints (`!inputs_done`): + /// + /// - Live values on the input side are marked as unavailable. + /// - The 'from' registers of fixed input reassignments are marked as available as they are + /// added. + /// - Input-side variables are marked as available. + /// + /// After finishing input constraints (`inputs_done`): + /// + /// - Live values on the input side are marked as unavailable. + /// - The 'to' registers of fixed input reassignments are marked as unavailable. + /// - Input-side variables are marked as available. + /// + regs_in: AllocatableSet, + + /// Available registers on the output side of the instruction / fixed input scratch space. + /// + /// While we're adding input constraints (`!inputs_done`): + /// + /// - The 'to' registers of fixed input reassignments are marked as unavailable. + /// + /// After finishing input constraints (`inputs_done`): + /// + /// - Live-through values are marked as unavailable. + /// - Fixed output assignments are marked as unavailable. + /// - Live-through variables are marked as available. + /// + regs_out: AllocatableSet, +} + +/// Interface for programming the constraints into the solver. +impl Solver { + /// Create a new empty solver. + pub fn new() -> Solver { + Solver { + assignments: SparseMap::new(), + vars: Vec::new(), + inputs_done: false, + regs_in: AllocatableSet::new(), + regs_out: AllocatableSet::new(), + } + } + + /// Reset the solver state and prepare solving for a new instruction with an initial set of + /// allocatable registers. + /// + /// The `regs` set is the allocatable registers before any reassignments are applied. + pub fn reset(&mut self, regs: &AllocatableSet) { + self.assignments.clear(); + self.vars.clear(); + self.inputs_done = false; + self.regs_in = regs.clone(); + // Used for tracking fixed input assignments while `!inputs_done`: + self.regs_out = AllocatableSet::new(); + } + + /// Add a fixed input reassignment of `value`. + /// + /// This means that `value` must be assigned to `to` and can't become a variable. Call with + /// `from == to` to ensure that `value` is not reassigned from its existing register location. + /// + /// In either case, `to` will not be available for variables on the input side of the + /// instruction. + pub fn reassign_in(&mut self, value: Value, rc: RegClass, from: RegUnit, to: RegUnit) { + debug_assert!(!self.inputs_done); + if self.regs_in.is_avail(rc, from) { + // It looks like `value` was already removed from the register set. It must have been + // added as a variable previously. A fixed constraint beats a variable, so convert it. + if let Some(idx) = self.vars.iter().position(|v| v.value == value) { + let v = self.vars.remove(idx); + dbg!("Converting variable {} to a fixed constraint", v); + // The spiller is responsible for ensuring that all constraints on the uses of a + // value are compatible. + assert!(v.constraint.contains(to), + "Incompatible constraints for {}", + value); + } else { + panic!("Invalid from register for fixed {} constraint", value); + } + } + self.regs_in.free(rc, from); + self.regs_out.take(rc, to); + self.assignments + .insert(Assignment { + value, + rc, + from, + to, + }); + } + + /// Add a variable representing an input side value with an existing register assignment. + /// + /// A variable is a value that should be reassigned to something in the `constraint` register + /// class. + /// + /// It is assumed initially that the value is also live on the output side of the instruction. + /// This can be changed by calling to `add_kill()`. + pub fn add_var(&mut self, + value: Value, + constraint: RegClass, + from: RegUnit, + reginfo: &RegInfo) { + // Check for existing entries for this value. + if self.regs_in.is_avail(constraint, from) { + // There cold be an existing variable entry. + if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) { + // We have an existing variable entry for `value`. Combine the constraints. + if let Some(rci) = v.constraint.intersect(constraint) { + v.constraint = reginfo.rc(rci); + return; + } else { + // The spiller should have made sure the same value is not used with disjoint + // constraints. + panic!("Incompatible constraints: {} + {}", constraint, *v) + } + } + + // No variable, then it must be a fixed reassignment. + if let Some(a) = self.assignments.get(value) { + assert!(constraint.contains(a.to), + "Incompatible constraints for {}", + value); + return; + } + + panic!("Wrong from register for {}", value); + } + self.regs_in.free(constraint, from); + if self.inputs_done { + self.regs_out.free(constraint, from); + } + self.vars + .push(Variable { + value, + constraint, + is_input: true, + is_output: true, + is_global: false, + is_define: false, + domain: 0, + solution: !0, + }); + } + + /// Check for conflicts between fixed input assignments and existing live values. + /// + /// Returns true if one of the live values conflicts with a fixed input assignment. Such a + /// conflicting value must be turned into a variable. + pub fn has_fixed_input_conflicts(&self) -> bool { + debug_assert!(!self.inputs_done); + // The `from` side of the fixed input diversions are taken from `regs_out`. + self.regs_out.interferes_with(&self.regs_in) + } + + /// Check if `rc, reg` specifically conflicts with the fixed input assignments. + pub fn is_fixed_input_conflict(&self, rc: RegClass, reg: RegUnit) -> bool { + debug_assert!(!self.inputs_done); + !self.regs_out.is_avail(rc, reg) + } + + /// Finish adding input side constraints. + /// + /// Call this method to indicate that there will be no more fixed input reassignments added + /// and prepare for the output side constraints. + pub fn inputs_done(&mut self) { + assert!(!self.has_fixed_input_conflicts()); + + // At this point, `regs_out` contains the `to` side of the input reassignments, and the + // `from` side has already been marked as available in `regs_in`. + // + // Remove the `to` assignments from `regs_in` so it now indicates the registers available + // to variables at the input side. + self.regs_in.intersect(&self.regs_out); + + // The meaning of `regs_out` now changes completely to indicate the registers available to + // variables on the output side. + // The initial mask will be modified by `add_kill()` and `add_fixed_output()`. + self.regs_out = self.regs_in.clone(); + + // Now we can't add more fixed input assignments, but `add_var()` is still allowed. + self.inputs_done = true; + } + + /// Record that an input register value is killed by the instruction. + /// + /// Even if a fixed reassignment has been added for the value, the `reg` argument should be the + /// original location before the reassignments. + /// + /// This means that the register is available on the output side. + pub fn add_kill(&mut self, value: Value, rc: RegClass, reg: RegUnit) { + debug_assert!(self.inputs_done); + + // If a fixed assignment is killed, the `to` register becomes available on the output side. + if let Some(a) = self.assignments.get(value) { + debug_assert_eq!(a.from, reg); + self.regs_out.free(a.rc, a.to); + return; + } + + // It's also possible that a variable is killed. That means it doesn't need to satisfy + // interference constraints on the output side. + // Variables representing tied operands will get their `is_output` flag set again later. + if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) { + assert!(v.is_input); + v.is_output = false; + return; + } + + // Alright, this is just a boring value being killed by the instruction. Just reclaim + // the assigned register. + self.regs_out.free(rc, reg); + } + + /// Add a fixed output assignment. + /// + /// This means that `to` will not be available for variables on the output side of the + /// instruction. + /// + /// Returns `false` if a live value conflicts with `to`, so it couldn't be added. Find the + /// conflicting live-through value and turn it into a variable before calling this method + /// again. + #[allow(dead_code)] + pub fn add_fixed_output(&mut self, rc: RegClass, reg: RegUnit) -> bool { + debug_assert!(self.inputs_done); + if self.regs_out.is_avail(rc, reg) { + self.regs_out.take(rc, reg); + true + } else { + false + } + } + + /// Add a defined output value. + /// + /// This is similar to `add_var`, except the value doesn't have a prior register assignment. + pub fn add_def(&mut self, value: Value, constraint: RegClass) { + debug_assert!(self.inputs_done); + self.vars + .push(Variable { + value, + constraint, + is_input: false, + is_output: true, + is_global: false, + is_define: true, + domain: 0, + solution: !0, + }); + } +} + +/// Interface for searching for a solution. +impl Solver { + /// Try a quick-and-dirty solution. + /// + /// This is expected to succeed for most instructions since the constraint problem is almost + /// always trivial. + /// + /// Returns `true` is a solution was found. + pub fn quick_solve(&mut self) -> Result { + self.find_solution() + } + + /// Search for a solution with the current list of variables. + /// + /// If a solution was found, returns `Ok(regs)` with the set of available registers on the + /// output side after the solution. If no solution could be found, returns `Err(rc)` with the + /// constraint register class that needs more available registers. + fn find_solution(&mut self) -> Result { + // Available registers on the input and output sides respectively. + let mut iregs = self.regs_in.clone(); + let mut oregs = self.regs_out.clone(); + + for v in &mut self.vars { + let rc = v.constraint; + let reg = match v.iter(&iregs, &oregs).next() { + None => return Err(rc), + Some(reg) => reg, + }; + + v.solution = reg; + if v.is_input { + iregs.take(rc, reg); + } + if v.is_output { + oregs.take(rc, reg); + } + } + + Ok(oregs) + } + + /// Get all the variables. + pub fn vars(&self) -> &[Variable] { + &self.vars + } +} From c571975a5c95d61fa61a91e5bcd1c3b051d16f1d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 May 2017 14:58:14 -0700 Subject: [PATCH 749/968] Use write! in utility code, rather than calling write_function directly. --- src/filetest/legalizer.rs | 5 +++-- src/filetest/regalloc.rs | 5 +++-- src/utils.rs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index 8fd7f45b16..aa6385c223 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -4,10 +4,11 @@ //! the result to filecheck. use std::borrow::Cow; -use cretonne::{self, write_function}; +use cretonne::{self}; use cretonne::ir::Function; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use std::fmt::Write; use utils::pretty_error; struct TestLegalizer; @@ -45,7 +46,7 @@ impl SubTest for TestLegalizer { .map_err(|e| pretty_error(&comp_ctx.func, e))?; let mut text = String::new(); - write_function(&mut text, &comp_ctx.func, Some(isa)) + write!(&mut text, "{}", &comp_ctx.func.display(Some(isa))) .map_err(|e| e.to_string())?; run_filecheck(&text, context) } diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index 79e7ad8e0f..4173b7c3d8 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -6,10 +6,11 @@ //! The resulting function is sent to `filecheck`. use cretonne::ir::Function; -use cretonne::{self, write_function}; +use cretonne::{self}; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; +use std::fmt::Write; use utils::pretty_error; struct TestRegalloc; @@ -53,7 +54,7 @@ impl SubTest for TestRegalloc { .map_err(|e| pretty_error(&comp_ctx.func, e))?; let mut text = String::new(); - write_function(&mut text, &comp_ctx.func, Some(isa)) + write!(&mut text, "{}", &comp_ctx.func.display(Some(isa))) .map_err(|e| e.to_string())?; run_filecheck(&text, context) } diff --git a/src/utils.rs b/src/utils.rs index 1bcba4891a..b637f7ca49 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ //! Utility functions. use cretonne::ir::entities::AnyEntity; -use cretonne::{ir, verifier, write_function}; +use cretonne::{ir, verifier}; use cretonne::result::CtonError; use std::fmt::Write; use std::fs::File; @@ -42,7 +42,7 @@ pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> Strin } _ => msg.push('\n'), } - write_function(&mut msg, func, None).unwrap(); + write!(msg, "{}", func).unwrap(); msg } From 526feb161a682ad681d395a0bf34d8ba4be5a379 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 11 May 2017 06:27:13 -0700 Subject: [PATCH 750/968] Fix rustfmt diffs. --- src/filetest/legalizer.rs | 2 +- src/filetest/regalloc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index aa6385c223..806730b4ed 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -4,7 +4,7 @@ //! the result to filecheck. use std::borrow::Cow; -use cretonne::{self}; +use cretonne; use cretonne::ir::Function; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index 4173b7c3d8..1699d384a8 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -6,7 +6,7 @@ //! The resulting function is sent to `filecheck`. use cretonne::ir::Function; -use cretonne::{self}; +use cretonne; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; From 5ff2257b12056aa44f99296ee0f4bbdb1a2de183 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 11 May 2017 10:21:59 -0700 Subject: [PATCH 751/968] Check for unknown instruction attributes. (#80) * Check for unknown instruction attributes. * Avoid has_key, at flake8's advice. * Use AssertionError instead of RuntimeError, per review request. --- lib/cretonne/meta/cdsl/instructions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index a900c95ab6..6724b775ea 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -122,6 +122,10 @@ class Instruction(object): i for i, o in enumerate(self.ins) if o.is_immediate()) self._verify_polymorphic() + for attr in kwargs: + if attr not in Instruction.ATTRIBS: + raise AssertionError( + "unknown instruction attribute '" + attr + "'") for attr in Instruction.ATTRIBS: setattr(self, attr, not not kwargs.get(attr, False)) InstructionGroup.append(self) From 9d4e23d8940f85e07f1dac466b5d8c7f0be2f7a0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 May 2017 11:03:18 -0700 Subject: [PATCH 752/968] Propagate a few more LiveRange properties to LiveValue. The live value tracker goes through the trouble of looking up the live range for each value it tracks. We can cache a few more interesting properties from the live range in the LiveValue struct. --- .../src/regalloc/live_value_tracker.rs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 4ef7df8d3a..76e2fcb0d7 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -11,6 +11,7 @@ use ir::{Inst, Ebb, Value, DataFlowGraph, ProgramOrder, ExpandedProgramPoint}; use partition_slice::partition_slice; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; +use regalloc::liverange::LiveRange; use std::collections::HashMap; @@ -45,6 +46,12 @@ pub struct LiveValue { /// This value is simply a copy of the affinity stored in the live range. We copy it because /// almost all users of `LiveValue` need to look at it. pub affinity: Affinity, + + /// The live range for this value never leaves its EBB. + pub is_local: bool, + + /// This value is dead - the live range ends immediately. + pub is_dead: bool, } struct LiveValueVec { @@ -66,13 +73,15 @@ impl LiveValueVec { } } - /// Add a new live value to `values`. - fn push(&mut self, value: Value, endpoint: Inst, affinity: Affinity) { + /// Add a new live value to `values`. Copy some properties from `lr`. + fn push(&mut self, value: Value, endpoint: Inst, lr: &LiveRange) { self.values .push(LiveValue { value, endpoint, - affinity, + affinity: lr.affinity, + is_local: lr.is_local(), + is_dead: lr.is_dead(), }); } @@ -175,7 +184,7 @@ impl LiveValueTracker { // Check if this value is live-in here. if let Some(endpoint) = lr.livein_local_end(ebb, program_order) { - self.live.push(value, endpoint, lr.affinity); + self.live.push(value, endpoint, lr); } } } @@ -189,7 +198,7 @@ impl LiveValueTracker { assert_eq!(lr.def(), ebb.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { - self.live.push(value, endpoint, lr.affinity); + self.live.push(value, endpoint, lr); } ExpandedProgramPoint::Ebb(local_ebb) => { // This is a dead EBB argument which is not even live into the first @@ -248,7 +257,7 @@ impl LiveValueTracker { assert_eq!(lr.def(), inst.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { - self.live.push(value, endpoint, lr.affinity); + self.live.push(value, endpoint, lr); } ExpandedProgramPoint::Ebb(ebb) => { panic!("Instruction result live range can't end at {}", ebb); From d03a21746680d325b75a89edb48b3ab6790783ad Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 May 2017 11:25:35 -0700 Subject: [PATCH 753/968] Keep dead EBB arguments around in LiveValueTracker::ebb_top(). Provide a drop_dead_args() function which deletes them instead. We still need to assign a register to dead EBB arguments, so they can't just be ignored. --- lib/cretonne/src/ir/layout.rs | 5 +++ lib/cretonne/src/regalloc/coloring.rs | 13 +++++- .../src/regalloc/live_value_tracker.rs | 40 ++++++++++++++----- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index b8ef83e75b..abf0f3f482 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -435,6 +435,11 @@ impl Layout { self.assign_inst_seq(inst); } + /// Fetch an ebb's first instruction. + pub fn first_inst(&self, ebb: Ebb) -> Option { + self.ebbs[ebb].first_inst.into() + } + /// Fetch an ebb's last instruction. pub fn last_inst(&self, ebb: Ebb) -> Option { self.ebbs[ebb].last_inst.into() diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 871d86e757..6c0c906f90 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -129,6 +129,7 @@ impl<'a> Context<'a> { fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { dbg!("Coloring {}:", ebb); let mut regs = self.visit_ebb_header(ebb, func, tracker); + tracker.drop_dead_args(); self.divert.clear(); // Now go through the instructions in `ebb` and color the values they define. @@ -162,14 +163,24 @@ impl<'a> Context<'a> { tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); // Arguments to the entry block have ABI constraints. - if func.layout.entry_block() == Some(ebb) { + let mut regs = if func.layout.entry_block() == Some(ebb) { assert_eq!(liveins.len(), 0); self.color_entry_args(&func.signature, args, &mut func.locations) } else { // The live-ins have already been assigned a register. Reconstruct the allocatable set. let regs = self.livein_regs(liveins, func); self.color_args(args, regs, &mut func.locations) + }; + + // Now forget about the dead arguments. + for lv in args.iter().filter(|&lv| lv.is_dead) { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + let reg = func.locations[lv.value].unwrap_reg(); + regs.free(rc, reg); + } } + regs } /// Initialize a set of allocatable registers from the values that are live-in to a block. diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index 76e2fcb0d7..a5ee8197e8 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -7,7 +7,7 @@ use dominator_tree::DominatorTree; use entity_list::{EntityList, ListPool}; use ir::instructions::BranchInfo; -use ir::{Inst, Ebb, Value, DataFlowGraph, ProgramOrder, ExpandedProgramPoint}; +use ir::{Inst, Ebb, Value, DataFlowGraph, Layout, ExpandedProgramPoint}; use partition_slice::partition_slice; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; @@ -116,6 +116,12 @@ impl LiveValueVec { let keep = self.live_after(next_inst); self.values.truncate(keep); } + + /// Remove any dead values. + fn remove_dead_values(&mut self) { + self.values.retain(|v| !v.is_dead); + self.live_prefix = None; + } } impl LiveValueTracker { @@ -150,14 +156,15 @@ impl LiveValueTracker { /// /// Returns `(liveins, args)` as a pair of slices. The first slice is the set of live-in values /// from the immediate dominator. The second slice is the set of `ebb` arguments that are live. - /// Dead arguments with no uses are ignored and not added to the set. - pub fn ebb_top(&mut self, - ebb: Ebb, - dfg: &DataFlowGraph, - liveness: &Liveness, - program_order: &PO, - domtree: &DominatorTree) - -> (&[LiveValue], &[LiveValue]) { + /// + /// Dead arguments with no uses are included in `args`. Call `drop_dead_args()` to remove them. + pub fn ebb_top(&mut self, + ebb: Ebb, + dfg: &DataFlowGraph, + liveness: &Liveness, + layout: &Layout, + domtree: &DominatorTree) + -> (&[LiveValue], &[LiveValue]) { // Start over, compute the set of live values at the top of the EBB from two sources: // // 1. Values that were live before `ebb`'s immediate dominator, filtered for those that are @@ -183,7 +190,7 @@ impl LiveValueTracker { .expect("Immediate dominator value has no live range"); // Check if this value is live-in here. - if let Some(endpoint) = lr.livein_local_end(ebb, program_order) { + if let Some(endpoint) = lr.livein_local_end(ebb, layout) { self.live.push(value, endpoint, lr); } } @@ -202,10 +209,14 @@ impl LiveValueTracker { } ExpandedProgramPoint::Ebb(local_ebb) => { // This is a dead EBB argument which is not even live into the first - // instruction in the EBB. We can ignore it. + // instruction in the EBB. assert_eq!(local_ebb, ebb, "EBB argument live range ends at wrong EBB header"); + // Give this value a fake endpoint that is the first instruction in the EBB. + // We expect it to be removed by calling `drop_dead_args()`. + self.live + .push(value, layout.first_inst(ebb).expect("Empty EBB"), lr); } } } @@ -281,6 +292,13 @@ impl LiveValueTracker { self.live.remove_kill_values(inst); } + /// Drop any values that are marked as `is_dead`. + /// + /// Use this after calling `ebb_top` to clean out dead EBB arguments. + pub fn drop_dead_args(&mut self) { + self.live.remove_dead_values(); + } + /// Save the current set of live values so it is associated with `idom`. fn save_idom_live_set(&mut self, idom: Inst) { let values = self.live.values.iter().map(|lv| lv.value); From 9f743cf3a52d54c11b36769e881ccf7790e22de3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 May 2017 11:42:44 -0700 Subject: [PATCH 754/968] Always create live ranges for dead EBB arguments. The live value tracker expects them to be there. We may eventually delete dead arguments from internal EBBs, but at least the entry block needs to be able to handle dead function arguments. --- filetests/regalloc/basic.cton | 7 +++++++ lib/cretonne/src/regalloc/liveness.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index 14fa263ac0..9db7b7563a 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -10,3 +10,10 @@ ebb0(v1: i32, v2: i32): ; sameln: iadd return } + +; Function with a dead argument. +function dead_arg(i32, i32) -> i32{ +ebb0(v1: i32, v2: i32): +; check: return $v1 + return v1 +} diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 737470b2d1..f2c9944d4b 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -311,6 +311,13 @@ impl Liveness { // elimination pass if we visit a post-order of the dominator tree? // TODO: Resolve value aliases while we're visiting instructions? for ebb in func.layout.ebbs() { + // Make sure we have created live ranges for dead EBB arguments. + // TODO: If these arguments are really dead, we could remove them, except for the entry + // block which must match the function signature. + for &arg in func.dfg.ebb_args(ebb) { + get_or_create(&mut self.ranges, arg, isa, func, &enc_info); + } + for inst in func.layout.ebb_insts(ebb) { // Make sure we have created live ranges for dead defs. // TODO: When we implement DCE, we can use the absence of a live range to indicate From 4dda3e02f189ee455890df1443a301ebc15e1eaf Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 May 2017 11:48:58 -0700 Subject: [PATCH 755/968] Simplify the dead EBB argument tracking. It is not necessary to to a second pass over the live values to update the set of available registers. The color_args() and color_entry_args() functions can do that in a single pass. --- lib/cretonne/src/regalloc/coloring.rs | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 6c0c906f90..521b1c9cba 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -163,24 +163,14 @@ impl<'a> Context<'a> { tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); // Arguments to the entry block have ABI constraints. - let mut regs = if func.layout.entry_block() == Some(ebb) { + if func.layout.entry_block() == Some(ebb) { assert_eq!(liveins.len(), 0); self.color_entry_args(&func.signature, args, &mut func.locations) } else { // The live-ins have already been assigned a register. Reconstruct the allocatable set. let regs = self.livein_regs(liveins, func); self.color_args(args, regs, &mut func.locations) - }; - - // Now forget about the dead arguments. - for lv in args.iter().filter(|&lv| lv.is_dead) { - if let Affinity::Reg(rci) = lv.affinity { - let rc = self.reginfo.rc(rci); - let reg = func.locations[lv.value].unwrap_reg(); - regs.free(rc, reg); - } } - regs } /// Initialize a set of allocatable registers from the values that are live-in to a block. @@ -221,7 +211,7 @@ impl<'a> Context<'a> { /// These are function arguments that should already have assigned register units in the /// function signature. /// - /// Return the set of remaining allocatable registers. + /// Return the set of remaining allocatable registers after filtering out the dead arguments. fn color_entry_args(&self, sig: &Signature, args: &[LiveValue], @@ -236,7 +226,9 @@ impl<'a> Context<'a> { Affinity::Reg(rci) => { let rc = self.reginfo.rc(rci); if let ArgumentLoc::Reg(reg) = abi.location { - regs.take(rc, reg); + if !lv.is_dead { + regs.take(rc, reg); + } *locations.ensure(lv.value) = ValueLoc::Reg(reg); } else { // This should have been fixed by the reload pass. @@ -278,6 +270,9 @@ impl<'a> Context<'a> { mut regs: AllocatableSet, locations: &mut EntityMap) -> AllocatableSet { + // Available registers *after* filtering out the dead arguments. + let mut live_regs = regs.clone(); + for lv in args { // Only look at the register arguments. if let Affinity::Reg(rci) = lv.affinity { @@ -287,11 +282,16 @@ impl<'a> Context<'a> { .next() .expect("Out of registers for arguments"); regs.take(rc, reg); + if !lv.is_dead { + live_regs.take(rc, reg); + } *locations.ensure(lv.value) = ValueLoc::Reg(reg); } } - regs + // All arguments are accounted for in `regs`. We don't care about the dead arguments now + // that we have made sure they don't interfere. + live_regs } /// Color the values defined by `inst` and insert any necessary shuffle code to satisfy From f8e466d60edb11ed57cdeef7737dc673b4942d75 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 10 May 2017 12:31:59 -0700 Subject: [PATCH 756/968] Solver variables keep track of where they came from. The register constraint solver has two kinds of variables: 1. Live values that were already in a register, and 2. Values defined by the instruction. Make a record of the original register holding the first kind of value. --- lib/cretonne/src/regalloc/coloring.rs | 2 +- lib/cretonne/src/regalloc/solver.rs | 63 ++++++++++++++++----------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 521b1c9cba..d8d3571ad6 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -357,7 +357,7 @@ impl<'a> Context<'a> { .unwrap_or_else(|_| self.iterate_solution()); // Apply the solution to the defs. - for v in self.solver.vars().iter().filter(|&v| v.is_define) { + for v in self.solver.vars().iter().filter(|&v| v.is_define()) { *locations.ensure(v.value) = ValueLoc::Reg(v.solution); } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index 05751c5155..0001169e96 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -129,6 +129,10 @@ pub struct Variable { /// The value whose register assignment we're looking for. pub value: Value, + /// Original register unit holding this live value before the instruction, or `None` for a + /// value that is defined by the instruction. + from: Option, + /// Avoid interference on the input side. is_input: bool, @@ -138,9 +142,6 @@ pub struct Variable { /// Avoid interference with the global registers. is_global: bool, - /// The value is defined by the current instruction. - pub is_define: bool, - /// Number of registers available in the domain of this variable. domain: u16, @@ -152,6 +153,37 @@ pub struct Variable { } impl Variable { + fn new_live(value: Value, constraint: RegClass, from: RegUnit) -> Variable { + Variable { + value, + constraint, + from: Some(from), + is_input: true, + is_output: true, + is_global: false, + domain: 0, + solution: !0, + } + } + + fn new_def(value: Value, constraint: RegClass) -> Variable { + Variable { + value, + constraint, + from: None, + is_input: false, + is_output: true, + is_global: false, + domain: 0, + solution: !0, + } + } + + /// Does this variable represent a value defined by the current instruction? + pub fn is_define(&self) -> bool { + self.from.is_none() + } + /// Get an iterator over possible register choices, given the available registers on the input /// and output sides respectively. fn iter(&self, iregs: &AllocatableSet, oregs: &AllocatableSet) -> RegSetIter { @@ -179,7 +211,7 @@ impl fmt::Display for Variable { if self.is_global { write!(f, ", global")?; } - if self.is_define { + if self.is_define() { write!(f, ", def")?; } if self.domain > 0 { @@ -369,16 +401,7 @@ impl Solver { self.regs_out.free(constraint, from); } self.vars - .push(Variable { - value, - constraint, - is_input: true, - is_output: true, - is_global: false, - is_define: false, - domain: 0, - solution: !0, - }); + .push(Variable::new_live(value, constraint, from)); } /// Check for conflicts between fixed input assignments and existing live values. @@ -474,17 +497,7 @@ impl Solver { /// This is similar to `add_var`, except the value doesn't have a prior register assignment. pub fn add_def(&mut self, value: Value, constraint: RegClass) { debug_assert!(self.inputs_done); - self.vars - .push(Variable { - value, - constraint, - is_input: false, - is_output: true, - is_global: false, - is_define: true, - domain: 0, - solution: !0, - }); + self.vars.push(Variable::new_def(value, constraint)); } } From 51fc887a5ab309cd25318cc554d753032a38b360 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 10 May 2017 15:36:14 -0700 Subject: [PATCH 757/968] Implement a move resolver for the register constraint solver. After finding a register solution, it need to be executed as a sequence of regmove instructions. This often requires a topological ordering of the moves so they don't conflict. When the solution contains cycles, try to grab an available scratch register to implement the copies. Panic if that fails (later, we'll implement emergency spilling in this case). Make sure we handle odd aliasing in the arm32 floating point register bank. Not everything is a simple cycle in that case, so make sure we don't assume so. --- lib/cretonne/src/isa/registers.rs | 6 + lib/cretonne/src/regalloc/coloring.rs | 27 ++- lib/cretonne/src/regalloc/solver.rs | 277 +++++++++++++++++++++++++- 3 files changed, 302 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 122dec8e71..91a25595f1 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -164,6 +164,12 @@ impl fmt::Display for RegClassData { } } +impl fmt::Debug for RegClassData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name) + } +} + /// A small reference to a register class. /// /// Use this when storing register classes in compact data structures. The `RegInfo::rc()` method diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index d8d3571ad6..b2f1863538 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -34,6 +34,7 @@ use entity_map::EntityMap; use dominator_tree::DominatorTree; use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Signature, ArgumentLoc}; +use ir::InstBuilder; use isa::{TargetIsa, Encoding, EncInfo, OperandConstraint, ConstraintKind}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; use regalloc::affinity::Affinity; @@ -302,7 +303,7 @@ impl<'a> Context<'a> { fn visit_inst(&mut self, inst: Inst, encoding: Encoding, - _pos: &mut Cursor, + pos: &mut Cursor, dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, @@ -356,6 +357,11 @@ impl<'a> Context<'a> { .quick_solve() .unwrap_or_else(|_| self.iterate_solution()); + + // The solution and/or fixed input constraints may require us to shuffle the set of live + // registers around. + self.shuffle_inputs(pos, dfg, regs); + // Apply the solution to the defs. for v in self.solver.vars().iter().filter(|&v| v.is_define()) { *locations.ensure(v.value) = ValueLoc::Reg(v.solution); @@ -497,4 +503,23 @@ impl<'a> Context<'a> { fn iterate_solution(&self) -> AllocatableSet { unimplemented!(); } + + /// Emit `regmove` instructions as needed to move the live registers into place before the + /// instruction. Also update `self.divert` accordingly. + /// + /// The `pos` cursor is expected to point at the instruction. The register moves are inserted + /// before. + /// + /// The solver needs to be reminded of the available registers before any moves are inserted. + fn shuffle_inputs(&mut self, + pos: &mut Cursor, + dfg: &mut DataFlowGraph, + regs: &mut AllocatableSet) { + self.solver.schedule_moves(regs); + + for m in self.solver.moves() { + self.divert.regmove(m.value, m.from, m.to); + dfg.ins(pos).regmove(m.value, m.from, m.to); + } + } } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index 0001169e96..5e3c43da5f 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -221,12 +221,12 @@ impl fmt::Display for Variable { } } - -struct Assignment { - value: Value, - from: RegUnit, - to: RegUnit, - rc: RegClass, +#[derive(Clone, Debug)] +pub struct Assignment { + pub value: Value, + pub from: RegUnit, + pub to: RegUnit, + pub rc: RegClass, } impl SparseMapValue for Assignment { @@ -235,6 +235,14 @@ impl SparseMapValue for Assignment { } } +#[cfg(test)] +impl PartialEq for Assignment { + fn eq(&self, other: &Assignment) -> bool { + self.value == other.value && self.from == other.from && self.to == other.to && + self.rc.index == other.rc.index + } +} + /// Constraint solver for register allocation around a single instruction. /// /// Start by programming in the instruction constraints. @@ -296,6 +304,11 @@ pub struct Solver { /// - Live-through variables are marked as available. /// regs_out: AllocatableSet, + + /// List of register moves scheduled to avoid conflicts. + /// + /// This is used as working space by the `schedule_moves()` function. + moves: Vec, } /// Interface for programming the constraints into the solver. @@ -308,6 +321,7 @@ impl Solver { inputs_done: false, regs_in: AllocatableSet::new(), regs_out: AllocatableSet::new(), + moves: Vec::new(), } } @@ -508,7 +522,7 @@ impl Solver { /// This is expected to succeed for most instructions since the constraint problem is almost /// always trivial. /// - /// Returns `true` is a solution was found. + /// Returns `Ok(regs)` if a solution was found. pub fn quick_solve(&mut self) -> Result { self.find_solution() } @@ -547,3 +561,252 @@ impl Solver { &self.vars } } + +/// Interface for working with parallel copies once a solution has been found. +impl Solver { + /// Collect all the register moves we need to execute. + fn collect_moves(&mut self) { + self.moves.clear(); + + // Collect moves from the chosen solution for all non-define variables. + for v in &self.vars { + if let Some(from) = v.from { + self.moves + .push(Assignment { + value: v.value, + from, + to: v.solution, + rc: v.constraint, + }); + } + } + + self.moves.extend(self.assignments.values().cloned()); + } + + /// Try to schedule a sequence of `regmove` instructions that will shuffle registers into + /// place. + /// + /// This may require the use of additional available registers, and it can fail if no + /// additional registers are available. + /// + /// TODO: Handle failure by generating a sequence of register swaps, or by temporarily spilling + /// a register. + /// + /// Returns the number of spills that had to be emitted. + pub fn schedule_moves(&mut self, regs: &AllocatableSet) -> usize { + self.collect_moves(); + + let mut avail = regs.clone(); + let mut i = 0; + while i < self.moves.len() { + // Find the first move that can be executed now. + if let Some(j) = self.moves[i..] + .iter() + .position(|m| avail.is_avail(m.rc, m.to)) { + // This move can be executed now. + self.moves.swap(i, i + j); + let m = &self.moves[i]; + avail.take(m.rc, m.to); + avail.free(m.rc, m.from); + i += 1; + continue; + } + + // When we get here, non of the `moves[i..]` can be executed. This means there are only + // cycles remaining. The cycles can be broken in a few ways: + // + // 1. Grab an available register and use it to break a cycle. + // 2. Move a value temporarily into a stack slot instead of a register. + // 3. Use swap instructions. + // + // TODO: So far we only implement 1. + + // Pick an assignment with the largest possible width. This is more likely to break up + // a cycle than an assignment with fewer register units. For example, it may be + // necessary to move two arm32 S-registers out of the way before a D-register can move + // into place. + // + // We use `min_by_key` and `!` instead of `max_by_key` because it preserves the + // existing order of moves with the same width. + let j = self.moves[i..] + .iter() + .enumerate() + .min_by_key(|&(_, m)| !m.rc.width) + .unwrap() + .0; + self.moves.swap(i, i + j); + + let m = self.moves[i].clone(); + if let Some(reg) = avail.iter(m.rc).next() { + // Alter the move so it is guaranteed to be picked up when we loop. It is important + // that this move is scheduled immediately, otherwise we would have multiple moves + // of the same value, and they would not be commutable. + self.moves[i].to = reg; + // Append a fixup move so we end up in the right place. This move will be scheduled + // later. That's ok because it is the single remaining move of `m.value` after the + // next iteration. + self.moves + .push(Assignment { + value: m.value, + rc: m.rc, + from: reg, + to: m.to, + }); + // TODO: What if allocating an extra register is not enough to break a cycle? This + // can happen when there are registers of different widths in a cycle. For ARM, we + // may have to move two S-registers out of the way before we can resolve a cycle + // involving a D-register. + } else { + panic!("Not enough registers in {} to schedule moves", m.rc); + } + } + + // Spilling not implemented yet. + 0 + } + + /// Borrow the scheduled set of register moves that was computed by `schedule_moves()`. + pub fn moves(&self) -> &[Assignment] { + &self.moves + } +} + +#[cfg(test)] +mod tests { + use entity_map::EntityRef; + use ir::Value; + use isa::{TargetIsa, RegClass, RegUnit}; + use regalloc::AllocatableSet; + use std::borrow::Borrow; + use super::{Solver, Assignment}; + + // Make an arm32 `TargetIsa`, if possible. + fn arm32() -> Option> { + use settings; + use isa; + + let shared_builder = settings::builder(); + let shared_flags = settings::Flags::new(&shared_builder); + + isa::lookup("arm32").map(|b| b.finish(shared_flags)) + } + + // Get a register class by name. + fn rc_by_name(isa: &TargetIsa, name: &str) -> RegClass { + isa.register_info() + .classes + .iter() + .find(|rc| rc.name == name) + .expect("Can't find named register class.") + } + + // Construct a move. + fn mov(value: Value, rc: RegClass, from: RegUnit, to: RegUnit) -> Assignment { + Assignment { + value, + rc, + from, + to, + } + } + + #[test] + fn simple_moves() { + let isa = arm32().expect("This test requires arm32 support"); + let isa = isa.borrow(); + let gpr = rc_by_name(isa, "GPR"); + let r0 = gpr.unit(0); + let r1 = gpr.unit(1); + let r2 = gpr.unit(2); + let mut regs = AllocatableSet::new(); + let mut solver = Solver::new(); + let v10 = Value::new(10); + let v11 = Value::new(11); + + // As simple as it gets: Value is in r1, we want r0. + regs.take(gpr, r1); + solver.reset(®s); + solver.reassign_in(v10, gpr, r1, r0); + solver.inputs_done(); + assert!(solver.quick_solve().is_ok()); + assert_eq!(solver.schedule_moves(®s), 0); + assert_eq!(solver.moves(), &[mov(v10, gpr, r1, r0)]); + + // A bit harder: r0, r1 need to go in r1, r2. + regs.take(gpr, r0); + solver.reset(®s); + solver.reassign_in(v10, gpr, r0, r1); + solver.reassign_in(v11, gpr, r1, r2); + solver.inputs_done(); + assert!(solver.quick_solve().is_ok()); + assert_eq!(solver.schedule_moves(®s), 0); + assert_eq!(solver.moves(), + &[mov(v11, gpr, r1, r2), mov(v10, gpr, r0, r1)]); + + // Swap r0 and r1 in three moves using r2 as a scratch. + solver.reset(®s); + solver.reassign_in(v10, gpr, r0, r1); + solver.reassign_in(v11, gpr, r1, r0); + solver.inputs_done(); + assert!(solver.quick_solve().is_ok()); + assert_eq!(solver.schedule_moves(®s), 0); + assert_eq!(solver.moves(), + &[mov(v10, gpr, r0, r2), + mov(v11, gpr, r1, r0), + mov(v10, gpr, r2, r1)]); + } + + #[test] + fn harder_move_cycles() { + let isa = arm32().expect("This test requires arm32 support"); + let isa = isa.borrow(); + let s = rc_by_name(isa, "S"); + let d = rc_by_name(isa, "D"); + let d0 = d.unit(0); + let d1 = d.unit(1); + let d2 = d.unit(2); + let s0 = s.unit(0); + let s1 = s.unit(1); + let s2 = s.unit(2); + let s3 = s.unit(3); + let mut regs = AllocatableSet::new(); + let mut solver = Solver::new(); + let v10 = Value::new(10); + let v11 = Value::new(11); + let v12 = Value::new(12); + + // Not a simple cycle: Swap d0 <-> (s2, s3) + regs.take(d, d0); + regs.take(d, d1); + solver.reset(®s); + solver.reassign_in(v10, d, d0, d1); + solver.reassign_in(v11, s, s2, s0); + solver.reassign_in(v12, s, s3, s1); + solver.inputs_done(); + assert!(solver.quick_solve().is_ok()); + assert_eq!(solver.schedule_moves(®s), 0); + assert_eq!(solver.moves(), + &[mov(v10, d, d0, d2), + mov(v11, s, s2, s0), + mov(v12, s, s3, s1), + mov(v10, d, d2, d1)]); + + // Same problem in the other direction: Swap (s0, s1) <-> d1. + // + // If we divert the moves in order, we will need to allocate *two* temporary S registers. A + // trivial algorithm might assume that allocating a single temp is enough. + solver.reset(®s); + solver.reassign_in(v11, s, s0, s2); + solver.reassign_in(v12, s, s1, s3); + solver.reassign_in(v10, d, d1, d0); + solver.inputs_done(); + assert!(solver.quick_solve().is_ok()); + assert_eq!(solver.schedule_moves(®s), 0); + assert_eq!(solver.moves(), + &[mov(v10, d, d1, d2), + mov(v12, s, s1, s3), + mov(v11, s, s0, s2), + mov(v10, d, d2, d0)]); + } +} From 663b50efccfe4e5032ae897e39d0e40ec60fa861 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 May 2017 10:11:28 -0700 Subject: [PATCH 758/968] Add fixed constraints for ABI arguments and return values. We can start adding some real test cases for the move resolver now. --- filetests/regalloc/basic.cton | 23 +++++++ lib/cretonne/src/regalloc/coloring.rs | 94 +++++++++++++++++++++------ 2 files changed, 98 insertions(+), 19 deletions(-) diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index 9db7b7563a..9046aeb711 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -3,6 +3,8 @@ test regalloc ; We can add more ISAs once they have defined encodings. isa riscv +; regex: RX=%x\d+ + function add(i32, i32) { ebb0(v1: i32, v2: i32): v3 = iadd v1, v2 @@ -14,6 +16,27 @@ ebb0(v1: i32, v2: i32): ; Function with a dead argument. function dead_arg(i32, i32) -> i32{ ebb0(v1: i32, v2: i32): +; not: regmove ; check: return $v1 return v1 } + +; Return a value from a different register. +function move1(i32, i32) -> i32 { +ebb0(v1: i32, v2: i32): +; not: regmove +; check: regmove $v2, %x11 -> %x10 +; nextln: return $v2 + return v2 +} + +; Swap two registers. +function swap(i32, i32) -> i32, i32 { +ebb0(v1: i32, v2: i32): +; not: regmove +; check: regmove $v2, %x11 -> $(tmp=$RX) +; nextln: regmove $v1, %x10 -> %x11 +; nextln: regmove $v2, $tmp -> %x10 +; nextln: return $v2, $v1 + return v2, v1 +} diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index b2f1863538..33013aa309 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -33,8 +33,8 @@ use entity_map::EntityMap; use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Signature, ArgumentLoc}; -use ir::InstBuilder; +use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph}; +use ir::{InstBuilder, Signature, ArgumentType, ArgumentLoc}; use isa::{TargetIsa, Encoding, EncInfo, OperandConstraint, ConstraintKind}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; use regalloc::affinity::Affinity; @@ -145,7 +145,8 @@ impl<'a> Context<'a> { &mut func.dfg, tracker, &mut regs, - &mut func.locations); + &mut func.locations, + &func.signature); tracker.drop_dead(inst); } @@ -307,7 +308,8 @@ impl<'a> Context<'a> { dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, - locations: &mut EntityMap) { + locations: &mut EntityMap, + func_signature: &Signature) { dbg!("Coloring [{}] {}", self.encinfo.display(encoding), dfg.display_inst(inst)); @@ -320,6 +322,12 @@ impl<'a> Context<'a> { // Program the solver with register constraints for the input side. self.solver.reset(regs); self.program_input_constraints(inst, constraints.ins, dfg, locations); + let call_sig = dfg.call_signature(inst); + if let Some(sig) = call_sig { + self.program_input_abi(inst, &dfg.signatures[sig].argument_types, dfg, locations); + } else if dfg[inst].opcode().is_return() { + self.program_input_abi(inst, &func_signature.return_types, dfg, locations); + } if self.solver.has_fixed_input_conflicts() { self.divert_fixed_input_conflicts(tracker.live(), locations); } @@ -342,12 +350,11 @@ impl<'a> Context<'a> { // detect conflicts between fixed outputs and tied operands where the input value hasn't // been converted to a solver variable. if constraints.fixed_outs { - self.program_fixed_output_constraints(inst, - constraints.outs, - defs, - throughs, - dfg, - locations); + self.program_fixed_outputs(constraints.outs, defs, throughs, locations); + } + if let Some(sig) = call_sig { + let abi = &dfg.signatures[sig].return_types; + self.program_output_abi(abi, defs, throughs, locations); } self.program_output_constraints(inst, constraints.outs, defs, dfg, locations); @@ -384,8 +391,8 @@ impl<'a> Context<'a> { fn program_input_constraints(&mut self, inst: Inst, constraints: &[OperandConstraint], - dfg: &mut DataFlowGraph, - locations: &mut EntityMap) { + dfg: &DataFlowGraph, + locations: &EntityMap) { for (op, &value) in constraints .iter() .zip(dfg.inst_args(inst)) @@ -412,6 +419,33 @@ impl<'a> Context<'a> { } } + /// Program the input-side ABI constraints for `inst` into the constraint solver. + /// + /// ABI constraints are the fixed register assignments used for calls and returns. + fn program_input_abi(&mut self, + inst: Inst, + abi_types: &[ArgumentType], + dfg: &DataFlowGraph, + locations: &EntityMap) { + for (abi, &value) in abi_types.iter().zip(dfg.inst_variable_args(inst)) { + if let ArgumentLoc::Reg(reg) = abi.location { + let cur_reg = self.divert.reg(value, locations); + if reg != cur_reg { + if let Affinity::Reg(rci) = + self.liveness + .get(value) + .expect("ABI register must have live range") + .affinity { + let rc = self.reginfo.rc(rci); + self.solver.reassign_in(value, rc, cur_reg, reg); + } else { + panic!("ABI argument {} should be in a register", value); + } + } + } + } + } + // Find existing live values that conflict with the fixed input register constraints programmed // into the constraint solver. Convert them to solver variables so they can be diverted. fn divert_fixed_input_conflicts(&mut self, @@ -431,13 +465,11 @@ impl<'a> Context<'a> { /// Program any fixed-register output constraints into the solver. This may also detect /// conflicts between live-through registers and fixed output registers. These live-through /// values need to be turned into solver variables so they can be reassigned. - fn program_fixed_output_constraints(&mut self, - _inst: Inst, - constraints: &[OperandConstraint], - defs: &[LiveValue], - throughs: &[LiveValue], - _dfg: &mut DataFlowGraph, - locations: &mut EntityMap) { + fn program_fixed_outputs(&mut self, + constraints: &[OperandConstraint], + defs: &[LiveValue], + throughs: &[LiveValue], + locations: &mut EntityMap) { for (op, lv) in constraints.iter().zip(defs) { if let ConstraintKind::FixedReg(reg) = op.kind { self.add_fixed_output(lv.value, op.regclass, reg, throughs, locations); @@ -445,6 +477,30 @@ impl<'a> Context<'a> { } } + /// Program the output-side ABI constraints for `inst` into the constraint solver. + /// + /// That means return values for a call instruction. + fn program_output_abi(&mut self, + abi_types: &[ArgumentType], + defs: &[LiveValue], + throughs: &[LiveValue], + locations: &mut EntityMap) { + // It's technically possible for a call instruction to have fixed results before the + // variable list of results, but we have no known instances of that. + // Just assume all results are variable return values. + assert_eq!(defs.len(), abi_types.len()); + for (abi, lv) in abi_types.iter().zip(defs) { + if let ArgumentLoc::Reg(reg) = abi.location { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + self.add_fixed_output(lv.value, rc, reg, throughs, locations); + } else { + panic!("ABI argument {} should be in a register", lv.value); + } + } + } + } + /// Add a single fixed output value to the solver. fn add_fixed_output(&mut self, value: Value, From 4158c4e09c18d76b5bd72d31366e681b7add68cd Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 11 May 2017 17:58:11 -0700 Subject: [PATCH 759/968] Implement reloc_names() for all targets. This gets rid of the last TargetIsa method with a default implementation. --- lib/cretonne/src/isa/arm32/binemit.rs | 2 ++ lib/cretonne/src/isa/arm32/mod.rs | 4 ++++ lib/cretonne/src/isa/arm64/binemit.rs | 2 ++ lib/cretonne/src/isa/arm64/mod.rs | 4 ++++ lib/cretonne/src/isa/mod.rs | 4 +--- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/isa/arm32/binemit.rs b/lib/cretonne/src/isa/arm32/binemit.rs index dcc76331a2..cf12bdbde2 100644 --- a/lib/cretonne/src/isa/arm32/binemit.rs +++ b/lib/cretonne/src/isa/arm32/binemit.rs @@ -4,3 +4,5 @@ use binemit::{CodeSink, bad_encoding}; use ir::{Function, Inst}; include!(concat!(env!("OUT_DIR"), "/binemit-arm32.rs")); + +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index e3128fdc0e..5657c7523b 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -94,4 +94,8 @@ impl TargetIsa for Isa { fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } + + fn reloc_names(&self) -> &'static [&'static str] { + &binemit::RELOC_NAMES + } } diff --git a/lib/cretonne/src/isa/arm64/binemit.rs b/lib/cretonne/src/isa/arm64/binemit.rs index 13885307d4..120115c0d8 100644 --- a/lib/cretonne/src/isa/arm64/binemit.rs +++ b/lib/cretonne/src/isa/arm64/binemit.rs @@ -4,3 +4,5 @@ use binemit::{CodeSink, bad_encoding}; use ir::{Function, Inst}; include!(concat!(env!("OUT_DIR"), "/binemit-arm64.rs")); + +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index f6fd70fb7a..fe81ea8c09 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -87,4 +87,8 @@ impl TargetIsa for Isa { fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { binemit::emit_inst(func, inst, sink) } + + fn reloc_names(&self) -> &'static [&'static str] { + &binemit::RELOC_NAMES + } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 34d3e21ef6..edc0034217 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -210,7 +210,5 @@ pub trait TargetIsa { /// /// This array can be indexed by the contents of `binemit::Reloc` objects passed to a /// `CodeSink`. - fn reloc_names(&self) -> &'static [&'static str] { - unimplemented!() - } + fn reloc_names(&self) -> &'static [&'static str]; } From 3aaa8b2f91fedfe919c81a7a6bc029f42231a8f6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 May 2017 15:31:08 -0700 Subject: [PATCH 760/968] Return the first applicable encoding from general_encoding(). We'll arrange encoding lists such that the first suitable encoding is the best choice for the legalizer. This is the most intuitive way of generating the encodings. After register allocation, we may choose a different encoding, but that will require looking at the whole list. --- lib/cretonne/src/isa/enc_tables.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index cfb0066b8c..d7dde71eed 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -114,11 +114,11 @@ const CODE_ALWAYS: EncListEntry = PRED_MASK; /// The encoding list terminator. const CODE_FAIL: EncListEntry = 0xffff; -/// Find the most general encoding of `inst`. +/// Find the first applicable general encoding of `inst`. /// /// Given an encoding list offset as returned by `lookup_enclist` above, search the encoding list -/// for the most general encoding that applies to `inst`. The encoding lists are laid out such that -/// this is the last valid entry in the list. +/// for the most first encoding that applies to `inst`. The encoding lists are laid out such that +/// this is the first valid entry in the list. /// /// This function takes two closures that are used to evaluate predicates: /// - `instp` is passed an instruction predicate number to be evaluated on the current instruction. @@ -133,14 +133,13 @@ pub fn general_encoding(offset: usize, where InstP: Fn(EncListEntry) -> bool, IsaP: Fn(EncListEntry) -> bool { - let mut found = None; let mut pos = offset; while enclist[pos] != CODE_FAIL { let pred = enclist[pos]; if pred <= CODE_ALWAYS { // This is an instruction predicate followed by recipe and encbits entries. if pred == CODE_ALWAYS || instp(pred) { - found = Some(Encoding::new(enclist[pos + 1], enclist[pos + 2])) + return Some(Encoding::new(enclist[pos + 1], enclist[pos + 2])); } pos += 3; } else { @@ -152,5 +151,6 @@ pub fn general_encoding(offset: usize, } } } - found + + None } From c998df6274ea7b94037719e14c3f7c92e1dcc1bb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 May 2017 10:35:18 -0700 Subject: [PATCH 761/968] Add subtract and logical instruction encodings to Intel-32. Also add versions with 8-bit and 32-bit immediate operands. --- filetests/isa/intel/binary32.cton | 70 ++++++++++++++++++++++-- lib/cretonne/meta/isa/intel/encodings.py | 16 +++++- lib/cretonne/meta/isa/intel/recipes.py | 14 ++++- lib/cretonne/src/isa/intel/binemit.rs | 24 ++++++++ lib/cretonne/src/isa/intel/enc_tables.rs | 3 +- 5 files changed, 117 insertions(+), 10 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index d609a0c030..4509507005 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -18,21 +18,79 @@ ebb0: [-,%rcx] v10 = iadd v1, v2 ; bin: 01 f1 ; asm: addl %ecx, %esi [-,%rsi] v11 = iadd v2, v1 ; bin: 01 ce + ; asm: subl %esi, %ecx + [-,%rcx] v12 = isub v1, v2 ; bin: 29 f1 + ; asm: subl %ecx, %esi + [-,%rsi] v13 = isub v2, v1 ; bin: 29 ce + + ; asm: andl %esi, %ecx + [-,%rcx] v14 = band v1, v2 ; bin: 21 f1 + ; asm: andl %ecx, %esi + [-,%rsi] v15 = band v2, v1 ; bin: 21 ce + ; asm: orl %esi, %ecx + [-,%rcx] v16 = bor v1, v2 ; bin: 09 f1 + ; asm: orl %ecx, %esi + [-,%rsi] v17 = bor v2, v1 ; bin: 09 ce + ; asm: xorl %esi, %ecx + [-,%rcx] v18 = bxor v1, v2 ; bin: 31 f1 + ; asm: xorl %ecx, %esi + [-,%rsi] v19 = bxor v2, v1 ; bin: 31 ce ; Dynamic shifts take the shift amount in %rcx. ; asm: shll %cl, %esi - [-,%rsi] v12 = ishl v2, v1 ; bin: d3 e6 + [-,%rsi] v20 = ishl v2, v1 ; bin: d3 e6 ; asm: shll %cl, %ecx - [-,%rcx] v13 = ishl v1, v1 ; bin: d3 e1 + [-,%rcx] v21 = ishl v1, v1 ; bin: d3 e1 ; asm: shrl %cl, %esi - [-,%rsi] v14 = ushr v2, v1 ; bin: d3 ee + [-,%rsi] v22 = ushr v2, v1 ; bin: d3 ee ; asm: shrl %cl, %ecx - [-,%rcx] v15 = ushr v1, v1 ; bin: d3 e9 + [-,%rcx] v23 = ushr v1, v1 ; bin: d3 e9 ; asm: sarl %cl, %esi - [-,%rsi] v16 = sshr v2, v1 ; bin: d3 fe + [-,%rsi] v24 = sshr v2, v1 ; bin: d3 fe ; asm: sarl %cl, %ecx - [-,%rcx] v17 = sshr v1, v1 ; bin: d3 f9 + [-,%rcx] v25 = sshr v1, v1 ; bin: d3 f9 + + ; Integer Register - Immediate 8-bit operations. + ; The 8-bit immediate is sign-extended. + + ; asm: addl $-128, %ecx + [-,%rcx] v30 = iadd_imm v1, -128 ; bin: 83 c1 80 + ; asm: addl $10, %esi + [-,%rsi] v31 = iadd_imm v2, 10 ; bin: 83 c6 0a + + ; asm: andl $-128, %ecx + [-,%rcx] v32 = band_imm v1, -128 ; bin: 83 e1 80 + ; asm: andl $10, %esi + [-,%rsi] v33 = band_imm v2, 10 ; bin: 83 e6 0a + ; asm: orl $-128, %ecx + [-,%rcx] v34 = bor_imm v1, -128 ; bin: 83 c9 80 + ; asm: orl $10, %esi + [-,%rsi] v35 = bor_imm v2, 10 ; bin: 83 ce 0a + ; asm: xorl $-128, %ecx + [-,%rcx] v36 = bxor_imm v1, -128 ; bin: 83 f1 80 + ; asm: xorl $10, %esi + [-,%rsi] v37 = bxor_imm v2, 10 ; bin: 83 f6 0a + + ; Integer Register - Immediate 32-bit operations. + + ; asm: addl $-128000, %ecx + [-,%rcx] v40 = iadd_imm v1, -128000 ; bin: 81 c1 fffe0c00 + ; asm: addl $1000000, %esi + [-,%rsi] v41 = iadd_imm v2, 1000000 ; bin: 81 c6 000f4240 + + ; asm: andl $-128000, %ecx + [-,%rcx] v42 = band_imm v1, -128000 ; bin: 81 e1 fffe0c00 + ; asm: andl $1000000, %esi + [-,%rsi] v43 = band_imm v2, 1000000 ; bin: 81 e6 000f4240 + ; asm: orl $-128000, %ecx + [-,%rcx] v44 = bor_imm v1, -128000 ; bin: 81 c9 fffe0c00 + ; asm: orl $1000000, %esi + [-,%rsi] v45 = bor_imm v2, 1000000 ; bin: 81 ce 000f4240 + ; asm: xorl $-128000, %ecx + [-,%rcx] v46 = bxor_imm v1, -128000 ; bin: 81 f1 fffe0c00 + ; asm: xorl $1000000, %esi + [-,%rsi] v47 = bxor_imm v2, 1000000 ; bin: 81 f6 000f4240 return } diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 3896532c85..967da0d857 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -4,10 +4,24 @@ Intel Encodings. from __future__ import absolute_import from base import instructions as base from .defs import I32 -from .recipes import Op1rr, Op1rc +from .recipes import Op1rr, Op1rc, Op1rib, Op1rid from .recipes import OP I32.enc(base.iadd.i32, Op1rr, OP(0x01)) +I32.enc(base.isub.i32, Op1rr, OP(0x29)) + +I32.enc(base.band.i32, Op1rr, OP(0x21)) +I32.enc(base.bor.i32, Op1rr, OP(0x09)) +I32.enc(base.bxor.i32, Op1rr, OP(0x31)) + +# Immediate instructions with sign-extended 8-bit and 32-bit immediate. +for inst, r in [ + (base.iadd_imm.i32, 0), + (base.band_imm.i32, 4), + (base.bor_imm.i32, 1), + (base.bxor_imm.i32, 6)]: + I32.enc(inst, Op1rib, OP(0x83, rrr=r)) + I32.enc(inst, Op1rid, OP(0x81, rrr=r)) # 32-bit shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 0ffd7e5c50..d4891fba27 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -3,8 +3,8 @@ Intel Encoding recipes. """ from __future__ import absolute_import from cdsl.isa import EncRecipe -# from cdsl.predicates import IsSignedInt -from base.formats import Binary +from cdsl.predicates import IsSignedInt +from base.formats import Binary, BinaryImm from .registers import GPR # Opcode representation. @@ -68,3 +68,13 @@ Op1rr = EncRecipe('Op1rr', Binary, size=2, ins=(GPR, GPR), outs=0) # XX /n with one arg in %rcx, for shifts. Op1rc = EncRecipe('Op1rc', Binary, size=2, ins=(GPR, GPR.rcx), outs=0) + +# XX /n ib with 8-bit immediate sign-extended. +Op1rib = EncRecipe( + 'Op1rib', BinaryImm, size=3, ins=GPR, outs=0, + instp=IsSignedInt(BinaryImm.imm, 8)) + +# XX /n id with 32-bit immediate sign-extended. +Op1rid = EncRecipe( + 'Op1rid', BinaryImm, size=6, ins=GPR, outs=0, + instp=IsSignedInt(BinaryImm.imm, 32)) diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 0644d583d3..75f8241e9c 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -53,3 +53,27 @@ fn recipe_op1rc(func: &Function, inst: Inst, sink: &mut C panic!("Expected Binary format: {:?}", func.dfg[inst]); } } + +fn recipe_op1rib(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { + let bits = func.encodings[inst].bits(); + put_op1(bits, sink); + modrm_r_bits(func.locations[arg].unwrap_reg(), bits, sink); + let imm: i64 = imm.into(); + sink.put1(imm as u8); + } else { + panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1rid(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { + let bits = func.encodings[inst].bits(); + put_op1(bits, sink); + modrm_r_bits(func.locations[arg].unwrap_reg(), bits, sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); + } else { + panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); + } +} diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 1ec93f00a9..e72da36b1e 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,11 +1,12 @@ //! Encoding tables for Intel ISAs. -use ir::{Opcode, InstructionData}; use ir::types; +use ir::{Opcode, InstructionData}; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::encoding::RecipeSizing; +use predicates; use super::registers::*; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); From 9629867d0cb45c41ceb52b53badd51631c9eb48f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 12 May 2017 14:26:44 -0700 Subject: [PATCH 762/968] Encodings for load/store instructions. We don't support the full set of Intel addressing modes yet. So far we have: - Register indirect, no displacement. - Register indirect, 8-bit signed displacement. - Register indirect, 32-bit signed displacement. The SIB addressing modes will need new Cretonne instruction formats to represent. --- filetests/isa/intel/binary32.cton | 105 +++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 57 ++++-- lib/cretonne/meta/isa/intel/recipes.py | 111 ++++++++++-- lib/cretonne/src/isa/intel/binemit.rs | 220 +++++++++++++++++++++++ lib/cretonne/src/predicates.rs | 4 +- 5 files changed, 470 insertions(+), 27 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 4509507005..5cac489f77 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -92,5 +92,110 @@ ebb0: ; asm: xorl $1000000, %esi [-,%rsi] v47 = bxor_imm v2, 1000000 ; bin: 81 f6 000f4240 + ; Load/Store instructions. + + ; Register indirect addressing with no displacement. + + ; asm: movl %ecx, (%esi) + store v1, v2 ; bin: 89 0e + ; asm: movl %esi, (%ecx) + store v2, v1 ; bin: 89 31 + ; asm: movw %cx, (%esi) + istore16 v1, v2 ; bin: 66 89 0e + ; asm: movw %si, (%ecx) + istore16 v2, v1 ; bin: 66 89 31 + ; asm: movb %cl, (%esi) + istore8 v1, v2 ; bin: 88 0e + ; Can't store %sil in 32-bit mode (needs REX prefix). + + ; asm: movl (%ecx), %edi + [-,%rdi] v100 = load.i32 v1 ; bin: 8b 39 + ; asm: movl (%esi), %edx + [-,%rdx] v101 = load.i32 v2 ; bin: 8b 16 + ; asm: movzwl (%ecx), %edi + [-,%rdi] v102 = uload16.i32 v1 ; bin: 0f b7 39 + ; asm: movzwl (%esi), %edx + [-,%rdx] v103 = uload16.i32 v2 ; bin: 0f b7 16 + ; asm: movswl (%ecx), %edi + [-,%rdi] v104 = sload16.i32 v1 ; bin: 0f bf 39 + ; asm: movswl (%esi), %edx + [-,%rdx] v105 = sload16.i32 v2 ; bin: 0f bf 16 + ; asm: movzbl (%ecx), %edi + [-,%rdi] v106 = uload8.i32 v1 ; bin: 0f b6 39 + ; asm: movzbl (%esi), %edx + [-,%rdx] v107 = uload8.i32 v2 ; bin: 0f b6 16 + ; asm: movsbl (%ecx), %edi + [-,%rdi] v108 = sload8.i32 v1 ; bin: 0f be 39 + ; asm: movsbl (%esi), %edx + [-,%rdx] v109 = sload8.i32 v2 ; bin: 0f be 16 + + ; Register-indirect with 8-bit signed displacement. + + ; asm: movl %ecx, 100(%esi) + store v1, v2+100 ; bin: 89 4e 64 + ; asm: movl %esi, -100(%ecx) + store v2, v1-100 ; bin: 89 71 9c + ; asm: movw %cx, 100(%esi) + istore16 v1, v2+100 ; bin: 66 89 4e 64 + ; asm: movw %si, -100(%ecx) + istore16 v2, v1-100 ; bin: 66 89 71 9c + ; asm: movb %cl, 100(%esi) + istore8 v1, v2+100 ; bin: 88 4e 64 + + ; asm: movl 50(%ecx), %edi + [-,%rdi] v110 = load.i32 v1+50 ; bin: 8b 79 32 + ; asm: movl -50(%esi), %edx + [-,%rdx] v111 = load.i32 v2-50 ; bin: 8b 56 ce + ; asm: movzwl 50(%ecx), %edi + [-,%rdi] v112 = uload16.i32 v1+50 ; bin: 0f b7 79 32 + ; asm: movzwl -50(%esi), %edx + [-,%rdx] v113 = uload16.i32 v2-50 ; bin: 0f b7 56 ce + ; asm: movswl 50(%ecx), %edi + [-,%rdi] v114 = sload16.i32 v1+50 ; bin: 0f bf 79 32 + ; asm: movswl -50(%esi), %edx + [-,%rdx] v115 = sload16.i32 v2-50 ; bin: 0f bf 56 ce + ; asm: movzbl 50(%ecx), %edi + [-,%rdi] v116 = uload8.i32 v1+50 ; bin: 0f b6 79 32 + ; asm: movzbl -50(%esi), %edx + [-,%rdx] v117 = uload8.i32 v2-50 ; bin: 0f b6 56 ce + ; asm: movsbl 50(%ecx), %edi + [-,%rdi] v118 = sload8.i32 v1+50 ; bin: 0f be 79 32 + ; asm: movsbl -50(%esi), %edx + [-,%rdx] v119 = sload8.i32 v2-50 ; bin: 0f be 56 ce + + ; Register-indirect with 32-bit signed displacement. + + ; asm: movl %ecx, 10000(%esi) + store v1, v2+10000 ; bin: 89 8e 00002710 + ; asm: movl %esi, -10000(%ecx) + store v2, v1-10000 ; bin: 89 b1 ffffd8f0 + ; asm: movw %cx, 10000(%esi) + istore16 v1, v2+10000 ; bin: 66 89 8e 00002710 + ; asm: movw %si, -10000(%ecx) + istore16 v2, v1-10000 ; bin: 66 89 b1 ffffd8f0 + ; asm: movb %cl, 10000(%esi) + istore8 v1, v2+10000 ; bin: 88 8e 00002710 + + ; asm: movl 50000(%ecx), %edi + [-,%rdi] v120 = load.i32 v1+50000 ; bin: 8b b9 0000c350 + ; asm: movl -50000(%esi), %edx + [-,%rdx] v121 = load.i32 v2-50000 ; bin: 8b 96 ffff3cb0 + ; asm: movzwl 50000(%ecx), %edi + [-,%rdi] v122 = uload16.i32 v1+50000 ; bin: 0f b7 b9 0000c350 + ; asm: movzwl -50000(%esi), %edx + [-,%rdx] v123 = uload16.i32 v2-50000 ; bin: 0f b7 96 ffff3cb0 + ; asm: movswl 50000(%ecx), %edi + [-,%rdi] v124 = sload16.i32 v1+50000 ; bin: 0f bf b9 0000c350 + ; asm: movswl -50000(%esi), %edx + [-,%rdx] v125 = sload16.i32 v2-50000 ; bin: 0f bf 96 ffff3cb0 + ; asm: movzbl 50000(%ecx), %edi + [-,%rdi] v126 = uload8.i32 v1+50000 ; bin: 0f b6 b9 0000c350 + ; asm: movzbl -50000(%esi), %edx + [-,%rdx] v127 = uload8.i32 v2-50000 ; bin: 0f b6 96 ffff3cb0 + ; asm: movsbl 50000(%ecx), %edi + [-,%rdi] v128 = sload8.i32 v1+50000 ; bin: 0f be b9 0000c350 + ; asm: movsbl -50000(%esi), %edx + [-,%rdx] v129 = sload8.i32 v2-50000 ; bin: 0f be 96 ffff3cb0 + return } diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 967da0d857..8d6d466aa7 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -4,15 +4,15 @@ Intel Encodings. from __future__ import absolute_import from base import instructions as base from .defs import I32 -from .recipes import Op1rr, Op1rc, Op1rib, Op1rid -from .recipes import OP +from . import recipes as rcp +from .recipes import OP, OP0F, MP66 -I32.enc(base.iadd.i32, Op1rr, OP(0x01)) -I32.enc(base.isub.i32, Op1rr, OP(0x29)) +I32.enc(base.iadd.i32, rcp.Op1rr, OP(0x01)) +I32.enc(base.isub.i32, rcp.Op1rr, OP(0x29)) -I32.enc(base.band.i32, Op1rr, OP(0x21)) -I32.enc(base.bor.i32, Op1rr, OP(0x09)) -I32.enc(base.bxor.i32, Op1rr, OP(0x31)) +I32.enc(base.band.i32, rcp.Op1rr, OP(0x21)) +I32.enc(base.bor.i32, rcp.Op1rr, OP(0x09)) +I32.enc(base.bxor.i32, rcp.Op1rr, OP(0x31)) # Immediate instructions with sign-extended 8-bit and 32-bit immediate. for inst, r in [ @@ -20,12 +20,45 @@ for inst, r in [ (base.band_imm.i32, 4), (base.bor_imm.i32, 1), (base.bxor_imm.i32, 6)]: - I32.enc(inst, Op1rib, OP(0x83, rrr=r)) - I32.enc(inst, Op1rid, OP(0x81, rrr=r)) + I32.enc(inst, rcp.Op1rib, OP(0x83, rrr=r)) + I32.enc(inst, rcp.Op1rid, OP(0x81, rrr=r)) # 32-bit shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit # and 16-bit shifts would need explicit masking. -I32.enc(base.ishl.i32.i32, Op1rc, OP(0xd3, rrr=4)) -I32.enc(base.ushr.i32.i32, Op1rc, OP(0xd3, rrr=5)) -I32.enc(base.sshr.i32.i32, Op1rc, OP(0xd3, rrr=7)) +I32.enc(base.ishl.i32.i32, rcp.Op1rc, OP(0xd3, rrr=4)) +I32.enc(base.ushr.i32.i32, rcp.Op1rc, OP(0xd3, rrr=5)) +I32.enc(base.sshr.i32.i32, rcp.Op1rc, OP(0xd3, rrr=7)) + +# Loads and stores. +I32.enc(base.store.i32.i32, rcp.Op1st, OP(0x89)) +I32.enc(base.store.i32.i32, rcp.Op1stDisp8, OP(0x89)) +I32.enc(base.store.i32.i32, rcp.Op1stDisp32, OP(0x89)) + +I32.enc(base.istore16.i32.i32, rcp.Mp1st, MP66(0x89)) +I32.enc(base.istore16.i32.i32, rcp.Mp1stDisp8, MP66(0x89)) +I32.enc(base.istore16.i32.i32, rcp.Mp1stDisp32, MP66(0x89)) + +I32.enc(base.istore8.i32.i32, rcp.Op1st_abcd, OP(0x88)) +I32.enc(base.istore8.i32.i32, rcp.Op1stDisp8_abcd, OP(0x88)) +I32.enc(base.istore8.i32.i32, rcp.Op1stDisp32_abcd, OP(0x88)) + +I32.enc(base.load.i32.i32, rcp.Op1ld, OP(0x8b)) +I32.enc(base.load.i32.i32, rcp.Op1ldDisp8, OP(0x8b)) +I32.enc(base.load.i32.i32, rcp.Op1ldDisp32, OP(0x8b)) + +I32.enc(base.uload16.i32.i32, rcp.Op2ld, OP0F(0xb7)) +I32.enc(base.uload16.i32.i32, rcp.Op2ldDisp8, OP0F(0xb7)) +I32.enc(base.uload16.i32.i32, rcp.Op2ldDisp32, OP0F(0xb7)) + +I32.enc(base.sload16.i32.i32, rcp.Op2ld, OP0F(0xbf)) +I32.enc(base.sload16.i32.i32, rcp.Op2ldDisp8, OP0F(0xbf)) +I32.enc(base.sload16.i32.i32, rcp.Op2ldDisp32, OP0F(0xbf)) + +I32.enc(base.uload8.i32.i32, rcp.Op2ld, OP0F(0xb6)) +I32.enc(base.uload8.i32.i32, rcp.Op2ldDisp8, OP0F(0xb6)) +I32.enc(base.uload8.i32.i32, rcp.Op2ldDisp32, OP0F(0xb6)) + +I32.enc(base.sload8.i32.i32, rcp.Op2ld, OP0F(0xbe)) +I32.enc(base.sload8.i32.i32, rcp.Op2ldDisp8, OP0F(0xbe)) +I32.enc(base.sload8.i32.i32, rcp.Op2ldDisp32, OP0F(0xbe)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index d4891fba27..6c72fe4690 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -3,9 +3,9 @@ Intel Encoding recipes. """ from __future__ import absolute_import from cdsl.isa import EncRecipe -from cdsl.predicates import IsSignedInt -from base.formats import Binary, BinaryImm -from .registers import GPR +from cdsl.predicates import IsSignedInt, IsEqual +from base.formats import Binary, BinaryImm, Store, Load +from .registers import GPR, ABCD # Opcode representation. # @@ -15,21 +15,21 @@ from .registers import GPR # name prefix: # # Op1* OP(op) -# 0F Op2* OP(op) +# 0F Op2* OP0F(op) # 0F 38 Op3* OP38(op) # 0F 3A Op3* OP3A(op) # 66 Mp1* MP66(op) -# 66 0F Mp2* MP66(op) -# 66 0F 38 Mp3* MP6638(op) -# 66 0F 3A Mp3* MP663A(op) +# 66 0F Mp2* MP660F(op) +# 66 0F 38 Mp3* MP660F38(op) +# 66 0F 3A Mp3* MP660F3A(op) # F2 Mp1* MPF2(op) -# F2 0F Mp2* MPF2(op) -# F2 0F 38 Mp3* MPF238(op) -# F2 0F 3A Mp3* MPF23A(op) +# F2 0F Mp2* MPF20F(op) +# F2 0F 38 Mp3* MPF20F38(op) +# F2 0F 3A Mp3* MPF20F3A(op) # F3 Mp1* MPF3(op) -# F3 0F Mp2* MPF3(op) -# F3 0F 38 Mp3* MPF338(op) -# F3 0F 3A Mp3* MPF33A(op) +# F3 0F Mp2* MP0FF3(op) +# F3 0F 38 Mp3* MPF30F38(op) +# F3 0F 3A Mp3* MPF30F3A(op) # # VEX/XOP and EVEX prefixes are not yet supported. # @@ -63,6 +63,16 @@ def OP(op, pp=0, mm=0, rrr=0, w=0): return op | (pp << 8) | (mm << 10) | (rrr << 12) | (w << 15) +def OP0F(op, rrr=0, w=0): + # type: (int, int, int) -> int + return OP(op, pp=0, mm=1, rrr=rrr, w=w) + + +def MP66(op, rrr=0, w=0): + # type: (int, int, int) -> int + return OP(op, pp=1, mm=0, rrr=rrr, w=w) + + # XX /r Op1rr = EncRecipe('Op1rr', Binary, size=2, ins=(GPR, GPR), outs=0) @@ -78,3 +88,78 @@ Op1rib = EncRecipe( Op1rid = EncRecipe( 'Op1rid', BinaryImm, size=6, ins=GPR, outs=0, instp=IsSignedInt(BinaryImm.imm, 32)) + +# +# Store recipes. +# + +# XX /r register-indirect store with no offset. +Op1st = EncRecipe( + 'Op1st', Store, size=2, ins=(GPR, GPR), outs=(), + instp=IsEqual(Store.offset, 0)) + +# XX /r register-indirect store with no offset. +# Only ABCD allowed for stored value. This is for byte stores. +Op1st_abcd = EncRecipe( + 'Op1st_abcd', Store, size=2, ins=(ABCD, GPR), outs=(), + instp=IsEqual(Store.offset, 0)) + +# XX /r register-indirect store with 8-bit offset. +Op1stDisp8 = EncRecipe( + 'Op1stDisp8', Store, size=3, ins=(GPR, GPR), outs=(), + instp=IsSignedInt(Store.offset, 8)) +Op1stDisp8_abcd = EncRecipe( + 'Op1stDisp8_abcd', Store, size=3, ins=(ABCD, GPR), outs=(), + instp=IsSignedInt(Store.offset, 8)) + +# XX /r register-indirect store with 32-bit offset. +Op1stDisp32 = EncRecipe('Op1stDisp32', Store, size=6, ins=(GPR, GPR), outs=()) +Op1stDisp32_abcd = EncRecipe( + 'Op1stDisp32_abcd', Store, size=6, ins=(ABCD, GPR), outs=()) + +# PP WW /r register-indirect store with no offset. +Mp1st = EncRecipe( + 'Mp1st', Store, size=3, ins=(GPR, GPR), outs=(), + instp=IsEqual(Store.offset, 0)) + +# PP XX /r register-indirect store with 8-bit offset. +Mp1stDisp8 = EncRecipe( + 'Mp1stDisp8', Store, size=4, ins=(GPR, GPR), outs=(), + instp=IsSignedInt(Store.offset, 8)) + +# PP XX /r register-indirect store with 32-bit offset. +Mp1stDisp32 = EncRecipe('Mp1stDisp32', Store, size=7, ins=(GPR, GPR), outs=()) + +# +# Load recipes +# + +# XX /r load with no offset. +Op1ld = EncRecipe( + 'Op1ld', Load, size=2, ins=(GPR), outs=(GPR), + instp=IsEqual(Load.offset, 0)) + +# XX /r load with 8-bit offset. +Op1ldDisp8 = EncRecipe( + 'Op1ldDisp8', Load, size=3, ins=(GPR), outs=(GPR), + instp=IsSignedInt(Load.offset, 8)) + +# XX /r load with 32-bit offset. +Op1ldDisp32 = EncRecipe( + 'Op1ldDisp32', Load, size=6, ins=(GPR), outs=(GPR), + instp=IsSignedInt(Load.offset, 32)) + +# 0F XX /r load with no offset. +Op2ld = EncRecipe( + 'Op2ld', Load, size=3, ins=(GPR), outs=(GPR), + instp=IsEqual(Load.offset, 0)) + +# XX /r load with 8-bit offset. +Op2ldDisp8 = EncRecipe( + 'Op2ldDisp8', Load, size=4, ins=(GPR), outs=(GPR), + instp=IsSignedInt(Load.offset, 8)) + +# XX /r load with 32-bit offset. +Op2ldDisp32 = EncRecipe( + 'Op2ldDisp32', Load, size=7, ins=(GPR), outs=(GPR), + instp=IsSignedInt(Load.offset, 32)) diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 75f8241e9c..834a9848c3 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -8,11 +8,30 @@ include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; +// Emit single-byte opcode. fn put_op1(bits: u16, sink: &mut CS) { debug_assert!(bits & 0x0f00 == 0, "Invalid encoding bits for Op1*"); sink.put1(bits as u8); } +// Emit two-byte opcode: 0F XX +fn put_op2(bits: u16, sink: &mut CS) { + debug_assert!(bits & 0x0f00 == 0x0400, "Invalid encoding bits for Op2*"); + sink.put1(0x0f); + sink.put1(bits as u8); +} + +// Mandatory prefix bytes for Mp* opcodes. +const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2]; + +// Emit single-byte opcode with mandatory prefix. +fn put_mp1(bits: u16, sink: &mut CS) { + debug_assert!(bits & 0x0c00 == 0, "Invalid encoding bits for Mp1*"); + let pp = (bits >> 8) & 3; + sink.put1(PREFIX[(pp - 1) as usize]); + sink.put1(bits as u8); +} + /// Emit a ModR/M byte for reg-reg operands. fn modrm_rr(rm: RegUnit, reg: RegUnit, sink: &mut CS) { let reg = reg as u8 & 7; @@ -33,6 +52,42 @@ fn modrm_r_bits(rm: RegUnit, bits: u16, sink: &mut CS) { sink.put1(b); } +/// Emit a mode 00 ModR/M byte. This is a register-indirect addressing mode with no offset. +/// Registers %rsp and %rbp are invalid for `rm`, %rsp indicates a SIB byte, and %rbp indicates an +/// absolute immediate 32-bit address. +fn modrm_rm(rm: RegUnit, reg: RegUnit, sink: &mut CS) { + let reg = reg as u8 & 7; + let rm = rm as u8 & 7; + let mut b = 0b00000000; + b |= reg << 3; + b |= rm; + sink.put1(b); +} + +/// Emit a mode 01 ModR/M byte. This is a register-indirect addressing mode with 8-bit +/// displacement. +/// Register %rsp is invalid for `rm`. It indicates the presence of a SIB byte. +fn modrm_disp8(rm: RegUnit, reg: RegUnit, sink: &mut CS) { + let reg = reg as u8 & 7; + let rm = rm as u8 & 7; + let mut b = 0b01000000; + b |= reg << 3; + b |= rm; + sink.put1(b); +} + +/// Emit a mode 10 ModR/M byte. This is a register-indirect addressing mode with 32-bit +/// displacement. +/// Register %rsp is invalid for `rm`. It indicates the presence of a SIB byte. +fn modrm_disp32(rm: RegUnit, reg: RegUnit, sink: &mut CS) { + let reg = reg as u8 & 7; + let rm = rm as u8 & 7; + let mut b = 0b10000000; + b |= reg << 3; + b |= rm; + sink.put1(b); +} + fn recipe_op1rr(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::Binary { args, .. } = func.dfg[inst] { put_op1(func.encodings[inst].bits(), sink); @@ -77,3 +132,168 @@ fn recipe_op1rid(func: &Function, inst: Inst, sink: &mut panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); } } + +// Store recipes. + +fn recipe_op1st(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Store { args, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_rm(func.locations[args[1]].unwrap_reg(), + func.locations[args[0]].unwrap_reg(), + sink); + } else { + panic!("Expected Store format: {:?}", func.dfg[inst]); + } +} + +// This is just a tighter register class constraint. +fn recipe_op1st_abcd(func: &Function, inst: Inst, sink: &mut CS) { + recipe_op1st(func, inst, sink) +} + +fn recipe_mp1st(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Store { args, .. } = func.dfg[inst] { + put_mp1(func.encodings[inst].bits(), sink); + modrm_rm(func.locations[args[1]].unwrap_reg(), + func.locations[args[0]].unwrap_reg(), + sink); + } else { + panic!("Expected Store format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1stdisp8(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_disp8(func.locations[args[1]].unwrap_reg(), + func.locations[args[0]].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + } else { + panic!("Expected Store format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1stdisp8_abcd(func: &Function, inst: Inst, sink: &mut CS) { + recipe_op1stdisp8(func, inst, sink) +} + +fn recipe_mp1stdisp8(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { + put_mp1(func.encodings[inst].bits(), sink); + modrm_disp8(func.locations[args[1]].unwrap_reg(), + func.locations[args[0]].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + } else { + panic!("Expected Store format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1stdisp32(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_disp32(func.locations[args[1]].unwrap_reg(), + func.locations[args[0]].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + } else { + panic!("Expected Store format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1stdisp32_abcd(func: &Function, inst: Inst, sink: &mut CS) { + recipe_op1stdisp32(func, inst, sink) +} + +fn recipe_mp1stdisp32(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { + put_mp1(func.encodings[inst].bits(), sink); + modrm_disp32(func.locations[args[1]].unwrap_reg(), + func.locations[args[0]].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + } else { + panic!("Expected Store format: {:?}", func.dfg[inst]); + } +} + +// Load recipes + +fn recipe_op1ld(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Load { arg, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_rm(func.locations[arg].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected Load format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1lddisp8(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_disp8(func.locations[arg].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + } else { + panic!("Expected Load format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1lddisp32(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + modrm_disp32(func.locations[arg].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + } else { + panic!("Expected Load format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op2ld(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Load { arg, .. } = func.dfg[inst] { + put_op2(func.encodings[inst].bits(), sink); + modrm_rm(func.locations[arg].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected Load format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op2lddisp8(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { + put_op2(func.encodings[inst].bits(), sink); + modrm_disp8(func.locations[arg].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + } else { + panic!("Expected Load format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op2lddisp32(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { + put_op2(func.encodings[inst].bits(), sink); + modrm_disp32(func.locations[arg].unwrap_reg(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + } else { + panic!("Expected Load format: {:?}", func.dfg[inst]); + } +} diff --git a/lib/cretonne/src/predicates.rs b/lib/cretonne/src/predicates.rs index 770857d100..6ce3e0a799 100644 --- a/lib/cretonne/src/predicates.rs +++ b/lib/cretonne/src/predicates.rs @@ -11,8 +11,8 @@ /// Check that `x` is the same as `y`. #[allow(dead_code)] -pub fn is_equal(x: T, y: T) -> bool { - x == y +pub fn is_equal + Copy>(x: T, y: O) -> bool { + x == y.into() } /// Check that `x` can be represented as a `wd`-bit signed integer with `sc` low zero bits. From 0694384728afedd37ea7dfe2494af37771b99b0f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Sun, 14 May 2017 11:53:44 -0700 Subject: [PATCH 763/968] Generate Intel encoding recipes on demand. Cretonne's encoding recipes need to have a fixed size so we can compute accurate branch destination addresses. Intel's instruction encoding has a lot of variance in the number of bytes needed to encode the opcode which leads to a number of duplicated encoding recipes that only differ in the opcode size. Add an Intel-specific TailEnc Python class which represents an abstraction over a set of recipes that are identical except for the opcode encoding. The TailEnc can then generate specific encoding recipes for each opcode format. The opcode format is a prefix of the recipe name, so for example, the 'rr' TailEnc will generate the 'Op1rr', 'Op2rr', 'Mp2rr' etc recipes. The TailEnc class provides a __call__ implementation that simply takes the sequence of opcode bytes as arguments. It then looks up the right prefix for the opcode bytes. --- lib/cretonne/meta/isa/intel/encodings.py | 73 ++++----- lib/cretonne/meta/isa/intel/recipes.py | 200 ++++++++++++++--------- 2 files changed, 155 insertions(+), 118 deletions(-) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 8d6d466aa7..575c36baca 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -4,61 +4,60 @@ Intel Encodings. from __future__ import absolute_import from base import instructions as base from .defs import I32 -from . import recipes as rcp -from .recipes import OP, OP0F, MP66 +from . import recipes as r -I32.enc(base.iadd.i32, rcp.Op1rr, OP(0x01)) -I32.enc(base.isub.i32, rcp.Op1rr, OP(0x29)) +I32.enc(base.iadd.i32, *r.rr(0x01)) +I32.enc(base.isub.i32, *r.rr(0x29)) -I32.enc(base.band.i32, rcp.Op1rr, OP(0x21)) -I32.enc(base.bor.i32, rcp.Op1rr, OP(0x09)) -I32.enc(base.bxor.i32, rcp.Op1rr, OP(0x31)) +I32.enc(base.band.i32, *r.rr(0x21)) +I32.enc(base.bor.i32, *r.rr(0x09)) +I32.enc(base.bxor.i32, *r.rr(0x31)) # Immediate instructions with sign-extended 8-bit and 32-bit immediate. -for inst, r in [ +for inst, rrr in [ (base.iadd_imm.i32, 0), (base.band_imm.i32, 4), (base.bor_imm.i32, 1), (base.bxor_imm.i32, 6)]: - I32.enc(inst, rcp.Op1rib, OP(0x83, rrr=r)) - I32.enc(inst, rcp.Op1rid, OP(0x81, rrr=r)) + I32.enc(inst, *r.rib(0x83, rrr=rrr)) + I32.enc(inst, *r.rid(0x81, rrr=rrr)) # 32-bit shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit # and 16-bit shifts would need explicit masking. -I32.enc(base.ishl.i32.i32, rcp.Op1rc, OP(0xd3, rrr=4)) -I32.enc(base.ushr.i32.i32, rcp.Op1rc, OP(0xd3, rrr=5)) -I32.enc(base.sshr.i32.i32, rcp.Op1rc, OP(0xd3, rrr=7)) +I32.enc(base.ishl.i32.i32, *r.rc(0xd3, rrr=4)) +I32.enc(base.ushr.i32.i32, *r.rc(0xd3, rrr=5)) +I32.enc(base.sshr.i32.i32, *r.rc(0xd3, rrr=7)) # Loads and stores. -I32.enc(base.store.i32.i32, rcp.Op1st, OP(0x89)) -I32.enc(base.store.i32.i32, rcp.Op1stDisp8, OP(0x89)) -I32.enc(base.store.i32.i32, rcp.Op1stDisp32, OP(0x89)) +I32.enc(base.store.i32.i32, *r.st(0x89)) +I32.enc(base.store.i32.i32, *r.stDisp8(0x89)) +I32.enc(base.store.i32.i32, *r.stDisp32(0x89)) -I32.enc(base.istore16.i32.i32, rcp.Mp1st, MP66(0x89)) -I32.enc(base.istore16.i32.i32, rcp.Mp1stDisp8, MP66(0x89)) -I32.enc(base.istore16.i32.i32, rcp.Mp1stDisp32, MP66(0x89)) +I32.enc(base.istore16.i32.i32, *r.st(0x66, 0x89)) +I32.enc(base.istore16.i32.i32, *r.stDisp8(0x66, 0x89)) +I32.enc(base.istore16.i32.i32, *r.stDisp32(0x66, 0x89)) -I32.enc(base.istore8.i32.i32, rcp.Op1st_abcd, OP(0x88)) -I32.enc(base.istore8.i32.i32, rcp.Op1stDisp8_abcd, OP(0x88)) -I32.enc(base.istore8.i32.i32, rcp.Op1stDisp32_abcd, OP(0x88)) +I32.enc(base.istore8.i32.i32, *r.st_abcd(0x88)) +I32.enc(base.istore8.i32.i32, *r.stDisp8_abcd(0x88)) +I32.enc(base.istore8.i32.i32, *r.stDisp32_abcd(0x88)) -I32.enc(base.load.i32.i32, rcp.Op1ld, OP(0x8b)) -I32.enc(base.load.i32.i32, rcp.Op1ldDisp8, OP(0x8b)) -I32.enc(base.load.i32.i32, rcp.Op1ldDisp32, OP(0x8b)) +I32.enc(base.load.i32.i32, *r.ld(0x8b)) +I32.enc(base.load.i32.i32, *r.ldDisp8(0x8b)) +I32.enc(base.load.i32.i32, *r.ldDisp32(0x8b)) -I32.enc(base.uload16.i32.i32, rcp.Op2ld, OP0F(0xb7)) -I32.enc(base.uload16.i32.i32, rcp.Op2ldDisp8, OP0F(0xb7)) -I32.enc(base.uload16.i32.i32, rcp.Op2ldDisp32, OP0F(0xb7)) +I32.enc(base.uload16.i32.i32, *r.ld(0x0f, 0xb7)) +I32.enc(base.uload16.i32.i32, *r.ldDisp8(0x0f, 0xb7)) +I32.enc(base.uload16.i32.i32, *r.ldDisp32(0x0f, 0xb7)) -I32.enc(base.sload16.i32.i32, rcp.Op2ld, OP0F(0xbf)) -I32.enc(base.sload16.i32.i32, rcp.Op2ldDisp8, OP0F(0xbf)) -I32.enc(base.sload16.i32.i32, rcp.Op2ldDisp32, OP0F(0xbf)) +I32.enc(base.sload16.i32.i32, *r.ld(0x0f, 0xbf)) +I32.enc(base.sload16.i32.i32, *r.ldDisp8(0x0f, 0xbf)) +I32.enc(base.sload16.i32.i32, *r.ldDisp32(0x0f, 0xbf)) -I32.enc(base.uload8.i32.i32, rcp.Op2ld, OP0F(0xb6)) -I32.enc(base.uload8.i32.i32, rcp.Op2ldDisp8, OP0F(0xb6)) -I32.enc(base.uload8.i32.i32, rcp.Op2ldDisp32, OP0F(0xb6)) +I32.enc(base.uload8.i32.i32, *r.ld(0x0f, 0xb6)) +I32.enc(base.uload8.i32.i32, *r.ldDisp8(0x0f, 0xb6)) +I32.enc(base.uload8.i32.i32, *r.ldDisp32(0x0f, 0xb6)) -I32.enc(base.sload8.i32.i32, rcp.Op2ld, OP0F(0xbe)) -I32.enc(base.sload8.i32.i32, rcp.Op2ldDisp8, OP0F(0xbe)) -I32.enc(base.sload8.i32.i32, rcp.Op2ldDisp32, OP0F(0xbe)) +I32.enc(base.sload8.i32.i32, *r.ld(0x0f, 0xbe)) +I32.enc(base.sload8.i32.i32, *r.ldDisp8(0x0f, 0xbe)) +I32.enc(base.sload8.i32.i32, *r.ldDisp32(0x0f, 0xbe)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 6c72fe4690..71b8260f42 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -7,30 +7,41 @@ from cdsl.predicates import IsSignedInt, IsEqual from base.formats import Binary, BinaryImm, Store, Load from .registers import GPR, ABCD +try: + from typing import Tuple, Dict # noqa + from cdsl.instructions import InstructionFormat # noqa + from cdsl.isa import ConstraintSeq, BranchRange, PredNode # noqa +except ImportError: + pass + + # Opcode representation. # # Cretonne requires each recipe to have a single encoding size in bytes, and # Intel opcodes are variable length, so we use separate recipes for different # styles of opcodes and prefixes. The opcode format is indicated by the recipe # name prefix: -# -# Op1* OP(op) -# 0F Op2* OP0F(op) -# 0F 38 Op3* OP38(op) -# 0F 3A Op3* OP3A(op) -# 66 Mp1* MP66(op) -# 66 0F Mp2* MP660F(op) -# 66 0F 38 Mp3* MP660F38(op) -# 66 0F 3A Mp3* MP660F3A(op) -# F2 Mp1* MPF2(op) -# F2 0F Mp2* MPF20F(op) -# F2 0F 38 Mp3* MPF20F38(op) -# F2 0F 3A Mp3* MPF20F3A(op) -# F3 Mp1* MPF3(op) -# F3 0F Mp2* MP0FF3(op) -# F3 0F 38 Mp3* MPF30F38(op) -# F3 0F 3A Mp3* MPF30F3A(op) -# + +OPCODE_PREFIX = { + # Prefix bytes Name mmpp + (): ('Op1', 0b0000), + (0x66,): ('Mp1', 0b0001), + (0xf3,): ('Mp1', 0b0010), + (0xf2,): ('Mp1', 0b0011), + (0x0f,): ('Op2', 0b0100), + (0x66, 0x0f): ('Mp2', 0b0101), + (0xf3, 0x0f): ('Mp2', 0b0110), + (0xf2, 0x0f): ('Mp2', 0b0111), + (0x0f, 0x38): ('Op3', 0b1000), + (0x66, 0x0f, 0x38): ('Mp3', 0b1001), + (0xf3, 0x0f, 0x38): ('Mp3', 0b1010), + (0xf2, 0x0f, 0x38): ('Mp3', 0b1011), + (0x0f, 0x3a): ('Op3', 0b1100), + (0x66, 0x0f, 0x3a): ('Mp3', 0b1101), + (0xf3, 0x0f, 0x3a): ('Mp3', 0b1110), + (0xf2, 0x0f, 0x3a): ('Mp3', 0b1111) + } + # VEX/XOP and EVEX prefixes are not yet supported. # # The encoding bits are: @@ -53,40 +64,95 @@ from .registers import GPR, ABCD # enough bits, and the pp+mm format is ready for supporting VEX prefixes. -def OP(op, pp=0, mm=0, rrr=0, w=0): - # type: (int, int, int, int, int) -> int - assert op <= 0xff - assert pp <= 0b11 - assert mm <= 0b11 +def decode_ops(ops, rrr=0, w=0): + # type: (Tuple[int, ...], int, int) -> Tuple[str, int] + """ + Given a sequence of opcode bytes, compute the recipe name prefix and + encoding bits. + """ assert rrr <= 0b111 assert w <= 1 - return op | (pp << 8) | (mm << 10) | (rrr << 12) | (w << 15) + name, mmpp = OPCODE_PREFIX[ops[:-1]] + op = ops[-1] + assert op <= 256 + return (name, op | (mmpp << 8) | (rrr << 12) | (w << 15)) -def OP0F(op, rrr=0, w=0): - # type: (int, int, int) -> int - return OP(op, pp=0, mm=1, rrr=rrr, w=w) +class TailRecipe: + """ + Generate encoding recipes on demand. + Intel encodings are somewhat orthogonal with the opcode representation on + one side and the ModR/M, SIB and immediate fields on the other side. -def MP66(op, rrr=0, w=0): - # type: (int, int, int) -> int - return OP(op, pp=1, mm=0, rrr=rrr, w=w) + A `TailRecipe` represents the part of an encoding that follow the opcode. + It is used to generate full encoding recipes on demand when combined with + an opcode. + + The arguments are the same as for an `EncRecipe`, except for `size` which + does not include the size of the opcode. + """ + + def __init__( + self, + name, # type: str + format, # type: InstructionFormat + size, # type: int + ins, # type: ConstraintSeq + outs, # type: ConstraintSeq + branch_range=None, # type: BranchRange + instp=None, # type: PredNode + isap=None # type: PredNode + ): + # type: (...) -> None + self.name = name + self.format = format + self.size = size + self.ins = ins + self.outs = outs + self.branch_range = branch_range + self.instp = instp + self.isap = isap + + # Cached recipes, keyed by name prefix. + self.recipes = dict() # type: Dict[str, EncRecipe] + + def __call__(self, *ops, **kwargs): + # type: (*int, **int) -> Tuple[EncRecipe, int] + """ + Create an encoding recipe and encoding bits for the opcode bytes in + `ops`. + """ + rrr = kwargs.get('rrr', 0) + w = kwargs.get('w', 0) + name, bits = decode_ops(ops, rrr, w) + if name not in self.recipes: + self.recipes[name] = EncRecipe( + name + self.name, + self.format, + len(ops) + self.size, + ins=self.ins, + outs=self.outs, + branch_range=self.branch_range, + instp=self.instp, + isap=self.isap) + return (self.recipes[name], bits) # XX /r -Op1rr = EncRecipe('Op1rr', Binary, size=2, ins=(GPR, GPR), outs=0) +rr = TailRecipe('rr', Binary, size=1, ins=(GPR, GPR), outs=0) # XX /n with one arg in %rcx, for shifts. -Op1rc = EncRecipe('Op1rc', Binary, size=2, ins=(GPR, GPR.rcx), outs=0) +rc = TailRecipe('rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0) # XX /n ib with 8-bit immediate sign-extended. -Op1rib = EncRecipe( - 'Op1rib', BinaryImm, size=3, ins=GPR, outs=0, +rib = TailRecipe( + 'rib', BinaryImm, size=2, ins=GPR, outs=0, instp=IsSignedInt(BinaryImm.imm, 8)) # XX /n id with 32-bit immediate sign-extended. -Op1rid = EncRecipe( - 'Op1rid', BinaryImm, size=6, ins=GPR, outs=0, +rid = TailRecipe( + 'rid', BinaryImm, size=5, ins=GPR, outs=0, instp=IsSignedInt(BinaryImm.imm, 32)) # @@ -94,72 +160,44 @@ Op1rid = EncRecipe( # # XX /r register-indirect store with no offset. -Op1st = EncRecipe( - 'Op1st', Store, size=2, ins=(GPR, GPR), outs=(), +st = TailRecipe( + 'st', Store, size=1, ins=(GPR, GPR), outs=(), instp=IsEqual(Store.offset, 0)) # XX /r register-indirect store with no offset. # Only ABCD allowed for stored value. This is for byte stores. -Op1st_abcd = EncRecipe( - 'Op1st_abcd', Store, size=2, ins=(ABCD, GPR), outs=(), +st_abcd = TailRecipe( + 'st_abcd', Store, size=1, ins=(ABCD, GPR), outs=(), instp=IsEqual(Store.offset, 0)) # XX /r register-indirect store with 8-bit offset. -Op1stDisp8 = EncRecipe( - 'Op1stDisp8', Store, size=3, ins=(GPR, GPR), outs=(), +stDisp8 = TailRecipe( + 'stDisp8', Store, size=2, ins=(GPR, GPR), outs=(), instp=IsSignedInt(Store.offset, 8)) -Op1stDisp8_abcd = EncRecipe( - 'Op1stDisp8_abcd', Store, size=3, ins=(ABCD, GPR), outs=(), +stDisp8_abcd = TailRecipe( + 'stDisp8_abcd', Store, size=2, ins=(ABCD, GPR), outs=(), instp=IsSignedInt(Store.offset, 8)) # XX /r register-indirect store with 32-bit offset. -Op1stDisp32 = EncRecipe('Op1stDisp32', Store, size=6, ins=(GPR, GPR), outs=()) -Op1stDisp32_abcd = EncRecipe( - 'Op1stDisp32_abcd', Store, size=6, ins=(ABCD, GPR), outs=()) - -# PP WW /r register-indirect store with no offset. -Mp1st = EncRecipe( - 'Mp1st', Store, size=3, ins=(GPR, GPR), outs=(), - instp=IsEqual(Store.offset, 0)) - -# PP XX /r register-indirect store with 8-bit offset. -Mp1stDisp8 = EncRecipe( - 'Mp1stDisp8', Store, size=4, ins=(GPR, GPR), outs=(), - instp=IsSignedInt(Store.offset, 8)) - -# PP XX /r register-indirect store with 32-bit offset. -Mp1stDisp32 = EncRecipe('Mp1stDisp32', Store, size=7, ins=(GPR, GPR), outs=()) +stDisp32 = TailRecipe('stDisp32', Store, size=5, ins=(GPR, GPR), outs=()) +stDisp32_abcd = TailRecipe( + 'stDisp32_abcd', Store, size=5, ins=(ABCD, GPR), outs=()) # # Load recipes # # XX /r load with no offset. -Op1ld = EncRecipe( - 'Op1ld', Load, size=2, ins=(GPR), outs=(GPR), +ld = TailRecipe( + 'ld', Load, size=1, ins=(GPR), outs=(GPR), instp=IsEqual(Load.offset, 0)) # XX /r load with 8-bit offset. -Op1ldDisp8 = EncRecipe( - 'Op1ldDisp8', Load, size=3, ins=(GPR), outs=(GPR), +ldDisp8 = TailRecipe( + 'ldDisp8', Load, size=2, ins=(GPR), outs=(GPR), instp=IsSignedInt(Load.offset, 8)) # XX /r load with 32-bit offset. -Op1ldDisp32 = EncRecipe( - 'Op1ldDisp32', Load, size=6, ins=(GPR), outs=(GPR), - instp=IsSignedInt(Load.offset, 32)) - -# 0F XX /r load with no offset. -Op2ld = EncRecipe( - 'Op2ld', Load, size=3, ins=(GPR), outs=(GPR), - instp=IsEqual(Load.offset, 0)) - -# XX /r load with 8-bit offset. -Op2ldDisp8 = EncRecipe( - 'Op2ldDisp8', Load, size=4, ins=(GPR), outs=(GPR), - instp=IsSignedInt(Load.offset, 8)) - -# XX /r load with 32-bit offset. -Op2ldDisp32 = EncRecipe( - 'Op2ldDisp32', Load, size=7, ins=(GPR), outs=(GPR), +ldDisp32 = TailRecipe( + 'ldDisp32', Load, size=5, ins=(GPR), outs=(GPR), instp=IsSignedInt(Load.offset, 32)) From a2fd9cf0cc0579b23ccecfa6cc2023fa84388e3b Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 16 May 2017 00:10:47 +0200 Subject: [PATCH 764/968] Update rustfmt to 0.8.4; (#81) --- check-rustfmt.sh | 2 +- lib/cretonne/src/binemit/relaxation.rs | 4 +- lib/cretonne/src/dominator_tree.rs | 3 +- lib/cretonne/src/entity_list.rs | 4 +- lib/cretonne/src/ir/builder.rs | 3 +- lib/cretonne/src/ir/dfg.rs | 20 +--- lib/cretonne/src/ir/instructions.rs | 30 +++--- lib/cretonne/src/ir/layout.rs | 3 +- lib/cretonne/src/isa/encoding.rs | 4 +- lib/cretonne/src/iterators.rs | 12 +-- lib/cretonne/src/legalizer/boundary.rs | 12 +-- lib/cretonne/src/regalloc/allocatable_set.rs | 28 +++--- lib/cretonne/src/regalloc/diversion.rs | 8 +- lib/cretonne/src/regalloc/solver.rs | 3 +- lib/cretonne/src/verifier/mod.rs | 32 +++---- lib/filecheck/src/checker.rs | 6 +- lib/reader/src/lexer.rs | 16 ++-- lib/reader/src/parser.rs | 98 +++++++++----------- src/filetest/concurrent.rs | 4 +- src/filetest/runner.rs | 4 +- 20 files changed, 120 insertions(+), 176 deletions(-) diff --git a/check-rustfmt.sh b/check-rustfmt.sh index c0c99c9a91..6e8563900f 100755 --- a/check-rustfmt.sh +++ b/check-rustfmt.sh @@ -15,7 +15,7 @@ # With the --install option, also tries to install the right version. # This version should always be bumped to the newest version available. -VERS="0.8.3" +VERS="0.8.4" if cargo install --list | grep -q "^rustfmt v$VERS"; then exit 0 diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index 74158a61e4..517867c725 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -97,9 +97,7 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { /// existing `fallthrough` instructions are correct. fn fallthroughs(func: &mut Function) { for (ebb, succ) in func.layout.ebbs().adjacent_pairs() { - let term = func.layout - .last_inst(ebb) - .expect("EBB has no terminator."); + let term = func.layout.last_inst(ebb).expect("EBB has no terminator."); if let InstructionData::Jump { ref mut opcode, destination, diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 13d1175d8a..ed6aa4dbd6 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -88,8 +88,7 @@ impl DominatorTree { // Run a finger up the dominator tree from b until we see a. // Do nothing if b is unreachable. while rpo_a < self.nodes[ebb_b].rpo_number { - b = self.idom(ebb_b) - .expect("Shouldn't meet unreachable here."); + b = self.idom(ebb_b).expect("Shouldn't meet unreachable here."); ebb_b = layout.inst_ebb(b).expect("Dominator got removed."); } diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 3b7493094e..b6cf3099eb 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -135,9 +135,7 @@ impl ListPool { // The `wrapping_sub` handles the special case 0, which is the empty list. This way, the // cost of the bounds check that we have to pay anyway is co-opted to handle the special // case of the empty list. - self.data - .get(idx.wrapping_sub(1)) - .map(|len| len.index()) + self.data.get(idx.wrapping_sub(1)).map(|len| len.index()) } /// Allocate a storage block with a size given by `sclass`. diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index a5a3719299..256845bcfb 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -132,8 +132,7 @@ impl<'c, 'fc, 'fd, Array> InstBuilderBase<'fd> for InsertReuseBuilder<'c, 'fc, ' let inst = self.dfg.make_inst(data); // Make an `Interator>`. let ru = self.reuse.as_ref().iter().cloned(); - self.dfg - .make_inst_results_reusing(inst, ctrl_typevar, ru); + self.dfg.make_inst_results_reusing(inst, ctrl_typevar, ru); self.pos.insert_inst(inst); (inst, self.dfg) } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 6e33c9d592..d5eb5ab73d 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -316,37 +316,25 @@ impl DataFlowGraph { /// Get the fixed value arguments on `inst` as a slice. pub fn inst_fixed_args(&self, inst: Inst) -> &[Value] { - let fixed_args = self[inst] - .opcode() - .constraints() - .fixed_value_arguments(); + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); &self.inst_args(inst)[..fixed_args] } /// Get the fixed value arguments on `inst` as a mutable slice. pub fn inst_fixed_args_mut(&mut self, inst: Inst) -> &mut [Value] { - let fixed_args = self[inst] - .opcode() - .constraints() - .fixed_value_arguments(); + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); &mut self.inst_args_mut(inst)[..fixed_args] } /// Get the variable value arguments on `inst` as a slice. pub fn inst_variable_args(&self, inst: Inst) -> &[Value] { - let fixed_args = self[inst] - .opcode() - .constraints() - .fixed_value_arguments(); + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); &self.inst_args(inst)[fixed_args..] } /// Get the variable value arguments on `inst` as a mutable slice. pub fn inst_variable_args_mut(&mut self, inst: Inst) -> &mut [Value] { - let fixed_args = self[inst] - .opcode() - .constraints() - .fixed_value_arguments(); + let fixed_args = self[inst].opcode().constraints().fixed_value_arguments(); &mut self.inst_args_mut(inst)[fixed_args..] } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 0b32929af6..4301c7796c 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -288,20 +288,20 @@ impl InstructionData { pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> { match self { &InstructionData::Jump { - destination, - ref args, - .. - } => BranchInfo::SingleDest(destination, &args.as_slice(pool)), + destination, + ref args, + .. + } => BranchInfo::SingleDest(destination, &args.as_slice(pool)), &InstructionData::Branch { - destination, - ref args, - .. - } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]), + destination, + ref args, + .. + } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]), &InstructionData::BranchIcmp { - destination, - ref args, - .. - } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]), + destination, + ref args, + .. + } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]), &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), _ => BranchInfo::NotABranch, } @@ -595,11 +595,7 @@ impl OperandConstraint { Same => Bound(ctrl_type), LaneOf => Bound(ctrl_type.lane_type()), AsBool => Bound(ctrl_type.as_bool()), - HalfWidth => { - Bound(ctrl_type - .half_width() - .expect("invalid type for half_width")) - } + HalfWidth => Bound(ctrl_type.half_width().expect("invalid type for half_width")), DoubleWidth => { Bound(ctrl_type .double_width() diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index abf0f3f482..4e6f738653 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -467,8 +467,7 @@ impl Layout { /// Remove `inst` from the layout. pub fn remove_inst(&mut self, inst: Inst) { - let ebb = self.inst_ebb(inst) - .expect("Instruction already removed."); + let ebb = self.inst_ebb(inst).expect("Instruction already removed."); // Clear the `inst` node and extract links. let prev; let next; diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index b5062d988c..9bc78d9ef0 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -130,8 +130,6 @@ impl EncInfo { /// /// This will never return `None` for a legal branch encoding. pub fn branch_range(&self, enc: Encoding) -> Option { - self.sizing - .get(enc.recipe()) - .and_then(|s| s.branch_range) + self.sizing.get(enc.recipe()).and_then(|s| s.branch_range) } } diff --git a/lib/cretonne/src/iterators.rs b/lib/cretonne/src/iterators.rs index bca8cea2e4..70d1165ed7 100644 --- a/lib/cretonne/src/iterators.rs +++ b/lib/cretonne/src/iterators.rs @@ -65,17 +65,9 @@ mod tests { .adjacent_pairs() .collect::>(), vec![(2, 3), (3, 4)]); - assert_eq!([3, 4] - .iter() - .cloned() - .adjacent_pairs() - .collect::>(), + assert_eq!([3, 4].iter().cloned().adjacent_pairs().collect::>(), vec![(3, 4)]); - assert_eq!([4] - .iter() - .cloned() - .adjacent_pairs() - .collect::>(), + assert_eq!([4].iter().cloned().adjacent_pairs().collect::>(), vec![]); assert_eq!([] .iter() diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index e0cf9a2f21..b65eea0984 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -251,9 +251,7 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, assert!(!ty.is_int()); let abi_ty = Type::int(ty.bits()).expect("Invalid type for conversion"); let arg = convert_from_abi(dfg, pos, abi_ty, None, get_arg); - dfg.ins(pos) - .with_results([into_result]) - .bitcast(ty, arg) + dfg.ins(pos).with_results([into_result]).bitcast(ty, arg) } // ABI argument is a sign-extended version of the value we want. ValueConversion::Sext(abi_ty) => { @@ -261,18 +259,14 @@ fn convert_from_abi(dfg: &mut DataFlowGraph, // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. // We could insert an `assert_sreduce` which would fold with a following `sextend` of // this value. - dfg.ins(pos) - .with_results([into_result]) - .ireduce(ty, arg) + dfg.ins(pos).with_results([into_result]).ireduce(ty, arg) } ValueConversion::Uext(abi_ty) => { let arg = convert_from_abi(dfg, pos, abi_ty, None, get_arg); // TODO: Currently, we don't take advantage of the ABI argument being sign-extended. // We could insert an `assert_ureduce` which would fold with a following `uextend` of // this value. - dfg.ins(pos) - .with_results([into_result]) - .ireduce(ty, arg) + dfg.ins(pos).with_results([into_result]).ireduce(ty, arg) } } } diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 714a82aab7..db5a593901 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -137,21 +137,21 @@ mod tests { // Register classes for testing. const GPR: RegClass = &RegClassData { - name: "GPR", - index: 0, - width: 1, - first: 28, - subclasses: 0, - mask: [0xf0000000, 0x0000000f, 0], - }; + name: "GPR", + index: 0, + width: 1, + first: 28, + subclasses: 0, + mask: [0xf0000000, 0x0000000f, 0], + }; const DPR: RegClass = &RegClassData { - name: "DPR", - index: 0, - width: 2, - first: 28, - subclasses: 0, - mask: [0x50000000, 0x0000000a, 0], - }; + name: "DPR", + index: 0, + width: 2, + first: 28, + subclasses: 0, + mask: [0x50000000, 0x0000000a, 0], + }; #[test] fn put_and_take() { diff --git a/lib/cretonne/src/regalloc/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs index 2096af8722..89decad82f 100644 --- a/lib/cretonne/src/regalloc/diversion.rs +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -100,10 +100,10 @@ mod tests { divs.regmove(v1, 10, 12); assert_eq!(divs.diversion(v1), Some(&Diversion { - value: v1, - from: 10, - to: 12, - })); + value: v1, + from: 10, + to: 12, + })); assert_eq!(divs.diversion(v2), None); divs.regmove(v1, 12, 11); diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index 5e3c43da5f..89aee41317 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -414,8 +414,7 @@ impl Solver { if self.inputs_done { self.regs_out.free(constraint, from); } - self.vars - .push(Variable::new_live(value, constraint, from)); + self.vars.push(Variable::new_live(value, constraint, from)); } /// Check for conflicts between fixed input assignments and existing live values. diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index e1568d5bab..5c283dd6d3 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -234,20 +234,20 @@ impl<'a> Verifier<'a> { self.verify_value_list(inst, args)?; } &Jump { - destination, - ref args, - .. - } | + destination, + ref args, + .. + } | &Branch { - destination, - ref args, - .. - } | + destination, + ref args, + .. + } | &BranchIcmp { - destination, - ref args, - .. - } => { + destination, + ref args, + .. + } => { self.verify_ebb(inst, destination)?; self.verify_value_list(inst, args)?; } @@ -381,8 +381,7 @@ impl<'a> Verifier<'a> { ebb); } // The defining EBB dominates the instruction using this value. - if !self.domtree - .ebb_dominates(ebb, loc_inst, &self.func.layout) { + if !self.domtree.ebb_dominates(ebb, loc_inst, &self.func.layout) { return err!(loc_inst, "uses value arg from non-dominating {}", ebb); } } @@ -639,10 +638,7 @@ impl<'a> Verifier<'a> { return err!(ebb, "cfg had unexpected successor(s) {:?}", excess_succs); } - expected_preds.extend(self.cfg - .get_predecessors(ebb) - .iter() - .map(|&(_, inst)| inst)); + expected_preds.extend(self.cfg.get_predecessors(ebb).iter().map(|&(_, inst)| inst)); got_preds.extend(cfg.get_predecessors(ebb).iter().map(|&(_, inst)| inst)); let missing_preds: Vec = expected_preds.difference(&got_preds).cloned().collect(); diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index e70768e32c..ee44a769e5 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -416,11 +416,9 @@ mod tests { Ok(true)); assert_eq!(b.directive("regex: X = tommy").map_err(e2s), Err("expected '=' after variable 'X' in regex: X = tommy".to_string())); - assert_eq!(b.directive("[arm]not: patt $x $(y) here") - .map_err(e2s), + assert_eq!(b.directive("[arm]not: patt $x $(y) here").map_err(e2s), Ok(true)); - assert_eq!(b.directive("[x86]sameln: $x $(y=[^]]*) there") - .map_err(e2s), + assert_eq!(b.directive("[x86]sameln: $x $(y=[^]]*) there").map_err(e2s), Ok(true)); // Windows line ending sneaking in. assert_eq!(b.directive("regex: Y=foo\r").map_err(e2s), Ok(true)); diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index 1262b63888..c5c2aa5d80 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -296,7 +296,7 @@ impl<'a> Lexer<'a> { token(split_entity_name(text) .and_then(|(prefix, number)| { Self::numbered_entity(prefix, number) - .or_else(|| Self::value_type(text, prefix, number)) + .or_else(|| Self::value_type(text, prefix, number)) }) .unwrap_or(Token::Identifier(text)), loc) @@ -413,14 +413,14 @@ impl<'a> Lexer<'a> { Some('%') => Some(self.scan_name()), Some('#') => Some(self.scan_hex_sequence()), Some(ch) if ch.is_whitespace() => { - self.next_ch(); - continue; - } + self.next_ch(); + continue; + } _ => { - // Skip invalid char, return error. - self.next_ch(); - Some(error(Error::InvalidChar, loc)) - } + // Skip invalid char, return error. + self.next_ch(); + Some(error(Error::InvalidChar, loc)) + } }; } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index b907ee426f..fa9f31df5a 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -29,12 +29,7 @@ use sourcemap::{SourceMap, MutableSourceMap}; /// /// Any test commands or ISA declarations are ignored. pub fn parse_functions(text: &str) -> Result> { - parse_test(text).map(|file| { - file.functions - .into_iter() - .map(|(func, _)| func) - .collect() - }) + parse_test(text).map(|file| file.functions.into_iter().map(|(func, _)| func).collect()) } /// Parse the entire `text` as a test case file. @@ -753,9 +748,7 @@ impl<'a> Parser<'a> { sig.return_types = self.parse_argument_list(unique_isa)?; } - if sig.argument_types - .iter() - .all(|a| a.location.is_assigned()) { + if sig.argument_types.iter().all(|a| a.location.is_assigned()) { sig.compute_argument_bytes(); } @@ -1319,48 +1312,50 @@ impl<'a> Parser<'a> { inst_data: &InstructionData) -> Result { let constraints = opcode.constraints(); - let ctrl_type = - match explicit_ctrl_type { - Some(t) => t, - None => { - if constraints.use_typevar_operand() { - // This is an opcode that supports type inference, AND there was no - // explicit type specified. Look up `ctrl_value` to see if it was defined - // already. - // TBD: If it is defined in another block, the type should have been - // specified explicitly. It is unfortunate that the correctness of IL - // depends on the layout of the blocks. - let ctrl_src_value = inst_data - .typevar_operand(&ctx.function.dfg.value_lists) - .expect("Constraints <-> Format inconsistency"); - ctx.function.dfg.value_type(match ctx.map.get_value(ctrl_src_value) { - Some(v) => v, - None => { - if let Some(v) = ctx.aliases - .get(&ctrl_src_value) - .and_then(|&(aliased, _)| ctx.map.get_value(aliased)) - { - v - } else { - return err!(self.loc, - "cannot determine type of operand {}", - ctrl_src_value); - } - } - }) - } else if constraints.is_polymorphic() { - // This opcode does not support type inference, so the explicit type - // variable is required. - return err!(self.loc, - "type variable required for polymorphic opcode, e.g. '{}.{}'", - opcode, - constraints.ctrl_typeset().unwrap().example()); - } else { - // This is a non-polymorphic opcode. No typevar needed. - VOID - } + let ctrl_type = match explicit_ctrl_type { + Some(t) => t, + None => { + if constraints.use_typevar_operand() { + // This is an opcode that supports type inference, AND there was no + // explicit type specified. Look up `ctrl_value` to see if it was defined + // already. + // TBD: If it is defined in another block, the type should have been + // specified explicitly. It is unfortunate that the correctness of IL + // depends on the layout of the blocks. + let ctrl_src_value = inst_data + .typevar_operand(&ctx.function.dfg.value_lists) + .expect("Constraints <-> Format inconsistency"); + ctx.function + .dfg + .value_type(match ctx.map.get_value(ctrl_src_value) { + Some(v) => v, + None => { + if let Some(v) = ctx.aliases + .get(&ctrl_src_value) + .and_then(|&(aliased, _)| { + ctx.map.get_value(aliased) + }) { + v + } else { + return err!(self.loc, + "cannot determine type of operand {}", + ctrl_src_value); + } + } + }) + } else if constraints.is_polymorphic() { + // This opcode does not support type inference, so the explicit type + // variable is required. + return err!(self.loc, + "type variable required for polymorphic opcode, e.g. '{}.{}'", + opcode, + constraints.ctrl_typeset().unwrap().example()); + } else { + // This is a non-polymorphic opcode. No typevar needed. + VOID } - }; + } + }; // Verify that `ctrl_type` is valid for the controlling type variable. We don't want to // attempt deriving types from an incorrect basis. @@ -1616,8 +1611,7 @@ impl<'a> Parser<'a> { InstructionFormat::BranchTable => { let arg = self.match_value("expected SSA value operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; - let table = self.match_jt() - .and_then(|num| ctx.get_jt(num, &self.loc))?; + let table = self.match_jt().and_then(|num| ctx.get_jt(num, &self.loc))?; InstructionData::BranchTable { opcode, arg, table } } InstructionFormat::StackLoad => { diff --git a/src/filetest/concurrent.rs b/src/filetest/concurrent.rs index dbdd76f35c..7dcbe1b4fc 100644 --- a/src/filetest/concurrent.rs +++ b/src/filetest/concurrent.rs @@ -119,9 +119,7 @@ fn worker_thread(thread_num: usize, // Tell them we're starting this job. // The receiver should always be present for this as long as we have jobs. - replies - .send(Reply::Starting { jobid, thread_num }) - .unwrap(); + replies.send(Reply::Starting { jobid, thread_num }).unwrap(); let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| { // The test panicked, leaving us a `Box`. diff --git a/src/filetest/runner.rs b/src/filetest/runner.rs index 23f94fe78f..9188ee00ad 100644 --- a/src/filetest/runner.rs +++ b/src/filetest/runner.rs @@ -207,9 +207,7 @@ impl TestRunner { } // Check for any asynchronous replies without blocking. - while let Some(reply) = self.threads - .as_mut() - .and_then(ConcurrentRunner::try_get) { + while let Some(reply) = self.threads.as_mut().and_then(ConcurrentRunner::try_get) { self.handle_reply(reply); } } From ca6eddaf88cabdb2a2993d1392df25ece7b49155 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 15 May 2017 13:01:41 -0700 Subject: [PATCH 765/968] Add a register bank index to RegClassData. This makes it possible to find the register bank that contains a register class. --- lib/cretonne/meta/cdsl/registers.py | 1 + lib/cretonne/meta/gen_registers.py | 13 +++++++------ lib/cretonne/src/isa/registers.rs | 3 +++ lib/cretonne/src/regalloc/allocatable_set.rs | 2 ++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index 1f6bfa682f..c16e760a5c 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -87,6 +87,7 @@ class RegBank(object): align = next_power_of_two(align) self.first_unit = (u + align - 1) & -align + self.index = len(isa.regbanks) isa.regbanks.append(self) def __repr__(self): diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index b1532761a6..4199dce268 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -34,13 +34,14 @@ def gen_regclass(rc, fmt): Emit a static data definition for a register class. """ with fmt.indented('RegClassData {', '},'): - fmt.line('name: "{}",'.format(rc.name)) - fmt.line('index: {},'.format(rc.index)) - fmt.line('width: {},'.format(rc.width)) - fmt.line('first: {},'.format(rc.bank.first_unit + rc.start)) - fmt.line('subclasses: 0x{:x},'.format(rc.subclass_mask())) + fmt.format('name: "{}",', rc.name) + fmt.format('index: {},', rc.index) + fmt.format('width: {},', rc.width) + fmt.format('bank: {},', rc.bank.index) + fmt.format('first: {},', rc.bank.first_unit + rc.start) + fmt.format('subclasses: 0x{:x},', rc.subclass_mask()) mask = ', '.join('0x{:08x}'.format(x) for x in rc.mask()) - fmt.line('mask: [{}],'.format(mask)) + fmt.format('mask: [{}],', mask) def gen_isa(isa, fmt): diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 91a25595f1..461309a9e9 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -108,6 +108,9 @@ pub struct RegClassData { /// How many register units to allocate per register. pub width: u8, + /// Index of the register bank this class belongs to. + pub bank: u8, + /// The first register unit in this class. pub first: RegUnit, diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index db5a593901..fa3e7a9153 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -140,6 +140,7 @@ mod tests { name: "GPR", index: 0, width: 1, + bank: 0, first: 28, subclasses: 0, mask: [0xf0000000, 0x0000000f, 0], @@ -148,6 +149,7 @@ mod tests { name: "DPR", index: 0, width: 2, + bank: 0, first: 28, subclasses: 0, mask: [0x50000000, 0x0000000a, 0], From 09ac27a7979ff41cd74f690f0aa21721ff4d7386 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 15 May 2017 13:39:59 -0700 Subject: [PATCH 766/968] Compute top-level register classes for each register bank. A top-level register class is one that has no sub-classes. It is possible to have multiple top-level register classes in the same register bank. For example, ARM's FPR bank has both D and Q top-level register classes. Number register classes such that all top-level register classes appear as a contiguous sequence starting from 0. This will be used by the register allocator when counting used registers per top-level register class. --- lib/cretonne/meta/cdsl/isa.py | 19 ++++++++++-- lib/cretonne/meta/cdsl/registers.py | 31 ++++++++++++++------ lib/cretonne/meta/gen_registers.py | 26 ++++++++-------- lib/cretonne/src/isa/registers.rs | 12 ++++++++ lib/cretonne/src/regalloc/allocatable_set.rs | 2 ++ 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index f1125f59da..9239bf2f70 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -42,6 +42,7 @@ class TargetISA(object): self.instruction_groups = instruction_groups self.cpumodes = list() # type: List[CPUMode] self.regbanks = list() # type: List[RegBank] + self.regclasses = list() # type: List[RegClass] def finish(self): # type: () -> TargetISA @@ -109,11 +110,23 @@ class TargetISA(object): Every register class needs a unique index, and the classes need to be topologically ordered. + + We also want all the top-level register classes to be first. """ - rc_index = 0 + # Compute subclasses and top-level classes in each bank. + # Collect the top-level classes so they get numbered consecutively. for bank in self.regbanks: - bank.finish_regclasses(rc_index) - rc_index += len(bank.classes) + bank.finish_regclasses() + self.regclasses.extend(bank.toprcs) + + # Collect all of the non-top-level register classes. + # They are numbered strictly after the top-level classes. + for bank in self.regbanks: + self.regclasses.extend( + rc for rc in bank.classes if not rc.is_toprc()) + + for idx, rc in enumerate(self.regclasses): + rc.index = idx class CPUMode(object): diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index c16e760a5c..caaacd20e4 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -75,6 +75,8 @@ class RegBank(object): self.prefix = prefix self.names = names self.classes = list() # type: List[RegClass] + self.toprcs = list() # type: List[RegClass] + self.first_toprc_index = None # type: int assert len(names) <= units @@ -95,11 +97,10 @@ class RegBank(object): return ('RegBank({}, units={}, first_unit={})' .format(self.name, self.units, self.first_unit)) - def finish_regclasses(self, first_index): - # type: (int) -> None + def finish_regclasses(self): + # type: () -> None """ - Assign indexes to the register classes in this bank, starting from - `first_index`. + Compute subclasses and the top-level register class. Verify that the set of register classes satisfies: @@ -115,14 +116,10 @@ class RegBank(object): """ cmap = dict() # type: Dict[RCTup, RegClass] - for idx, rc in enumerate(self.classes): + for rc in self.classes: # All register classes must be given a name. assert rc.name, "Anonymous register class found" - # Assign a unique index. - assert rc.index is None - rc.index = idx + first_index - # Check for duplicates. tup = rc.rctup() if tup in cmap: @@ -133,6 +130,7 @@ class RegBank(object): # Check intersections and topological order. for idx, rc1 in enumerate(self.classes): + rc1.toprc = rc1 for rc2 in self.classes[0:idx]: itup = rc1.intersect(rc2) if itup is None: @@ -151,6 +149,10 @@ class RegBank(object): # The intersection of rc1 and rc2 is rc1, so it must be a # sub-class. rc2.subclasses.append(rc1) + rc1.toprc = rc2.toprc + + if rc1.is_toprc(): + self.toprcs.append(rc1) def unit_by_name(self, name): # type: (str) -> int @@ -192,6 +194,7 @@ class RegClass(object): # This is computed later in `finish_regclasses()`. self.subclasses = list() # type: List[RegClass] + self.toprc = None # type: RegClass assert width > 0 assert start >= 0 and start < bank.units @@ -206,6 +209,16 @@ class RegClass(object): # type: () -> str return self.name + def is_toprc(self): + # type: () -> bool + """ + Is this a top-level register class? + + A top-level register class has no sub-classes. This can only be + answered aster running `finish_regclasses()`. + """ + return self.toprc is self + def rctup(self): # type: () -> RCTup """ diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index 4199dce268..463364709a 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -19,13 +19,15 @@ def gen_regbank(regbank, fmt): Emit a static data definition for regbank. """ with fmt.indented('RegBank {', '},'): - fmt.line('name: "{}",'.format(regbank.name)) - fmt.line('first_unit: {},'.format(regbank.first_unit)) - fmt.line('units: {},'.format(regbank.units)) - fmt.line( - 'names: &[{}],' - .format(', '.join('"{}"'.format(n) for n in regbank.names))) - fmt.line('prefix: "{}",'.format(regbank.prefix)) + fmt.format('name: "{}",', regbank.name) + fmt.format('first_unit: {},', regbank.first_unit) + fmt.format('units: {},', regbank.units) + fmt.format( + 'names: &[{}],', + ', '.join('"{}"'.format(n) for n in regbank.names)) + fmt.format('prefix: "{}",', regbank.prefix) + fmt.format('first_toprc: {},', regbank.toprcs[0].index) + fmt.format('num_toprcs: {},', len(regbank.toprcs)) def gen_regclass(rc, fmt): @@ -38,6 +40,7 @@ def gen_regclass(rc, fmt): fmt.format('index: {},', rc.index) fmt.format('width: {},', rc.width) fmt.format('bank: {},', rc.bank.index) + fmt.format('toprc: {},', rc.toprc.index) fmt.format('first: {},', rc.bank.first_unit + rc.start) fmt.format('subclasses: 0x{:x},', rc.subclass_mask()) mask = ', '.join('0x{:08x}'.format(x) for x in rc.mask()) @@ -52,24 +55,23 @@ def gen_isa(isa, fmt): if not isa.regbanks: print('cargo:warning={} has no register banks'.format(isa.name)) - rcs = list() # type: List[RegClass] with fmt.indented('pub static INFO: RegInfo = RegInfo {', '};'): # Bank descriptors. with fmt.indented('banks: &[', '],'): for regbank in isa.regbanks: gen_regbank(regbank, fmt) - rcs += regbank.classes fmt.line('classes: &CLASSES,') # Register class descriptors. with fmt.indented( - 'const CLASSES: [RegClassData; {}] = ['.format(len(rcs)), '];'): - for idx, rc in enumerate(rcs): + 'const CLASSES: [RegClassData; {}] = [' + .format(len(isa.regclasses)), '];'): + for idx, rc in enumerate(isa.regclasses): assert idx == rc.index gen_regclass(rc, fmt) # Emit constants referencing the register classes. - for rc in rcs: + for rc in isa.regclasses: fmt.line('#[allow(dead_code)]') fmt.line( 'pub const {}: RegClass = &CLASSES[{}];' diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 461309a9e9..a4527733c5 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -42,6 +42,15 @@ pub struct RegBank { /// The remaining register units will be named this prefix followed by their decimal offset in /// the bank. So with a prefix `r`, registers will be named `r8`, `r9`, ... pub prefix: &'static str, + + /// Index of the first top-level register class in this bank. + pub first_toprc: usize, + + /// Number of top-level register classes in this bank. + /// + /// The top-level register classes in a bank are guaranteed to be numbered sequentially from + /// `first_toprc`, and all top-level register classes across banks come before any sub-classes. + pub num_toprcs: usize, } impl RegBank { @@ -111,6 +120,9 @@ pub struct RegClassData { /// Index of the register bank this class belongs to. pub bank: u8, + /// Index of the top-level register class contains this one. + pub toprc: u8, + /// The first register unit in this class. pub first: RegUnit, diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index fa3e7a9153..756ad1d349 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -141,6 +141,7 @@ mod tests { index: 0, width: 1, bank: 0, + toprc: 0, first: 28, subclasses: 0, mask: [0xf0000000, 0x0000000f, 0], @@ -150,6 +151,7 @@ mod tests { index: 0, width: 2, bank: 0, + toprc: 0, first: 28, subclasses: 0, mask: [0x50000000, 0x0000000a, 0], From 66d2c0a95af604425575f8bbac317d5fe2856e79 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 15 May 2017 16:01:24 -0700 Subject: [PATCH 767/968] Add a RegClassMask typedef and a MAX_TOPRCS constant. Avoid spreading u32 as a bitmask of register classes throughout the code. Enforce the limit of 32 register classes total. This could easily be raised if needed. The MAX_TOPRCS constant is the highest possible number of top-level register classes in an ISA. The RegClassData.toprc field is always smaller than this limit. --- lib/cretonne/meta/cdsl/isa.py | 10 ++++++++++ lib/cretonne/src/isa/registers.rs | 14 +++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 9239bf2f70..a2387bc0ff 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -119,6 +119,11 @@ class TargetISA(object): bank.finish_regclasses() self.regclasses.extend(bank.toprcs) + # The limit on the number of top-level register classes can be raised. + # This should be coordinated with the `MAX_TOPRCS` constant in + # `isa/registers.rs`. + assert len(self.regclasses) <= 4, "Too many top-level register classes" + # Collect all of the non-top-level register classes. # They are numbered strictly after the top-level classes. for bank in self.regbanks: @@ -128,6 +133,11 @@ class TargetISA(object): for idx, rc in enumerate(self.regclasses): rc.index = idx + # The limit on the number of register classes can be changed. It should + # be coordinated with the `RegClassMask` and `RegClassIndex` types in + # `isa/registers.rs`. + assert len(self.regclasses) <= 32, "Too many register classes" + class CPUMode(object): """ diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index a4527733c5..5549e98157 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -20,6 +20,18 @@ pub type RegUnit = u16; /// This type should be coordinated with meta/cdsl/registers.py. pub type RegUnitMask = [u32; 3]; +/// A bit mask indexed by register classes. +/// +/// The size of this type is determined by the ISA with the most register classes. +/// +/// This type should be coordinated with meta/cdsl/isa.py. +pub type RegClassMask = u32; + +/// Guaranteed maximum number of top-level register classes in any ISA. +/// +/// This can be increased, but should be coordinated with meta/cdsl/isa.py. +pub const MAX_TOPRCS: usize = 4; + /// The register units in a target ISA are divided into disjoint register banks. Each bank covers a /// contiguous range of register units. /// @@ -129,7 +141,7 @@ pub struct RegClassData { /// Bit-mask of sub-classes of this register class, including itself. /// /// Bits correspond to RC indexes. - pub subclasses: u32, + pub subclasses: RegClassMask, /// Mask of register units in the class. If `width > 1`, the mask only has a bit set for the /// first register unit in each allocatable register. From 06afd3b77b07fbcd6e72444ca30674e5309c7da8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 16 May 2017 10:46:19 -0700 Subject: [PATCH 768/968] Implement ExactSizeIterator for RegSetIter. The set of available registers in a register class is known ahead of time. It can be computed with pop-count. --- lib/cretonne/src/regalloc/allocatable_set.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 756ad1d349..6ad8019ba9 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -5,8 +5,9 @@ //! "register unit" abstraction. Every register contains one or more register units. Registers that //! share a register unit can't be in use at the same time. -use std::mem::size_of_val; use isa::registers::{RegUnit, RegUnitMask, RegClass}; +use std::iter::ExactSizeIterator; +use std::mem::size_of_val; /// Set of registers available for allocation. #[derive(Clone)] @@ -128,8 +129,15 @@ impl Iterator for RegSetIter { // All of `self.regs` is 0. None } + + fn size_hint(&self) -> (usize, Option) { + let bits = self.regs.iter().map(|&w| w.count_ones() as usize).sum(); + (bits, Some(bits)) + } } +impl ExactSizeIterator for RegSetIter {} + #[cfg(test)] mod tests { use super::*; @@ -162,6 +170,7 @@ mod tests { let mut regs = AllocatableSet::new(); // `GPR` has units 28-36. + assert_eq!(regs.iter(GPR).len(), 8); assert_eq!(regs.iter(GPR).count(), 8); assert_eq!(regs.iter(DPR).collect::>(), [28, 30, 33, 35]); From 0c7b2c7b68873de17a0c767db2f8facf49e28d43 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 16 May 2017 10:02:57 -0700 Subject: [PATCH 769/968] Add a register pressure tracker. The spilling and reload passes need to ensure that the set of live ranges with register affinity can always be assigned registers. The register pressure tracker can count how many registers are in use for each top-level register class and give guidance on the type of registers that need to be spilled when limits are exceeded. Pressure tracking is extra complicated for the arm32 floating point register bank because there are multiple top-level register classes (S, D, Q) competing for the same register units. --- lib/cretonne/src/regalloc/mod.rs | 1 + lib/cretonne/src/regalloc/pressure.rs | 296 ++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 lib/cretonne/src/regalloc/pressure.rs diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index fd2e78e9c3..473a9fdb28 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -11,6 +11,7 @@ pub mod coloring; mod affinity; mod context; mod diversion; +mod pressure; mod solver; pub use self::allocatable_set::AllocatableSet; diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs new file mode 100644 index 0000000000..14e4441697 --- /dev/null +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -0,0 +1,296 @@ +//! Register pressure tracking. +//! +//! SSA-based register allocation depends on a spilling phase that "lowers register pressure +//! sufficiently". This module defines the data structures needed to measure register pressure +//! accurately enough to guarantee that the coloring phase will not run out of registers. +//! +//! Ideally, measuring register pressure amounts to simply counting the number of live registers at +//! any given program point. This simplistic method has two problems: +//! +//! 1. Registers are not interchangeable. Most ISAs have separate integer and floating-point +//! register banks, so we need to at least count the number of live registers in each register +//! bank separately. +//! +//! 2. Some ISAs have complicated register aliasing properties. In particular, the 32-bit ARM +//! ISA has a floating-point register bank where two 32-bit registers alias one 64-bit register. +//! This makes it difficult to accurately measure register pressure. +//! +//! This module deals with the problems via *register banks* and *top-level register classes*. +//! Register classes in different register banks are completely independent, so we can count +//! registers in one bank without worrying about the other bank at all. +//! +//! All register classes have a unique top-level register class, and we will count registers for +//! each top-level register class individually. However, a register bank can have multiple +//! top-level register classes that interfere with each other, so all top-level counts need to +//! be considered when determining how many more registers can be allocated. +//! +//! Currently, the only register bank with multiple top-level registers is the `arm32` +//! floating-point register bank which has `S`, `D`, and `Q` top-level classes. + +// Remove once we're using the pressure tracker. +#![allow(dead_code)] + +use isa::registers::{RegInfo, MAX_TOPRCS, RegClass, RegClassMask}; +use regalloc::AllocatableSet; +use std::cmp::min; +use std::iter::ExactSizeIterator; + +/// Information per top-level register class. +/// +/// Everything but the count is static information computed from the constructor arguments. +#[derive(Default)] +struct TopRC { + // Number of registers currently used from this register class. + count: u32, + + // Max number of registers that can be allocated. + limit: u32, + + // Register units per register. + width: u8, + + // The first aliasing top-level RC. + first_toprc: u8, + + // The number of aliasing top-level RCs. + num_toprcs: u8, +} + +pub struct Pressure { + // Bit mask of top-level register classes that are aliased by other top-level register classes. + // Unaliased register classes can use a simpler interference algorithm. + aliased: RegClassMask, + + // Current register counts per top-level register class. + toprc: [TopRC; MAX_TOPRCS], +} + +impl Pressure { + /// Create a new register pressure tracker. + pub fn new(reginfo: &RegInfo, usable: &AllocatableSet) -> Pressure { + let mut p = Pressure { + aliased: 0, + toprc: Default::default(), + }; + + // Get the layout of aliasing top-level register classes from the register banks. + for bank in reginfo.banks { + let first = bank.first_toprc; + let num = bank.num_toprcs; + for rc in &mut p.toprc[first..first + num] { + rc.first_toprc = first as u8; + rc.num_toprcs = num as u8; + } + + // Flag the top-level register classes with aliases. + if num > 1 { + p.aliased |= ((1 << num) - 1) << first; + } + } + + // Compute per-class limits from `usable`. + for (toprc, rc) in p.toprc + .iter_mut() + .take_while(|t| t.num_toprcs > 0) + .zip(reginfo.classes) { + toprc.limit = usable.iter(rc).len() as u32; + toprc.width = rc.width; + } + + p + } + + /// Check for an available register in the register class `rc`. + /// + /// If it is possible to allocate one more register from `rc`'s top-level register class, + /// returns 0. + /// + /// If not, returns a bit-mask of top-level register classes that are interfering. Register + /// pressure should be eased in one of the returned top-level register classes before calling + /// `can_take()` to check again. + pub fn check_avail(&self, rc: RegClass) -> RegClassMask { + let entry = &self.toprc[rc.toprc as usize]; + let mask = 1 << rc.toprc; + if self.aliased & mask == 0 { + // This is a simple unaliased top-level register class. + if entry.count < entry.limit { 0 } else { mask } + } else { + // This is the more complicated case. The top-level register class has aliases. + self.check_avail_aliased(entry) + } + } + + /// Check for an available register in a top-level register class that may have aliases. + /// + /// This is the out-of-line slow path for `check_avail()`. + fn check_avail_aliased(&self, entry: &TopRC) -> RegClassMask { + let first = entry.first_toprc as usize; + let num = entry.num_toprcs as usize; + let width = entry.width as u32; + let ulimit = entry.limit * width; + + // Count up the number of available register units. + let mut units = 0; + for (rc, rci) in self.toprc[first..first + num].iter().zip(first..) { + let rcw = rc.width as u32; + // If `rc.width` is smaller than `width`, each register in `rc` could potentially block + // one of ours. This is assuming that none of the smaller registers are straddling the + // bigger ones. + // + // If `rc.width` is larger than `width`, we are also assuming that the registers are + // aligned and `rc.width` is a multiple of `width`. + let u = if rcw < width { + // We can't take more than the total number of register units in the class. + // This matters for arm32 S-registers which can only ever lock out 16 D-registers. + min(rc.count * width, rc.limit * rcw) + } else { + rc.count * rcw + }; + + // If this top-level RC on its own is responsible for exceeding our limit, return it + // early to guarantee that registers here are spilled before spilling other registers + // unnecessarily. + if u >= ulimit { + return 1 << rci; + } + + units += u; + } + + // We've counted up the worst-case number of register units claimed by all aliasing + // classes. Compare to the unit limit in this class. + if units < ulimit { + 0 + } else { + // Registers need to be spilled from any one of the aliasing classes. + ((1 << num) - 1) << first + } + } + + /// Take a register from `rc`. + /// + /// This assumes that `can_take(rc)` already returned 0. + pub fn take(&mut self, rc: RegClass) { + self.toprc[rc.toprc as usize].count += 1 + } + + /// Free a register in `rc`. + pub fn free(&mut self, rc: RegClass) { + self.toprc[rc.toprc as usize].count -= 1 + } + + /// Reset all counts to 0. + pub fn reset(&mut self) { + for e in self.toprc.iter_mut() { + e.count = 0; + } + } +} + +#[cfg(test)] +mod tests { + use isa::{TargetIsa, RegClass}; + use regalloc::AllocatableSet; + use std::borrow::Borrow; + use super::Pressure; + + // Make an arm32 `TargetIsa`, if possible. + fn arm32() -> Option> { + use settings; + use isa; + + let shared_builder = settings::builder(); + let shared_flags = settings::Flags::new(&shared_builder); + + isa::lookup("arm32").map(|b| b.finish(shared_flags)) + } + + // Get a register class by name. + fn rc_by_name(isa: &TargetIsa, name: &str) -> RegClass { + isa.register_info() + .classes + .iter() + .find(|rc| rc.name == name) + .expect("Can't find named register class.") + } + + #[test] + fn basic_counting() { + let isa = arm32().expect("This test requires arm32 support"); + let isa = isa.borrow(); + let gpr = rc_by_name(isa, "GPR"); + let s = rc_by_name(isa, "S"); + let reginfo = isa.register_info(); + let regs = AllocatableSet::new(); + + let mut pressure = Pressure::new(®info, ®s); + let mut count = 0; + while pressure.check_avail(gpr) == 0 { + pressure.take(gpr); + count += 1; + } + assert_eq!(count, 16); + assert_eq!(pressure.check_avail(gpr), 1 << gpr.toprc); + assert_eq!(pressure.check_avail(s), 0); + pressure.free(gpr); + assert_eq!(pressure.check_avail(gpr), 0); + pressure.take(gpr); + assert_eq!(pressure.check_avail(gpr), 1 << gpr.toprc); + assert_eq!(pressure.check_avail(s), 0); + pressure.reset(); + assert_eq!(pressure.check_avail(gpr), 0); + assert_eq!(pressure.check_avail(s), 0); + } + + #[test] + fn arm_float_bank() { + let isa = arm32().expect("This test requires arm32 support"); + let isa = isa.borrow(); + let s = rc_by_name(isa, "S"); + let d = rc_by_name(isa, "D"); + let q = rc_by_name(isa, "Q"); + let reginfo = isa.register_info(); + let regs = AllocatableSet::new(); + + let mut pressure = Pressure::new(®info, ®s); + assert_eq!(pressure.check_avail(s), 0); + assert_eq!(pressure.check_avail(d), 0); + assert_eq!(pressure.check_avail(q), 0); + + // Allocating a single S-register should not affect availability. + pressure.take(s); + assert_eq!(pressure.check_avail(s), 0); + assert_eq!(pressure.check_avail(d), 0); + assert_eq!(pressure.check_avail(q), 0); + + pressure.take(d); + assert_eq!(pressure.check_avail(s), 0); + assert_eq!(pressure.check_avail(d), 0); + assert_eq!(pressure.check_avail(q), 0); + + pressure.take(q); + assert_eq!(pressure.check_avail(s), 0); + assert_eq!(pressure.check_avail(d), 0); + assert_eq!(pressure.check_avail(q), 0); + + // Take a total of 16 S-regs. + for _ in 1..16 { + pressure.take(s); + } + assert_eq!(pressure.check_avail(s), 0); + assert_eq!(pressure.check_avail(d), 0); + assert_eq!(pressure.check_avail(q), 0); + + // We've taken 16 S, 1 D, and 1 Q. There should be 6 more Qs. + for _ in 0..6 { + assert_eq!(pressure.check_avail(d), 0); + assert_eq!(pressure.check_avail(q), 0); + pressure.take(q); + } + + // We've taken 16 S, 1 D, and 7 Qs. + assert!(pressure.check_avail(s) != 0); + assert_eq!(pressure.check_avail(d), 0); + assert!(pressure.check_avail(q) != 0); + } +} From dc809628f4c00ddbfad27a98d578273e2b46406f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 18 May 2017 18:18:57 -0700 Subject: [PATCH 770/968] Start a very simple GVN pass (#79) * Skeleton simple_gvn pass. * Basic testing infrastructure for simple-gvn. * Add can_load and can_store flags to instructions. * Move the replace_values function into the DataFlowGraph. * Make InstructionData derive from Hash, PartialEq, and Eq. * Make EntityList's hash and eq functions panic. * Change Ieee32 and Ieee64 to store u32 and u64, respectively. --- docs/testing.rst | 8 ++++ filetests/simple_gvn/basic.cton | 11 +++++ lib/cretonne/meta/base/instructions.py | 30 ++++++------ lib/cretonne/meta/cdsl/instructions.py | 4 ++ lib/cretonne/meta/gen_instr.py | 2 +- lib/cretonne/src/context.rs | 17 +++++-- lib/cretonne/src/entity_list.rs | 18 +++++++ lib/cretonne/src/ir/condcodes.rs | 4 +- lib/cretonne/src/ir/dfg.rs | 40 ++++++++++++++++ lib/cretonne/src/ir/immediates.rs | 56 +++++++++------------- lib/cretonne/src/ir/instructions.rs | 2 +- lib/cretonne/src/ir/memflags.rs | 2 +- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/simple_gvn.rs | 65 ++++++++++++++++++++++++++ src/filetest/mod.rs | 2 + src/filetest/simple_gvn.rs | 51 ++++++++++++++++++++ 16 files changed, 256 insertions(+), 57 deletions(-) create mode 100644 filetests/simple_gvn/basic.cton create mode 100644 lib/cretonne/src/simple_gvn.rs create mode 100644 src/filetest/simple_gvn.rs diff --git a/docs/testing.rst b/docs/testing.rst index f58a413033..d4aa1a881c 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -344,3 +344,11 @@ sequences. Instead the test will fail. Value locations must be present if they are required to compute the binary bits. Missing value locations will cause the test to crash. + +`test simple-gvn` +----------------- + +Test the simple GVN pass. + +The simple GVN pass is run on each function, and then results are run +through filecheck. diff --git a/filetests/simple_gvn/basic.cton b/filetests/simple_gvn/basic.cton new file mode 100644 index 0000000000..20544666fc --- /dev/null +++ b/filetests/simple_gvn/basic.cton @@ -0,0 +1,11 @@ +test simple-gvn + +function simple_redundancy(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = iadd v0, v1 + v3 = iadd v0, v1 +; check: v3 -> v2 + v4 = imul v2, v3 +; check: v4 = imul $v2, $v3 + return v4 +} diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 5777ed76ea..b4458ec585 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -201,7 +201,7 @@ load = Instruction( This is a polymorphic instruction that can load any value type which has a memory representation. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) store = Instruction( 'store', r""" @@ -210,7 +210,7 @@ store = Instruction( This is a polymorphic instruction that can store any value type with a memory representation. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) iExt8 = TypeVar( 'iExt8', 'An integer type with more than 8 bits', @@ -224,7 +224,7 @@ uload8 = Instruction( This is equivalent to ``load.i8`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) sload8 = Instruction( 'sload8', r""" @@ -232,7 +232,7 @@ sload8 = Instruction( This is equivalent to ``load.i8`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) istore8 = Instruction( 'istore8', r""" @@ -240,7 +240,7 @@ istore8 = Instruction( This is equivalent to ``ireduce.i8`` followed by ``store.i8``. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) iExt16 = TypeVar( 'iExt16', 'An integer type with more than 16 bits', @@ -254,7 +254,7 @@ uload16 = Instruction( This is equivalent to ``load.i16`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) sload16 = Instruction( 'sload16', r""" @@ -262,7 +262,7 @@ sload16 = Instruction( This is equivalent to ``load.i16`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) istore16 = Instruction( 'istore16', r""" @@ -270,7 +270,7 @@ istore16 = Instruction( This is equivalent to ``ireduce.i16`` followed by ``store.i8``. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) iExt32 = TypeVar( 'iExt32', 'An integer type with more than 32 bits', @@ -284,7 +284,7 @@ uload32 = Instruction( This is equivalent to ``load.i32`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) sload32 = Instruction( 'sload32', r""" @@ -292,7 +292,7 @@ sload32 = Instruction( This is equivalent to ``load.i32`` followed by ``uextend``. """, - ins=(Flags, p, Offset), outs=a) + ins=(Flags, p, Offset), outs=a, can_load=True) istore32 = Instruction( 'istore32', r""" @@ -300,7 +300,7 @@ istore32 = Instruction( This is equivalent to ``ireduce.i32`` followed by ``store.i8``. """, - ins=(Flags, x, p, Offset)) + ins=(Flags, x, p, Offset), can_store=True) x = Operand('x', Mem, doc='Value to be stored') a = Operand('a', Mem, doc='Value loaded') @@ -316,7 +316,7 @@ stack_load = Instruction( access cannot go out of bounds, i.e. :math:`sizeof(a) + Offset <= sizeof(SS)`. """, - ins=(SS, Offset), outs=a) + ins=(SS, Offset), outs=a, can_load=True) stack_store = Instruction( 'stack_store', r""" @@ -329,7 +329,7 @@ stack_store = Instruction( access cannot go out of bounds, i.e. :math:`sizeof(a) + Offset <= sizeof(SS)`. """, - ins=(x, SS, Offset)) + ins=(x, SS, Offset), can_store=True) stack_addr = Instruction( 'stack_addr', r""" @@ -357,7 +357,7 @@ heap_load = Instruction( Trap if the heap access would be out of bounds. """, - ins=(p, Offset), outs=a) + ins=(p, Offset), outs=a, can_load=True) heap_store = Instruction( 'heap_store', r""" @@ -365,7 +365,7 @@ heap_store = Instruction( Trap if the heap access would be out of bounds. """, - ins=(x, p, Offset)) + ins=(x, p, Offset), can_store=True) heap_addr = Instruction( 'heap_addr', r""" diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 6724b775ea..d8e9d24e5f 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -85,6 +85,8 @@ class Instruction(object): :param is_call: This is a call instruction. :param is_return: This is a return instruction. :param can_trap: This instruction can trap. + :param can_load: This instruction can load from memory. + :param can_store: This instruction can store to memory. """ # Boolean instruction attributes that can be passed as keyword arguments to @@ -95,6 +97,8 @@ class Instruction(object): 'is_branch': 'True for all branch or jump instructions.', 'is_call': 'Is this a call instruction?', 'is_return': 'Is this a return instruction?', + 'can_load': 'Can this instruction read from memory?', + 'can_store': 'Can this instruction write to memory?', 'can_trap': 'Can this instruction cause a trap?', } diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index f3a4343ab2..548cd45a29 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -228,7 +228,7 @@ def gen_opcodes(groups, fmt): fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') fmt.doc_comment('All instructions from all supported ISAs are present.') - fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]') + fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]') instrs = [] # We explicitly set the discriminant of the first variant to 1, which diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index d8b6af4d0b..ce4b269574 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -17,6 +17,7 @@ use legalize_function; use regalloc; use result::CtonResult; use verifier; +use simple_gvn::do_simple_gvn; /// Persistent data structures and compilation pipeline. pub struct Context { @@ -51,16 +52,16 @@ impl Context { /// /// Also check that the dominator tree and control flow graph are consistent with the function. /// - /// The `TargetIsa` argument is currently unused, but the verifier will soon be able to also + /// The `isa` argument is currently unused, but the verifier will soon be able to also /// check ISA-dependent constraints. - pub fn verify<'a, ISA: Into>>(&self, isa: ISA) -> verifier::Result { - verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa.into()) + pub fn verify<'a>(&self, isa: Option<&TargetIsa>) -> verifier::Result { + verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa) } /// Run the verifier only if the `enable_verifier` setting is true. pub fn verify_if(&self, isa: &TargetIsa) -> CtonResult { if isa.flags().enable_verifier() { - self.verify(isa).map_err(Into::into) + self.verify(Some(isa)).map_err(Into::into) } else { Ok(()) } @@ -78,6 +79,14 @@ impl Context { self.domtree.compute(&self.func, &self.cfg); } + /// Perform simple GVN on the function. + pub fn simple_gvn(&mut self) -> CtonResult { + do_simple_gvn(&mut self.func, &mut self.cfg); + // TODO: Factor things such that we can get a Flags and test + // enable_verifier(). + self.verify(None).map_err(Into::into) + } + /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index b6cf3099eb..5121d8e4cc 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -46,6 +46,7 @@ //! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is //! reserved for the empty list which isn't allocated in the vector. +use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::mem; @@ -59,6 +60,10 @@ use entity_map::EntityRef; /// Entity lists can be cloned, but that operation should only be used as part of cloning the whole /// function they belong to. *Cloning an entity list does not allocate new memory for the clone*. /// It creates an alias of the same memory. +/// +/// Entity lists can also be hashed and compared for equality, but those operations just panic if, +/// they're ever actually called, because it's not possible to compare the contents of the list +/// without the pool reference. #[derive(Clone, Debug)] pub struct EntityList { index: u32, @@ -75,6 +80,19 @@ impl Default for EntityList { } } +impl Hash for EntityList { + fn hash(&self, _: &mut H) { + panic!("hash called on EntityList"); + } +} + +impl PartialEq for EntityList { + fn eq(&self, _: &EntityList) -> bool { + panic!("eq called on EntityList"); + } +} +impl Eq for EntityList {} + /// A memory pool for storing lists of `T`. #[derive(Clone)] pub struct ListPool { diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs index 5117e39d9d..520014471d 100644 --- a/lib/cretonne/src/ir/condcodes.rs +++ b/lib/cretonne/src/ir/condcodes.rs @@ -27,7 +27,7 @@ pub trait CondCode: Copy { /// This condition code is used by the `icmp` instruction to compare integer values. There are /// separate codes for comparing the integers as signed or unsigned numbers where it makes a /// difference. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum IntCC { /// `==`. Equal, @@ -139,7 +139,7 @@ impl FromStr for IntCC { /// The condition codes described here are used to produce a single boolean value from the /// comparison. The 14 condition codes here cover every possible combination of the relation above /// except the impossible `!UN & !EQ & !LT & !GT` and the always true `UN | EQ | LT | GT`. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum FloatCC { /// EQ | LT | GT Ordered, diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index d5eb5ab73d..81c3142680 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -243,6 +243,46 @@ impl DataFlowGraph { self.values[dest] = ValueData::Alias { ty, original }; } + /// Replace the results of one instruction with aliases to the results of another. + /// + /// Change all the results of `dest_inst` to behave as aliases of + /// corresponding results of `src_inst`, as if calling change_to_alias for + /// each. + /// + /// After calling this instruction, `dest_inst` will have had its results + /// cleared, so it likely needs to be removed from the graph. + /// + pub fn replace_with_aliases(&mut self, dest_inst: Inst, src_inst: Inst) { + debug_assert_ne!(dest_inst, + src_inst, + "Replacing {} with itself would create a loop", + dest_inst); + debug_assert_eq!(self.results[dest_inst].len(&self.value_lists), + self.results[src_inst].len(&self.value_lists), + "Replacing {} with {} would produce a different number of results.", + dest_inst, + src_inst); + + for (&dest, &src) in self.results[dest_inst] + .as_slice(&self.value_lists) + .iter() + .zip(self.results[src_inst].as_slice(&self.value_lists)) { + let original = src; + let ty = self.value_type(original); + assert_eq!(self.value_type(dest), + ty, + "Aliasing {} to {} would change its type {} to {}", + dest, + src, + self.value_type(dest), + ty); + + self.values[dest] = ValueData::Alias { ty, original }; + } + + self.clear_results(dest_inst); + } + /// Create a new value alias. /// /// Note that this function should only be called by the parser. diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index e061b88482..44c4cd3fc3 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -14,7 +14,7 @@ use std::str::FromStr; /// /// An `Imm64` operand can also be used to represent immediate values of smaller integer types by /// sign-extending to `i64`. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Imm64(i64); impl Imm64 { @@ -153,7 +153,7 @@ pub type Uimm8 = u8; /// /// This is used to encode an immediate offset for load/store instructions. All supported ISAs have /// a maximum load/store offset that fits in an `i32`. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Offset32(i32); impl Offset32 { @@ -220,7 +220,7 @@ impl FromStr for Offset32 { /// 32-bit unsigned immediate offset. /// /// This is used to encode an immediate offset for WebAssembly heap_load/heap_store instructions. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Uoffset32(u32); impl Uoffset32 { @@ -282,17 +282,19 @@ impl FromStr for Uoffset32 { } } -/// An IEEE binary32 immediate floating point value. +/// An IEEE binary32 immediate floating point value, represented as a u32 +/// containing the bitpattern. /// /// All bit patterns are allowed. -#[derive(Copy, Clone, Debug)] -pub struct Ieee32(f32); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Ieee32(u32); -/// An IEEE binary64 immediate floating point value. +/// An IEEE binary64 immediate floating point value, represented as a u64 +/// containing the bitpattern. /// /// All bit patterns are allowed. -#[derive(Copy, Clone, Debug)] -pub struct Ieee64(f64); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Ieee64(u64); // Format a floating point number in a way that is reasonably human-readable, and that can be // converted back to binary without any rounding issues. The hexadecimal formatting of normal and @@ -531,18 +533,13 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { impl Ieee32 { /// Create a new `Ieee32` representing the number `x`. pub fn new(x: f32) -> Ieee32 { - Ieee32(x) - } - - /// Construct `Ieee32` immediate from raw bits. - pub fn from_bits(x: u32) -> Ieee32 { Ieee32(unsafe { mem::transmute(x) }) } } impl Display for Ieee32 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let bits: u32 = unsafe { mem::transmute(self.0) }; + let bits: u32 = self.0; format_float(bits as u64, 8, 23, f) } } @@ -552,7 +549,7 @@ impl FromStr for Ieee32 { fn from_str(s: &str) -> Result { match parse_float(s, 8, 23) { - Ok(b) => Ok(Ieee32::from_bits(b as u32)), + Ok(b) => Ok(Ieee32(b as u32)), Err(s) => Err(s), } } @@ -561,18 +558,13 @@ impl FromStr for Ieee32 { impl Ieee64 { /// Create a new `Ieee64` representing the number `x`. pub fn new(x: f64) -> Ieee64 { - Ieee64(x) - } - - /// Construct `Ieee64` immediate from raw bits. - pub fn from_bits(x: u64) -> Ieee64 { Ieee64(unsafe { mem::transmute(x) }) } } impl Display for Ieee64 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let bits: u64 = unsafe { mem::transmute(self.0) }; + let bits: u64 = self.0; format_float(bits, 11, 52, f) } } @@ -582,7 +574,7 @@ impl FromStr for Ieee64 { fn from_str(s: &str) -> Result { match parse_float(s, 11, 52) { - Ok(b) => Ok(Ieee64::from_bits(b)), + Ok(b) => Ok(Ieee64(b)), Err(s) => Err(s), } } @@ -743,11 +735,11 @@ mod tests { assert_eq!(Ieee32::new(f32::NAN).to_string(), "+NaN"); assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(Ieee32::from_bits(0x7fc00001).to_string(), "+NaN:0x1"); - assert_eq!(Ieee32::from_bits(0x7ff00001).to_string(), "+NaN:0x300001"); + assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1"); + assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001"); // Signaling NaNs. - assert_eq!(Ieee32::from_bits(0x7f800001).to_string(), "+sNaN:0x1"); - assert_eq!(Ieee32::from_bits(0x7fa00001).to_string(), "+sNaN:0x200001"); + assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee32(0x7fa00001).to_string(), "+sNaN:0x200001"); } #[test] @@ -845,14 +837,12 @@ mod tests { assert_eq!(Ieee64::new(f64::NAN).to_string(), "+NaN"); assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. - assert_eq!(Ieee64::from_bits(0x7ff8000000000001).to_string(), - "+NaN:0x1"); - assert_eq!(Ieee64::from_bits(0x7ffc000000000001).to_string(), + assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1"); + assert_eq!(Ieee64(0x7ffc000000000001).to_string(), "+NaN:0x4000000000001"); // Signaling NaNs. - assert_eq!(Ieee64::from_bits(0x7ff0000000000001).to_string(), - "+sNaN:0x1"); - assert_eq!(Ieee64::from_bits(0x7ff4000000000001).to_string(), + assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee64(0x7ff4000000000001).to_string(), "+sNaN:0x4000000000001"); } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 4301c7796c..66c6d039b0 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -98,7 +98,7 @@ impl FromStr for Opcode { /// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a /// `Box` to store the additional information out of line. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[allow(missing_docs)] pub enum InstructionData { Nullary { opcode: Opcode }, diff --git a/lib/cretonne/src/ir/memflags.rs b/lib/cretonne/src/ir/memflags.rs index bd2f2bab19..8d0694e1a0 100644 --- a/lib/cretonne/src/ir/memflags.rs +++ b/lib/cretonne/src/ir/memflags.rs @@ -14,7 +14,7 @@ const NAMES: [&str; 2] = ["notrap", "aligned"]; /// Each of these flags introduce a limited form of undefined behavior. The flags each enable /// certain optimizations that need to make additional assumptions. Generally, the semantics of a /// program does not change when a flag is removed, but adding a flag will. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct MemFlags { bits: u8, } diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 1f51b38d3f..264e808cbf 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -35,5 +35,6 @@ mod packed_option; mod partition_slice; mod predicates; mod ref_slice; +mod simple_gvn; mod topo_order; mod write; diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs new file mode 100644 index 0000000000..cfd13128fe --- /dev/null +++ b/lib/cretonne/src/simple_gvn.rs @@ -0,0 +1,65 @@ +//! A simple GVN pass. + +use flowgraph::ControlFlowGraph; +use dominator_tree::DominatorTree; +use ir::{Cursor, InstructionData, Function, Inst, Opcode}; +use std::collections::HashMap; + +/// Test whether the given opcode is unsafe to even consider for GVN. +fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool { + opcode.is_call() || opcode.is_branch() || opcode.is_terminator() || opcode.is_return() || + opcode.can_trap() +} + +/// Perform simple GVN on `func`. +/// +pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { + let mut visible_values: HashMap = HashMap::new(); + + let domtree = DominatorTree::with_function(func, &cfg); + + // Visit EBBs in a reverse post-order. + let mut postorder = cfg.postorder_ebbs(); + let mut pos = Cursor::new(&mut func.layout); + + while let Some(ebb) = postorder.pop() { + pos.goto_top(ebb); + + while let Some(inst) = pos.next_inst() { + let opcode = func.dfg[inst].opcode(); + + if trivially_unsafe_for_gvn(opcode) { + continue; + } + + // TODO: Implement simple redundant-load elimination. + if opcode.can_store() { + continue; + } + if opcode.can_load() { + continue; + } + + let key = func.dfg[inst].clone(); + let entry = visible_values.entry(key); + use std::collections::hash_map::Entry::*; + match entry { + Occupied(mut entry) => { + if domtree.dominates(*entry.get(), inst, &pos.layout) { + func.dfg.replace_with_aliases(inst, *entry.get()); + pos.remove_inst(); + } else { + // The prior instruction doesn't dominate inst, so it + // won't dominate any subsequent instructions we'll + // visit, so just replace it. + *entry.get_mut() = inst; + continue; + } + } + Vacant(entry) => { + entry.insert(inst); + } + } + } + } +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 7a379ec0fe..961c3ff2f2 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -20,6 +20,7 @@ mod legalizer; mod regalloc; mod runner; mod runone; +mod simple_gvn; mod verifier; /// The result of running the test in a file. @@ -62,6 +63,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "legalizer" => legalizer::subtest(parsed), "regalloc" => regalloc::subtest(parsed), "binemit" => binemit::subtest(parsed), + "simple-gvn" => simple_gvn::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/filetest/simple_gvn.rs b/src/filetest/simple_gvn.rs new file mode 100644 index 0000000000..f220cbdde7 --- /dev/null +++ b/src/filetest/simple_gvn.rs @@ -0,0 +1,51 @@ +//! Test command for testing the simple GVN pass. +//! +//! The `simple-gvn` test command runs each function through the simple GVN pass after ensuring +//! that all instructions are legal for the target. +//! +//! The resulting function is sent to `filecheck`. + +use cretonne::ir::Function; +use cretonne; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use std::borrow::Cow; +use std::fmt::Write; +use utils::pretty_error; + +struct TestSimpleGVN; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "simple-gvn"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestSimpleGVN)) + } +} + +impl SubTest for TestSimpleGVN { + fn name(&self) -> Cow { + Cow::from("simple-gvn") + } + + fn is_mutating(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + comp_ctx.flowgraph(); + comp_ctx + .simple_gvn() + .map_err(|e| pretty_error(&comp_ctx.func, e))?; + + let mut text = String::new(); + write!(&mut text, "{}", &comp_ctx.func) + .map_err(|e| e.to_string())?; + run_filecheck(&text, context) + } +} From 059845880c18839e6c5519aadeae50f3ec73662f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 25 May 2017 16:37:31 -0700 Subject: [PATCH 771/968] Fix more GVN issues (#83) * Fix GVN skipping the instruction after a deleted instruction. * Teach GVN to resolve aliases as it proceeds. * Clean up an obsolete reference to extended_values. --- filetests/simple_gvn/basic.cton | 14 +++++++++-- lib/cretonne/src/ir/dfg.rs | 42 ++++++++++++++++++++++++--------- lib/cretonne/src/ir/layout.rs | 12 ++++++++++ lib/cretonne/src/simple_gvn.rs | 5 +++- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/filetests/simple_gvn/basic.cton b/filetests/simple_gvn/basic.cton index 20544666fc..06d0989d1e 100644 --- a/filetests/simple_gvn/basic.cton +++ b/filetests/simple_gvn/basic.cton @@ -4,8 +4,18 @@ function simple_redundancy(i32, i32) -> i32 { ebb0(v0: i32, v1: i32): v2 = iadd v0, v1 v3 = iadd v0, v1 -; check: v3 -> v2 v4 = imul v2, v3 -; check: v4 = imul $v2, $v3 +; check: v4 = imul $v2, $v2 return v4 } + +function cascading_redundancy(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = iadd v0, v1 + v3 = iadd v0, v1 + v4 = imul v2, v3 + v5 = imul v2, v2 + v6 = iadd v4, v5 +; check: v6 = iadd $v4, $v4 + return v6 +} diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 81c3142680..35d7a18fa4 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -106,6 +106,23 @@ impl DataFlowGraph { } } +/// Resolve value aliases. +/// +/// Find the original SSA value that `value` aliases. +fn resolve_aliases(values: &EntityMap, value: Value) -> Value { + let mut v = value; + + // Note that values may be empty here. + for _ in 0..1 + values.len() { + if let ValueData::Alias { original, .. } = values[v] { + v = original; + } else { + return v; + } + } + panic!("Value alias loop detected for {}", value); +} + /// Handling values. /// /// Values are either EBB arguments or instruction results. @@ -176,17 +193,7 @@ impl DataFlowGraph { /// /// Find the original SSA value that `value` aliases. pub fn resolve_aliases(&self, value: Value) -> Value { - let mut v = value; - - // Note that extended_values may be empty here. - for _ in 0..1 + self.values.len() { - if let ValueData::Alias { original, .. } = self.values[v] { - v = original; - } else { - return v; - } - } - panic!("Value alias loop detected for {}", value); + resolve_aliases(&self.values, value) } /// Resolve value copies. @@ -216,6 +223,19 @@ impl DataFlowGraph { panic!("Copy loop detected for {}", value); } + /// Resolve all aliases among inst's arguments. + /// + /// For each argument of inst which is defined by an alias, replace the + /// alias with the aliased value. + pub fn resolve_aliases_in_arguments(&mut self, inst: Inst) { + for arg in self.insts[inst].arguments_mut(&mut self.value_lists) { + let resolved = resolve_aliases(&self.values, *arg); + if resolved != *arg { + *arg = resolved; + } + } + } + /// Turn a value into an alias of another. /// /// Change the `dest` value to behave as an alias of `src`. This means that all uses of `dest` diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 4e6f738653..06ab92612b 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -923,6 +923,18 @@ impl<'f> Cursor<'f> { inst } + /// Remove the instruction under the cursor. + /// + /// The cursor is left pointing at the position preceding the current instruction. + /// + /// Return the instruction that was removed. + pub fn remove_inst_and_step_back(&mut self) -> Inst { + let inst = self.current_inst().expect("No instruction to remove"); + self.prev_inst(); + self.layout.remove_inst(inst); + inst + } + /// Insert an EBB at the current position and switch to it. /// /// As far as possible, this method behaves as if the EBB header were an instruction inserted diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs index cfd13128fe..a7665e965d 100644 --- a/lib/cretonne/src/simple_gvn.rs +++ b/lib/cretonne/src/simple_gvn.rs @@ -28,6 +28,9 @@ pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { while let Some(inst) = pos.next_inst() { let opcode = func.dfg[inst].opcode(); + // Resolve aliases, particularly aliases we created earlier. + func.dfg.resolve_aliases_in_arguments(inst); + if trivially_unsafe_for_gvn(opcode) { continue; } @@ -47,7 +50,7 @@ pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { Occupied(mut entry) => { if domtree.dominates(*entry.get(), inst, &pos.layout) { func.dfg.replace_with_aliases(inst, *entry.get()); - pos.remove_inst(); + pos.remove_inst_and_step_back(); } else { // The prior instruction doesn't dominate inst, so it // won't dominate any subsequent instructions we'll From cb35869803728d0f8ed9f8dbdb8844e233b146ae Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Thu, 1 Jun 2017 20:48:36 +0500 Subject: [PATCH 772/968] Remove unnecessary cloned() in reader::lexer::trailing_digits() --- lib/reader/src/lexer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index c5c2aa5d80..bbbb023490 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -87,8 +87,7 @@ fn trailing_digits(s: &str) -> usize { s.as_bytes() .iter() .rev() - .cloned() - .take_while(|&b| b'0' <= b && b <= b'9') + .take_while(|&&b| b'0' <= b && b <= b'9') .count() } From 7d6113e479210e3985372b90e346983008367e8a Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Fri, 2 Jun 2017 15:00:29 -0700 Subject: [PATCH 773/968] Loop analysis of the IL * Implemented in two passes * First pass discovers the loops headers (they dominate one of their predecessors) * Second pass traverses the blocks of each loop * Discovers the loop tree structure * Offers a new LoopAnalysis data structure queried from outside the module --- lib/cretonne/src/context.rs | 5 + lib/cretonne/src/entity_list.rs | 2 +- lib/cretonne/src/entity_map.rs | 20 +- lib/cretonne/src/ir/dfg.rs | 1 - lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/loop_analysis.rs | 336 ++++++++++++++++++++++++++++++ 6 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 lib/cretonne/src/loop_analysis.rs diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index ce4b269574..9b0b3b31a7 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -12,6 +12,7 @@ use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; +use loop_analysis::LoopAnalysis; use isa::TargetIsa; use legalize_function; use regalloc; @@ -32,6 +33,9 @@ pub struct Context { /// Register allocation context. pub regalloc: regalloc::Context, + + /// Loop analysis of `func`. + pub loop_analysis: LoopAnalysis, } impl Context { @@ -45,6 +49,7 @@ impl Context { cfg: ControlFlowGraph::new(), domtree: DominatorTree::new(), regalloc: regalloc::Context::new(), + loop_analysis: LoopAnalysis::new(), } } diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 5121d8e4cc..946bb7ba04 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -94,7 +94,7 @@ impl PartialEq for EntityList { impl Eq for EntityList {} /// A memory pool for storing lists of `T`. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ListPool { // The main array containing the lists. data: Vec, diff --git a/lib/cretonne/src/entity_map.rs b/lib/cretonne/src/entity_map.rs index cee4215a4e..8227a19fb6 100644 --- a/lib/cretonne/src/entity_map.rs +++ b/lib/cretonne/src/entity_map.rs @@ -71,7 +71,7 @@ impl EntityMap pub fn keys(&self) -> Keys { Keys { pos: 0, - len: self.elems.len(), + rev_pos: self.elems.len(), unused: PhantomData, } } @@ -183,7 +183,7 @@ pub struct Keys where K: EntityRef { pos: usize, - len: usize, + rev_pos: usize, unused: PhantomData, } @@ -193,7 +193,7 @@ impl Iterator for Keys type Item = K; fn next(&mut self) -> Option { - if self.pos < self.len { + if self.pos < self.rev_pos { let k = K::new(self.pos); self.pos += 1; Some(k) @@ -203,6 +203,20 @@ impl Iterator for Keys } } +impl DoubleEndedIterator for Keys + where K: EntityRef +{ + fn next_back(&mut self) -> Option { + if self.rev_pos > self.pos { + let k = K::new(self.rev_pos - 1); + self.rev_pos -= 1; + Some(k) + } else { + None + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 35d7a18fa4..c3c5422e75 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -8,7 +8,6 @@ use ir::layout::Cursor; use ir::types; use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueListPool}; use write::write_operands; - use std::fmt; use std::iter; use std::ops::{Index, IndexMut}; diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 264e808cbf..5ac263d4ff 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -20,6 +20,7 @@ pub mod entity_map; pub mod flowgraph; pub mod ir; pub mod isa; +pub mod loop_analysis; pub mod regalloc; pub mod result; pub mod settings; diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs new file mode 100644 index 0000000000..1e36ced8f9 --- /dev/null +++ b/lib/cretonne/src/loop_analysis.rs @@ -0,0 +1,336 @@ +//! A loop analysis represented as mappings of loops to their header Ebb +//! and parent in the loop tree. + +use ir::{Function, Ebb, Layout}; +use flowgraph::ControlFlowGraph; +use dominator_tree::DominatorTree; +use entity_map::{EntityMap, PrimaryEntityData}; +use packed_option::{PackedOption, ReservedValue}; +use entity_map::{EntityRef, Keys}; +use std::u32; + +/// A opaque reference to a code loop. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct Loop(u32); +impl EntityRef for Loop { + fn new(index: usize) -> Self { + assert!(index < (u32::MAX as usize)); + Loop(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } +} + +impl ReservedValue for Loop { + fn reserved_value() -> Loop { + Loop(u32::MAX) + } +} + + +/// Loop tree information for a single function. +/// +/// Loops are referenced by the Loop object, and for each loop you can access its header EBB, +/// its eventual parent in the loop tree and all the EBB belonging to the loop. +pub struct LoopAnalysis { + loops: EntityMap, + ebb_loop_map: EntityMap>, +} + +struct LoopData { + header: Ebb, + parent: PackedOption, +} + +impl PrimaryEntityData for LoopData {} + +impl LoopData { + /// Creates a `LoopData` object with the loop header and its eventual parent in the loop tree. + pub fn new(header: Ebb, parent: Option) -> LoopData { + LoopData { + header: header, + parent: parent.into(), + } + } +} + +/// Methods for querying the loop analysis. +impl LoopAnalysis { + /// Allocate a new blank loop analysis struct. Use `compute` to compute the loop analysis for + /// a function. + pub fn new() -> LoopAnalysis { + LoopAnalysis { + loops: EntityMap::new(), + ebb_loop_map: EntityMap::new(), + } + } + + /// Returns all the loops contained in a function. + pub fn loops(&self) -> Keys { + self.loops.keys() + } + + /// Returns the header EBB of a particular loop. + /// + /// The characteristic property of a loop header block is that it dominates some of its + /// predecessors. + pub fn loop_header(&self, lp: Loop) -> Ebb { + self.loops[lp].header + } + + /// Return the eventual parent of a loop in the loop tree. + pub fn loop_parent(&self, lp: Loop) -> Option { + self.loops[lp].parent.expand() + } + + /// Determine if an Ebb belongs to a loop by running a finger along the loop tree. + /// + /// Returns `true` if `ebb` is in loop `lp`. + pub fn is_in_loop(&self, ebb: Ebb, lp: Loop) -> bool { + let ebb_loop = self.ebb_loop_map[ebb]; + match ebb_loop.expand() { + None => false, + Some(ebb_loop) => self.is_child_loop(ebb_loop, lp), + } + } + + /// Determines if a loop is contained in another loop. + /// + /// `is_child_loop(child,parent)` returns `true` if and only if `child` is a child loop of + /// `parent` (or `child == parent`). + pub fn is_child_loop(&self, child: Loop, parent: Loop) -> bool { + let mut finger = Some(child); + while let Some(finger_loop) = finger { + if finger_loop == parent { + return true; + } + finger = self.loop_parent(finger_loop); + } + false + } +} + +impl LoopAnalysis { + /// Detects the loops in a function. Needs the control flow graph and the dominator tree. + pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) { + self.loops.clear(); + self.ebb_loop_map.clear(); + self.ebb_loop_map.resize(func.dfg.num_ebbs()); + self.find_loop_headers(cfg, domtree, &func.layout); + self.discover_loop_blocks(cfg, domtree, &func.layout) + } + + // Traverses the CFG in reverse postorder and create a loop object for every EBB having a + // back edge. + fn find_loop_headers(&mut self, + cfg: &ControlFlowGraph, + domtree: &DominatorTree, + layout: &Layout) { + // We traverse the CFg in reverse postorder + for ebb in cfg.postorder_ebbs().iter().rev() { + for &(_, pred_inst) in cfg.get_predecessors(*ebb) { + // If the ebb dominates one of its predecessors it is a back edge + if domtree.ebb_dominates(ebb.clone(), pred_inst, layout) { + // This ebb is a loop header, so we create its associated loop + let lp = self.loops.push(LoopData::new(*ebb, None)); + self.ebb_loop_map[*ebb] = lp.into(); + break; + // We break because we only need one back edge to identify a loop header. + } + } + } + } + + // Intended to be called after `find_loop_headers`. For each detected loop header, + // discovers all the ebb belonging to the loop and its inner loops. After a call to this + // function, the loop tree is fully constructed. + fn discover_loop_blocks(&mut self, + cfg: &ControlFlowGraph, + domtree: &DominatorTree, + layout: &Layout) { + let mut stack: Vec = Vec::new(); + // We handle each loop header in reverse order, corresponding to a pesudo postorder + // traversal of the graph. + for lp in self.loops().rev() { + for &(pred, pred_inst) in cfg.get_predecessors(self.loops[lp].header) { + // We follow the back edges + if domtree.ebb_dominates(self.loops[lp].header, pred_inst, layout) { + stack.push(pred); + } + } + while let Some(node) = stack.pop() { + let continue_dfs: Option; + match self.ebb_loop_map[node].expand() { + None => { + // The node hasn't been visited yet, we tag it as part of the loop + self.ebb_loop_map[node] = PackedOption::from(lp); + continue_dfs = Some(node); + } + Some(node_loop) => { + // We copy the node_loop into a mutable reference passed along the while + let mut node_loop = node_loop; + // The node is part of a loop, which can be lp or an inner loop + let mut node_loop_parent_option = self.loops[node_loop].parent; + while let Some(node_loop_parent) = node_loop_parent_option.expand() { + if node_loop_parent == lp { + // We have encounterd lp so we stop (already visited) + break; + } else { + // + node_loop = node_loop_parent; + // We lookup the parent loop + node_loop_parent_option = self.loops[node_loop].parent; + } + } + // Now node_loop_parent is either: + // - None and node_loop is an new inner loop of lp + // - Some(...) and the initial node_loop was a known inner loop of lp + match node_loop_parent_option.expand() { + Some(_) => continue_dfs = None, + None => { + if node_loop != lp { + self.loops[node_loop].parent = lp.into(); + continue_dfs = Some(self.loops[node_loop].header) + } else { + // If lp is a one-block loop then we make sure we stop + continue_dfs = None + } + } + } + } + } + // Now we have handled the popped node and need to continue the DFS by adding the + // predecessors of that node + if let Some(continue_dfs) = continue_dfs { + for &(pred, _) in cfg.get_predecessors(continue_dfs) { + stack.push(pred) + } + } + } + + } + } +} + +#[cfg(test)] +mod test { + + use ir::{Function, InstBuilder, Cursor, types}; + use loop_analysis::{Loop, LoopAnalysis}; + use flowgraph::ControlFlowGraph; + use dominator_tree::DominatorTree; + + #[test] + fn nested_loops_detection() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + let ebb3 = func.dfg.make_ebb(); + let cond = func.dfg.append_ebb_arg(ebb0, types::I32); + + { + let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); + + cur.insert_ebb(ebb0); + dfg.ins(cur).jump(ebb1, &[]); + + cur.insert_ebb(ebb1); + dfg.ins(cur).jump(ebb2, &[]); + + cur.insert_ebb(ebb2); + dfg.ins(cur).brnz(cond, ebb1, &[]); + dfg.ins(cur).jump(ebb3, &[]); + + cur.insert_ebb(ebb3); + dfg.ins(cur).brnz(cond, ebb0, &[]); + + } + + let mut loop_analysis = LoopAnalysis::new(); + let mut cfg = ControlFlowGraph::new(); + let mut domtree = DominatorTree::new(); + cfg.compute(&func); + domtree.compute(&func, &cfg); + loop_analysis.compute(&func, &cfg, &domtree); + + let loops = loop_analysis.loops().collect::>(); + assert_eq!(loops.len(), 2); + assert_eq!(loop_analysis.loop_header(loops[0]), ebb0); + assert_eq!(loop_analysis.loop_header(loops[1]), ebb1); + assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0])); + assert_eq!(loop_analysis.loop_parent(loops[0]), None); + assert_eq!(loop_analysis.is_in_loop(ebb0, loops[0]), true); + assert_eq!(loop_analysis.is_in_loop(ebb0, loops[1]), false); + assert_eq!(loop_analysis.is_in_loop(ebb1, loops[1]), true); + assert_eq!(loop_analysis.is_in_loop(ebb1, loops[0]), true); + assert_eq!(loop_analysis.is_in_loop(ebb2, loops[1]), true); + assert_eq!(loop_analysis.is_in_loop(ebb2, loops[0]), true); + assert_eq!(loop_analysis.is_in_loop(ebb3, loops[0]), true); + assert_eq!(loop_analysis.is_in_loop(ebb0, loops[1]), false); + } + + #[test] + fn complex_loop_detection() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + let ebb3 = func.dfg.make_ebb(); + let ebb4 = func.dfg.make_ebb(); + let ebb5 = func.dfg.make_ebb(); + let cond = func.dfg.append_ebb_arg(ebb0, types::I32); + + { + let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); + + cur.insert_ebb(ebb0); + dfg.ins(cur).brnz(cond, ebb1, &[]); + dfg.ins(cur).jump(ebb3, &[]); + + cur.insert_ebb(ebb1); + dfg.ins(cur).jump(ebb2, &[]); + + cur.insert_ebb(ebb2); + dfg.ins(cur).brnz(cond, ebb1, &[]); + dfg.ins(cur).jump(ebb5, &[]); + + cur.insert_ebb(ebb3); + dfg.ins(cur).jump(ebb4, &[]); + + cur.insert_ebb(ebb4); + dfg.ins(cur).brnz(cond, ebb3, &[]); + dfg.ins(cur).jump(ebb5, &[]); + + cur.insert_ebb(ebb5); + dfg.ins(cur).brnz(cond, ebb0, &[]); + + } + + let mut loop_analysis = LoopAnalysis::new(); + let mut cfg = ControlFlowGraph::new(); + let mut domtree = DominatorTree::new(); + cfg.compute(&func); + domtree.compute(&func, &cfg); + loop_analysis.compute(&func, &cfg, &domtree); + + let loops = loop_analysis.loops().collect::>(); + assert_eq!(loops.len(), 3); + assert_eq!(loop_analysis.loop_header(loops[0]), ebb0); + assert_eq!(loop_analysis.loop_header(loops[1]), ebb1); + assert_eq!(loop_analysis.loop_header(loops[2]), ebb3); + assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0])); + assert_eq!(loop_analysis.loop_parent(loops[2]), Some(loops[0])); + assert_eq!(loop_analysis.loop_parent(loops[0]), None); + assert_eq!(loop_analysis.is_in_loop(ebb0, loops[0]), true); + assert_eq!(loop_analysis.is_in_loop(ebb1, loops[1]), true); + assert_eq!(loop_analysis.is_in_loop(ebb2, loops[1]), true); + assert_eq!(loop_analysis.is_in_loop(ebb3, loops[2]), true); + assert_eq!(loop_analysis.is_in_loop(ebb4, loops[2]), true); + assert_eq!(loop_analysis.is_in_loop(ebb5, loops[0]), true); + } +} From 22ad3c0bf8e8f0f64470e471c19f1b8c0ec2c799 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 2 Jun 2017 15:08:02 -0700 Subject: [PATCH 774/968] Compute a CFG post-order when building the dominator tree. The DominatorTree has existing DomNodes per EBB that can be used in lieu of expensive HastSets for the depth-first traversal of the CFG. Make the computed and cached post-order available for other passes through the `cfg_postorder()` method which returns a slice. The post-order algorithm is essentially the same as the one in ControlFlowGraph::postorder_ebbs(), except it will never push a successor node that has already been visited once. This is more efficient, but it generates a different post-order. Change the cfg_traversal tests to check this new algorithm. --- lib/cretonne/src/dominator_tree.rs | 115 +++++++++++++++++++++++------ tests/cfg_traversal.rs | 37 +++++----- 2 files changed, 111 insertions(+), 41 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index ed6aa4dbd6..6d42cefb36 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -25,6 +25,12 @@ struct DomNode { /// The dominator tree for a single function. pub struct DominatorTree { nodes: EntityMap, + + // CFG post-order of all reachable EBBs. + postorder: Vec, + + // Scratch memory used by `compute_postorder()`. + stack: Vec, } /// Methods for querying the dominator tree. @@ -34,6 +40,14 @@ impl DominatorTree { self.nodes[ebb].rpo_number != 0 } + /// Get the CFG post-order of EBBs that was used to compute the dominator tree. + /// + /// Note that this post-order is not updated automatically when the CFG is modified. It is + /// computed from scratch and cached by `compute()`. + pub fn cfg_postorder(&self) -> &[Ebb] { + &self.postorder + } + /// Returns the immediate dominator of `ebb`. /// /// The immediate dominator of an extended basic block is a basic block which we represent by @@ -134,7 +148,11 @@ impl DominatorTree { /// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a /// function. pub fn new() -> DominatorTree { - DominatorTree { nodes: EntityMap::new() } + DominatorTree { + nodes: EntityMap::new(), + postorder: Vec::new(), + stack: Vec::new(), + } } /// Allocate and compute a dominator tree. @@ -144,39 +162,91 @@ impl DominatorTree { domtree } - /// Build a dominator tree from a control flow graph using Keith D. Cooper's - /// "Simple, Fast Dominator Algorithm." + /// Reset and compute a CFG post-order and dominator tree. pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) { + self.compute_postorder(func, cfg); + self.compute_domtree(func, cfg); + } + + /// Reset all internal data structures and compute a post-order for `cfg`. + /// + /// This leaves `rpo_number == 1` for all reachable EBBs, 0 for unreachable ones. + fn compute_postorder(&mut self, func: &Function, cfg: &ControlFlowGraph) { self.nodes.clear(); self.nodes.resize(func.dfg.num_ebbs()); + self.postorder.clear(); + assert!(self.stack.is_empty()); - // We'll be iterating over a reverse post-order of the CFG. - // This vector only contains reachable EBBs. - let mut postorder = cfg.postorder_ebbs(); + // During this algorithm only, use `rpo_number` to hold the following state: + // + // 0: EBB never reached. + // 2: EBB has been pushed once, so it shouldn't be pushed again. + // 1: EBB has already been popped once, and should be added to the post-order next time. + const SEEN: u32 = 2; + const DONE: u32 = 1; - // Remove the entry block, and abort if the function is empty. - // The last block visited in a post-order traversal must be the entry block. - let entry_block = match postorder.pop() { - Some(ebb) => ebb, + match func.layout.entry_block() { + Some(ebb) => { + self.nodes[ebb].rpo_number = SEEN; + self.stack.push(ebb) + } + None => return, + } + + while let Some(ebb) = self.stack.pop() { + match self.nodes[ebb].rpo_number { + // This is the first time we visit `ebb`, forming a pre-order. + SEEN => { + // Mark it as done and re-queue it to be visited after its children. + self.nodes[ebb].rpo_number = DONE; + self.stack.push(ebb); + for &succ in cfg.get_successors(ebb) { + // Only push children that haven't been seen before. + if self.nodes[succ].rpo_number == 0 { + self.nodes[succ].rpo_number = SEEN; + self.stack.push(succ); + } + } + } + // This is the second time we popped `ebb`, so all its children have been visited. + // This is the post-order. + DONE => self.postorder.push(ebb), + _ => panic!("Inconsistent stack rpo_number"), + } + } + } + + /// Build a dominator tree from a control flow graph using Keith D. Cooper's + /// "Simple, Fast Dominator Algorithm." + fn compute_domtree(&mut self, func: &Function, cfg: &ControlFlowGraph) { + // During this algorithm, `rpo_number` has the following values: + // + // 0: EBB is not reachable. + // 1: EBB is reachable, but has not yet been visited during the first pass. This is set by + // `compute_postorder`. + // 2+: EBB is reachable and has an assigned RPO number. + + // We'll be iterating over a reverse post-order of the CFG, skipping the entry block. + let (entry_block, postorder) = match self.postorder.as_slice().split_last() { + Some((&eb, rest)) => (eb, rest), None => return, }; - assert_eq!(Some(entry_block), func.layout.entry_block()); + debug_assert_eq!(Some(entry_block), func.layout.entry_block()); // Do a first pass where we assign RPO numbers to all reachable nodes. - self.nodes[entry_block].rpo_number = 1; + self.nodes[entry_block].rpo_number = 2; for (rpo_idx, &ebb) in postorder.iter().rev().enumerate() { // Update the current node and give it an RPO number. - // The entry block got 1, the rest start at 2. + // The entry block got 2, the rest start at 3. // - // Nodes do not appear as reachable until the have an assigned RPO number, and - // `compute_idom` will only look at reachable nodes. This means that the function will - // never see an uninitialized predecessor. + // Since `compute_idom` will only look at nodes with an assigned RPO number, the + // function will never see an uninitialized predecessor. // // Due to the nature of the post-order traversal, every node we visit will have at // least one predecessor that has previously been visited during this RPO. self.nodes[ebb] = DomNode { idom: self.compute_idom(ebb, cfg, &func.layout).into(), - rpo_number: rpo_idx as u32 + 2, + rpo_number: rpo_idx as u32 + 3, } } @@ -200,13 +270,13 @@ impl DominatorTree { // Compute the immediate dominator for `ebb` using the current `idom` states for the reachable // nodes. fn compute_idom(&self, ebb: Ebb, cfg: &ControlFlowGraph, layout: &Layout) -> Inst { - // Get an iterator with just the reachable predecessors to `ebb`. - // Note that during the first pass, `is_reachable` returns false for blocks that haven't - // been visited yet. + // Get an iterator with just the reachable, already visited predecessors to `ebb`. + // Note that during the first pass, `rpo_number` is 1 for reachable blocks that haven't + // been visited yet, 0 for unreachable blocks. let mut reachable_preds = cfg.get_predecessors(ebb) .iter() .cloned() - .filter(|&(ebb, _)| self.is_reachable(ebb)); + .filter(|&(pred, _)| self.nodes[pred].rpo_number > 1); // The RPO must visit at least one predecessor before this node. let mut idom = reachable_preds @@ -233,6 +303,7 @@ mod test { let cfg = ControlFlowGraph::with_function(&func); let dtree = DominatorTree::with_function(&func, &cfg); assert_eq!(0, dtree.nodes.keys().count()); + assert_eq!(dtree.cfg_postorder(), &[]); } #[test] @@ -277,5 +348,7 @@ mod test { assert!(dt.dominates(br_ebb1_ebb0, br_ebb1_ebb0, &func.layout)); assert!(!dt.dominates(br_ebb1_ebb0, jmp_ebb3_ebb1, &func.layout)); assert!(dt.dominates(jmp_ebb3_ebb1, br_ebb1_ebb0, &func.layout)); + + assert_eq!(dt.cfg_postorder(), &[ebb2, ebb0, ebb1, ebb3]); } } diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index 903b1a1ec1..21e2a086b2 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -1,30 +1,27 @@ extern crate cretonne; extern crate cton_reader; -use self::cretonne::entity_map::EntityMap; use self::cretonne::flowgraph::ControlFlowGraph; +use self::cretonne::dominator_tree::DominatorTree; use self::cretonne::ir::Ebb; use self::cton_reader::parse_functions; fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) { let func = &parse_functions(function_source).unwrap()[0]; let cfg = ControlFlowGraph::with_function(&func); - let ebbs = ebb_order + let domtree = DominatorTree::with_function(&func, &cfg); + + let got = domtree + .cfg_postorder() .iter() - .map(|n| Ebb::with_number(*n).unwrap()) - .collect::>(); - - let mut postorder_ebbs = cfg.postorder_ebbs(); - let mut postorder_map = EntityMap::with_capacity(postorder_ebbs.len()); - for (i, ebb) in postorder_ebbs.iter().enumerate() { - postorder_map[ebb.clone()] = i + 1; - } - postorder_ebbs.reverse(); - - assert_eq!(postorder_ebbs.len(), ebbs.len()); - for ebb in postorder_ebbs { - assert_eq!(ebb, ebbs[ebbs.len() - postorder_map[ebb]]); - } + .rev() + .cloned() + .collect::>(); + let want = ebb_order + .iter() + .map(|&n| Ebb::with_number(n).unwrap()) + .collect::>(); + assert_eq!(got, want); } #[test] @@ -53,7 +50,7 @@ fn simple_traversal() { trap } ", - vec![0, 2, 1, 3, 4, 5]); + vec![0, 1, 3, 2, 4, 5]); } #[test] @@ -71,7 +68,7 @@ fn loops_one() { return } ", - vec![0, 1, 2, 3]); + vec![0, 1, 3, 2]); } #[test] @@ -96,7 +93,7 @@ fn loops_two() { return } ", - vec![0, 1, 2, 5, 4, 3]); + vec![0, 1, 2, 4, 3, 5]); } #[test] @@ -126,7 +123,7 @@ fn loops_three() { return } ", - vec![0, 1, 2, 5, 4, 3, 6, 7]); + vec![0, 1, 2, 4, 3, 6, 7, 5]); } #[test] From 920e32ed49bc9e5209c7b69f89657ea163572846 Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Sat, 3 Jun 2017 16:16:39 +0500 Subject: [PATCH 775/968] Move lib/filecheck/src/tests directory to lib/filecheck --- lib/filecheck/{src => }/tests/basic.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/filecheck/{src => }/tests/basic.rs (100%) diff --git a/lib/filecheck/src/tests/basic.rs b/lib/filecheck/tests/basic.rs similarity index 100% rename from lib/filecheck/src/tests/basic.rs rename to lib/filecheck/tests/basic.rs From 80d92ccbb5cdd8c98073e889a7bd47d87a9658b7 Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Sat, 3 Jun 2017 16:20:41 +0500 Subject: [PATCH 776/968] Run rustfmt on lib/filecheck/tests/basic.rs --- lib/filecheck/tests/basic.rs | 65 +++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/lib/filecheck/tests/basic.rs b/lib/filecheck/tests/basic.rs index 595158192c..15e74788d7 100644 --- a/lib/filecheck/tests/basic.rs +++ b/lib/filecheck/tests/basic.rs @@ -28,7 +28,10 @@ fn no_directives() { #[test] fn no_matches() { - let c = CheckerBuilder::new().text("regex: FOO=bar").unwrap().finish(); + let c = CheckerBuilder::new() + .text("regex: FOO=bar") + .unwrap() + .finish(); assert!(!c.is_empty()); // An empty checker matches anything. @@ -260,14 +263,22 @@ fn unordered() { .unwrap() .finish(); - assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), + Ok(true)); - assert_eq!(c.check("one two four three four", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three four two four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two four three four", NO_VARIABLES) + .map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three four two four", NO_VARIABLES) + .map_err(e2s), + Ok(true)); - assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), Ok(false)); - assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), Ok(false)); + assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), + Ok(false)); + assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), + Ok(false)); } #[test] @@ -281,14 +292,22 @@ fn leading_unordered() { .unwrap() .finish(); - assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), + Ok(true)); - assert_eq!(c.check("one two four three four", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three four two four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two four three four", NO_VARIABLES) + .map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three four two four", NO_VARIABLES) + .map_err(e2s), + Ok(true)); - assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), Ok(false)); - assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), Ok(false)); + assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), + Ok(false)); + assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), + Ok(false)); } #[test] @@ -302,12 +321,20 @@ fn trailing_unordered() { .unwrap() .finish(); - assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two three four", NO_VARIABLES).map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three two four", NO_VARIABLES).map_err(e2s), + Ok(true)); - assert_eq!(c.check("one two four three four", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three four two four", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two four three four", NO_VARIABLES) + .map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three four two four", NO_VARIABLES) + .map_err(e2s), + Ok(true)); - assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), Ok(true)); + assert_eq!(c.check("one two four three", NO_VARIABLES).map_err(e2s), + Ok(true)); + assert_eq!(c.check("one three four two", NO_VARIABLES).map_err(e2s), + Ok(true)); } From 90958729fa607542195dd5b71c355cdb5c9016db Mon Sep 17 00:00:00 2001 From: Igor Date: Sat, 3 Jun 2017 22:40:53 +0300 Subject: [PATCH 777/968] Updated the regex crate to 0.2.2 as per issue #88 (#90) * Updated the regex crate to 0.2.2 as per issue #88 * Added potential fix for cryptic CI error. * Fixed incorrect handling of regex name call. Fixes #88 --- lib/filecheck/Cargo.toml | 2 +- lib/filecheck/src/checker.rs | 19 ++++++++++--------- lib/filecheck/src/pattern.rs | 10 +++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/filecheck/Cargo.toml b/lib/filecheck/Cargo.toml index ee31a8fa42..41effb059d 100644 --- a/lib/filecheck/Cargo.toml +++ b/lib/filecheck/Cargo.toml @@ -11,4 +11,4 @@ documentation = "https://docs.rs/filecheck" name = "filecheck" [dependencies] -regex = "0.1.71" +regex = "0.2.2" diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index ee44a769e5..6dcbdd2620 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -30,8 +30,8 @@ const DIRECTIVE_RX: &str = r"\b(check|sameln|nextln|unordered|not|regex):\s+(.*) impl Directive { /// Create a new directive from a `DIRECTIVE_RX` match. fn new(caps: Captures) -> Result { - let cmd = caps.at(1).expect("group 1 must match"); - let rest = caps.at(2).expect("group 2 must match"); + let cmd = caps.get(1).map(|m| m.as_str()).expect("group 1 must match"); + let rest = caps.get(2).map(|m| m.as_str()).expect("group 2 must match"); if cmd == "regex" { return Directive::regex(rest); @@ -208,11 +208,12 @@ impl Checker { // Verify any pending `not:` directives now that we know their range. for (not_idx, not_begin, rx) in nots.drain(..) { state.recorder.directive(not_idx); - if let Some((s, e)) = rx.find(&text[not_begin..match_begin]) { + if let Some(mat) = rx.find(&text[not_begin..match_begin]) { // Matched `not:` pattern. state .recorder - .matched_not(rx.as_str(), (not_begin + s, not_begin + e)); + .matched_not(rx.as_str(), + (not_begin + mat.start(), not_begin + mat.end())); return Ok(false); } else { state @@ -337,23 +338,23 @@ impl<'a> State<'a> { } else { // We need the captures to define variables. rx.captures(txt).map(|caps| { - let matched_range = caps.pos(0).expect("whole expression must match"); + let matched_range = caps.get(0).expect("whole expression must match"); for var in defs { - let txtval = caps.name(var).unwrap_or(""); + let txtval = caps.name(var).map(|mat| mat.as_str()).unwrap_or(""); self.recorder.defined_var(var, txtval); let vardef = VarDef { value: Value::Text(Cow::Borrowed(txtval)), // This offset is the end of the whole matched pattern, not just the text // defining the variable. - offset: range.0 + matched_range.1, + offset: range.0 + matched_range.end(), }; self.vars.insert(var.clone(), vardef); } matched_range }) }; - Ok(if let Some((b, e)) = matched_range { - let r = (range.0 + b, range.0 + e); + Ok(if let Some(mat) = matched_range { + let r = (range.0 + mat.start(), range.0 + mat.end()); self.recorder.matched_check(rx.as_str(), r); Some(r) } else { diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs index 67ec359c4a..7eb3c8fa30 100644 --- a/lib/filecheck/src/pattern.rs +++ b/lib/filecheck/src/pattern.rs @@ -4,7 +4,7 @@ use error::{Error, Result}; use variable::{varname_prefix, VariableMap, Value}; use std::str::FromStr; use std::fmt::{self, Display, Formatter, Write}; -use regex::{Regex, RegexBuilder, quote}; +use regex::{Regex, RegexBuilder, escape}; /// A pattern to match as specified in a directive. /// @@ -313,7 +313,7 @@ impl Pattern { for part in &self.parts { match *part { Part::Text(ref s) => { - out.push_str("e(s)); + out.push_str(&escape(s)); } Part::Regex(ref rx) => out.push_str(rx), Part::Var(ref var) => { @@ -322,7 +322,7 @@ impl Pattern { None => { return Err(Error::UndefVariable(format!("undefined variable ${}", var))) } - Some(Value::Text(s)) => out.push_str("e(&s)), + Some(Value::Text(s)) => out.push_str(&escape(&s)), // Wrap regex in non-capturing group for safe concatenation. Some(Value::Regex(rx)) => write!(out, "(?:{})", rx).unwrap(), } @@ -335,7 +335,7 @@ impl Pattern { None => { return Err(Error::UndefVariable(format!("undefined variable ${}", var))) } - Some(Value::Text(s)) => write!(out, "{})", quote(&s[..])).unwrap(), + Some(Value::Text(s)) => write!(out, "{})", escape(&s[..])).unwrap(), Some(Value::Regex(rx)) => write!(out, "{})", rx).unwrap(), } } @@ -353,7 +353,7 @@ impl Pattern { } } - Ok(RegexBuilder::new(&out).multi_line(true).compile()?) + Ok(RegexBuilder::new(&out).multi_line(true).build()?) } } From 2d8588d72a436b9ac82234e16da5c0c09f35bfe7 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 5 Jun 2017 15:07:48 -0700 Subject: [PATCH 778/968] Add a dfg::replace_result() method. This is analogous to replace_ebb_arg(). It replaces an instruction result value with a new value, leaving the old value in a detached state. --- lib/cretonne/src/ir/dfg.rs | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index c3c5422e75..363b80d2c1 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -10,6 +10,7 @@ use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueLis use write::write_operands; use std::fmt; use std::iter; +use std::mem; use std::ops::{Index, IndexMut}; use std::u16; @@ -509,6 +510,36 @@ impl DataFlowGraph { }; } + /// Replace an instruction result with a new value of type `new_type`. + /// + /// The `old_value` must be an attached instruction result. + /// + /// The old value is left detached, so it should probably be changed into something else. + /// + /// Returns the new value. + pub fn replace_result(&mut self, old_value: Value, new_type: Type) -> Value { + let (num, inst) = match self.values[old_value] { + ValueData::Inst { num, inst, .. } => (num, inst), + _ => panic!("{} is not an instruction result value", old_value), + }; + let new_value = self.make_value(ValueData::Inst { + ty: new_type, + num, + inst, + }); + let num = num as usize; + let attached = mem::replace(self.results[inst] + .get_mut(num, &mut self.value_lists) + .expect("Replacing detached result"), + new_value); + assert_eq!(attached, + old_value, + "{} wasn't detached from {}", + old_value, + self.display_inst(inst)); + new_value + } + /// Append a new instruction result value to `inst`. pub fn append_result(&mut self, inst: Inst, ty: Type) -> Value { let res = self.values.next_key(); @@ -767,6 +798,15 @@ mod tests { assert_eq!(dfg.value_def(val), ValueDef::Res(inst, 0)); assert_eq!(dfg.value_type(val), types::I32); + + // Replacing results. + assert!(dfg.value_is_attached(val)); + let v2 = dfg.replace_result(val, types::F64); + assert!(!dfg.value_is_attached(val)); + assert!(dfg.value_is_attached(v2)); + assert_eq!(dfg.inst_results(inst), &[v2]); + assert_eq!(dfg.value_def(v2), ValueDef::Res(inst, 0)); + assert_eq!(dfg.value_type(v2), types::F64); } #[test] From 9b06f760573f836c2c7eba8df931fe30fda42fa4 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Wed, 7 Jun 2017 11:27:22 -0700 Subject: [PATCH 779/968] LICM pass (#87) * LICM pass * Uses loop analysis to detect loop tree * For each loop (starting with the inner ones), create a pre-header and move there loop-invariant instructions * An instruction is loop invariant if it does not use as argument a value defined earlier in the loop * File tests to check LICM's correctness * Optimized pre-header creation If the loop already has a natural pre-header, we use it instead of creating a new one. The natural pre-header of a loop is the only predecessor of the header it doesn't dominate. --- filetests/licm/basic.cton | 31 +++++ filetests/licm/complex.cton | 81 +++++++++++ filetests/licm/multiple-blocks.cton | 46 ++++++ filetests/licm/nested_loops.cton | 52 +++++++ lib/cretonne/src/context.rs | 10 ++ lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/licm.rs | 208 ++++++++++++++++++++++++++++ lib/cretonne/src/loop_analysis.rs | 10 +- src/filetest/licm.rs | 51 +++++++ src/filetest/mod.rs | 2 + 10 files changed, 487 insertions(+), 5 deletions(-) create mode 100644 filetests/licm/basic.cton create mode 100644 filetests/licm/complex.cton create mode 100644 filetests/licm/multiple-blocks.cton create mode 100644 filetests/licm/nested_loops.cton create mode 100644 lib/cretonne/src/licm.rs create mode 100644 src/filetest/licm.rs diff --git a/filetests/licm/basic.cton b/filetests/licm/basic.cton new file mode 100644 index 0000000000..637b910f53 --- /dev/null +++ b/filetests/licm/basic.cton @@ -0,0 +1,31 @@ +test licm + +function simple_loop(i32) -> i32 { + +ebb1(v0: i32): + v1 = iconst.i32 1 + v2 = iconst.i32 2 + v3 = iadd v1, v2 + brz v0, ebb2(v0) + v4 = isub v0, v1 + jump ebb1(v4) + +ebb2(v5: i32): + return v5 + +} +; sameln: function simple_loop(i32) -> i32 { +; nextln: ebb2(v6: i32): +; nextln: v1 = iconst.i32 1 +; nextln: v2 = iconst.i32 2 +; nextln: v3 = iadd v1, v2 +; nextln: jump ebb0(v6) +; nextln: +; nextln: ebb0(v0: i32): +; nextln: brz v0, ebb1(v0) +; nextln: v4 = isub v0, v1 +; nextln: jump ebb0(v4) +; nextln: +; nextln: ebb1(v5: i32): +; nextln: return v5 +; nextln: } diff --git a/filetests/licm/complex.cton b/filetests/licm/complex.cton new file mode 100644 index 0000000000..fead0cf746 --- /dev/null +++ b/filetests/licm/complex.cton @@ -0,0 +1,81 @@ +test licm + +function complex(i32) -> i32 { + +ebb0(v0: i32): + v1 = iconst.i32 1 + v19 = iconst.i32 4 + v2 = iadd v1, v0 + brz v0, ebb1(v1) + jump ebb3(v2) + +ebb1(v3: i32): + v4 = iconst.i32 2 + v5 = iadd v3, v2 + v6 = iadd v4, v0 + jump ebb2(v6) + +ebb2(v7: i32): + v8 = iadd v7, v3 + v9 = iadd v0, v2 + brz v0, ebb1(v7) + jump ebb5(v8) + +ebb3(v10: i32): + v11 = iconst.i32 3 + v12 = iadd v10, v11 + v13 = iadd v2, v11 + jump ebb4(v11) + +ebb4(v14: i32): + v15 = iadd v12, v2 + brz v0, ebb3(v14) + jump ebb5(v14) + +ebb5(v16: i32): + v17 = iadd v16, v1 + v18 = iadd v1, v19 + brz v0, ebb0(v18) + return v17 +} + +; sameln: function complex(i32) -> i32 { +; nextln: ebb6(v20: i32): +; nextln: v1 = iconst.i32 1 +; nextln: v2 = iconst.i32 4 +; nextln: v5 = iconst.i32 2 +; nextln: v12 = iconst.i32 3 +; nextln: v19 = iadd v1, v2 +; nextln: jump ebb0(v20) +; nextln: +; nextln: ebb0(v0: i32): +; nextln: v3 = iadd.i32 v1, v0 +; nextln: v7 = iadd.i32 v5, v0 +; nextln: v10 = iadd v0, v3 +; nextln: brz v0, ebb1(v1) +; nextln: v14 = iadd v3, v12 +; nextln: jump ebb3(v3) +; nextln: +; nextln: ebb1(v4: i32): +; nextln: v6 = iadd v4, v3 +; nextln: jump ebb2(v7) +; nextln: +; nextln: ebb2(v8: i32): +; nextln: v9 = iadd v8, v4 +; nextln: brz.i32 v0, ebb1(v8) +; nextln: jump ebb5(v9) +; nextln: +; nextln: ebb3(v11: i32): +; nextln: v13 = iadd v11, v12 +; nextln: jump ebb4(v12) +; nextln: +; nextln: ebb4(v15: i32): +; nextln: v16 = iadd.i32 v13, v3 +; nextln: brz.i32 v0, ebb3(v15) +; nextln: jump ebb5(v15) +; nextln: +; nextln: ebb5(v17: i32): +; nextln: v18 = iadd v17, v1 +; nextln: brz.i32 v0, ebb0(v19) +; nextln: return v18 +; nextln: } diff --git a/filetests/licm/multiple-blocks.cton b/filetests/licm/multiple-blocks.cton new file mode 100644 index 0000000000..54db640501 --- /dev/null +++ b/filetests/licm/multiple-blocks.cton @@ -0,0 +1,46 @@ +test licm + +function multiple_blocks(i32) -> i32 { + +ebb0(v0: i32): + jump ebb1(v0) + +ebb1(v10: i32): + v11 = iconst.i32 1 + v12 = iconst.i32 2 + v13 = iadd v11, v12 + brz v10, ebb2(v10) + v15 = isub v10, v11 + brz v15, ebb3(v15) + v14 = isub v10, v11 + jump ebb1(v14) + +ebb2(v20: i32): + return v20 + +ebb3(v30: i32): + v31 = iadd v11, v13 + jump ebb1(v30) + +} +; sameln:function multiple_blocks(i32) -> i32 { +; nextln: ebb0(v0: i32): +; nextln: v2 = iconst.i32 1 +; nextln: v3 = iconst.i32 2 +; nextln: v4 = iadd v2, v3 +; nextln: v9 = iadd v2, v4 +; nextln: jump ebb1(v0) +; nextln: +; nextln: ebb1(v1: i32): +; nextln: brz v1, ebb2(v1) +; nextln: v5 = isub v1, v2 +; nextln: brz v5, ebb3(v5) +; nextln: v6 = isub v1, v2 +; nextln: jump ebb1(v6) +; nextln: +; nextln: ebb2(v7: i32): +; nextln: return v7 +; nextln: +; nextln: ebb3(v8: i32): +; nextln: jump ebb1(v8) +; nextln: } diff --git a/filetests/licm/nested_loops.cton b/filetests/licm/nested_loops.cton new file mode 100644 index 0000000000..e2d3846a0f --- /dev/null +++ b/filetests/licm/nested_loops.cton @@ -0,0 +1,52 @@ +test licm + +function nested_loops(i32) -> i32 { + +ebb0(v0: i32): + v1 = iconst.i32 1 + v2 = iconst.i32 2 + v3 = iadd v1, v2 + v4 = isub v0, v1 + jump ebb1(v4,v4) + +ebb1(v10: i32,v11: i32): + brz v11, ebb2(v10) + v12 = iconst.i32 1 + v15 = iadd v12, v4 + v13 = isub v11, v12 + jump ebb1(v10,v13) + +ebb2(v20: i32): + brz v20, ebb3(v20) + jump ebb0(v20) + +ebb3(v30: i32): + return v30 + +} + +; sameln:function nested_loops(i32) -> i32 { +; nextln: ebb4(v12: i32): +; nextln: v1 = iconst.i32 1 +; nextln: v2 = iconst.i32 2 +; nextln: v3 = iadd v1, v2 +; nextln: v7 = iconst.i32 1 +; nextln: jump ebb0(v12) +; nextln: +; nextln: ebb0(v0: i32): +; nextln: v4 = isub v0, v1 +; nextln: v8 = iadd.i32 v7, v4 +; nextln: jump ebb1(v4, v4) +; nextln: +; nextln: ebb1(v5: i32, v6: i32): +; nextln: brz v6, ebb2(v5) +; nextln: v9 = isub v6, v7 +; nextln: jump ebb1(v5, v9) +; nextln: +; nextln: ebb2(v10: i32): +; nextln: brz v10, ebb3(v10) +; nextln: jump ebb0(v10) +; nextln: +; nextln: ebb3(v11: i32): +; nextln: return v11 +; nextln: } diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 9b0b3b31a7..a67a4bde0e 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -19,6 +19,7 @@ use regalloc; use result::CtonResult; use verifier; use simple_gvn::do_simple_gvn; +use licm::do_licm; /// Persistent data structures and compilation pipeline. pub struct Context { @@ -92,6 +93,15 @@ impl Context { self.verify(None).map_err(Into::into) } + /// Perform LICM on the function. + pub fn licm(&mut self) -> CtonResult { + do_licm(&mut self.func, + &mut self.cfg, + &mut self.domtree, + &mut self.loop_analysis); + self.verify(None).map_err(Into::into) + } + /// Run the register allocator. pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult { self.regalloc diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 5ac263d4ff..6ac2f7993b 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -32,6 +32,7 @@ mod constant_hash; mod context; mod iterators; mod legalizer; +mod licm; mod packed_option; mod partition_slice; mod predicates; diff --git a/lib/cretonne/src/licm.rs b/lib/cretonne/src/licm.rs new file mode 100644 index 0000000000..fc4b7e251b --- /dev/null +++ b/lib/cretonne/src/licm.rs @@ -0,0 +1,208 @@ +//! A Loop Invariant Code Motion optimization pass + +use ir::{Function, Ebb, Inst, Value, Cursor, Type, InstBuilder, Layout}; +use flowgraph::ControlFlowGraph; +use std::collections::HashSet; +use dominator_tree::DominatorTree; +use entity_list::{EntityList, ListPool}; +use loop_analysis::{Loop, LoopAnalysis}; + +/// Performs the LICM pass by detecting loops within the CFG and moving +/// loop-invariant instructions out of them. +/// Changes the CFG and domtree in-place during the operation. +pub fn do_licm(func: &mut Function, + cfg: &mut ControlFlowGraph, + domtree: &mut DominatorTree, + loop_analysis: &mut LoopAnalysis) { + loop_analysis.compute(func, cfg, domtree); + for lp in loop_analysis.loops() { + // For each loop that we want to optimize we determine the set of loop-invariant + // instructions + let invariant_inst = remove_loop_invariant_instructions(lp, func, cfg, loop_analysis); + // Then we create the loop's pre-header and fill it with the invariant instructions + // Then we remove the invariant instructions from the loop body + if invariant_inst.len() > 0 { + // If the loop has a natural pre-header we use it, otherwise we create it. + let mut pos; + match has_pre_header(&func.layout, + cfg, + domtree, + loop_analysis.loop_header(lp).clone()) { + None => { + let pre_header = create_pre_header(loop_analysis.loop_header(lp).clone(), + func, + cfg, + domtree); + pos = Cursor::new(&mut func.layout); + pos.goto_bottom(pre_header); + pos.prev_inst(); + } + // If there is a natural pre-header we insert new instructions just before the + // related jumping instruction (which is not necessarily at the end). + Some((_, last_inst)) => { + pos = Cursor::new(&mut func.layout); + pos.goto_inst(last_inst); + } + }; + // The last instruction of the pre-header is the termination instruction (usually + // a jump) so we need to insert just before this. + for inst in invariant_inst.iter() { + pos.insert_inst(inst.clone()); + } + } + } + // We have to recompute the domtree to account for the changes + cfg.compute(func); + domtree.compute(func, cfg); +} + +// Insert a pre-header before the header, modifying the function layout and CFG to reflect it. +// A jump instruction to the header is placed at the end of the pre-header. +fn create_pre_header(header: Ebb, + func: &mut Function, + cfg: &mut ControlFlowGraph, + domtree: &DominatorTree) + -> Ebb { + let pool = &mut ListPool::::new(); + let header_args_values: Vec = func.dfg + .ebb_args(header) + .into_iter() + .map(|val| *val) + .collect(); + let header_args_types: Vec = header_args_values + .clone() + .into_iter() + .map(|val| func.dfg.value_type(val)) + .collect(); + let pre_header = func.dfg.make_ebb(); + let mut pre_header_args_value: EntityList = EntityList::new(); + for typ in header_args_types { + pre_header_args_value.push(func.dfg.append_ebb_arg(pre_header, typ), pool); + } + for &(_, last_inst) in cfg.get_predecessors(header) { + // We only follow normal edges (not the back edges) + if !domtree.ebb_dominates(header.clone(), last_inst, &func.layout) { + change_branch_jump_destination(last_inst, pre_header, func); + } + } + { + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(header); + // Inserts the pre-header at the right place in the layout. + pos.insert_ebb(pre_header); + pos.next_inst(); + func.dfg + .ins(&mut pos) + .jump(header, pre_header_args_value.as_slice(pool)); + } + pre_header +} + +// Detects if a loop header has a natural pre-header. +// +// A loop header has a pre-header if there is only one predecessor that the header doesn't +// dominate. +// Returns the pre-header Ebb and the instruction jumping to the header. +fn has_pre_header(layout: &Layout, + cfg: &ControlFlowGraph, + domtree: &DominatorTree, + header: Ebb) + -> Option<(Ebb, Inst)> { + let mut result = None; + let mut found = false; + for &(pred_ebb, last_inst) in cfg.get_predecessors(header) { + // We only count normal edges (not the back edges) + if !domtree.ebb_dominates(header.clone(), last_inst, layout) { + if found { + // We have already found one, there are more than one + return None; + } else { + result = Some((pred_ebb, last_inst)); + found = true; + } + } + } + result +} + + +// Change the destination of a jump or branch instruction. Does nothing if called with a non-jump +// or non-branch instruction. +fn change_branch_jump_destination(inst: Inst, new_ebb: Ebb, func: &mut Function) { + match func.dfg[inst].branch_destination_mut() { + None => (), + Some(instruction_dest) => *instruction_dest = new_ebb, + } +} + +// Traverses a loop in reverse post-order from a header EBB and identify lopp-invariant +// instructions. Theseloop-invariant instructions are then removed from the code and returned +// (in reverse post-order) for later use. +fn remove_loop_invariant_instructions(lp: Loop, + func: &mut Function, + cfg: &ControlFlowGraph, + loop_analysis: &LoopAnalysis) + -> Vec { + let mut loop_values: HashSet = HashSet::new(); + let mut invariant_inst: Vec = Vec::new(); + let mut pos = Cursor::new(&mut func.layout); + // We traverse the loop EBB in reverse post-order. + for ebb in postorder_ebbs_loop(loop_analysis, cfg, lp).iter().rev() { + // Arguments of the EBB are loop values + for val in func.dfg.ebb_args(*ebb) { + loop_values.insert(val.clone()); + } + pos.goto_top(*ebb); + while let Some(inst) = pos.next_inst() { + if func.dfg.has_results(inst) && + func.dfg + .inst_args(inst) + .into_iter() + .all(|arg| !loop_values.contains(arg)) { + // If all the instruction's argument are defined outside the loop + // then this instruction is loop-invariant + invariant_inst.push(inst); + // We remove it from the loop + pos.remove_inst(); + pos.prev_inst(); + } else { + // If the instruction is not loop-invariant we push its results in the set of + // loop values + for out in func.dfg.inst_results(inst) { + loop_values.insert(out.clone()); + } + } + } + } + invariant_inst +} + +/// Return ebbs from a loop in post-order, starting from an entry point in the block. +pub fn postorder_ebbs_loop(loop_analysis: &LoopAnalysis, + cfg: &ControlFlowGraph, + lp: Loop) + -> Vec { + let mut grey = HashSet::new(); + let mut black = HashSet::new(); + let mut stack = vec![loop_analysis.loop_header(lp).clone()]; + let mut postorder = Vec::new(); + + while !stack.is_empty() { + let node = stack.pop().unwrap(); + if !grey.contains(&node) { + // This is a white node. Mark it as gray. + grey.insert(node); + stack.push(node); + // Get any children we've never seen before. + for child in cfg.get_successors(node) { + if loop_analysis.is_in_loop(child.clone(), lp) && !grey.contains(child) { + stack.push(child.clone()); + } + } + } else if !black.contains(&node) { + postorder.push(node.clone()); + black.insert(node.clone()); + } + } + postorder +} diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs index 1e36ced8f9..f67a18e9d0 100644 --- a/lib/cretonne/src/loop_analysis.rs +++ b/lib/cretonne/src/loop_analysis.rs @@ -129,13 +129,13 @@ impl LoopAnalysis { domtree: &DominatorTree, layout: &Layout) { // We traverse the CFg in reverse postorder - for ebb in cfg.postorder_ebbs().iter().rev() { - for &(_, pred_inst) in cfg.get_predecessors(*ebb) { + for &ebb in cfg.postorder_ebbs().iter().rev() { + for &(_, pred_inst) in cfg.get_predecessors(ebb) { // If the ebb dominates one of its predecessors it is a back edge - if domtree.ebb_dominates(ebb.clone(), pred_inst, layout) { + if domtree.ebb_dominates(ebb, pred_inst, layout) { // This ebb is a loop header, so we create its associated loop - let lp = self.loops.push(LoopData::new(*ebb, None)); - self.ebb_loop_map[*ebb] = lp.into(); + let lp = self.loops.push(LoopData::new(ebb, None)); + self.ebb_loop_map[ebb] = lp.into(); break; // We break because we only need one back edge to identify a loop header. } diff --git a/src/filetest/licm.rs b/src/filetest/licm.rs new file mode 100644 index 0000000000..dcde7dd7be --- /dev/null +++ b/src/filetest/licm.rs @@ -0,0 +1,51 @@ +//! Test command for testing the LICM pass. +//! +//! The `licm` test command runs each function through the LICM pass after ensuring +//! that all instructions are legal for the target. +//! +//! The resulting function is sent to `filecheck`. + +use cretonne::ir::Function; +use cretonne; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use std::borrow::Cow; +use std::fmt::Write; +use utils::pretty_error; + +struct TestLICM; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "licm"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestLICM)) + } +} + +impl SubTest for TestLICM { + fn name(&self) -> Cow { + Cow::from("licm") + } + + fn is_mutating(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + comp_ctx.flowgraph(); + comp_ctx + .licm() + .map_err(|e| pretty_error(&comp_ctx.func, e))?; + + let mut text = String::new(); + write!(&mut text, "{}", &comp_ctx.func) + .map_err(|e| e.to_string())?; + run_filecheck(&text, context) + } +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 961c3ff2f2..9c03cc6d4a 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -17,6 +17,7 @@ mod binemit; mod concurrent; mod domtree; mod legalizer; +mod licm; mod regalloc; mod runner; mod runone; @@ -61,6 +62,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "domtree" => domtree::subtest(parsed), "verifier" => verifier::subtest(parsed), "legalizer" => legalizer::subtest(parsed), + "licm" => licm::subtest(parsed), "regalloc" => regalloc::subtest(parsed), "binemit" => binemit::subtest(parsed), "simple-gvn" => simple_gvn::subtest(parsed), From f545c97cb0b553391f8dc08f91df64f26237854a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 7 Jun 2017 10:39:47 -0700 Subject: [PATCH 780/968] Add Liveness methods for updating live ranges. The create_dead() methods can create a live range for a new value, and extend_local() can extend a live range within an EBB where it is already live. This is enough to update liveness for new values as long as they stay local to their EBB. --- lib/cretonne/src/regalloc/liveness.rs | 43 +++++++++++++++++++++++++- lib/cretonne/src/regalloc/liverange.rs | 8 +++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index f2c9944d4b..3ced8ebc00 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -177,7 +177,7 @@ use flowgraph::ControlFlowGraph; use ir::dfg::ValueDef; -use ir::{Function, Value, Inst, Ebb}; +use ir::{Function, Value, Inst, Ebb, Layout, ProgramPoint}; use isa::{TargetIsa, EncInfo}; use regalloc::affinity::Affinity; use regalloc::liverange::LiveRange; @@ -297,6 +297,47 @@ impl Liveness { self.ranges.get(value) } + /// Create a new live range for `value`. + /// + /// The new live range will be defined at `def` with no extent, like a dead value. + /// + /// This asserts that `value` does not have an existing live range. + pub fn create_dead(&mut self, value: Value, def: PP, affinity: Affinity) + where PP: Into + { + let old = self.ranges + .insert(LiveRange::new(value, def.into(), affinity)); + assert!(old.is_none(), "{} already has a live range", value); + } + + /// Move the definition of `value` to `def`. + /// + /// The old and new def points must be in the same EBB, and before the end of the live range. + pub fn move_def_locally(&mut self, value: Value, def: PP) + where PP: Into + { + let mut lr = self.ranges.get_mut(value).expect("Value has no live range"); + lr.move_def_locally(def.into()); + } + + /// Locally extend the live range for `value` to reach `user`. + /// + /// It is assumed the `value` is already live before `user` in `ebb`. + /// + /// Returns a mutable reference to the value's affinity in case that also needs to be updated. + pub fn extend_locally(&mut self, + value: Value, + ebb: Ebb, + user: Inst, + layout: &Layout) + -> &mut Affinity { + debug_assert_eq!(Some(ebb), layout.inst_ebb(user)); + let mut lr = self.ranges.get_mut(value).expect("Value has no live range"); + let livein = lr.extend_in_ebb(ebb, user, layout); + assert!(!livein, "{} should already be live in {}", value, ebb); + &mut lr.affinity + } + /// Compute the live ranges of all SSA values used in `func`. /// This clears out any existing analysis stored in this data structure. pub fn compute(&mut self, isa: &TargetIsa, func: &Function, cfg: &ControlFlowGraph) { diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index a854cfc9ea..1cabd113dd 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -347,6 +347,14 @@ impl LiveRange { self.def_begin } + /// Move the definition of this value to a new program point. + /// + /// It is only valid to move the definition within the same EBB, and it can't be moved beyond + /// `def_local_end()`. + pub fn move_def_locally(&mut self, def: ProgramPoint) { + self.def_begin = def; + } + /// Get the local end-point of this live range in the EBB where it is defined. /// /// This can be the EBB header itself in the case of a dead EBB argument. From ac1db6e3c97ad81d89d09b76db6c8a81ba01d843 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 7 Jun 2017 11:33:47 -0700 Subject: [PATCH 781/968] Implement a conversion from ValueDef into ProgramPoint. A ValueDef is really no more than a program point plus an argument/result number. --- lib/cretonne/src/ir/progpoint.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index 7d57ab8cb9..c99e79dbe6 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -1,7 +1,7 @@ //! Program points. use entity_map::EntityRef; -use ir::{Ebb, Inst}; +use ir::{Ebb, Inst, ValueDef}; use std::fmt; use std::u32; use std::cmp; @@ -32,6 +32,15 @@ impl From for ProgramPoint { } } +impl From for ProgramPoint { + fn from(def: ValueDef) -> ProgramPoint { + match def { + ValueDef::Res(inst, _) => inst.into(), + ValueDef::Arg(ebb, _) => ebb.into(), + } + } +} + /// An expanded program point directly exposes the variants, but takes twice the space to /// represent. #[derive(PartialEq, Eq, Clone, Copy)] From d94bd8c236c0a05733d23d2093af67a9a4c4cdb5 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 24 May 2017 09:28:15 -0700 Subject: [PATCH 782/968] Add a minimalistic reload pass. The reload pass inserts spill and fill instructions as needed so instructions that operate on registers will never see a value with stack affinity. This is a very basic implementation, and we can't write good test cases until we have a spilling pass. --- lib/cretonne/src/ir/dfg.rs | 10 + lib/cretonne/src/regalloc/affinity.rs | 8 + lib/cretonne/src/regalloc/context.rs | 19 +- lib/cretonne/src/regalloc/mod.rs | 1 + lib/cretonne/src/regalloc/reload.rs | 269 ++++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 lib/cretonne/src/regalloc/reload.rs diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 363b80d2c1..a402274d5e 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -323,6 +323,16 @@ pub enum ValueDef { Arg(Ebb, usize), } +impl ValueDef { + /// Unwrap the instruction where the value was defined, or panic. + pub fn unwrap_inst(&self) -> Inst { + match self { + &ValueDef::Res(inst, _) => inst, + _ => panic!("Value is not an instruction result"), + } + } +} + // Internal table storage for extended values. #[derive(Clone, Debug)] enum ValueData { diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs index 64d2b640df..e7ee0515ad 100644 --- a/lib/cretonne/src/regalloc/affinity.rs +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -64,6 +64,14 @@ impl Affinity { } } + /// Is this the `Stack` affinity? + pub fn is_stack(self) -> bool { + match self { + Affinity::Stack => true, + _ => false, + } + } + /// Merge an operand constraint into this affinity. /// /// Note that this does not guarantee that the register allocator will pick a register that diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index b8cebd1bcc..3cf2b5dd81 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -11,6 +11,7 @@ use isa::TargetIsa; use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; +use regalloc::reload::Reload; use result::CtonResult; use topo_order::TopoOrder; use verifier::{verify_context, verify_liveness}; @@ -20,6 +21,7 @@ pub struct Context { liveness: Liveness, topo: TopoOrder, tracker: LiveValueTracker, + reload: Reload, coloring: Coloring, } @@ -33,6 +35,7 @@ impl Context { liveness: Liveness::new(), topo: TopoOrder::new(), tracker: LiveValueTracker::new(), + reload: Reload::new(), coloring: Coloring::new(), } } @@ -61,7 +64,21 @@ impl Context { // TODO: Second pass: Spilling. - // Third pass: Reload and coloring. + // Third pass: Reload. + self.reload + .run(isa, + func, + domtree, + &mut self.liveness, + &mut self.topo, + &mut self.tracker); + + if isa.flags().enable_verifier() { + verify_context(func, cfg, domtree, Some(isa))?; + verify_liveness(isa, func, cfg, &self.liveness)?; + } + + // Fourth pass: Coloring. self.coloring .run(isa, func, diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 473a9fdb28..4a86abbe93 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -12,6 +12,7 @@ mod affinity; mod context; mod diversion; mod pressure; +mod reload; mod solver; pub use self::allocatable_set::AllocatableSet; diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs new file mode 100644 index 0000000000..533dd45595 --- /dev/null +++ b/lib/cretonne/src/regalloc/reload.rs @@ -0,0 +1,269 @@ +//! Reload pass +//! +//! The reload pass runs between the spilling and coloring passes. Its primary responsibility is to +//! insert `spill` and `fill` instructions such that instruction operands expecting a register will +//! get a value with register affinity, and operands expecting a stack slot will get a value with +//! stack affinity. +//! +//! The secondary responsibility of the reload pass is to reuse values in registers as much as +//! possible to minimize the number of `fill` instructions needed. This must not cause the register +//! pressure limits to be exceeded. + +use dominator_tree::DominatorTree; +use ir::{Ebb, Inst, Value, Function, DataFlowGraph}; +use ir::layout::{Cursor, CursorPosition}; +use ir::{InstBuilder, ArgumentLoc}; +use isa::RegClass; +use isa::{TargetIsa, Encoding, EncInfo, ConstraintKind}; +use regalloc::affinity::Affinity; +use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; +use regalloc::liveness::Liveness; +use sparse_map::{SparseMap, SparseMapValue}; +use topo_order::TopoOrder; + +/// Reusable data structures for the reload pass. +pub struct Reload { + candidates: Vec, + reloads: SparseMap, +} + +/// Context data structure that gets instantiated once per pass. +struct Context<'a> { + // Cached ISA information. + // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. + encinfo: EncInfo, + + // References to contextual data structures we need. + domtree: &'a DominatorTree, + liveness: &'a mut Liveness, + topo: &'a mut TopoOrder, + + candidates: &'a mut Vec, + reloads: &'a mut SparseMap, +} + +impl Reload { + /// Create a new blank reload pass. + pub fn new() -> Reload { + Reload { + candidates: Vec::new(), + reloads: SparseMap::new(), + } + } + + /// Run the reload algorithm over `func`. + pub fn run(&mut self, + isa: &TargetIsa, + func: &mut Function, + domtree: &DominatorTree, + liveness: &mut Liveness, + topo: &mut TopoOrder, + tracker: &mut LiveValueTracker) { + let mut ctx = Context { + encinfo: isa.encoding_info(), + domtree, + liveness, + topo, + candidates: &mut self.candidates, + reloads: &mut self.reloads, + }; + ctx.run(func, tracker) + } +} + +/// A reload candidate. +/// +/// This represents a stack value that is used by the current instruction where a register is +/// needed. +struct ReloadCandidate { + value: Value, + regclass: RegClass, +} + +/// A Reloaded value. +/// +/// This represents a value that has been reloaded into a register value from the stack. +struct ReloadedValue { + stack: Value, + reg: Value, +} + +impl SparseMapValue for ReloadedValue { + fn key(&self) -> Value { + self.stack + } +} + +impl<'a> Context<'a> { + fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { + self.topo.reset(func.layout.ebbs()); + while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { + self.visit_ebb(ebb, func, tracker); + } + } + + fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + dbg!("Reloading {}:", ebb); + let start_from = self.visit_ebb_header(ebb, func, tracker); + tracker.drop_dead_args(); + + let mut pos = Cursor::new(&mut func.layout); + pos.set_position(start_from); + while let Some(inst) = pos.current_inst() { + let encoding = func.encodings[inst]; + if encoding.is_legal() { + self.visit_inst(ebb, inst, encoding, &mut pos, &mut func.dfg, tracker); + tracker.drop_dead(inst); + } else { + pos.next_inst(); + } + } + } + + /// Process the EBB parameters. Return the next instruction in the EBB to be processed + fn visit_ebb_header(&self, + ebb: Ebb, + func: &mut Function, + tracker: &mut LiveValueTracker) + -> CursorPosition { + let (liveins, args) = + tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + + if func.layout.entry_block() == Some(ebb) { + assert_eq!(liveins.len(), 0); + self.visit_entry_args(ebb, func, args) + } else { + self.visit_ebb_args(ebb, func, args) + } + } + + /// Visit the arguments to the entry block. + /// These values have ABI constraints from the function signature. + fn visit_entry_args(&self, + ebb: Ebb, + func: &mut Function, + args: &[LiveValue]) + -> CursorPosition { + assert_eq!(func.signature.argument_types.len(), args.len()); + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(ebb); + pos.next_inst(); + + for (abi, arg) in func.signature.argument_types.iter().zip(args) { + match abi.location { + ArgumentLoc::Reg(_) => { + if arg.affinity.is_stack() { + // An incoming register parameter was spilled. Replace the parameter value + // with a temporary register value that is immediately spilled. + let reg = func.dfg.replace_ebb_arg(arg.value, abi.value_type); + func.dfg.ins(&mut pos).with_result(arg.value).spill(reg); + // TODO: Update live ranges. + } + } + ArgumentLoc::Stack(_) => { + assert!(arg.affinity.is_stack()); + } + ArgumentLoc::Unassigned => panic!("Unexpected ABI location"), + } + } + pos.position() + } + + fn visit_ebb_args(&self, ebb: Ebb, func: &mut Function, _args: &[LiveValue]) -> CursorPosition { + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(ebb); + pos.next_inst(); + pos.position() + } + + /// Process the instruction pointed to by `pos`, and advance the cursor to the next instruction + /// that needs processing. + fn visit_inst(&mut self, + ebb: Ebb, + inst: Inst, + encoding: Encoding, + pos: &mut Cursor, + dfg: &mut DataFlowGraph, + tracker: &mut LiveValueTracker) { + // Get the operand constraints for `inst` that we are trying to satisfy. + let constraints = self.encinfo + .operand_constraints(encoding) + .expect("Missing instruction encoding"); + + assert!(self.candidates.is_empty()); + + // Identify reload candidates. + for (op, &arg) in constraints.ins.iter().zip(dfg.inst_args(inst)) { + if op.kind != ConstraintKind::Stack { + let lv = self.liveness.get(arg).expect("Missing live range for arg"); + if lv.affinity.is_stack() { + self.candidates + .push(ReloadCandidate { + value: arg, + regclass: op.regclass, + }) + } + } + } + + // Insert fill instructions before `inst`. + while let Some(cand) = self.candidates.pop() { + if let Some(_reload) = self.reloads.get_mut(cand.value) { + continue; + } + + let reg = dfg.ins(pos).fill(cand.value); + self.reloads + .insert(ReloadedValue { + stack: cand.value, + reg: reg, + }); + + // Create a live range for the new reload. + let affinity = Affinity::Reg(cand.regclass.into()); + self.liveness.create_dead(reg, dfg.value_def(reg), affinity); + self.liveness.extend_locally(reg, ebb, inst, &pos.layout); + } + + // Rewrite arguments. + for arg in dfg.inst_args_mut(inst) { + if let Some(reload) = self.reloads.get(*arg) { + *arg = reload.reg; + } + } + + // TODO: Reuse reloads for future instructions. + self.reloads.clear(); + + let (_throughs, _kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + + // Advance to the next instruction so we can insert any spills after the instruction. + pos.next_inst(); + + // Rewrite register defs that need to be spilled. + // + // Change: + // + // v2 = inst ... + // + // Into: + // + // v7 = inst ... + // v2 = spill v7 + // + // That way, we don't need to rewrite all future uses of v2. + for (lv, op) in defs.iter().zip(constraints.outs) { + if lv.affinity.is_stack() && op.kind != ConstraintKind::Stack { + let value_type = dfg.value_type(lv.value); + let reg = dfg.replace_result(lv.value, value_type); + dfg.ins(pos).with_result(lv.value).spill(reg); + let spill = dfg.value_def(lv.value).unwrap_inst(); + + // Create a live range for reg. + self.liveness.create_dead(reg, inst, Affinity::new(op)); + self.liveness.extend_locally(reg, ebb, spill, &pos.layout); + self.liveness.move_def_locally(lv.value, spill); + } + } + } +} From 94872cc971fb6990dd121c5d2c4b31869e5caa44 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 7 Jun 2017 13:38:27 -0700 Subject: [PATCH 783/968] Stop using cfg.postorder_ebbs(). Switch to the new domtree.cfg_postorder() which returns a reference to a pre-computed post-order instead of allocating memory and computing a new post-order. --- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/legalizer/mod.rs | 13 ++++++++----- lib/cretonne/src/loop_analysis.rs | 4 ++-- lib/cretonne/src/simple_gvn.rs | 3 +-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index a67a4bde0e..90c4948b40 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -75,7 +75,7 @@ impl Context { /// Run the legalizer for `isa` on the function. pub fn legalize(&mut self, isa: &TargetIsa) -> CtonResult { - legalize_function(&mut self.func, &mut self.cfg, isa); + legalize_function(&mut self.func, &mut self.cfg, &self.domtree, isa); self.verify_if(isa) } diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index edd233ccee..803f5808c2 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -13,6 +13,7 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. +use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; use ir::condcodes::IntCC; @@ -26,17 +27,19 @@ mod split; /// - Transform any instructions that don't have a legal representation in `isa`. /// - Fill out `func.encodings`. /// -pub fn legalize_function(func: &mut Function, cfg: &mut ControlFlowGraph, isa: &TargetIsa) { +pub fn legalize_function(func: &mut Function, + cfg: &mut ControlFlowGraph, + domtree: &DominatorTree, + isa: &TargetIsa) { boundary::legalize_signatures(func, isa); func.encodings.resize(func.dfg.num_insts()); - // Process EBBs in a reverse post-order. This minimizes the number of split instructions we - // need. - let mut postorder = cfg.postorder_ebbs(); let mut pos = Cursor::new(&mut func.layout); - while let Some(ebb) = postorder.pop() { + // Process EBBs in a reverse post-order. This minimizes the number of split instructions we + // need. + for &ebb in domtree.cfg_postorder().iter().rev() { pos.goto_top(ebb); // Keep track of the cursor position before the instruction being processed, so we can diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs index f67a18e9d0..f5ea45424e 100644 --- a/lib/cretonne/src/loop_analysis.rs +++ b/lib/cretonne/src/loop_analysis.rs @@ -128,8 +128,8 @@ impl LoopAnalysis { cfg: &ControlFlowGraph, domtree: &DominatorTree, layout: &Layout) { - // We traverse the CFg in reverse postorder - for &ebb in cfg.postorder_ebbs().iter().rev() { + // We traverse the CFG in reverse postorder + for &ebb in domtree.cfg_postorder().iter().rev() { for &(_, pred_inst) in cfg.get_predecessors(ebb) { // If the ebb dominates one of its predecessors it is a back edge if domtree.ebb_dominates(ebb, pred_inst, layout) { diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs index a7665e965d..e8f89f4a1b 100644 --- a/lib/cretonne/src/simple_gvn.rs +++ b/lib/cretonne/src/simple_gvn.rs @@ -19,10 +19,9 @@ pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { let domtree = DominatorTree::with_function(func, &cfg); // Visit EBBs in a reverse post-order. - let mut postorder = cfg.postorder_ebbs(); let mut pos = Cursor::new(&mut func.layout); - while let Some(ebb) = postorder.pop() { + for &ebb in domtree.cfg_postorder().iter().rev() { pos.goto_top(ebb); while let Some(inst) = pos.next_inst() { From eec980618b7c0acb946440c04fe2750ea0eaafeb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 7 Jun 2017 13:41:29 -0700 Subject: [PATCH 784/968] Remove cfg.postorder_ebbs(). This is now unused. Use domtree.cfg_postorder() instead. Also remove the dead cfg.ebbs(). --- lib/cretonne/src/flowgraph.rs | 49 +++-------------------------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/lib/cretonne/src/flowgraph.rs b/lib/cretonne/src/flowgraph.rs index f445d2c8f1..45596d41ec 100644 --- a/lib/cretonne/src/flowgraph.rs +++ b/lib/cretonne/src/flowgraph.rs @@ -25,8 +25,7 @@ use ir::{Function, Inst, Ebb}; use ir::instructions::BranchInfo; -use entity_map::{EntityMap, Keys}; -use std::collections::HashSet; +use entity_map::EntityMap; use std::mem; /// A basic block denoted by its enclosing Ebb and last instruction. @@ -132,45 +131,6 @@ impl ControlFlowGraph { pub fn get_successors(&self, ebb: Ebb) -> &[Ebb] { &self.data[ebb].successors } - - /// Return [reachable] ebbs in post-order. - pub fn postorder_ebbs(&self) -> Vec { - let entry_block = match self.entry_block { - None => { - return Vec::new(); - } - Some(eb) => eb, - }; - - let mut grey = HashSet::new(); - let mut black = HashSet::new(); - let mut stack = vec![entry_block.clone()]; - let mut postorder = Vec::new(); - - while !stack.is_empty() { - let node = stack.pop().unwrap(); - if !grey.contains(&node) { - // This is a white node. Mark it as gray. - grey.insert(node); - stack.push(node); - // Get any children we've never seen before. - for child in self.get_successors(node) { - if !grey.contains(child) { - stack.push(child.clone()); - } - } - } else if !black.contains(&node) { - postorder.push(node.clone()); - black.insert(node.clone()); - } - } - postorder - } - - /// An iterator across all of the ebbs stored in the CFG. - pub fn ebbs(&self) -> Keys { - self.data.keys() - } } #[cfg(test)] @@ -181,8 +141,7 @@ mod tests { #[test] fn empty() { let func = Function::new(); - let cfg = ControlFlowGraph::with_function(&func); - assert_eq!(None, cfg.ebbs().next()); + ControlFlowGraph::with_function(&func); } #[test] @@ -196,11 +155,9 @@ mod tests { func.layout.append_ebb(ebb2); let cfg = ControlFlowGraph::with_function(&func); - let nodes = cfg.ebbs().collect::>(); - assert_eq!(nodes.len(), 3); let mut fun_ebbs = func.layout.ebbs(); - for ebb in nodes { + for ebb in func.layout.ebbs() { assert_eq!(ebb, fun_ebbs.next().unwrap()); assert_eq!(cfg.get_predecessors(ebb).len(), 0); assert_eq!(cfg.get_successors(ebb).len(), 0); From dacc4003a3951d43cf3927e440298da8617289a4 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Thu, 8 Jun 2017 09:41:57 -0700 Subject: [PATCH 785/968] LICM pass: small changes after code review (#94) --- lib/cretonne/src/licm.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/src/licm.rs b/lib/cretonne/src/licm.rs index fc4b7e251b..1c9e1930fd 100644 --- a/lib/cretonne/src/licm.rs +++ b/lib/cretonne/src/licm.rs @@ -46,7 +46,7 @@ pub fn do_licm(func: &mut Function, }; // The last instruction of the pre-header is the termination instruction (usually // a jump) so we need to insert just before this. - for inst in invariant_inst.iter() { + for inst in invariant_inst { pos.insert_inst(inst.clone()); } } @@ -64,11 +64,7 @@ fn create_pre_header(header: Ebb, domtree: &DominatorTree) -> Ebb { let pool = &mut ListPool::::new(); - let header_args_values: Vec = func.dfg - .ebb_args(header) - .into_iter() - .map(|val| *val) - .collect(); + let header_args_values: Vec = func.dfg.ebb_args(header).into_iter().cloned().collect(); let header_args_types: Vec = header_args_values .clone() .into_iter() @@ -135,8 +131,8 @@ fn change_branch_jump_destination(inst: Inst, new_ebb: Ebb, func: &mut Function) } } -// Traverses a loop in reverse post-order from a header EBB and identify lopp-invariant -// instructions. Theseloop-invariant instructions are then removed from the code and returned +// Traverses a loop in reverse post-order from a header EBB and identify loop-invariant +// instructions. These loop-invariant instructions are then removed from the code and returned // (in reverse post-order) for later use. fn remove_loop_invariant_instructions(lp: Loop, func: &mut Function, @@ -163,8 +159,7 @@ fn remove_loop_invariant_instructions(lp: Loop, // then this instruction is loop-invariant invariant_inst.push(inst); // We remove it from the loop - pos.remove_inst(); - pos.prev_inst(); + pos.remove_inst_and_step_back(); } else { // If the instruction is not loop-invariant we push its results in the set of // loop values @@ -178,10 +173,7 @@ fn remove_loop_invariant_instructions(lp: Loop, } /// Return ebbs from a loop in post-order, starting from an entry point in the block. -pub fn postorder_ebbs_loop(loop_analysis: &LoopAnalysis, - cfg: &ControlFlowGraph, - lp: Loop) - -> Vec { +fn postorder_ebbs_loop(loop_analysis: &LoopAnalysis, cfg: &ControlFlowGraph, lp: Loop) -> Vec { let mut grey = HashSet::new(); let mut black = HashSet::new(); let mut stack = vec![loop_analysis.loop_header(lp).clone()]; From 731278aad86d4ee4fddc2f54f0e3ec1fb32be5a3 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Fri, 9 Jun 2017 16:20:38 -0700 Subject: [PATCH 786/968] Improved DFG API (#95) * Improved DFG API with swap_remove_ebb_args and append_inst_arg * Implemented EntityList::swap_remove And used it for dfg::swap_remove_ebb_arg --- lib/cretonne/src/entity_list.rs | 18 ++++++++- lib/cretonne/src/ir/dfg.rs | 66 ++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 946bb7ba04..1ccddc3aed 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -416,7 +416,7 @@ impl EntityList { } } - /// Removes the element at position `index` from the list. + /// Removes the element at position `index` from the list. Potentially linear complexity. pub fn remove(&mut self, index: usize, pool: &mut ListPool) { let len; { @@ -448,6 +448,22 @@ impl EntityList { pool.data[block] = T::new(len - 1); } + /// Removes the element at `index` in constant time by switching it with the last element of + /// the list. + pub fn swap_remove(&mut self, index: usize, pool: &mut ListPool) { + let len = self.len(pool); + assert!(index < len); + if index == len - 1 { + self.remove(index, pool); + } else { + { + let seq = self.as_mut_slice(pool); + seq.swap(index, len - 1); + } + self.remove(len - 1, pool); + } + } + /// Grow the list by inserting `count` elements at `index`. /// /// The new elements are not initialized, they will contain whatever happened to be in memory. diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index a402274d5e..36607e76f1 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -127,7 +127,7 @@ fn resolve_aliases(values: &EntityMap, value: Value) -> Value /// /// Values are either EBB arguments or instruction results. impl DataFlowGraph { - // Allocate an extended value entry. + /// Allocate an extended value entry. fn make_value(&mut self, data: ValueData) -> Value { self.values.push(data) } @@ -562,6 +562,17 @@ impl DataFlowGraph { }) } + /// Append a new value argument to an instruction. + /// + /// Panics if the instruction doesn't support arguments. + pub fn append_inst_arg(&mut self, inst: Inst, new_arg: Value) { + let mut branch_values = self.insts[inst] + .take_value_list() + .expect("the instruction doesn't have value arguments"); + branch_values.push(new_arg, &mut self.value_lists); + self.insts[inst].put_value_list(branch_values) + } + /// Get the first result of an instruction. /// /// This function panics if the instruction doesn't have any result. @@ -682,6 +693,35 @@ impl DataFlowGraph { }) } + /// Removes `val` from `ebb`'s argument by swapping it with the last argument of `ebb`. + /// Returns the position of `val` before removal. + /// + /// *Important*: to ensure O(1) deletion, this method swaps the removed argument with the + /// last `Ebb` argument. This can disrupt all the branch instructions jumping to this + /// `Ebb` for which you have to change the jump argument order if necessary. + /// + /// Panics if `val` is not an `Ebb` argument. Returns `true` if `Ebb` arguments have been + /// swapped. + pub fn swap_remove_ebb_arg(&mut self, val: Value) -> usize { + let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = self.values[val] { + (ebb, num) + } else { + panic!("{} must be an EBB argument", val); + }; + self.ebbs[ebb] + .args + .swap_remove(num as usize, &mut self.value_lists); + if let Some(last_arg_val) = self.ebbs[ebb].args.get(num as usize, &self.value_lists) { + // We update the position of the old last arg. + if let ValueData::Arg { num: ref mut old_num, .. } = self.values[last_arg_val] { + *old_num = num; + } else { + panic!("{} should be an Ebb argument but is not", last_arg_val); + } + } + num as usize + } + /// Append an existing argument value to `ebb`. /// /// The appended value can't already be attached to something else. @@ -899,6 +939,30 @@ mod tests { assert_eq!(dfg.ebb_args(ebb), &[new1, new3, arg1]); } + #[test] + fn swap_remove_ebb_arguments() { + let mut dfg = DataFlowGraph::new(); + + let ebb = dfg.make_ebb(); + let arg1 = dfg.append_ebb_arg(ebb, types::F32); + let arg2 = dfg.append_ebb_arg(ebb, types::F32); + let arg3 = dfg.append_ebb_arg(ebb, types::F32); + assert_eq!(dfg.ebb_args(ebb), &[arg1, arg2, arg3]); + + dfg.swap_remove_ebb_arg(arg1); + assert_eq!(dfg.value_is_attached(arg1), false); + assert_eq!(dfg.value_is_attached(arg2), true); + assert_eq!(dfg.value_is_attached(arg3), true); + assert_eq!(dfg.ebb_args(ebb), &[arg3, arg2]); + dfg.swap_remove_ebb_arg(arg2); + assert_eq!(dfg.value_is_attached(arg2), false); + assert_eq!(dfg.value_is_attached(arg3), true); + assert_eq!(dfg.ebb_args(ebb), &[arg3]); + dfg.swap_remove_ebb_arg(arg3); + assert_eq!(dfg.value_is_attached(arg3), false); + assert_eq!(dfg.ebb_args(ebb), &[]); + } + #[test] fn aliases() { use ir::InstBuilder; From 8b484b1c77d7e267ff1694905fcba252232d378c Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Sat, 10 Jun 2017 22:30:37 +0500 Subject: [PATCH 787/968] Binary function names (#91) * Function names should start with % * Create FunctionName from string * Implement displaying of FunctionName as %nnnn with fallback to #xxxx * Run rustfmt and fix FunctionName::with_string in parser * Implement FunctionName::new as a generic function * Binary function names should start with # * Implement NameRepr for function name * Fix examples in docs to reflect that function names start with % * Rebase and fix filecheck tests --- docs/example.cton | 2 +- docs/langref.rst | 6 +- docs/testing.rst | 20 ++-- filetests/cfg/loop.cton | 4 +- filetests/cfg/traps_early.cton | 4 +- filetests/cfg/unused_node.cton | 4 +- filetests/domtree/basic.cton | 2 +- filetests/domtree/loops.cton | 2 +- filetests/domtree/loops2.cton | 2 +- filetests/domtree/tall-tree.cton | 2 +- filetests/domtree/wide-tree.cton | 2 +- filetests/isa/intel/binary32.cton | 2 +- filetests/isa/riscv/abi-e.cton | 2 +- filetests/isa/riscv/abi.cton | 2 +- filetests/isa/riscv/binary32.cton | 4 +- filetests/isa/riscv/encoding.cton | 2 +- filetests/isa/riscv/expand-i32.cton | 4 +- filetests/isa/riscv/legalize-abi.cton | 28 ++--- filetests/isa/riscv/legalize-i64.cton | 8 +- filetests/isa/riscv/parse-encoding.cton | 8 +- filetests/isa/riscv/split-args.cton | 6 +- filetests/isa/riscv/verify-encoding.cton | 8 +- filetests/licm/basic.cton | 4 +- filetests/licm/complex.cton | 4 +- filetests/licm/multiple-blocks.cton | 4 +- filetests/licm/nested_loops.cton | 4 +- filetests/parser/branch.cton | 24 ++-- filetests/parser/call.cton | 34 +++--- filetests/parser/instruction_encoding.cton | 4 +- filetests/parser/keywords.cton | 4 +- filetests/parser/rewrite.cton | 8 +- filetests/parser/ternary.cton | 4 +- filetests/parser/tiny.cton | 44 +++---- filetests/regalloc/basic.cton | 8 +- filetests/simple_gvn/basic.cton | 4 +- filetests/verifier/bad_layout.cton | 4 +- lib/cretonne/src/ir/funcname.rs | 128 ++++++++++++++------- lib/cretonne/src/write.rs | 14 +-- lib/reader/src/parser.rs | 82 ++++++++++--- lib/reader/src/sourcemap.rs | 2 +- tests/cfg_traversal.rs | 10 +- 41 files changed, 306 insertions(+), 208 deletions(-) diff --git a/docs/example.cton b/docs/example.cton index db87e891d1..8b9c43c6b9 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -1,6 +1,6 @@ test verifier -function average(i32, i32) -> f32 { +function %average(i32, i32) -> f32 { ss1 = stack_slot 8 ; Stack slot for ``sum``. ebb1(v1: i32, v2: i32): diff --git a/docs/langref.rst b/docs/langref.rst index e6a0e2b3d5..24b5961ec3 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -389,8 +389,8 @@ preamble`: This simple example illustrates direct function calls and signatures:: - function gcd(i32 uext, i32 uext) -> i32 uext "C" { - fn1 = function divmod(i32 uext, i32 uext) -> i32 uext, i32 uext + function %gcd(i32 uext, i32 uext) -> i32 uext "C" { + fn1 = function %divmod(i32 uext, i32 uext) -> i32 uext, i32 uext ebb1(v1: i32, v2: i32): brz v2, ebb2 @@ -530,7 +530,7 @@ and address computations from the memory accesses. A small example using heaps:: - function vdup(i32, i32) { + function %vdup(i32, i32) { h1 = heap "main" ebb1(v1: i32, v2: i32): diff --git a/docs/testing.rst b/docs/testing.rst index d4aa1a881c..b72ace08d3 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -130,7 +130,7 @@ The ``set`` lines apply settings cumulatively:: set is_64bit=0 isa riscv supports_m=false - function foo() {} + function %foo() {} This example will run the legalizer test twice. Both runs will have ``opt_level=best``, but they will have different ``is_64bit`` settings. The 32-bit @@ -184,13 +184,13 @@ against the associated filecheck directives. Example:: - function r1() -> i32, f32 { + function %r1() -> i32, f32 { ebb1: v10 = iconst.i32 3 v20 = f32const 0.0 return v10, v20 } - ; sameln: function r1() -> i32, f32 { + ; sameln: function %r1() -> i32, f32 { ; nextln: ebb0: ; nextln: v0 = iconst.i32 3 ; nextln: v1 = f32const 0.0 @@ -201,13 +201,13 @@ Notice that the values ``v10`` and ``v20`` in the source were renumbered to ``v0`` and ``v1`` respectively during parsing. The equivalent test using filecheck variables would be:: - function r1() -> i32, f32 { + function %r1() -> i32, f32 { ebb1: v10 = iconst.i32 3 v20 = f32const 0.0 return v10, v20 } - ; sameln: function r1() -> i32, f32 { + ; sameln: function %r1() -> i32, f32 { ; nextln: ebb0: ; nextln: $v10 = iconst.i32 3 ; nextln: $v20 = f32const 0.0 @@ -226,7 +226,7 @@ reported location of the error is verified:: test verifier - function test(i32) { + function %test(i32) { ebb0(v0: i32): jump ebb1 ; error: terminator return @@ -250,8 +250,8 @@ command:: test print-cfg test verifier - function nonsense(i32, i32) -> f32 { - ; check: digraph nonsense { + function %nonsense(i32, i32) -> f32 { + ; check: digraph %nonsense { ; regex: I=\binst\d+\b ; check: label="{ebb0 | <$(BRZ=$I)>brz ebb2 | <$(JUMP=$I)>jump ebb1}"] @@ -276,7 +276,7 @@ Compute the dominator tree of each function and validate it against the test domtree - function test(i32) { + function %test(i32) { ebb0(v0: i32): jump ebb1 ; dominates: ebb1 ebb1: @@ -328,7 +328,7 @@ that instruction is compared to the directive:: test binemit isa riscv - function int32() { + function %int32() { ebb0: [-,%x5] v1 = iconst.i32 1 [-,%x6] v2 = iconst.i32 2 diff --git a/filetests/cfg/loop.cton b/filetests/cfg/loop.cton index 3f7892a9d9..06aa848c7f 100644 --- a/filetests/cfg/loop.cton +++ b/filetests/cfg/loop.cton @@ -2,8 +2,8 @@ test print-cfg test verifier -function nonsense(i32, i32) -> f32 { -; check: digraph nonsense { +function %nonsense(i32, i32) -> f32 { +; check: digraph %nonsense { ; regex: I=\binst\d+\b ; check: label="{ebb0 | <$(BRZ=$I)>brz ebb2 | <$(JUMP=$I)>jump ebb1}"] diff --git a/filetests/cfg/traps_early.cton b/filetests/cfg/traps_early.cton index 9648190bc5..814e251f51 100644 --- a/filetests/cfg/traps_early.cton +++ b/filetests/cfg/traps_early.cton @@ -3,8 +3,8 @@ test print-cfg test verifier -function nonsense(i32) { -; check: digraph nonsense { +function %nonsense(i32) { +; check: digraph %nonsense { ebb0(v1: i32): trap ; error: terminator instruction was encountered before the end diff --git a/filetests/cfg/unused_node.cton b/filetests/cfg/unused_node.cton index d4be2deddd..cbde9757bc 100644 --- a/filetests/cfg/unused_node.cton +++ b/filetests/cfg/unused_node.cton @@ -1,8 +1,8 @@ ; For testing cfg generation where some block is never reached. test print-cfg -function not_reached(i32) -> i32 { -; check: digraph not_reached { +function %not_reached(i32) -> i32 { +; check: digraph %not_reached { ; check: ebb0 [shape=record, label="{ebb0 | brnz ebb2}"] ; check: ebb1 [shape=record, label="{ebb1 | jump ebb0}"] ; check: ebb2 [shape=record, label="{ebb2}"] diff --git a/filetests/domtree/basic.cton b/filetests/domtree/basic.cton index 0b8536effd..e46c73e67e 100644 --- a/filetests/domtree/basic.cton +++ b/filetests/domtree/basic.cton @@ -1,6 +1,6 @@ test domtree -function test(i32) { +function %test(i32) { ebb0(v0: i32): jump ebb1 ; dominates: ebb1 ebb1: diff --git a/filetests/domtree/loops.cton b/filetests/domtree/loops.cton index cdc88544cc..43ad1e1c08 100644 --- a/filetests/domtree/loops.cton +++ b/filetests/domtree/loops.cton @@ -1,6 +1,6 @@ test domtree -function test(i32) { +function %test(i32) { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 jump ebb2 ; dominates: ebb2 diff --git a/filetests/domtree/loops2.cton b/filetests/domtree/loops2.cton index d2e3ba4f2c..eeac8343bd 100644 --- a/filetests/domtree/loops2.cton +++ b/filetests/domtree/loops2.cton @@ -1,6 +1,6 @@ test domtree -function test(i32) { +function %test(i32) { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb6 brnz v0, ebb2 ; dominates: ebb2 ebb9 diff --git a/filetests/domtree/tall-tree.cton b/filetests/domtree/tall-tree.cton index 3dd6f51da6..89821d0744 100644 --- a/filetests/domtree/tall-tree.cton +++ b/filetests/domtree/tall-tree.cton @@ -1,6 +1,6 @@ test domtree -function test(i32) { +function %test(i32) { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 brnz v0, ebb2 ; dominates: ebb2 ebb5 diff --git a/filetests/domtree/wide-tree.cton b/filetests/domtree/wide-tree.cton index f4e822cd81..3a9b4fff37 100644 --- a/filetests/domtree/wide-tree.cton +++ b/filetests/domtree/wide-tree.cton @@ -1,6 +1,6 @@ test domtree -function test(i32) { +function %test(i32) { ebb0(v0: i32): brz v0, ebb13 ; dominates: ebb13 jump ebb1 ; dominates: ebb1 diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 5cac489f77..818335d349 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -7,7 +7,7 @@ isa intel ; sed -ne 's/^ *; asm: *//p' filetests/isa/intel/binary32.cton | llvm-mc -show-encoding -triple=i386 ; -function I32() { +function %I32() { ebb0: [-,%rcx] v1 = iconst.i32 1 [-,%rsi] v2 = iconst.i32 2 diff --git a/filetests/isa/riscv/abi-e.cton b/filetests/isa/riscv/abi-e.cton index f770a3558f..543b55079f 100644 --- a/filetests/isa/riscv/abi-e.cton +++ b/filetests/isa/riscv/abi-e.cton @@ -4,7 +4,7 @@ isa riscv enable_e ; regex: V=v\d+ -function f() { +function %f() { ; Spilling into the stack args after %x15 since %16 and up are not ; available in RV32E. sig0 = signature(i64, i64, i64, i64) -> i64 diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index eba5609f3e..f26c7686cf 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -4,7 +4,7 @@ isa riscv ; regex: V=v\d+ -function f() { +function %f() { sig0 = signature(i32) -> i32 ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index f62a6187e0..052342f233 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -2,8 +2,8 @@ test binemit isa riscv -function RV32I(i32 link [%x1]) -> i32 link [%x1] { - fn0 = function foo() +function %RV32I(i32 link [%x1]) -> i32 link [%x1] { + fn0 = function %foo() ebb0(v9999: i32): [-,%x10] v1 = iconst.i32 1 diff --git a/filetests/isa/riscv/encoding.cton b/filetests/isa/riscv/encoding.cton index fdd3ee4329..71961ba353 100644 --- a/filetests/isa/riscv/encoding.cton +++ b/filetests/isa/riscv/encoding.cton @@ -1,7 +1,7 @@ test legalizer isa riscv supports_m=1 -function int32(i32, i32) { +function %int32(i32, i32) { ebb0(v1: i32, v2: i32): v10 = iadd v1, v2 ; check: [R#0c] diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index fe93521567..2bffad234c 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -9,7 +9,7 @@ isa riscv supports_m=1 ; regex: V=v\d+ -function carry_out(i32, i32) -> i32, b1 { +function %carry_out(i32, i32) -> i32, b1 { ebb0(v1: i32, v2: i32): v3, v4 = iadd_cout v1, v2 return v3, v4 @@ -20,7 +20,7 @@ ebb0(v1: i32, v2: i32): ; Expanding illegal immediate constants. ; Note that at some point we'll probably expand the iconst as well. -function large_imm(i32) -> i32 { +function %large_imm(i32) -> i32 { ebb0(v0: i32): v1 = iadd_imm v0, 1000000000 return v1 diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index a3632edca3..4d6f4e6e7f 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -4,7 +4,7 @@ isa riscv ; regex: V=v\d+ -function int_split_args(i64) -> i64 { +function %int_split_args(i64) -> i64 { ebb0(v0: i64): ; check: $ebb0($(v0l=$V): i32, $(v0h=$V): i32, $(link=$V): i32): ; check: $v0 = iconcat $v0l, $v0h @@ -14,9 +14,9 @@ ebb0(v0: i64): return v1 } -function split_call_arg(i32) { - fn1 = function foo(i64) - fn2 = function foo(i32, i64) +function %split_call_arg(i32) { + fn1 = function %foo(i64) + fn2 = function %foo(i32, i64) ebb0(v0: i32): v1 = uextend.i64 v0 call fn1(v1) @@ -27,8 +27,8 @@ ebb0(v0: i32): return } -function split_ret_val() { - fn1 = function foo() -> i64 +function %split_ret_val() { + fn1 = function %foo() -> i64 ebb0: v1 = call fn1() ; check: $ebb0($(link=$V): i32): @@ -42,8 +42,8 @@ ebb1(v10: i64): } ; First return value is fine, second one is expanded. -function split_ret_val2() { - fn1 = function foo() -> i32, i64 +function %split_ret_val2() { + fn1 = function %foo() -> i32, i64 ebb0: v1, v2 = call fn1() ; check: $ebb0($(link=$V): i32): @@ -56,7 +56,7 @@ ebb1(v9: i32, v10: i64): jump ebb1(v9, v10) } -function int_ext(i8, i8 sext, i8 uext) -> i8 uext { +function %int_ext(i8, i8 sext, i8 uext) -> i8 uext { ebb0(v1: i8, v2: i8, v3: i8): ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32, $(link=$V): i32): ; check: $v2 = ireduce.i8 $v2x @@ -67,8 +67,8 @@ ebb0(v1: i8, v2: i8, v3: i8): } ; Function produces single return value, still need to copy. -function ext_ret_val() { - fn1 = function foo() -> i8 sext +function %ext_ret_val() { + fn1 = function %foo() -> i8 sext ebb0: v1 = call fn1() ; check: $ebb0($V: i32): @@ -81,7 +81,7 @@ ebb1(v10: i8): jump ebb1(v10) } -function vector_split_args(i64x4) -> i64x4 { +function %vector_split_args(i64x4) -> i64x4 { ebb0(v0: i64x4): ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32, $(link=$V): i32): ; check: $(v0a=$V) = iconcat $v0al, $v0ah @@ -103,7 +103,7 @@ ebb0(v0: i64x4): return v1 } -function indirect(i32) { +function %indirect(i32) { sig1 = signature() ebb0(v0: i32): call_indirect sig1, v0() @@ -111,7 +111,7 @@ ebb0(v0: i32): } ; The first argument to call_indirect doesn't get altered. -function indirect_arg(i32, f32x2) { +function %indirect_arg(i32, f32x2) { sig1 = signature(f32x2) ebb0(v0: i32, v1: f32x2): call_indirect sig1, v0(v1) diff --git a/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton index 516502c4d4..e7a3441ee9 100644 --- a/filetests/isa/riscv/legalize-i64.cton +++ b/filetests/isa/riscv/legalize-i64.cton @@ -4,7 +4,7 @@ isa riscv supports_m=1 ; regex: V=v\d+ -function bitwise_and(i64, i64) -> i64 { +function %bitwise_and(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = band v1, v2 return v3 @@ -17,7 +17,7 @@ ebb0(v1: i64, v2: i64): ; check: $v3 = iconcat $v3l, $v3h ; check: return $v3l, $v3h, $link -function bitwise_or(i64, i64) -> i64 { +function %bitwise_or(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bor v1, v2 return v3 @@ -30,7 +30,7 @@ ebb0(v1: i64, v2: i64): ; check: $v3 = iconcat $v3l, $v3h ; check: return $v3l, $v3h, $link -function bitwise_xor(i64, i64) -> i64 { +function %bitwise_xor(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): v3 = bxor v1, v2 return v3 @@ -43,7 +43,7 @@ ebb0(v1: i64, v2: i64): ; check: $v3 = iconcat $v3l, $v3h ; check: return $v3l, $v3h, $link -function arith_add(i64, i64) -> i64 { +function %arith_add(i64, i64) -> i64 { ; Legalizing iadd.i64 requires two steps: ; 1. Narrow to iadd_cout.i32, then ; 2. Expand iadd_cout.i32 since RISC-V has no carry flag. diff --git a/filetests/isa/riscv/parse-encoding.cton b/filetests/isa/riscv/parse-encoding.cton index 69a25ce744..cd02a3ca47 100644 --- a/filetests/isa/riscv/parse-encoding.cton +++ b/filetests/isa/riscv/parse-encoding.cton @@ -2,8 +2,8 @@ test legalizer isa riscv -function parse_encoding(i32 [%x5]) -> i32 [%x10] { - ; check: function parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] { +function %parse_encoding(i32 [%x5]) -> i32 [%x10] { + ; check: function %parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] { sig0 = signature(i32 [%x10]) -> i32 [%x10] ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] @@ -27,9 +27,9 @@ function parse_encoding(i32 [%x5]) -> i32 [%x10] { ; check: sig5 = signature() -> f32 [0] ; function + signature - fn15 = function bar(i32 [%x10]) -> b1 [%x10] + fn15 = function %bar(i32 [%x10]) -> b1 [%x10] ; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] - ; nextln: fn0 = sig6 bar + ; nextln: fn0 = sig6 %bar ebb0(v0: i32): return v0 diff --git a/filetests/isa/riscv/split-args.cton b/filetests/isa/riscv/split-args.cton index a568204658..be1370dc12 100644 --- a/filetests/isa/riscv/split-args.cton +++ b/filetests/isa/riscv/split-args.cton @@ -4,7 +4,7 @@ isa riscv ; regex: V=v\d+ -function simple(i64, i64) -> i64 { +function %simple(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): ; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): jump ebb1(v1) @@ -19,7 +19,7 @@ ebb1(v3: i64): ; check: return $v4l, $v4h, $link } -function multi(i64) -> i64 { +function %multi(i64) -> i64 { ebb1(v1: i64): ; check: $ebb1($(v1l=$V): i32, $(v1h=$V): i32, $(link=$V): i32): jump ebb2(v1, v1) @@ -39,7 +39,7 @@ ebb3(v4: i64): ; check: return $v5l, $v5h, $link } -function loop(i64, i64) -> i64 { +function %loop(i64, i64) -> i64 { ebb0(v1: i64, v2: i64): ; check: $ebb0($(v1l=$V): i32, $(v1h=$V): i32, $(v2l=$V): i32, $(v2h=$V): i32, $(link=$V): i32): jump ebb1(v1) diff --git a/filetests/isa/riscv/verify-encoding.cton b/filetests/isa/riscv/verify-encoding.cton index 73d28c842a..52b8d6d79c 100644 --- a/filetests/isa/riscv/verify-encoding.cton +++ b/filetests/isa/riscv/verify-encoding.cton @@ -1,8 +1,8 @@ test verifier isa riscv -function RV32I(i32 link [%x1]) -> i32 link [%x1] { - fn0 = function foo() +function %RV32I(i32 link [%x1]) -> i32 link [%x1] { + fn0 = function %foo() ebb0(v9999: i32): ; iconst.i32 needs legalizing, so it should throw a @@ -10,8 +10,8 @@ ebb0(v9999: i32): return v9999 } -function RV32I(i32 link [%x1]) -> i32 link [%x1] { - fn0 = function foo() +function %RV32I(i32 link [%x1]) -> i32 link [%x1] { + fn0 = function %foo() ebb0(v9999: i32): v1 = iconst.i32 1 diff --git a/filetests/licm/basic.cton b/filetests/licm/basic.cton index 637b910f53..36b7864cfe 100644 --- a/filetests/licm/basic.cton +++ b/filetests/licm/basic.cton @@ -1,6 +1,6 @@ test licm -function simple_loop(i32) -> i32 { +function %simple_loop(i32) -> i32 { ebb1(v0: i32): v1 = iconst.i32 1 @@ -14,7 +14,7 @@ ebb2(v5: i32): return v5 } -; sameln: function simple_loop(i32) -> i32 { +; sameln: function %simple_loop(i32) -> i32 { ; nextln: ebb2(v6: i32): ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 2 diff --git a/filetests/licm/complex.cton b/filetests/licm/complex.cton index fead0cf746..b5063d807f 100644 --- a/filetests/licm/complex.cton +++ b/filetests/licm/complex.cton @@ -1,6 +1,6 @@ test licm -function complex(i32) -> i32 { +function %complex(i32) -> i32 { ebb0(v0: i32): v1 = iconst.i32 1 @@ -39,7 +39,7 @@ ebb5(v16: i32): return v17 } -; sameln: function complex(i32) -> i32 { +; sameln: function %complex(i32) -> i32 { ; nextln: ebb6(v20: i32): ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 4 diff --git a/filetests/licm/multiple-blocks.cton b/filetests/licm/multiple-blocks.cton index 54db640501..4738081d9a 100644 --- a/filetests/licm/multiple-blocks.cton +++ b/filetests/licm/multiple-blocks.cton @@ -1,6 +1,6 @@ test licm -function multiple_blocks(i32) -> i32 { +function %multiple_blocks(i32) -> i32 { ebb0(v0: i32): jump ebb1(v0) @@ -23,7 +23,7 @@ ebb3(v30: i32): jump ebb1(v30) } -; sameln:function multiple_blocks(i32) -> i32 { +; sameln:function %multiple_blocks(i32) -> i32 { ; nextln: ebb0(v0: i32): ; nextln: v2 = iconst.i32 1 ; nextln: v3 = iconst.i32 2 diff --git a/filetests/licm/nested_loops.cton b/filetests/licm/nested_loops.cton index e2d3846a0f..a32dd4b498 100644 --- a/filetests/licm/nested_loops.cton +++ b/filetests/licm/nested_loops.cton @@ -1,6 +1,6 @@ test licm -function nested_loops(i32) -> i32 { +function %nested_loops(i32) -> i32 { ebb0(v0: i32): v1 = iconst.i32 1 @@ -25,7 +25,7 @@ ebb3(v30: i32): } -; sameln:function nested_loops(i32) -> i32 { +; sameln:function %nested_loops(i32) -> i32 { ; nextln: ebb4(v12: i32): ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 2 diff --git a/filetests/parser/branch.cton b/filetests/parser/branch.cton index ac979bbb26..4adb4b5d27 100644 --- a/filetests/parser/branch.cton +++ b/filetests/parser/branch.cton @@ -2,14 +2,14 @@ test cat ; Jumps with no arguments. The '()' empty argument list is optional. -function minimal() { +function %minimal() { ebb0: jump ebb1 ebb1: jump ebb0() } -; sameln: function minimal() { +; sameln: function %minimal() { ; nextln: ebb0: ; nextln: jump ebb1 ; nextln: @@ -18,14 +18,14 @@ ebb1: ; nextln: } ; Jumps with 1 arg. -function onearg(i32) { +function %onearg(i32) { ebb0(v90: i32): jump ebb1(v90) ebb1(v91: i32): jump ebb0(v91) } -; sameln: function onearg(i32) { +; sameln: function %onearg(i32) { ; nextln: ebb0($v90: i32): ; nextln: jump ebb1($v90) ; nextln: @@ -34,14 +34,14 @@ ebb1(v91: i32): ; nextln: } ; Jumps with 2 args. -function twoargs(i32, f32) { +function %twoargs(i32, f32) { ebb0(v90: i32, v91: f32): jump ebb1(v90, v91) ebb1(v92: i32, v93: f32): jump ebb0(v92, v93) } -; sameln: function twoargs(i32, f32) { +; sameln: function %twoargs(i32, f32) { ; nextln: ebb0($v90: i32, $v91: f32): ; nextln: jump ebb1($v90, $v91) ; nextln: @@ -50,14 +50,14 @@ ebb1(v92: i32, v93: f32): ; nextln: } ; Branches with no arguments. The '()' empty argument list is optional. -function minimal(i32) { +function %minimal(i32) { ebb0(v90: i32): brz v90, ebb1 ebb1: brnz v90, ebb1() } -; sameln: function minimal(i32) { +; sameln: function %minimal(i32) { ; nextln: ebb0($v90: i32): ; nextln: brz $v90, ebb1 ; nextln: @@ -65,14 +65,14 @@ ebb1: ; nextln: brnz.i32 $v90, ebb1 ; nextln: } -function twoargs(i32, f32) { +function %twoargs(i32, f32) { ebb0(v90: i32, v91: f32): brz v90, ebb1(v90, v91) ebb1(v92: i32, v93: f32): brnz v90, ebb0(v92, v93) } -; sameln: function twoargs(i32, f32) { +; sameln: function %twoargs(i32, f32) { ; nextln: ebb0($v90: i32, $v91: f32): ; nextln: brz $v90, ebb1($v90, $v91) ; nextln: @@ -80,7 +80,7 @@ ebb1(v92: i32, v93: f32): ; nextln: brnz.i32 $v90, ebb0($v92, $v93) ; nextln: } -function jumptable(i32) { +function %jumptable(i32) { jt200 = jump_table 0, 0 jt2 = jump_table 0, 0, ebb10, ebb40, ebb20, ebb30 @@ -94,7 +94,7 @@ ebb30: ebb40: trap } -; sameln: function jumptable(i32) { +; sameln: function %jumptable(i32) { ; nextln: jt0 = jump_table 0 ; nextln: jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 ; nextln: diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton index 67472f7efc..9d7c2c3d16 100644 --- a/filetests/parser/call.cton +++ b/filetests/parser/call.cton @@ -1,46 +1,46 @@ ; Parser tests for call and return syntax. test cat -function mini() { +function %mini() { ebb1: return } -; sameln: function mini() { +; sameln: function %mini() { ; nextln: ebb0: ; nextln: return ; nextln: } -function r1() -> i32, f32 { +function %r1() -> i32, f32 { ebb1: v1 = iconst.i32 3 v2 = f32const 0.0 return v1, v2 } -; sameln: function r1() -> i32, f32 { +; sameln: function %r1() -> i32, f32 { ; nextln: ebb0: ; nextln: $v1 = iconst.i32 3 ; nextln: $v2 = f32const 0.0 ; nextln: return $v1, $v2 ; nextln: } -function signatures() { +function %signatures() { sig10 = signature() sig11 = signature(i32, f64) -> i32, b1 - fn5 = sig11 foo - fn8 = function bar(i32) -> b1 + fn5 = sig11 %foo + fn8 = function %bar(i32) -> b1 } -; sameln: function signatures() { +; sameln: function %signatures() { ; nextln: $sig10 = signature() ; nextln: $sig11 = signature(i32, f64) -> i32, b1 ; nextln: sig2 = signature(i32) -> b1 -; nextln: $fn5 = $sig11 foo -; nextln: $fn8 = sig2 bar +; nextln: $fn5 = $sig11 %foo +; nextln: $fn8 = sig2 %bar ; nextln: } -function direct() { - fn0 = function none() - fn1 = function one() -> i32 - fn2 = function two() -> i32, f32 +function %direct() { + fn0 = function %none() + fn1 = function %one() -> i32 + fn2 = function %two() -> i32, f32 ebb0: call fn0() @@ -53,7 +53,7 @@ ebb0: ; check: $v2, $v3 = call $fn2() ; check: return -function indirect(i64) { +function %indirect(i64) { sig0 = signature(i64) sig1 = signature() -> i32 sig2 = signature() -> i32, f32 @@ -70,11 +70,11 @@ ebb0(v0: i64): ; check: return ; Special purpose function arguments -function special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { +function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { ebb0(v1: i32, v2: i32, v3: i32, v4: i32): return v4, v2, v3, v1 } -; check: function special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { +; check: function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { ; check: ebb0($v1: i32, $v2: i32, $v3: i32, $v4: i32): ; check: return $v4, $v2, $v3, $v1 ; check: } diff --git a/filetests/parser/instruction_encoding.cton b/filetests/parser/instruction_encoding.cton index 4971cba510..bc2e1dd239 100644 --- a/filetests/parser/instruction_encoding.cton +++ b/filetests/parser/instruction_encoding.cton @@ -4,7 +4,7 @@ isa riscv ; regex: WS=[ \t]* -function foo(i32, i32) { +function %foo(i32, i32) { ebb1(v0: i32, v1: i32): [-,-] v2 = iadd v0, v1 [-] trap @@ -13,7 +13,7 @@ ebb1(v0: i32, v1: i32): v9 = iadd v8, v7 [Iret#5] return v0, v8 } -; sameln: function foo(i32, i32) { +; sameln: function %foo(i32, i32) { ; nextln: $ebb1($v0: i32, $v1: i32): ; nextln: [-,-]$WS $v2 = iadd $v0, $v1 ; nextln: [-]$WS trap diff --git a/filetests/parser/keywords.cton b/filetests/parser/keywords.cton index eb15f2624d..37d0390a58 100644 --- a/filetests/parser/keywords.cton +++ b/filetests/parser/keywords.cton @@ -1,5 +1,5 @@ test cat ; 'function' is not a keyword, and can be used as the name of a function too. -function function() {} -; check: function function() +function %function() {} +; check: function %function() diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton index e01391d156..f7ebfa3876 100644 --- a/filetests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -9,13 +9,13 @@ test cat ; Check that defining numbers are rewritten. -function defs() { +function %defs() { ebb100(v20: i32): v1000 = iconst.i32x8 5 v9200 = f64const 0x4.0p0 trap } -; sameln: function defs() { +; sameln: function %defs() { ; nextln: $ebb100($v20: i32): ; nextln: $v1000 = iconst.i32x8 5 ; nextln: $v9200 = f64const 0x1.0000000000000p2 @@ -23,13 +23,13 @@ ebb100(v20: i32): ; nextln: } ; Using values. -function use_value() { +function %use_value() { ebb100(v20: i32): v1000 = iadd_imm v20, 5 v200 = iadd v20, v1000 jump ebb100(v1000) } -; sameln: function use_value() { +; sameln: function %use_value() { ; nextln: ebb0($v20: i32): ; nextln: $v1000 = iadd_imm $v20, 5 ; nextln: $v200 = iadd $v20, $v1000 diff --git a/filetests/parser/ternary.cton b/filetests/parser/ternary.cton index 99cb15b566..3f320a61f0 100644 --- a/filetests/parser/ternary.cton +++ b/filetests/parser/ternary.cton @@ -1,7 +1,7 @@ test cat test verifier -function add_i96(i32, i32, i32, i32, i32, i32) -> i32, i32, i32 { +function %add_i96(i32, i32, i32, i32, i32, i32) -> i32, i32, i32 { ebb1(v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32): v10, v11 = iadd_cout v1, v4 ;check: $v10, $v11 = iadd_cout $v1, $v4 @@ -12,7 +12,7 @@ ebb1(v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32): return v10, v20, v30 } -function sub_i96(i32, i32, i32, i32, i32, i32) -> i32, i32, i32 { +function %sub_i96(i32, i32, i32, i32, i32, i32) -> i32, i32, i32 { ebb1(v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32): v10, v11 = isub_bout v1, v4 ;check: $v10, $v11 = isub_bout $v1, $v4 diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 5ad2f7a39d..1414668a52 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -1,24 +1,24 @@ test cat ; The smallest possible function. -function minimal() { +function %minimal() { ebb0: trap } -; sameln: function minimal() { +; sameln: function %minimal() { ; nextln: ebb0: ; nextln: trap ; nextln: } ; Create and use values. ; Polymorphic instructions with type suffix. -function ivalues() { +function %ivalues() { ebb0: v0 = iconst.i32 2 v1 = iconst.i8 6 v2 = ishl v0, v1 } -; sameln: function ivalues() { +; sameln: function %ivalues() { ; nextln: ebb0: ; nextln: $v0 = iconst.i32 2 ; nextln: $v1 = iconst.i8 6 @@ -26,23 +26,23 @@ ebb0: ; nextln: } ; Polymorphic istruction controlled by second operand. -function select() { +function %select() { ebb0(v90: i32, v91: i32, v92: b1): v0 = select v92, v90, v91 } -; sameln: function select() { +; sameln: function %select() { ; nextln: ebb0($v90: i32, $v91: i32, $v92: b1): ; nextln: $v0 = select $v92, $v90, $v91 ; nextln: } ; Lane indexes. -function lanes() { +function %lanes() { ebb0: v0 = iconst.i32x4 2 v1 = extractlane v0, 3 v2 = insertlane v0, 1, v1 } -; sameln: function lanes() { +; sameln: function %lanes() { ; nextln: ebb0: ; nextln: $v0 = iconst.i32x4 2 ; nextln: $v1 = extractlane $v0, 3 @@ -50,7 +50,7 @@ ebb0: ; nextln: } ; Integer condition codes. -function icmp(i32, i32) { +function %icmp(i32, i32) { ebb0(v90: i32, v91: i32): v0 = icmp eq v90, v91 v1 = icmp ult v90, v91 @@ -58,7 +58,7 @@ ebb0(v90: i32, v91: i32): v3 = irsub_imm v91, 45 br_icmp eq v90, v91, ebb0(v91, v90) } -; sameln: function icmp(i32, i32) { +; sameln: function %icmp(i32, i32) { ; nextln: ebb0($v90: i32, $v91: i32): ; nextln: $v0 = icmp eq $v90, $v91 ; nextln: $v1 = icmp ult $v90, $v91 @@ -68,13 +68,13 @@ ebb0(v90: i32, v91: i32): ; nextln: } ; Floating condition codes. -function fcmp(f32, f32) { +function %fcmp(f32, f32) { ebb0(v90: f32, v91: f32): v0 = fcmp eq v90, v91 v1 = fcmp uno v90, v91 v2 = fcmp lt v90, v91 } -; sameln: function fcmp(f32, f32) { +; sameln: function %fcmp(f32, f32) { ; nextln: ebb0($v90: f32, $v91: f32): ; nextln: $v0 = fcmp eq $v90, $v91 ; nextln: $v1 = fcmp uno $v90, $v91 @@ -83,19 +83,19 @@ ebb0(v90: f32, v91: f32): ; The bitcast instruction has two type variables: The controlling type variable ; controls the outout type, and the input type is a free variable. -function bitcast(i32, f32) { +function %bitcast(i32, f32) { ebb0(v90: i32, v91: f32): v0 = bitcast.i8x4 v90 v1 = bitcast.i32 v91 } -; sameln: function bitcast(i32, f32) { +; sameln: function %bitcast(i32, f32) { ; nextln: ebb0($v90: i32, $v91: f32): ; nextln: $v0 = bitcast.i8x4 $v90 ; nextln: $v1 = bitcast.i32 $v91 ; nextln: } ; Stack slot references -function stack() { +function %stack() { ss10 = stack_slot 8 ss2 = stack_slot 4 @@ -105,7 +105,7 @@ ebb0: stack_store v1, ss10+2 stack_store v2, ss2 } -; sameln: function stack() { +; sameln: function %stack() { ; nextln: $ss10 = stack_slot 8 ; nextln: $ss2 = stack_slot 4 @@ -116,21 +116,21 @@ ebb0: ; nextln: stack_store $v2, $ss2 ; Heap access instructions. -function heap(i32) { +function %heap(i32) { ; TODO: heap0 = heap %foo ebb0(v1: i32): v2 = heap_load.f32 v1 v3 = heap_load.f32 v1+12 heap_store v3, v1 } -; sameln: function heap(i32) { +; sameln: function %heap(i32) { ; nextln: ebb0($v1: i32): ; nextln: $v2 = heap_load.f32 $v1 ; nextln: $v3 = heap_load.f32 $v1+12 ; nextln: heap_store $v3, $v1 ; Memory access instructions. -function memory(i32) { +function %memory(i32) { ebb0(v1: i32): v2 = load.i64 v1 v3 = load.i64 aligned v1 @@ -143,7 +143,7 @@ ebb0(v1: i32): store aligned v3, v1+12 store notrap aligned v3, v1-12 } -; sameln: function memory(i32) { +; sameln: function %memory(i32) { ; nextln: ebb0($v1: i32): ; nextln: $v2 = load.i64 $v1 ; nextln: $v3 = load.i64 aligned $v1 @@ -158,13 +158,13 @@ ebb0(v1: i32): ; Register diversions. ; This test file has no ISA, so we can unly use register unit numbers. -function diversion(i32) { +function %diversion(i32) { ebb0(v1: i32): regmove v1, %10 -> %20 regmove v1, %20 -> %10 return } -; sameln: function diversion(i32) { +; sameln: function %diversion(i32) { ; nextln: ebb0($v1: i32): ; nextln: regmove $v1, %10 -> %20 ; nextln: regmove $v1, %20 -> %10 diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index 9046aeb711..36c0e5c81c 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -5,7 +5,7 @@ isa riscv ; regex: RX=%x\d+ -function add(i32, i32) { +function %add(i32, i32) { ebb0(v1: i32, v2: i32): v3 = iadd v1, v2 ; check: [R#0c,%x5] @@ -14,7 +14,7 @@ ebb0(v1: i32, v2: i32): } ; Function with a dead argument. -function dead_arg(i32, i32) -> i32{ +function %dead_arg(i32, i32) -> i32{ ebb0(v1: i32, v2: i32): ; not: regmove ; check: return $v1 @@ -22,7 +22,7 @@ ebb0(v1: i32, v2: i32): } ; Return a value from a different register. -function move1(i32, i32) -> i32 { +function %move1(i32, i32) -> i32 { ebb0(v1: i32, v2: i32): ; not: regmove ; check: regmove $v2, %x11 -> %x10 @@ -31,7 +31,7 @@ ebb0(v1: i32, v2: i32): } ; Swap two registers. -function swap(i32, i32) -> i32, i32 { +function %swap(i32, i32) -> i32, i32 { ebb0(v1: i32, v2: i32): ; not: regmove ; check: regmove $v2, %x11 -> $(tmp=$RX) diff --git a/filetests/simple_gvn/basic.cton b/filetests/simple_gvn/basic.cton index 06d0989d1e..c76ec12b88 100644 --- a/filetests/simple_gvn/basic.cton +++ b/filetests/simple_gvn/basic.cton @@ -1,6 +1,6 @@ test simple-gvn -function simple_redundancy(i32, i32) -> i32 { +function %simple_redundancy(i32, i32) -> i32 { ebb0(v0: i32, v1: i32): v2 = iadd v0, v1 v3 = iadd v0, v1 @@ -9,7 +9,7 @@ ebb0(v0: i32, v1: i32): return v4 } -function cascading_redundancy(i32, i32) -> i32 { +function %cascading_redundancy(i32, i32) -> i32 { ebb0(v0: i32, v1: i32): v2 = iadd v0, v1 v3 = iadd v0, v1 diff --git a/filetests/verifier/bad_layout.cton b/filetests/verifier/bad_layout.cton index ac29452958..fd597359be 100644 --- a/filetests/verifier/bad_layout.cton +++ b/filetests/verifier/bad_layout.cton @@ -1,6 +1,6 @@ test verifier -function test(i32) { +function %test(i32) { ebb0(v0: i32): jump ebb1 ; error: terminator return @@ -13,7 +13,7 @@ function test(i32) { return } -function test(i32) { ; Ok +function %test(i32) { ; Ok ebb0(v0: i32): return } diff --git a/lib/cretonne/src/ir/funcname.rs b/lib/cretonne/src/ir/funcname.rs index 0074c97502..4ab76d2001 100644 --- a/lib/cretonne/src/ir/funcname.rs +++ b/lib/cretonne/src/ir/funcname.rs @@ -6,73 +6,119 @@ use std::fmt::{self, Write}; use std::ascii::AsciiExt; -/// The name of a function can be any UTF-8 string. +/// The name of a function can be any sequence of bytes. /// /// Function names are mostly a testing and debugging tool. /// In particular, `.cton` files use function names to identify functions. #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct FunctionName(String); +pub struct FunctionName(NameRepr); impl FunctionName { - /// Create new function name equal to `s`. - pub fn new>(s: S) -> FunctionName { - FunctionName(s.into()) + /// Creates a new function name from a sequence of bytes. + /// + /// # Examples + /// + /// ```rust + /// # use cretonne::ir::FunctionName; + /// // Create `FunctionName` from a string. + /// let name = FunctionName::new("hello"); + /// assert_eq!(name.to_string(), "%hello"); + /// + /// // Create `FunctionName` from a sequence of bytes. + /// let bytes: &[u8] = &[10, 9, 8]; + /// let name = FunctionName::new(bytes); + /// assert_eq!(name.to_string(), "#0a0908"); + /// ``` + pub fn new(v: T) -> FunctionName + where T: Into> + { + let vec = v.into(); + if vec.len() <= NAME_LENGTH_THRESHOLD { + let mut bytes = [0u8; NAME_LENGTH_THRESHOLD]; + for (i, &byte) in vec.iter().enumerate() { + bytes[i] = byte; + } + FunctionName(NameRepr::Short { + length: vec.len() as u8, + bytes: bytes, + }) + } else { + FunctionName(NameRepr::Long(vec)) + } } } -fn is_id_start(c: char) -> bool { - c.is_ascii() && (c == '_' || c.is_alphabetic()) +/// Tries to interpret bytes as ASCII alphanumerical characters and `_`. +fn try_as_name(bytes: &[u8]) -> Option { + let mut name = String::with_capacity(bytes.len()); + for c in bytes.iter().map(|&b| b as char) { + if c.is_ascii() && c.is_alphanumeric() || c == '_' { + name.push(c); + } else { + return None; + } + } + Some(name) } -fn is_id_continue(c: char) -> bool { - c.is_ascii() && (c == '_' || c.is_alphanumeric()) +const NAME_LENGTH_THRESHOLD: usize = 22; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum NameRepr { + Short { + length: u8, + bytes: [u8; NAME_LENGTH_THRESHOLD], + }, + Long(Vec), } -// The function name may need quotes if it doesn't parse as an identifier. -fn needs_quotes(name: &str) -> bool { - let mut iter = name.chars(); - if let Some(ch) = iter.next() { - !is_id_start(ch) || !iter.all(is_id_continue) - } else { - // A blank function name needs quotes. - true +impl AsRef<[u8]> for NameRepr { + fn as_ref(&self) -> &[u8] { + match *self { + NameRepr::Short { length, ref bytes } => &bytes[0..length as usize], + NameRepr::Long(ref vec) => vec.as_ref(), + } + } +} + +impl Default for NameRepr { + fn default() -> Self { + NameRepr::Short { + length: 0, + bytes: [0; NAME_LENGTH_THRESHOLD], + } } } impl fmt::Display for FunctionName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if needs_quotes(&self.0) { - f.write_char('"')?; - for c in self.0.chars().flat_map(char::escape_default) { - f.write_char(c)?; - } - f.write_char('"') + if let Some(name) = try_as_name(self.0.as_ref()) { + write!(f, "%{}", name) } else { - f.write_str(&self.0) + f.write_char('#')?; + for byte in self.0.as_ref() { + write!(f, "{:02x}", byte)?; + } + Ok(()) } } } #[cfg(test)] mod tests { - use super::{needs_quotes, FunctionName}; + use super::FunctionName; #[test] - fn quoting() { - assert_eq!(needs_quotes(""), true); - assert_eq!(needs_quotes("x"), false); - assert_eq!(needs_quotes(" "), true); - assert_eq!(needs_quotes("0"), true); - assert_eq!(needs_quotes("x0"), false); - } - - #[test] - fn escaping() { - assert_eq!(FunctionName::new("").to_string(), "\"\""); - assert_eq!(FunctionName::new("x").to_string(), "x"); - assert_eq!(FunctionName::new(" ").to_string(), "\" \""); - assert_eq!(FunctionName::new(" \n").to_string(), "\" \\n\""); - assert_eq!(FunctionName::new("a\u{1000}v").to_string(), - "\"a\\u{1000}v\""); + fn displaying() { + assert_eq!(FunctionName::new("").to_string(), "%"); + assert_eq!(FunctionName::new("x").to_string(), "%x"); + assert_eq!(FunctionName::new("x_1").to_string(), "%x_1"); + assert_eq!(FunctionName::new(" ").to_string(), "#20"); + assert_eq!(FunctionName::new("кретон").to_string(), + "#d0bad180d0b5d182d0bed0bd"); + assert_eq!(FunctionName::new("印花棉布").to_string(), + "#e58db0e88ab1e6a389e5b883"); + assert_eq!(FunctionName::new(vec![0, 1, 2, 3, 4, 5]).to_string(), + "#000102030405"); } } diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index af66cc8868..818e4d9700 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -365,26 +365,26 @@ mod tests { #[test] fn basic() { let mut f = Function::new(); - assert_eq!(f.to_string(), "function \"\"() {\n}\n"); + assert_eq!(f.to_string(), "function %() {\n}\n"); - f.name = FunctionName::new("foo".to_string()); - assert_eq!(f.to_string(), "function foo() {\n}\n"); + f.name = FunctionName::new("foo"); + assert_eq!(f.to_string(), "function %foo() {\n}\n"); f.stack_slots.push(StackSlotData::new(4)); assert_eq!(f.to_string(), - "function foo() {\n ss0 = stack_slot 4\n}\n"); + "function %foo() {\n ss0 = stack_slot 4\n}\n"); let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); assert_eq!(f.to_string(), - "function foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); + "function %foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); f.dfg.append_ebb_arg(ebb, types::I8); assert_eq!(f.to_string(), - "function foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8):\n}\n"); + "function %foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8):\n}\n"); f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(f.to_string(), - "function foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); + "function %foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index fa9f31df5a..3529bc95a3 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -723,10 +723,25 @@ impl<'a> Parser<'a> { // fn parse_function_name(&mut self) -> Result { match self.token() { - Some(Token::Identifier(s)) => { + Some(Token::Name(s)) => { self.consume(); Ok(FunctionName::new(s)) } + Some(Token::HexSequence(s)) => { + if s.len() % 2 != 0 { + return err!(self.loc, + "expected binary function name to have length multiple of two"); + } + let mut bin_name = Vec::with_capacity(s.len() / 2); + let mut i = 0; + while i + 2 <= s.len() { + let byte = u8::from_str_radix(&s[i..i + 2], 16).unwrap(); + bin_name.push(byte); + i += 2; + } + self.consume(); + Ok(FunctionName::new(bin_name)) + } _ => err!(self.loc, "expected function name"), } } @@ -1723,7 +1738,7 @@ mod tests { #[test] fn aliases() { - let (func, details) = Parser::new("function qux() { + let (func, details) = Parser::new("function %qux() { ebb0: v4 = iconst.i8 6 v3 -> v4 @@ -1731,7 +1746,7 @@ mod tests { }") .parse_function(None) .unwrap(); - assert_eq!(func.name.to_string(), "qux"); + assert_eq!(func.name.to_string(), "%qux"); let v4 = details.map.lookup_str("v4").unwrap(); assert_eq!(v4.to_string(), "v0"); let v3 = details.map.lookup_str("v3").unwrap(); @@ -1777,13 +1792,13 @@ mod tests { #[test] fn stack_slot_decl() { - let (func, _) = Parser::new("function foo() { + let (func, _) = Parser::new("function %foo() { ss3 = stack_slot 13 ss1 = stack_slot 1 }") .parse_function(None) .unwrap(); - assert_eq!(func.name.to_string(), "foo"); + assert_eq!(func.name.to_string(), "%foo"); let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); assert_eq!(ss0.to_string(), "ss0"); @@ -1794,7 +1809,7 @@ mod tests { assert_eq!(iter.next(), None); // Catch duplicate definitions. - assert_eq!(Parser::new("function bar() { + assert_eq!(Parser::new("function %bar() { ss1 = stack_slot 13 ss1 = stack_slot 1 }") @@ -1806,13 +1821,13 @@ mod tests { #[test] fn ebb_header() { - let (func, _) = Parser::new("function ebbs() { + let (func, _) = Parser::new("function %ebbs() { ebb0: ebb4(v3: i32): }") .parse_function(None) .unwrap(); - assert_eq!(func.name.to_string(), "ebbs"); + assert_eq!(func.name.to_string(), "%ebbs"); let mut ebbs = func.layout.ebbs(); @@ -1828,7 +1843,7 @@ mod tests { #[test] fn comments() { let (func, Details { comments, .. }) = Parser::new("; before - function comment() { ; decl + function %comment() { ; decl ss10 = stack_slot 13 ; stackslot. ; Still stackslot. jt10 = jump_table ebb0 @@ -1839,7 +1854,7 @@ mod tests { ; More trailing.") .parse_function(None) .unwrap(); - assert_eq!(func.name.to_string(), "comment"); + assert_eq!(func.name.to_string(), "%comment"); assert_eq!(comments.len(), 8); // no 'before' comment. assert_eq!(comments[0], Comment { @@ -1868,7 +1883,7 @@ mod tests { test verify set enable_float=false ; still preamble - function comment() {}") + function %comment() {}") .unwrap(); assert_eq!(tf.commands.len(), 2); assert_eq!(tf.commands[0].command, "cfg"); @@ -1884,23 +1899,23 @@ mod tests { assert_eq!(tf.preamble_comments[0].text, "; before"); assert_eq!(tf.preamble_comments[1].text, "; still preamble"); assert_eq!(tf.functions.len(), 1); - assert_eq!(tf.functions[0].0.name.to_string(), "comment"); + assert_eq!(tf.functions[0].0.name.to_string(), "%comment"); } #[test] fn isa_spec() { assert!(parse_test("isa - function foo() {}") + function %foo() {}") .is_err()); assert!(parse_test("isa riscv set enable_float=false - function foo() {}") + function %foo() {}") .is_err()); match parse_test("set enable_float=false isa riscv - function foo() {}") + function %foo() {}") .unwrap() .isa_spec { IsaSpec::None(_) => panic!("Expected some ISA"), @@ -1910,4 +1925,41 @@ mod tests { } } } + + #[test] + fn binary_function_name() { + // Valid characters in the name. + let func = Parser::new("function #1234567890AbCdEf() { + ebb0: + trap + }") + .parse_function(None) + .unwrap() + .0; + assert_eq!(func.name.to_string(), "#1234567890abcdef"); + + // Invalid characters in the name. + let mut parser = Parser::new("function #12ww() { + ebb0: + trap + }"); + assert!(parser.parse_function(None).is_err()); + + // The length of binary function name should be multiple of two. + let mut parser = Parser::new("function #1() { + ebb0: + trap + }"); + assert!(parser.parse_function(None).is_err()); + + // Empty binary function name should be valid. + let func = Parser::new("function #() { + ebb0: + trap + }") + .parse_function(None) + .unwrap() + .0; + assert_eq!(func.name.to_string(), "%"); + } } diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index f84c792d89..824eb97621 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -222,7 +222,7 @@ mod tests { #[test] fn details() { - let tf = parse_test("function detail() { + let tf = parse_test("function %detail() { ss10 = stack_slot 13 jt10 = jump_table ebb0 ebb0(v4: i32, v7: i32): diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index 21e2a086b2..a0c9a64d90 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -27,7 +27,7 @@ fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) #[test] fn simple_traversal() { test_reverse_postorder_traversal(" - function test(i32) { + function %test(i32) { ebb0(v0: i32): brz v0, ebb1 jump ebb2 @@ -56,7 +56,7 @@ fn simple_traversal() { #[test] fn loops_one() { test_reverse_postorder_traversal(" - function test(i32) { + function %test(i32) { ebb0(v0: i32): jump ebb1 ebb1: @@ -74,7 +74,7 @@ fn loops_one() { #[test] fn loops_two() { test_reverse_postorder_traversal(" - function test(i32) { + function %test(i32) { ebb0(v0: i32): brz v0, ebb1 jump ebb2 @@ -99,7 +99,7 @@ fn loops_two() { #[test] fn loops_three() { test_reverse_postorder_traversal(" - function test(i32) { + function %test(i32) { ebb0(v0: i32): brz v0, ebb1 jump ebb2 @@ -129,7 +129,7 @@ fn loops_three() { #[test] fn back_edge_one() { test_reverse_postorder_traversal(" - function test(i32) { + function %test(i32) { ebb0(v0: i32): brz v0, ebb1 jump ebb2 From 9090bbda245e99fd0db70986af26683517fd05e1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 12 Jun 2017 13:53:12 -0700 Subject: [PATCH 788/968] Generalize rpo_cmp to handle all program points. When comparing instructions in the same EBB, behave like the RPO visits instructions in program order. - Add a Layout::pp_ebb() method for convenience. It gets the EBB containing any program point. - Add a conversion from ValueDef to ExpandedProgramPoint so it can be used with the rpo_cmp method. --- lib/cretonne/src/dominator_tree.rs | 32 +++++++++++++++++++++++++----- lib/cretonne/src/ir/layout.rs | 12 +++++++++++ lib/cretonne/src/ir/progpoint.rs | 9 +++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 6d42cefb36..66fdeb2ff0 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -2,7 +2,7 @@ use entity_map::EntityMap; use flowgraph::{ControlFlowGraph, BasicBlock}; -use ir::{Ebb, Inst, Function, Layout, ProgramOrder}; +use ir::{Ebb, Inst, Function, Layout, ProgramOrder, ExpandedProgramPoint}; use packed_option::PackedOption; use std::cmp::Ordering; @@ -66,11 +66,26 @@ impl DominatorTree { self.nodes[ebb].idom.into() } - /// Compare two EBBs relative to a reverse post-order traversal of the control-flow graph. + /// Compare two EBBs relative to the reverse post-order. + fn rpo_cmp_ebb(&self, a: Ebb, b: Ebb) -> Ordering { + + self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number) + } + + /// Compare two program points relative to a reverse post-order traversal of the control-flow + /// graph. /// /// Return `Ordering::Less` if `a` comes before `b` in the RPO. - pub fn rpo_cmp(&self, a: Ebb, b: Ebb) -> Ordering { - self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number) + /// + /// If `a` and `b` belong to the same EBB, compare their relative position in the EBB. + pub fn rpo_cmp(&self, a: A, b: B, layout: &Layout) -> Ordering + where A: Into, + B: Into + { + let a = a.into(); + let b = b.into(); + self.rpo_cmp_ebb(layout.pp_ebb(a), layout.pp_ebb(b)) + .then(layout.cmp(a, b)) } /// Returns `true` if `a` dominates `b`. @@ -118,7 +133,7 @@ impl DominatorTree { layout: &Layout) -> BasicBlock { loop { - match self.rpo_cmp(a.0, b.0) { + match self.rpo_cmp_ebb(a.0, b.0) { Ordering::Less => { // `a` comes before `b` in the RPO. Move `b` up. let idom = self.nodes[b.0].idom.expect("Unreachable basic block?"); @@ -349,6 +364,13 @@ mod test { assert!(!dt.dominates(br_ebb1_ebb0, jmp_ebb3_ebb1, &func.layout)); assert!(dt.dominates(jmp_ebb3_ebb1, br_ebb1_ebb0, &func.layout)); + assert_eq!(dt.rpo_cmp(ebb3, ebb3, &func.layout), Ordering::Equal); + assert_eq!(dt.rpo_cmp(ebb3, ebb1, &func.layout), Ordering::Less); + assert_eq!(dt.rpo_cmp(ebb3, jmp_ebb3_ebb1, &func.layout), + Ordering::Less); + assert_eq!(dt.rpo_cmp(jmp_ebb3_ebb1, jmp_ebb1_ebb2, &func.layout), + Ordering::Less); + assert_eq!(dt.cfg_postorder(), &[ebb2, ebb0, ebb1, ebb3]); } } diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 06ab92612b..3d569adcc6 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -412,6 +412,18 @@ impl Layout { } } + /// Get the EBB containing the program point `pp`. Panic if `pp` is not in the layout. + pub fn pp_ebb(&self, pp: PP) -> Ebb + where PP: Into + { + match pp.into() { + ExpandedProgramPoint::Ebb(ebb) => ebb, + ExpandedProgramPoint::Inst(inst) => { + self.inst_ebb(inst).expect("Program point not in layout") + } + } + } + /// Append `inst` to the end of `ebb`. pub fn append_inst(&mut self, inst: Inst, ebb: Ebb) { assert_eq!(self.inst_ebb(inst), None); diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index c99e79dbe6..3134a53603 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -63,6 +63,15 @@ impl From for ExpandedProgramPoint { } } +impl From for ExpandedProgramPoint { + fn from(def: ValueDef) -> ExpandedProgramPoint { + match def { + ValueDef::Res(inst, _) => inst.into(), + ValueDef::Arg(ebb, _) => ebb.into(), + } + } +} + impl From for ExpandedProgramPoint { fn from(pp: ProgramPoint) -> ExpandedProgramPoint { if pp.0 & 1 == 0 { From d2dc7232c211654c41ea571ca7cad4f84da61393 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 12 Jun 2017 14:52:42 -0700 Subject: [PATCH 789/968] Generalize DominatorTree::dominates. This is now a generic function that can test arbitrary combinations of instructions and EBBs for dominance. It can handle anything that converts into an expanded program point, including a ValueDef. Also fix a bug if the earlier dominates() function which didn't properly handle block layouts that were not topologically ordered. --- lib/cretonne/src/dominator_tree.rs | 120 ++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 66fdeb2ff0..8022ee1096 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -97,9 +97,48 @@ impl DominatorTree { /// is unreachable. /// /// An instruction is considered to dominate itself. - pub fn dominates(&self, a: Inst, b: Inst, layout: &Layout) -> bool { - let ebb_a = layout.inst_ebb(a).expect("Instruction not in layout."); - self.ebb_dominates(ebb_a, b, layout) && layout.cmp(a, b) != Ordering::Greater + pub fn dominates(&self, a: A, b: B, layout: &Layout) -> bool + where A: Into, + B: Into + { + let a = a.into(); + let b = b.into(); + match a { + ExpandedProgramPoint::Ebb(ebb_a) => { + a == b || self.last_dominator(ebb_a, b, layout).is_some() + } + ExpandedProgramPoint::Inst(inst_a) => { + let ebb_a = layout.inst_ebb(inst_a).expect("Instruction not in layout."); + match self.last_dominator(ebb_a, b, layout) { + Some(last) => layout.cmp(inst_a, last) != Ordering::Greater, + None => false, + } + } + } + } + + /// Find the last instruction in `a` that dominates `b`. + /// If no instructions in `a` dominate `b`, return `None`. + fn last_dominator(&self, a: Ebb, b: B, layout: &Layout) -> Option + where B: Into + { + let (mut ebb_b, mut inst_b) = match b.into() { + ExpandedProgramPoint::Ebb(ebb) => (ebb, None), + ExpandedProgramPoint::Inst(inst) => { + (layout.inst_ebb(inst).expect("Instruction not in layout."), Some(inst)) + } + }; + let rpo_a = self.nodes[a].rpo_number; + + // Run a finger up the dominator tree from b until we see a. + // Do nothing if b is unreachable. + while rpo_a < self.nodes[ebb_b].rpo_number { + let idom = self.idom(ebb_b).expect("Shouldn't meet unreachable here."); + ebb_b = layout.inst_ebb(idom).expect("Dominator got removed."); + inst_b = Some(idom); + } + + if a == ebb_b { inst_b } else { None } } /// Returns `true` if `ebb_a` dominates `b`. @@ -373,4 +412,79 @@ mod test { assert_eq!(dt.cfg_postorder(), &[ebb2, ebb0, ebb1, ebb3]); } + + #[test] + fn backwards_layout() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + + let jmp02; + let jmp21; + let trap; + { + let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); + + cur.insert_ebb(ebb0); + jmp02 = dfg.ins(cur).jump(ebb2, &[]); + + cur.insert_ebb(ebb1); + trap = dfg.ins(cur).trap(); + + cur.insert_ebb(ebb2); + jmp21 = dfg.ins(cur).jump(ebb1, &[]); + } + + let cfg = ControlFlowGraph::with_function(&func); + let dt = DominatorTree::with_function(&func, &cfg); + + assert_eq!(func.layout.entry_block(), Some(ebb0)); + assert_eq!(dt.idom(ebb0), None); + assert_eq!(dt.idom(ebb1), Some(jmp21)); + assert_eq!(dt.idom(ebb2), Some(jmp02)); + + assert!(dt.dominates(ebb0, ebb0, &func.layout)); + assert!(dt.dominates(ebb0, jmp02, &func.layout)); + assert!(dt.dominates(ebb0, ebb1, &func.layout)); + assert!(dt.dominates(ebb0, trap, &func.layout)); + assert!(dt.dominates(ebb0, ebb2, &func.layout)); + assert!(dt.dominates(ebb0, jmp21, &func.layout)); + + assert!(!dt.dominates(jmp02, ebb0, &func.layout)); + assert!(dt.dominates(jmp02, jmp02, &func.layout)); + assert!(dt.dominates(jmp02, ebb1, &func.layout)); + assert!(dt.dominates(jmp02, trap, &func.layout)); + assert!(dt.dominates(jmp02, ebb2, &func.layout)); + assert!(dt.dominates(jmp02, jmp21, &func.layout)); + + assert!(!dt.dominates(ebb1, ebb0, &func.layout)); + assert!(!dt.dominates(ebb1, jmp02, &func.layout)); + assert!(dt.dominates(ebb1, ebb1, &func.layout)); + assert!(dt.dominates(ebb1, trap, &func.layout)); + assert!(!dt.dominates(ebb1, ebb2, &func.layout)); + assert!(!dt.dominates(ebb1, jmp21, &func.layout)); + + assert!(!dt.dominates(trap, ebb0, &func.layout)); + assert!(!dt.dominates(trap, jmp02, &func.layout)); + assert!(!dt.dominates(trap, ebb1, &func.layout)); + assert!(dt.dominates(trap, trap, &func.layout)); + assert!(!dt.dominates(trap, ebb2, &func.layout)); + assert!(!dt.dominates(trap, jmp21, &func.layout)); + + assert!(!dt.dominates(ebb2, ebb0, &func.layout)); + assert!(!dt.dominates(ebb2, jmp02, &func.layout)); + assert!(dt.dominates(ebb2, ebb1, &func.layout)); + assert!(dt.dominates(ebb2, trap, &func.layout)); + assert!(dt.dominates(ebb2, ebb2, &func.layout)); + assert!(dt.dominates(ebb2, jmp21, &func.layout)); + + assert!(!dt.dominates(jmp21, ebb0, &func.layout)); + assert!(!dt.dominates(jmp21, jmp02, &func.layout)); + assert!(dt.dominates(jmp21, ebb1, &func.layout)); + assert!(dt.dominates(jmp21, trap, &func.layout)); + assert!(!dt.dominates(jmp21, ebb2, &func.layout)); + assert!(dt.dominates(jmp21, jmp21, &func.layout)); + } } From 2875c6ddf95e800c5af1dc0c66c1c76027c54852 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 12 Jun 2017 14:59:34 -0700 Subject: [PATCH 790/968] Remove the ebb_dominates function. This is now subsumed by the generic 'dominates' function. --- lib/cretonne/src/dominator_tree.rs | 22 ---------------------- lib/cretonne/src/licm.rs | 4 ++-- lib/cretonne/src/loop_analysis.rs | 4 ++-- lib/cretonne/src/verifier/mod.rs | 2 +- 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 8022ee1096..14f08e4263 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -141,28 +141,6 @@ impl DominatorTree { if a == ebb_b { inst_b } else { None } } - /// Returns `true` if `ebb_a` dominates `b`. - /// - /// This means that every control-flow path from the function entry to `b` must go through - /// `ebb_a`. - /// - /// Dominance is ill defined for unreachable blocks. This function can always determine - /// dominance for instructions in the same EBB, but otherwise returns `false` if either block - /// is unreachable. - pub fn ebb_dominates(&self, ebb_a: Ebb, mut b: Inst, layout: &Layout) -> bool { - let mut ebb_b = layout.inst_ebb(b).expect("Instruction not in layout."); - let rpo_a = self.nodes[ebb_a].rpo_number; - - // Run a finger up the dominator tree from b until we see a. - // Do nothing if b is unreachable. - while rpo_a < self.nodes[ebb_b].rpo_number { - b = self.idom(ebb_b).expect("Shouldn't meet unreachable here."); - ebb_b = layout.inst_ebb(b).expect("Dominator got removed."); - } - - ebb_a == ebb_b - } - /// Compute the common dominator of two basic blocks. /// /// Both basic blocks are assumed to be reachable. diff --git a/lib/cretonne/src/licm.rs b/lib/cretonne/src/licm.rs index 1c9e1930fd..0fc8212460 100644 --- a/lib/cretonne/src/licm.rs +++ b/lib/cretonne/src/licm.rs @@ -77,7 +77,7 @@ fn create_pre_header(header: Ebb, } for &(_, last_inst) in cfg.get_predecessors(header) { // We only follow normal edges (not the back edges) - if !domtree.ebb_dominates(header.clone(), last_inst, &func.layout) { + if !domtree.dominates(header, last_inst, &func.layout) { change_branch_jump_destination(last_inst, pre_header, func); } } @@ -108,7 +108,7 @@ fn has_pre_header(layout: &Layout, let mut found = false; for &(pred_ebb, last_inst) in cfg.get_predecessors(header) { // We only count normal edges (not the back edges) - if !domtree.ebb_dominates(header.clone(), last_inst, layout) { + if !domtree.dominates(header, last_inst, layout) { if found { // We have already found one, there are more than one return None; diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs index f5ea45424e..5ed5f8a4d8 100644 --- a/lib/cretonne/src/loop_analysis.rs +++ b/lib/cretonne/src/loop_analysis.rs @@ -132,7 +132,7 @@ impl LoopAnalysis { for &ebb in domtree.cfg_postorder().iter().rev() { for &(_, pred_inst) in cfg.get_predecessors(ebb) { // If the ebb dominates one of its predecessors it is a back edge - if domtree.ebb_dominates(ebb, pred_inst, layout) { + if domtree.dominates(ebb, pred_inst, layout) { // This ebb is a loop header, so we create its associated loop let lp = self.loops.push(LoopData::new(ebb, None)); self.ebb_loop_map[ebb] = lp.into(); @@ -156,7 +156,7 @@ impl LoopAnalysis { for lp in self.loops().rev() { for &(pred, pred_inst) in cfg.get_predecessors(self.loops[lp].header) { // We follow the back edges - if domtree.ebb_dominates(self.loops[lp].header, pred_inst, layout) { + if domtree.dominates(self.loops[lp].header, pred_inst, layout) { stack.push(pred); } } diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 5c283dd6d3..a2f806a447 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -381,7 +381,7 @@ impl<'a> Verifier<'a> { ebb); } // The defining EBB dominates the instruction using this value. - if !self.domtree.ebb_dominates(ebb, loc_inst, &self.func.layout) { + if !self.domtree.dominates(ebb, loc_inst, &self.func.layout) { return err!(loc_inst, "uses value arg from non-dominating {}", ebb); } } From 00551dbc5fa5071c0298efe099a753e067ee77de Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Jun 2017 13:39:52 -0700 Subject: [PATCH 791/968] Add RISC-V encodings for spill and fill. Add a Stack() class for specifying operand constraints for values on the stack. Add encoding recipes for RISC-V spill and fill instructions. Don't implement the encoding recipe functions yet since we don't have the stack slot layout yet. --- lib/cretonne/meta/cdsl/isa.py | 9 ++++++--- lib/cretonne/meta/cdsl/registers.py | 12 ++++++++++++ lib/cretonne/meta/gen_encoding.py | 6 +++++- lib/cretonne/meta/isa/riscv/encodings.py | 11 ++++++++++- lib/cretonne/meta/isa/riscv/recipes.py | 13 ++++++++++++- lib/cretonne/src/isa/riscv/binemit.rs | 8 ++++++++ 6 files changed, 53 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index a2387bc0ff..bc85f7fb8f 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,7 +1,7 @@ """Defining instruction set architectures.""" from __future__ import absolute_import from .predicates import And -from .registers import RegClass, Register +from .registers import RegClass, Register, Stack from .ast import Apply # The typing module is only required by mypy, and we don't use these imports @@ -14,7 +14,7 @@ try: from .settings import SettingGroup # noqa from .types import ValueType # noqa from .registers import RegBank # noqa - OperandConstraint = Union[RegClass, Register, int] + OperandConstraint = Union[RegClass, Register, int, Stack] ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] # Instruction specification for encodings. Allows for predicated # instructions. @@ -187,6 +187,7 @@ class EncRecipe(object): - A `Register` specifying a fixed-register operand. - An integer indicating that this result is tied to a value operand, so they must use the same register. + - A `Stack` specifying a value in a stack slot. The `branch_range` argument must be provided for recipes that can encode branch instructions. It is an `(origin, bits)` tuple describing the exact @@ -245,7 +246,9 @@ class EncRecipe(object): # Check that it is in range. assert c >= 0 and c < len(self.ins) else: - assert isinstance(c, RegClass) or isinstance(c, Register) + assert (isinstance(c, RegClass) + or isinstance(c, Register) + or isinstance(c, Stack)) return seq def ties(self): diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index caaacd20e4..880f9096d4 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -340,3 +340,15 @@ class Register(object): # type: (RegClass, int) -> None self.regclass = rc self.unit = unit + + +class Stack(object): + """ + An operand that must be in a stack slot. + + A `Stack` object can be used to indicate an operand constraint for a value + operand that must live in a stack slot. + """ + def __init__(self, rc): + # type: (RegClass) -> None + self.regclass = rc diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index c56db8fc79..135940dd23 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -56,7 +56,7 @@ from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict import math import itertools -from cdsl.registers import RegClass, Register +from cdsl.registers import RegClass, Register, Stack from cdsl.predicates import FieldPredicate try: @@ -519,6 +519,10 @@ def emit_operand_constraints( assert cons == tied[n], "Invalid tied constraint" fmt.format('kind: ConstraintKind::Tied({}),', cons) fmt.format('regclass: {},', recipe.ins[cons]) + elif isinstance(cons, Stack): + assert n not in tied, "Can't tie stack operand" + fmt.line('kind: ConstraintKind::Stack,') + fmt.format('regclass: {},', cons.regclass) else: raise AssertionError( 'Unsupported constraint {}'.format(cons)) diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index ffc848ef06..2b59964007 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -6,8 +6,9 @@ from base import instructions as base from base.immediates import intcc from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL +from .recipes import LOAD, STORE from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret -from .recipes import U, UJ, UJcall, SB, SBzero +from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi from .settings import use_m from cdsl.ast import Var @@ -114,3 +115,11 @@ for inst, f3 in [ # is added by legalize_signature(). RV32.enc(base.x_return, Iret, JALR()) RV64.enc(base.x_return, Iret, JALR()) + +# Spill and fill. +RV32.enc(base.spill.i32, GPsp, STORE(0b010)) +RV64.enc(base.spill.i32, GPsp, STORE(0b010)) +RV64.enc(base.spill.i64, GPsp, STORE(0b011)) +RV32.enc(base.fill.i32, GPfi, LOAD(0b010)) +RV64.enc(base.fill.i32, GPfi, LOAD(0b010)) +RV64.enc(base.fill.i64, GPfi, LOAD(0b011)) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index b5aa56bc85..1a7e98d0fe 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -11,8 +11,9 @@ instruction formats described in the reference: from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt +from cdsl.registers import Stack from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm -from base.formats import UnaryImm, BranchIcmp, Branch, Jump, Call +from base.formats import Unary, UnaryImm, BranchIcmp, Branch, Jump, Call from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -134,3 +135,13 @@ SBzero = EncRecipe( 'SBzero', Branch, size=4, ins=(GPR), outs=(), branch_range=(0, 13)) + +# Spill of a GPR. +GPsp = EncRecipe( + 'GPsp', Unary, size=4, + ins=GPR, outs=Stack(GPR)) + +# Fill of a GPR. +GPfi = EncRecipe( + 'GPfi', Unary, size=4, + ins=Stack(GPR), outs=GPR) diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 12a3eb462f..e025b3f9d4 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -333,3 +333,11 @@ fn recipe_ujcall(func: &Function, inst: Inst, sink: &mut panic!("Expected Call format: {:?}", func.dfg[inst]); } } + +fn recipe_gpsp(_func: &Function, _inst: Inst, _sink: &mut CS) { + unimplemented!(); +} + +fn recipe_gpfi(_func: &Function, _inst: Inst, _sink: &mut CS) { + unimplemented!(); +} From 96fe287f67e215bd68a6f6aae2611bf20b53fd1c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 12 Jun 2017 11:55:01 -0700 Subject: [PATCH 792/968] Track transient register counts in Pressure. The register pressure tracker now has to separate register counts: base and transient. The transient counts are used to track spikes of register pressure, such as dead defs or temporary registers needed to satisfy instruction constraints. The base counts represent long-lived variables. --- lib/cretonne/src/regalloc/pressure.rs | 60 ++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index 14e4441697..6ddf6bdf4e 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -26,6 +26,12 @@ //! //! Currently, the only register bank with multiple top-level registers is the `arm32` //! floating-point register bank which has `S`, `D`, and `Q` top-level classes. +//! +//! # Base and transient counts +//! +//! We maintain two separate register counts per top-level register class: base counts and +//! transient counts. The base counts are adjusted with the `take` and `free` functions. The +//! transient counts are adjusted with `take_transient` and `free_transient`. // Remove once we're using the pressure tracker. #![allow(dead_code)] @@ -37,11 +43,12 @@ use std::iter::ExactSizeIterator; /// Information per top-level register class. /// -/// Everything but the count is static information computed from the constructor arguments. +/// Everything but the counts is static information computed from the constructor arguments. #[derive(Default)] struct TopRC { // Number of registers currently used from this register class. - count: u32, + base_count: u32, + transient_count: u32, // Max number of registers that can be allocated. limit: u32, @@ -56,6 +63,12 @@ struct TopRC { num_toprcs: u8, } +impl TopRC { + fn total_count(&self) -> u32 { + self.base_count + self.transient_count + } +} + pub struct Pressure { // Bit mask of top-level register classes that are aliased by other top-level register classes. // Unaliased register classes can use a simpler interference algorithm. @@ -108,12 +121,16 @@ impl Pressure { /// If not, returns a bit-mask of top-level register classes that are interfering. Register /// pressure should be eased in one of the returned top-level register classes before calling /// `can_take()` to check again. - pub fn check_avail(&self, rc: RegClass) -> RegClassMask { + fn check_avail(&self, rc: RegClass) -> RegClassMask { let entry = &self.toprc[rc.toprc as usize]; let mask = 1 << rc.toprc; if self.aliased & mask == 0 { // This is a simple unaliased top-level register class. - if entry.count < entry.limit { 0 } else { mask } + if entry.total_count() < entry.limit { + 0 + } else { + mask + } } else { // This is the more complicated case. The top-level register class has aliases. self.check_avail_aliased(entry) @@ -142,9 +159,9 @@ impl Pressure { let u = if rcw < width { // We can't take more than the total number of register units in the class. // This matters for arm32 S-registers which can only ever lock out 16 D-registers. - min(rc.count * width, rc.limit * rcw) + min(rc.total_count() * width, rc.limit * rcw) } else { - rc.count * rcw + rc.total_count() * rcw }; // If this top-level RC on its own is responsible for exceeding our limit, return it @@ -169,20 +186,41 @@ impl Pressure { /// Take a register from `rc`. /// - /// This assumes that `can_take(rc)` already returned 0. + /// This does not check if there are enough registers available. pub fn take(&mut self, rc: RegClass) { - self.toprc[rc.toprc as usize].count += 1 + self.toprc[rc.toprc as usize].base_count += 1 } /// Free a register in `rc`. pub fn free(&mut self, rc: RegClass) { - self.toprc[rc.toprc as usize].count -= 1 + self.toprc[rc.toprc as usize].base_count -= 1 } - /// Reset all counts to 0. + /// Reset all counts to 0, both base and transient. pub fn reset(&mut self) { for e in self.toprc.iter_mut() { - e.count = 0; + e.base_count = 0; + e.transient_count = 0; + } + } + + /// Try to increment a transient counter. + /// + /// This will fail if there are not enough registers available. + pub fn take_transient(&mut self, rc: RegClass) -> Result<(), RegClassMask> { + let mask = self.check_avail(rc); + if mask == 0 { + self.toprc[rc.toprc as usize].transient_count += 1; + Ok(()) + } else { + Err(mask) + } + } + + /// Reset all transient counts to 0. + pub fn reset_transient(&mut self) { + for e in self.toprc.iter_mut() { + e.transient_count = 0; } } } From 36f189810e663bb1e70c1549bf7017e47806642a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 8 Jun 2017 10:33:22 -0700 Subject: [PATCH 793/968] Basic spilling implementation. Add a spilling pass which lowers register pressure by assigning SSA values to the stack. Important missing features: - Resolve conflicts where an instruction uses the same value more than once in incompatible ways. - Deal with EBB arguments. Fix bugs in the reload pass exposed by the first test case: - Create live ranges for temporary registers. - Set encodings on created spill and fill instructions. --- filetests/regalloc/spill.cton | 44 +++ lib/cretonne/src/regalloc/context.rs | 17 +- .../src/regalloc/live_value_tracker.rs | 15 +- lib/cretonne/src/regalloc/liveness.rs | 8 + lib/cretonne/src/regalloc/mod.rs | 1 + lib/cretonne/src/regalloc/pressure.rs | 11 + lib/cretonne/src/regalloc/reload.rs | 31 ++- lib/cretonne/src/regalloc/spilling.rs | 259 ++++++++++++++++++ 8 files changed, 380 insertions(+), 6 deletions(-) create mode 100644 filetests/regalloc/spill.cton create mode 100644 lib/cretonne/src/regalloc/spilling.rs diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton new file mode 100644 index 0000000000..a6565b987e --- /dev/null +++ b/filetests/regalloc/spill.cton @@ -0,0 +1,44 @@ +test regalloc + +; Test the spiler on an ISA with few registers. +; RV32E has 16 registers, where: +; - %x0 is hardwired to zero. +; - %x1 is the return address. +; - %x2 is the stack pointer. +; - %x3 is the global pointer. +; - %x4 is the thread pointer. +; - %x10-%x15 are function arguments. +; +; regex: V=v\d+ + +isa riscv enable_e + +; In straight-line code, the first value defined is spilled. +; That is the argument. +function %pyramid(i32) -> i32 { +ebb0(v1: i32): +; check: $v1 = spill $(rv1=$V) + v2 = iadd_imm v1, 12 + v3 = iadd_imm v2, 12 + v4 = iadd_imm v3, 12 + v5 = iadd_imm v4, 12 + v6 = iadd_imm v5, 12 + v7 = iadd_imm v6, 12 + v8 = iadd_imm v7, 12 + v9 = iadd_imm v8, 12 + v10 = iadd_imm v9, 12 + v11 = iadd_imm v10, 12 + v12 = iadd_imm v11, 12 + v31 = iadd v11, v12 + v30 = iadd v31, v10 + v29 = iadd v30, v9 + v28 = iadd v29, v8 + v27 = iadd v28, v7 + v26 = iadd v27, v6 + v25 = iadd v26, v5 + v24 = iadd v25, v4 + v23 = iadd v24, v3 + v22 = iadd v23, v2 + v21 = iadd v22, v1 + return v21 +} diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 3cf2b5dd81..b404602a34 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -12,6 +12,7 @@ use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; use regalloc::reload::Reload; +use regalloc::spilling::Spilling; use result::CtonResult; use topo_order::TopoOrder; use verifier::{verify_context, verify_liveness}; @@ -21,6 +22,7 @@ pub struct Context { liveness: Liveness, topo: TopoOrder, tracker: LiveValueTracker, + spilling: Spilling, reload: Reload, coloring: Coloring, } @@ -35,6 +37,7 @@ impl Context { liveness: Liveness::new(), topo: TopoOrder::new(), tracker: LiveValueTracker::new(), + spilling: Spilling::new(), reload: Reload::new(), coloring: Coloring::new(), } @@ -62,7 +65,19 @@ impl Context { verify_liveness(isa, func, cfg, &self.liveness)?; } - // TODO: Second pass: Spilling. + // Second pass: Spilling. + self.spilling + .run(isa, + func, + domtree, + &mut self.liveness, + &mut self.topo, + &mut self.tracker); + + if isa.flags().enable_verifier() { + verify_context(func, cfg, domtree, Some(isa))?; + verify_liveness(isa, func, cfg, &self.liveness)?; + } // Third pass: Reload. self.reload diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index a5ee8197e8..ccaf6c9209 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -12,7 +12,6 @@ use partition_slice::partition_slice; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; use regalloc::liverange::LiveRange; - use std::collections::HashMap; type ValueList = EntityList; @@ -299,6 +298,20 @@ impl LiveValueTracker { self.live.remove_dead_values(); } + /// Process new spills. + /// + /// Any values where `f` returns true are spilled and will be treated as if their affinity was + /// `Stack`. + pub fn process_spills(&mut self, mut f: F) + where F: FnMut(Value) -> bool + { + for lv in &mut self.live.values { + if f(lv.value) { + lv.affinity = Affinity::Stack; + } + } + } + /// Save the current set of live values so it is associated with `idom`. fn save_idom_live_set(&mut self, idom: Inst) { let values = self.live.values.iter().map(|lv| lv.value); diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 3ced8ebc00..afccbffaa3 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -182,6 +182,7 @@ use isa::{TargetIsa, EncInfo}; use regalloc::affinity::Affinity; use regalloc::liverange::LiveRange; use sparse_map::SparseMap; +use std::mem; /// A set of live ranges, indexed by value number. type LiveRangeSet = SparseMap; @@ -338,6 +339,13 @@ impl Liveness { &mut lr.affinity } + /// Change the affinity of `value` to `Stack` and return the previous affinity. + pub fn spill(&mut self, value: Value) -> Affinity { + let mut lr = self.ranges.get_mut(value).expect("Value has no live range"); + mem::replace(&mut lr.affinity, Affinity::Stack) + } + + /// Compute the live ranges of all SSA values used in `func`. /// This clears out any existing analysis stored in this data structure. pub fn compute(&mut self, isa: &TargetIsa, func: &Function, cfg: &ControlFlowGraph) { diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 4a86abbe93..59eb341eb0 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -14,6 +14,7 @@ mod diversion; mod pressure; mod reload; mod solver; +mod spilling; pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index 6ddf6bdf4e..1398bff8d6 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -39,6 +39,7 @@ use isa::registers::{RegInfo, MAX_TOPRCS, RegClass, RegClassMask}; use regalloc::AllocatableSet; use std::cmp::min; +use std::fmt; use std::iter::ExactSizeIterator; /// Information per top-level register class. @@ -225,6 +226,16 @@ impl Pressure { } } +impl fmt::Display for Pressure { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Pressure[")?; + for rc in &self.toprc { + write!(f, " {}+{}/{}", rc.base_count, rc.transient_count, rc.limit)?; + } + write!(f, " ]") + } +} + #[cfg(test)] mod tests { use isa::{TargetIsa, RegClass}; diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 533dd45595..1342625380 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -10,6 +10,7 @@ //! pressure limits to be exceeded. use dominator_tree::DominatorTree; +use entity_map::EntityMap; use ir::{Ebb, Inst, Value, Function, DataFlowGraph}; use ir::layout::{Cursor, CursorPosition}; use ir::{InstBuilder, ArgumentLoc}; @@ -29,6 +30,7 @@ pub struct Reload { /// Context data structure that gets instantiated once per pass. struct Context<'a> { + isa: &'a TargetIsa, // Cached ISA information. // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. encinfo: EncInfo, @@ -60,6 +62,7 @@ impl Reload { topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { let mut ctx = Context { + isa, encinfo: isa.encoding_info(), domtree, liveness, @@ -112,7 +115,13 @@ impl<'a> Context<'a> { while let Some(inst) = pos.current_inst() { let encoding = func.encodings[inst]; if encoding.is_legal() { - self.visit_inst(ebb, inst, encoding, &mut pos, &mut func.dfg, tracker); + self.visit_inst(ebb, + inst, + encoding, + &mut pos, + &mut func.dfg, + &mut func.encodings, + tracker); tracker.drop_dead(inst); } else { pos.next_inst(); @@ -121,7 +130,7 @@ impl<'a> Context<'a> { } /// Process the EBB parameters. Return the next instruction in the EBB to be processed - fn visit_ebb_header(&self, + fn visit_ebb_header(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) @@ -139,7 +148,7 @@ impl<'a> Context<'a> { /// Visit the arguments to the entry block. /// These values have ABI constraints from the function signature. - fn visit_entry_args(&self, + fn visit_entry_args(&mut self, ebb: Ebb, func: &mut Function, args: &[LiveValue]) @@ -157,7 +166,15 @@ impl<'a> Context<'a> { // with a temporary register value that is immediately spilled. let reg = func.dfg.replace_ebb_arg(arg.value, abi.value_type); func.dfg.ins(&mut pos).with_result(arg.value).spill(reg); - // TODO: Update live ranges. + let spill = func.dfg.value_def(arg.value).unwrap_inst(); + *func.encodings.ensure(spill) = self.isa + .encode(&func.dfg, &func.dfg[spill], abi.value_type) + .expect("Can't encode spill"); + // Update live ranges. + self.liveness.move_def_locally(arg.value, spill); + let affinity = Affinity::abi(abi, self.isa); + self.liveness.create_dead(reg, ebb, affinity); + self.liveness.extend_locally(reg, ebb, spill, pos.layout); } } ArgumentLoc::Stack(_) => { @@ -184,6 +201,7 @@ impl<'a> Context<'a> { encoding: Encoding, pos: &mut Cursor, dfg: &mut DataFlowGraph, + encodings: &mut EntityMap, tracker: &mut LiveValueTracker) { // Get the operand constraints for `inst` that we are trying to satisfy. let constraints = self.encinfo @@ -213,6 +231,11 @@ impl<'a> Context<'a> { } let reg = dfg.ins(pos).fill(cand.value); + let fill = dfg.value_def(reg).unwrap_inst(); + *encodings.ensure(fill) = self.isa + .encode(dfg, &dfg[fill], dfg.value_type(reg)) + .expect("Can't encode fill"); + self.reloads .insert(ReloadedValue { stack: cand.value, diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs new file mode 100644 index 0000000000..8938944952 --- /dev/null +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -0,0 +1,259 @@ +//! Spilling pass. +//! +//! The spilling pass is the first to run after the liveness analysis. Its primary function is to +//! ensure that the register pressure never exceeds the number of available registers by moving +//! some SSA values to spill slots on the stack. This is encoded in the affinity of the value's +//! live range. + +use dominator_tree::DominatorTree; +use ir::{DataFlowGraph, Layout, Cursor}; +use ir::{Function, Ebb, Inst, Value}; +use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; +use isa::registers::RegClassMask; +use regalloc::affinity::Affinity; +use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; +use regalloc::liveness::Liveness; +use regalloc::pressure::Pressure; +use topo_order::TopoOrder; + +/// Persistent data structures for the spilling pass. +pub struct Spilling { + spills: Vec, +} + +/// Context data structure that gets instantiated once per pass. +struct Context<'a> { + // Cached ISA information. + reginfo: RegInfo, + encinfo: EncInfo, + + // References to contextual data structures we need. + domtree: &'a DominatorTree, + liveness: &'a mut Liveness, + topo: &'a mut TopoOrder, + + // Current register pressure. + pressure: Pressure, + + // Values spilled for the current instruction. These values have already been removed from the + // pressure tracker, but they are still present in the live value tracker and their affinity + // hasn't been changed yet. + spills: &'a mut Vec, +} + +impl Spilling { + /// Create a new spilling data structure. + pub fn new() -> Spilling { + Spilling { spills: Vec::new() } + } + + /// Run the spilling algorithm over `func`. + pub fn run(&mut self, + isa: &TargetIsa, + func: &mut Function, + domtree: &DominatorTree, + liveness: &mut Liveness, + topo: &mut TopoOrder, + tracker: &mut LiveValueTracker) { + dbg!("Spilling for:\n{}", func.display(isa)); + let reginfo = isa.register_info(); + let usable_regs = isa.allocatable_registers(func); + let mut ctx = Context { + reginfo: isa.register_info(), + encinfo: isa.encoding_info(), + domtree, + liveness, + topo, + pressure: Pressure::new(®info, &usable_regs), + spills: &mut self.spills, + }; + ctx.run(func, tracker) + } +} + +impl<'a> Context<'a> { + fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { + self.topo.reset(func.layout.ebbs()); + while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { + self.visit_ebb(ebb, func, tracker); + } + } + + fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + dbg!("Spilling {}:", ebb); + self.visit_ebb_header(ebb, func, tracker); + tracker.drop_dead_args(); + + let mut pos = Cursor::new(&mut func.layout); + pos.goto_top(ebb); + while let Some(inst) = pos.next_inst() { + if let Some(constraints) = self.encinfo.operand_constraints(func.encodings[inst]) { + self.visit_inst(inst, constraints, &mut pos, &mut func.dfg, tracker); + tracker.drop_dead(inst); + self.process_spills(tracker); + } + } + } + + // Take all live registers in `regs` from the pressure set. + // This doesn't cause any spilling, it is assumed there are enough registers. + fn take_live_regs(&mut self, regs: &[LiveValue]) { + for lv in regs { + if !lv.is_dead { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + self.pressure.take(rc); + } + } + } + } + + // Free all registers in `kills` from the pressure set. + fn free_regs(&mut self, kills: &[LiveValue]) { + for lv in kills { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + self.pressure.free(rc); + } + } + } + + fn visit_ebb_header(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + let (liveins, args) = + tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + + // Count the live-in registers. These should already fit in registers; they did at the + // dominator. + self.pressure.reset(); + self.take_live_regs(liveins); + + // TODO: Process and count EBB arguments. Some may need spilling. + self.take_live_regs(args); + } + + fn visit_inst(&mut self, + inst: Inst, + constraints: &RecipeConstraints, + pos: &mut Cursor, + dfg: &DataFlowGraph, + tracker: &mut LiveValueTracker) { + dbg!("Inst {}, {}", dfg.display_inst(inst), self.pressure); + // TODO: Repair constraint violations by copying input values. + // + // - Tied use of value that is not killed. + // - Inconsistent uses of the same value. + // + // Each inserted copy may increase register pressure. Fix by spilling something not used by + // the instruction. + // + // Count pressure for register uses of spilled values too. + // + // Finally, reset pressure state to level from before the input adjustments, minus spills. + // + // Spills should be removed from tracker. Otherwise they could be double-counted by + // free_regs below. + + // Update the live value tracker with this instruction. + let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + + + // Remove kills from the pressure tracker. + self.free_regs(kills); + + // Make sure we have enough registers for the register defs. + // Dead defs are included here. They need a register too. + // No need to process call return values, they are in fixed registers. + for op in constraints.outs { + if op.kind != ConstraintKind::Stack { + // Add register def to pressure, spill if needed. + while let Err(mask) = self.pressure.take_transient(op.regclass) { + dbg!("Need {} reg from {} throughs", op.regclass, throughs.len()); + self.spill_from(mask, throughs, dfg, &pos.layout); + } + } + } + self.pressure.reset_transient(); + + // Restore pressure state, compute pressure with affinities from `defs`. + // Exclude dead defs. Includes call return values. + // This won't cause spilling. + self.take_live_regs(defs); + } + + // Spill a candidate from `candidates` whose top-level register class is in `mask`. + fn spill_from<'ii, II>(&mut self, + mask: RegClassMask, + candidates: II, + dfg: &DataFlowGraph, + layout: &Layout) + where II: IntoIterator + { + // Find the best viable spill candidate. + // + // The very simple strategy implemented here is to spill the value with the earliest def in + // the reverse post-order. This strategy depends on a good reload pass to generate good + // code. + // + // We know that all candidate defs dominate the current instruction, so one of them will + // dominate the others. That is the earliest def. + let best = candidates + .into_iter() + .filter_map(|lv| { + // Viable candidates are registers in one of the `mask` classes, and not already in + // the spill set. + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + if (mask & (1 << rc.toprc)) != 0 && !self.spills.contains(&lv.value) { + // Here, `lv` is a viable spill candidate. + return Some(lv.value); + } + } + None + }) + .min_by(|&a, &b| { + // Find the minimum candidate according to the RPO of their defs. + self.domtree + .rpo_cmp(dfg.value_def(a), dfg.value_def(b), layout) + }); + + if let Some(value) = best { + // Found a spill candidate. + self.spill_reg(value); + } else { + panic!("Ran out of registers for mask={}", mask); + } + } + + /// Spill `value` immediately by + /// + /// 1. Changing its affinity to `Stack` which marks the spill. + /// 2. Removing the value from the pressure tracker. + /// 3. Adding the value to `self.spills` for later reference by `process_spills`. + /// + /// Note that this does not update the cached affinity in the live value tracker. Call + /// `process_spills` to do that. + fn spill_reg(&mut self, value: Value) { + if let Affinity::Reg(rci) = self.liveness.spill(value) { + let rc = self.reginfo.rc(rci); + self.pressure.free(rc); + self.spills.push(value); + dbg!("Spilled {}:{} -> {}", value, rc, self.pressure); + } else { + panic!("Cannot spill {} that was already on the stack", value); + } + } + + /// Process any pending spills in the `self.spills` vector. + /// + /// It is assumed that spills are removed from the pressure tracker immediately, see + /// `spill_from` above. + /// + /// We also need to update the live range affinity and remove spilled values from the live + /// value tracker. + fn process_spills(&mut self, tracker: &mut LiveValueTracker) { + if !self.spills.is_empty() { + tracker.process_spills(|v| self.spills.contains(&v)); + self.spills.clear() + } + } +} From 6381da948fb37f2c8202fe60d576d6ff5279a46f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Jun 2017 15:13:36 -0700 Subject: [PATCH 794/968] Handle ABI arguments correctly in the reload pass. Values passed as arguments to calls and return instructions may also be reload candidates. --- filetests/regalloc/spill.cton | 15 ++++- lib/cretonne/src/ir/valueloc.rs | 8 +++ lib/cretonne/src/regalloc/coloring.rs | 1 + lib/cretonne/src/regalloc/reload.rs | 82 +++++++++++++++++++++------ 4 files changed, 86 insertions(+), 20 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index a6565b987e..d3da74ddf0 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -14,10 +14,15 @@ test regalloc isa riscv enable_e ; In straight-line code, the first value defined is spilled. -; That is the argument. +; That is in order: +; 1. The argument v1. +; 2. The link register. function %pyramid(i32) -> i32 { ebb0(v1: i32): -; check: $v1 = spill $(rv1=$V) +; check: $ebb0($(rv1=$V): i32, $(rlink=$V): i32) + ; check: $v1 = spill $rv1 + ; nextln: $(link=$V) = spill $rlink + ; not: spill v2 = iadd_imm v1, 12 v3 = iadd_imm v2, 12 v4 = iadd_imm v3, 12 @@ -29,7 +34,9 @@ ebb0(v1: i32): v10 = iadd_imm v9, 12 v11 = iadd_imm v10, 12 v12 = iadd_imm v11, 12 - v31 = iadd v11, v12 + v13 = iadd_imm v12, 12 + v32 = iadd v12, v13 + v31 = iadd v32, v11 v30 = iadd v31, v10 v29 = iadd v30, v9 v28 = iadd v29, v8 @@ -40,5 +47,7 @@ ebb0(v1: i32): v23 = iadd v24, v3 v22 = iadd v23, v2 v21 = iadd v22, v1 + ; check: $(rlink2=$V) = fill $link return v21 + ; check: return $v21, $rlink2 } diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index 2d6fc644b1..b664fe70e5 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -104,6 +104,14 @@ impl ArgumentLoc { } } + /// Is this a register location? + pub fn is_reg(&self) -> bool { + match self { + &ArgumentLoc::Reg(_) => true, + _ => false, + } + } + /// Return an object that can display this argument location, using the register info from the /// target ISA. pub fn display<'a, R: Into>>(self, regs: R) -> DisplayArgumentLoc<'a> { diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 33013aa309..8fd602fe39 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -101,6 +101,7 @@ impl Coloring { liveness: &mut Liveness, topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { + dbg!("Coloring for:\n{}", func.display(isa)); let mut ctx = Context { reginfo: isa.register_info(), encinfo: isa.encoding_info(), diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 1342625380..9786f45b67 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -11,11 +11,11 @@ use dominator_tree::DominatorTree; use entity_map::EntityMap; -use ir::{Ebb, Inst, Value, Function, DataFlowGraph}; +use ir::{Ebb, Inst, Value, Function, Signature, DataFlowGraph}; use ir::layout::{Cursor, CursorPosition}; -use ir::{InstBuilder, ArgumentLoc}; +use ir::{InstBuilder, ArgumentType, ArgumentLoc}; use isa::RegClass; -use isa::{TargetIsa, Encoding, EncInfo, ConstraintKind}; +use isa::{TargetIsa, Encoding, EncInfo, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; @@ -61,6 +61,7 @@ impl Reload { liveness: &mut Liveness, topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { + dbg!("Reload for:\n{}", func.display(isa)); let mut ctx = Context { isa, encinfo: isa.encoding_info(), @@ -121,6 +122,7 @@ impl<'a> Context<'a> { &mut pos, &mut func.dfg, &mut func.encodings, + &func.signature, tracker); tracker.drop_dead(inst); } else { @@ -202,27 +204,16 @@ impl<'a> Context<'a> { pos: &mut Cursor, dfg: &mut DataFlowGraph, encodings: &mut EntityMap, + func_signature: &Signature, tracker: &mut LiveValueTracker) { // Get the operand constraints for `inst` that we are trying to satisfy. let constraints = self.encinfo .operand_constraints(encoding) .expect("Missing instruction encoding"); - assert!(self.candidates.is_empty()); - // Identify reload candidates. - for (op, &arg) in constraints.ins.iter().zip(dfg.inst_args(inst)) { - if op.kind != ConstraintKind::Stack { - let lv = self.liveness.get(arg).expect("Missing live range for arg"); - if lv.affinity.is_stack() { - self.candidates - .push(ReloadCandidate { - value: arg, - regclass: op.regclass, - }) - } - } - } + assert!(self.candidates.is_empty()); + self.find_candidates(inst, constraints, func_signature, dfg); // Insert fill instructions before `inst`. while let Some(cand) = self.candidates.pop() { @@ -289,4 +280,61 @@ impl<'a> Context<'a> { } } } + + // Find reload candidates for `inst` and add them to `self.condidates`. + // + // These are uses of spilled values where the operand constraint requires a register. + fn find_candidates(&mut self, + inst: Inst, + constraints: &RecipeConstraints, + func_signature: &Signature, + dfg: &DataFlowGraph) { + let args = dfg.inst_args(inst); + + for (op, &arg) in constraints.ins.iter().zip(args) { + if op.kind != ConstraintKind::Stack { + let lv = self.liveness.get(arg).expect("Missing live range for arg"); + if lv.affinity.is_stack() { + self.candidates + .push(ReloadCandidate { + value: arg, + regclass: op.regclass, + }) + } + } + } + + // If we only have the fixed arguments, we're done now. + if args.len() == constraints.ins.len() { + return; + } + let var_args = &args[constraints.ins.len()..]; + + // Handle ABI arguments. + if let Some(sig) = dfg.call_signature(inst) { + self.handle_abi_args(&dfg.signatures[sig].argument_types, var_args); + } else if dfg[inst].opcode().is_return() { + self.handle_abi_args(&func_signature.return_types, var_args); + } + } + + /// Find reload candidates in the instruction's ABI variable arguments. This handles both + /// return values and call arguments. + fn handle_abi_args(&mut self, abi_types: &[ArgumentType], var_args: &[Value]) { + assert_eq!(abi_types.len(), var_args.len()); + for (abi, &arg) in abi_types.iter().zip(var_args) { + if abi.location.is_reg() { + let lv = self.liveness + .get(arg) + .expect("Missing live range for ABI arg"); + if lv.affinity.is_stack() { + self.candidates + .push(ReloadCandidate { + value: arg, + regclass: self.isa.regclass_for_abi_type(abi.value_type), + }); + } + } + } + } } From 5a23f975fccaadef1cd132fc65dc750e3d37e19a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 13 Jun 2017 15:43:14 -0700 Subject: [PATCH 795/968] Extract spill insertion into a reload::insert_spill function. Make sure that spill instructions are generated in the same way everywhere, including adding encoding and updating live ranges. --- filetests/regalloc/spill.cton | 13 ++++++- lib/cretonne/src/regalloc/reload.rs | 54 +++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index d3da74ddf0..a43ba4cf8f 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -17,6 +17,7 @@ isa riscv enable_e ; That is in order: ; 1. The argument v1. ; 2. The link register. +; 3. The first computed value, v2 function %pyramid(i32) -> i32 { ebb0(v1: i32): ; check: $ebb0($(rv1=$V): i32, $(rlink=$V): i32) @@ -24,6 +25,9 @@ ebb0(v1: i32): ; nextln: $(link=$V) = spill $rlink ; not: spill v2 = iadd_imm v1, 12 + ; check: $(r1v2=$V) = iadd_imm + ; nextln: $v2 = spill $r1v2 + ; not: spill v3 = iadd_imm v2, 12 v4 = iadd_imm v3, 12 v5 = iadd_imm v4, 12 @@ -35,7 +39,10 @@ ebb0(v1: i32): v11 = iadd_imm v10, 12 v12 = iadd_imm v11, 12 v13 = iadd_imm v12, 12 - v32 = iadd v12, v13 + v14 = iadd_imm v13, 12 + v33 = iadd v13, v14 + ; check: iadd $v13 + v32 = iadd v33, v12 v31 = iadd v32, v11 v30 = iadd v31, v10 v29 = iadd v30, v9 @@ -46,7 +53,11 @@ ebb0(v1: i32): v24 = iadd v25, v4 v23 = iadd v24, v3 v22 = iadd v23, v2 + ; check: $(r2v2=$V) = fill $v2 + ; check: $v22 = iadd $v23, $r2v2 v21 = iadd v22, v1 + ; check: $(r2v1=$V) = fill $v1 + ; check: $v21 = iadd $v22, $r2v1 ; check: $(rlink2=$V) = fill $link return v21 ; check: return $v21, $rlink2 diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 9786f45b67..dd623de2b5 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -13,7 +13,7 @@ use dominator_tree::DominatorTree; use entity_map::EntityMap; use ir::{Ebb, Inst, Value, Function, Signature, DataFlowGraph}; use ir::layout::{Cursor, CursorPosition}; -use ir::{InstBuilder, ArgumentType, ArgumentLoc}; +use ir::{InstBuilder, Opcode, ArgumentType, ArgumentLoc}; use isa::RegClass; use isa::{TargetIsa, Encoding, EncInfo, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; @@ -167,16 +167,14 @@ impl<'a> Context<'a> { // An incoming register parameter was spilled. Replace the parameter value // with a temporary register value that is immediately spilled. let reg = func.dfg.replace_ebb_arg(arg.value, abi.value_type); - func.dfg.ins(&mut pos).with_result(arg.value).spill(reg); - let spill = func.dfg.value_def(arg.value).unwrap_inst(); - *func.encodings.ensure(spill) = self.isa - .encode(&func.dfg, &func.dfg[spill], abi.value_type) - .expect("Can't encode spill"); - // Update live ranges. - self.liveness.move_def_locally(arg.value, spill); let affinity = Affinity::abi(abi, self.isa); self.liveness.create_dead(reg, ebb, affinity); - self.liveness.extend_locally(reg, ebb, spill, pos.layout); + self.insert_spill(ebb, + arg.value, + reg, + &mut pos, + &mut func.encodings, + &mut func.dfg); } } ArgumentLoc::Stack(_) => { @@ -270,13 +268,8 @@ impl<'a> Context<'a> { if lv.affinity.is_stack() && op.kind != ConstraintKind::Stack { let value_type = dfg.value_type(lv.value); let reg = dfg.replace_result(lv.value, value_type); - dfg.ins(pos).with_result(lv.value).spill(reg); - let spill = dfg.value_def(lv.value).unwrap_inst(); - - // Create a live range for reg. self.liveness.create_dead(reg, inst, Affinity::new(op)); - self.liveness.extend_locally(reg, ebb, spill, &pos.layout); - self.liveness.move_def_locally(lv.value, spill); + self.insert_spill(ebb, lv.value, reg, pos, encodings, dfg); } } } @@ -337,4 +330,35 @@ impl<'a> Context<'a> { } } } + + /// Insert a spill at `pos` and update data structures. + /// + /// - Insert `stack = spill reg` at `pos`, and assign an encoding. + /// - Move the `stack` live range starting point to the new instruction. + /// - Extend the `reg` live range to reach the new instruction. + fn insert_spill(&mut self, + ebb: Ebb, + stack: Value, + reg: Value, + pos: &mut Cursor, + encodings: &mut EntityMap, + dfg: &mut DataFlowGraph) { + let ty = dfg.value_type(reg); + + // Insert spill instruction. Use the low-level `Unary` constructor because it returns an + // instruction reference directly rather than a result value (which we know is equal to + // `stack`). + let (inst, _) = dfg.ins(pos) + .with_result(stack) + .Unary(Opcode::Spill, ty, reg); + + // Give it an encoding. + *encodings.ensure(inst) = self.isa + .encode(dfg, &dfg[inst], ty) + .expect("Can't encode spill"); + + // Update live ranges. + self.liveness.move_def_locally(stack, inst); + self.liveness.extend_locally(reg, ebb, inst, pos.layout); + } } From f2f162f37ed71b94e254bcf08d069c9448571d41 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Jun 2017 08:55:01 -0700 Subject: [PATCH 796/968] Spill values live across calls. Calls clobber many registers, so spill everything that is live across a call for now. In the future, we may add support for callee-saved registers. --- filetests/regalloc/spill.cton | 13 +++++++++++++ lib/cretonne/src/regalloc/affinity.rs | 8 ++++++++ lib/cretonne/src/regalloc/spilling.rs | 13 ++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index a43ba4cf8f..eaea391504 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -62,3 +62,16 @@ ebb0(v1: i32): return v21 ; check: return $v21, $rlink2 } + +; All values live across a call must be spilled +function %across_call(i32) { + fn0 = function %foo(i32) +ebb0(v1: i32): + ; check: $v1 = spill + call fn0(v1) + ; check: call $fn0 + call fn0(v1) + ; check: fill $v1 + ; check: call $fn0 + return +} diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs index e7ee0515ad..a7c95df99f 100644 --- a/lib/cretonne/src/regalloc/affinity.rs +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -64,6 +64,14 @@ impl Affinity { } } + /// Is this the `Reg` affinity? + pub fn is_reg(self) -> bool { + match self { + Affinity::Reg(_) => true, + _ => false, + } + } + /// Is this the `Stack` affinity? pub fn is_stack(self) -> bool { match self { diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 8938944952..da0453f549 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -152,14 +152,25 @@ impl<'a> Context<'a> { // // Spills should be removed from tracker. Otherwise they could be double-counted by // free_regs below. + let call_sig = dfg.call_signature(inst); // Update the live value tracker with this instruction. let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); - // Remove kills from the pressure tracker. self.free_regs(kills); + // If inst is a call, spill all register values that are live across the call. + // This means that we don't currently take advantage of callee-saved registers. + // TODO: Be more sophisticated. + if call_sig.is_some() { + for lv in throughs { + if lv.affinity.is_reg() && !self.spills.contains(&lv.value) { + self.spill_reg(lv.value); + } + } + } + // Make sure we have enough registers for the register defs. // Dead defs are included here. They need a register too. // No need to process call return values, they are in fixed registers. From e7c6efa31e3e8e76568cc533f7998956228bd404 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Jun 2017 10:35:01 -0700 Subject: [PATCH 797/968] Update docopt dependency to 0.8.0. This breaks our depending on two different versions of the regex library. This updated docopt uses serde instead of rustc_serialize. --- Cargo.toml | 7 ++++--- src/cton-util.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d0da28f918..06eed706e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,9 @@ path = "src/cton-util.rs" cretonne = { path = "lib/cretonne" } cretonne-reader = { path = "lib/reader" } filecheck = { path = "lib/filecheck" } -docopt = "0.6.86" -rustc-serialize = "0.3.19" -num_cpus = "1.1.0" +docopt = "0.8.0" +serde = "1.0.8" +serde_derive = "1.0.8" +num_cpus = "1.5.1" [workspace] diff --git a/src/cton-util.rs b/src/cton-util.rs index 70fcbc3c84..28b4b90de0 100644 --- a/src/cton-util.rs +++ b/src/cton-util.rs @@ -2,7 +2,9 @@ extern crate cretonne; extern crate cton_reader; extern crate docopt; -extern crate rustc_serialize; +extern crate serde; +#[macro_use] +extern crate serde_derive; extern crate filecheck; extern crate num_cpus; @@ -34,7 +36,7 @@ Options: "; -#[derive(RustcDecodable, Debug)] +#[derive(Deserialize, Debug)] struct Args { cmd_test: bool, cmd_cat: bool, @@ -49,12 +51,12 @@ pub type CommandResult = Result<(), String>; /// Parse the command line arguments and run the requested command. fn cton_util() -> CommandResult { - // Parse comand line arguments. + // Parse command line arguments. let args: Args = Docopt::new(USAGE) .and_then(|d| { d.help(true) .version(Some(format!("Cretonne {}", VERSION))) - .decode() + .deserialize() }) .unwrap_or_else(|e| e.exit()); From 8955b136207d0dd7a593ec7f3364871fbea37226 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Jun 2017 12:05:47 -0700 Subject: [PATCH 798/968] Always call reassign_in for register ABI arguments. Even if an argument is already in the correct register, make sure that we detect conflicts by registering the no-op move. This also means that the ABI argument register won't be turned into a variable for the solver. --- lib/cretonne/src/regalloc/coloring.rs | 22 ++++++++++------------ lib/cretonne/src/regalloc/solver.rs | 16 +++++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 8fd602fe39..53546de07f 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -430,18 +430,16 @@ impl<'a> Context<'a> { locations: &EntityMap) { for (abi, &value) in abi_types.iter().zip(dfg.inst_variable_args(inst)) { if let ArgumentLoc::Reg(reg) = abi.location { - let cur_reg = self.divert.reg(value, locations); - if reg != cur_reg { - if let Affinity::Reg(rci) = - self.liveness - .get(value) - .expect("ABI register must have live range") - .affinity { - let rc = self.reginfo.rc(rci); - self.solver.reassign_in(value, rc, cur_reg, reg); - } else { - panic!("ABI argument {} should be in a register", value); - } + if let Affinity::Reg(rci) = + self.liveness + .get(value) + .expect("ABI register must have live range") + .affinity { + let rc = self.reginfo.rc(rci); + let cur_reg = self.divert.reg(value, locations); + self.solver.reassign_in(value, rc, cur_reg, reg); + } else { + panic!("ABI argument {} should be in a register", value); } } } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index 89aee41317..d94cf3e72d 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -364,13 +364,15 @@ impl Solver { } self.regs_in.free(rc, from); self.regs_out.take(rc, to); - self.assignments - .insert(Assignment { - value, - rc, - from, - to, - }); + if from != to { + self.assignments + .insert(Assignment { + value, + rc, + from, + to, + }); + } } /// Add a variable representing an input side value with an existing register assignment. From 66af915eed294f18046f7184728526f7b2335e9d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Jun 2017 15:36:25 -0700 Subject: [PATCH 799/968] Add RISC-V encodings for copy instructions. --- filetests/isa/riscv/binary32.cton | 4 ++++ lib/cretonne/meta/isa/riscv/encodings.py | 7 ++++++- lib/cretonne/meta/isa/riscv/recipes.py | 3 +++ lib/cretonne/src/isa/riscv/binemit.rs | 12 ++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 052342f233..923b6eb537 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -77,6 +77,10 @@ ebb0(v9999: i32): [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 + ; Copies alias to iadd_imm. + [-,%x7] v150 = copy v1 ; bin: 00050393 + [-,%x16] v151 = copy v2 ; bin: 000a8813 + ; Control Transfer Instructions ; jal %x1, fn0 diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 2b59964007..5d3c121a14 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -7,7 +7,7 @@ from base.immediates import intcc from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL from .recipes import LOAD, STORE -from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret +from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret, Icopy from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi from .settings import use_m from cdsl.ast import Var @@ -123,3 +123,8 @@ RV64.enc(base.spill.i64, GPsp, STORE(0b011)) RV32.enc(base.fill.i32, GPfi, LOAD(0b010)) RV64.enc(base.fill.i32, GPfi, LOAD(0b010)) RV64.enc(base.fill.i64, GPfi, LOAD(0b011)) + +# Register copies. +RV32.enc(base.copy.i32, Icopy, OPIMM(0b000)) +RV64.enc(base.copy.i64, Icopy, OPIMM(0b000)) +RV64.enc(base.copy.i32, Icopy, OPIMM32(0b000)) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 1a7e98d0fe..8a6be46c5f 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -113,6 +113,9 @@ Iicmp = EncRecipe( # The variable return values are not encoded. Iret = EncRecipe('Iret', MultiAry, size=4, ins=(), outs=()) +# Copy of a GPR is implemented as addi x, 0. +Icopy = EncRecipe('Icopy', Unary, size=4, ins=GPR, outs=GPR) + # U-type instructions have a 20-bit immediate that targets bits 12-31. U = EncRecipe( 'U', UnaryImm, size=4, ins=(), outs=GPR, diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index e025b3f9d4..0beaf51a8d 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -182,6 +182,18 @@ fn recipe_iret(func: &Function, inst: Inst, sink: &mut CS sink); } +fn recipe_icopy(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Unary { arg, .. } = func.dfg[inst] { + put_i(func.encodings[inst].bits(), + func.locations[arg].unwrap_reg(), + 0, + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected Unary format: {:?}", func.dfg[inst]); + } +} + /// U-type instructions. /// /// 31 11 6 From 9eb0778f9b972975dee66d5b23cb61bfa0ee6122 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Jun 2017 16:14:16 -0700 Subject: [PATCH 800/968] Add RISC-V encodings for call_indirect. --- filetests/isa/riscv/binary32.cton | 5 +++++ lib/cretonne/meta/isa/riscv/encodings.py | 4 +++- lib/cretonne/meta/isa/riscv/recipes.py | 6 +++++- lib/cretonne/src/isa/riscv/binemit.rs | 9 +++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 923b6eb537..209d9922c0 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -4,6 +4,7 @@ isa riscv function %RV32I(i32 link [%x1]) -> i32 link [%x1] { fn0 = function %foo() + sig0 = signature() ebb0(v9999: i32): [-,%x10] v1 = iconst.i32 1 @@ -86,6 +87,10 @@ ebb0(v9999: i32): ; jal %x1, fn0 call fn0() ; bin: Call(fn0) 000000ef + ; jalr %x1, %x10 + call_indirect sig0, v1() ; bin: 000500e7 + call_indirect sig0, v2() ; bin: 000a80e7 + brz v1, ebb3 brnz v1, ebb1 diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 5d3c121a14..374cc951af 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -7,7 +7,7 @@ from base.immediates import intcc from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL from .recipes import LOAD, STORE -from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret, Icopy +from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret, Icall, Icopy from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi from .settings import use_m from cdsl.ast import Var @@ -115,6 +115,8 @@ for inst, f3 in [ # is added by legalize_signature(). RV32.enc(base.x_return, Iret, JALR()) RV64.enc(base.x_return, Iret, JALR()) +RV32.enc(base.call_indirect.i32, Icall, JALR()) +RV64.enc(base.call_indirect.i64, Icall, JALR()) # Spill and fill. RV32.enc(base.spill.i32, GPsp, STORE(0b010)) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 8a6be46c5f..60a67fbc00 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -13,7 +13,8 @@ from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from cdsl.registers import Stack from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm -from base.formats import Unary, UnaryImm, BranchIcmp, Branch, Jump, Call +from base.formats import Unary, UnaryImm, BranchIcmp, Branch, Jump +from base.formats import Call, IndirectCall from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -113,6 +114,9 @@ Iicmp = EncRecipe( # The variable return values are not encoded. Iret = EncRecipe('Iret', MultiAry, size=4, ins=(), outs=()) +# I-type encoding for `jalr` as an indirect call. +Icall = EncRecipe('Icall', IndirectCall, size=4, ins=GPR, outs=()) + # Copy of a GPR is implemented as addi x, 0. Icopy = EncRecipe('Icopy', Unary, size=4, ins=GPR, outs=GPR) diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 0beaf51a8d..0c25f9ec85 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -182,6 +182,15 @@ fn recipe_iret(func: &Function, inst: Inst, sink: &mut CS sink); } +fn recipe_icall(func: &Function, inst: Inst, sink: &mut CS) { + // Indirect instructions are jalr with rd=%x1. + put_i(func.encodings[inst].bits(), + func.locations[func.dfg.inst_args(inst)[0]].unwrap_reg(), + 0, // no offset. + 1, // rd = %x1: link register. + sink); +} + fn recipe_icopy(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::Unary { arg, .. } = func.dfg[inst] { put_i(func.encodings[inst].bits(), From 66bc0a9c8b8151ec65d9ea1fec862cca5ec9a889 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 14 Jun 2017 13:56:06 -0700 Subject: [PATCH 801/968] Make register copies for incompatible operands. An instruction may have fixed operand constraints that make it impossibly to use a single register value to satisfy two at a time. Detect when the same value is used for multiple fixed register operands and insert copies during the spilling pass. --- filetests/regalloc/spill.cton | 29 ++++ lib/cretonne/src/ir/entities.rs | 2 +- lib/cretonne/src/regalloc/spilling.rs | 235 ++++++++++++++++++++++---- 3 files changed, 235 insertions(+), 31 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index eaea391504..f163c478b3 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -75,3 +75,32 @@ ebb0(v1: i32): ; check: call $fn0 return } + +; The same value used for two function arguments. +function %doubleuse(i32) { + fn0 = function %xx(i32, i32) +ebb0(v0: i32): + ; check: $(c=$V) = copy $v0 + call fn0(v0, v0) + ; check: call $fn0($v0, $c) + return +} + +; The same value used as indirect callee and argument. +function %doubleuse_icall1(i32) { + sig0 = signature(i32) +ebb0(v0: i32): + ; not:copy + call_indirect sig0, v0(v0) + return +} + +; The same value used as indirect callee and two arguments. +function %doubleuse_icall2(i32) { + sig0 = signature(i32, i32) +ebb0(v0: i32): + ; check: $(c=$V) = copy $v0 + call_indirect sig0, v0(v0, v0) + ; check: call_indirect $sig0, $v0($v0, $c) + return +} diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 813ec978c2..04b54e0488 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -74,7 +74,7 @@ impl Ebb { } /// An opaque reference to an SSA value. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Value(u32); entity_impl!(Value, "v"); diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index da0453f549..23d4799dad 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -4,12 +4,23 @@ //! ensure that the register pressure never exceeds the number of available registers by moving //! some SSA values to spill slots on the stack. This is encoded in the affinity of the value's //! live range. +//! +//! Some instruction operand constraints may require additional registers to resolve. Since this +//! can cause spilling, the spilling pass is also responsible for resolving those constraints by +//! inserting copies. The extra constraints are: +//! +//! 1. A value used by a tied operand must be killed by the instruction. This is resolved by +//! inserting a copy to a temporary value when necessary. +//! 2. When the same value is used more than once by an instruction, the operand constraints must +//! be compatible. Otherwise, the value must be copied into a new register for some of the +//! operands. use dominator_tree::DominatorTree; -use ir::{DataFlowGraph, Layout, Cursor}; -use ir::{Function, Ebb, Inst, Value}; -use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; -use isa::registers::RegClassMask; +use entity_map::EntityMap; +use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; +use ir::{Function, Ebb, Inst, Value, SigRef}; +use isa::registers::{RegClass, RegClassMask}; +use isa::{TargetIsa, RegInfo, EncInfo, Encoding, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; @@ -19,14 +30,19 @@ use topo_order::TopoOrder; /// Persistent data structures for the spilling pass. pub struct Spilling { spills: Vec, + reg_uses: Vec, } /// Context data structure that gets instantiated once per pass. struct Context<'a> { + isa: &'a TargetIsa, // Cached ISA information. reginfo: RegInfo, encinfo: EncInfo, + // References to parts of the current function. + encodings: &'a mut EntityMap, + // References to contextual data structures we need. domtree: &'a DominatorTree, liveness: &'a mut Liveness, @@ -39,12 +55,18 @@ struct Context<'a> { // pressure tracker, but they are still present in the live value tracker and their affinity // hasn't been changed yet. spills: &'a mut Vec, + + // Uses of register values in the current instruction. + reg_uses: &'a mut Vec, } impl Spilling { /// Create a new spilling data structure. pub fn new() -> Spilling { - Spilling { spills: Vec::new() } + Spilling { + spills: Vec::new(), + reg_uses: Vec::new(), + } } /// Run the spilling algorithm over `func`. @@ -59,36 +81,46 @@ impl Spilling { let reginfo = isa.register_info(); let usable_regs = isa.allocatable_registers(func); let mut ctx = Context { + isa, reginfo: isa.register_info(), encinfo: isa.encoding_info(), + encodings: &mut func.encodings, domtree, liveness, topo, pressure: Pressure::new(®info, &usable_regs), spills: &mut self.spills, + reg_uses: &mut self.reg_uses, }; - ctx.run(func, tracker) + ctx.run(&mut func.layout, &mut func.dfg, tracker) } } impl<'a> Context<'a> { - fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { - self.topo.reset(func.layout.ebbs()); - while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { - self.visit_ebb(ebb, func, tracker); + fn run(&mut self, + layout: &mut Layout, + dfg: &mut DataFlowGraph, + tracker: &mut LiveValueTracker) { + self.topo.reset(layout.ebbs()); + while let Some(ebb) = self.topo.next(layout, self.domtree) { + self.visit_ebb(ebb, layout, dfg, tracker); } } - fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + fn visit_ebb(&mut self, + ebb: Ebb, + layout: &mut Layout, + dfg: &mut DataFlowGraph, + tracker: &mut LiveValueTracker) { dbg!("Spilling {}:", ebb); - self.visit_ebb_header(ebb, func, tracker); + self.visit_ebb_header(ebb, layout, dfg, tracker); tracker.drop_dead_args(); - let mut pos = Cursor::new(&mut func.layout); + let mut pos = Cursor::new(layout); pos.goto_top(ebb); while let Some(inst) = pos.next_inst() { - if let Some(constraints) = self.encinfo.operand_constraints(func.encodings[inst]) { - self.visit_inst(inst, constraints, &mut pos, &mut func.dfg, tracker); + if let Some(constraints) = self.encinfo.operand_constraints(self.encodings[inst]) { + self.visit_inst(inst, constraints, &mut pos, dfg, tracker); tracker.drop_dead(inst); self.process_spills(tracker); } @@ -118,9 +150,12 @@ impl<'a> Context<'a> { } } - fn visit_ebb_header(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { - let (liveins, args) = - tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + fn visit_ebb_header(&mut self, + ebb: Ebb, + layout: &mut Layout, + dfg: &mut DataFlowGraph, + tracker: &mut LiveValueTracker) { + let (liveins, args) = tracker.ebb_top(ebb, dfg, self.liveness, layout, self.domtree); // Count the live-in registers. These should already fit in registers; they did at the // dominator. @@ -135,24 +170,32 @@ impl<'a> Context<'a> { inst: Inst, constraints: &RecipeConstraints, pos: &mut Cursor, - dfg: &DataFlowGraph, + dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker) { dbg!("Inst {}, {}", dfg.display_inst(inst), self.pressure); // TODO: Repair constraint violations by copying input values. // // - Tied use of value that is not killed. - // - Inconsistent uses of the same value. - // - // Each inserted copy may increase register pressure. Fix by spilling something not used by - // the instruction. - // - // Count pressure for register uses of spilled values too. - // - // Finally, reset pressure state to level from before the input adjustments, minus spills. - // - // Spills should be removed from tracker. Otherwise they could be double-counted by - // free_regs below. + // - Count pressure for register uses of spilled values too. + + assert!(self.reg_uses.is_empty()); + + // If the instruction has any fixed register operands, we may need to resolve register + // constraints. + if constraints.fixed_ins { + self.collect_reg_uses(inst, constraints, dfg); + } + + // Calls usually have fixed register uses. let call_sig = dfg.call_signature(inst); + if let Some(sig) = call_sig { + self.collect_abi_reg_uses(inst, sig, dfg); + } + + if !self.reg_uses.is_empty() { + self.process_reg_uses(inst, pos, dfg, tracker); + } + // Update the live value tracker with this instruction. let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); @@ -190,6 +233,85 @@ impl<'a> Context<'a> { // This won't cause spilling. self.take_live_regs(defs); } + // Collect register uses from the fixed input constraints. + // + // We are assuming here that if a value is used both by a fixed register operand and a register + // class operand, they two are compatible. We are also assuming that two register class + // operands are always compatible. + fn collect_reg_uses(&mut self, + inst: Inst, + constraints: &RecipeConstraints, + dfg: &DataFlowGraph) { + let args = dfg.inst_args(inst); + for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() { + match op.kind { + ConstraintKind::FixedReg(_) => { + self.reg_uses.push(RegUse::new(arg, idx)); + } + _ => {} + } + } + } + + // Collect register uses from the ABI input constraints. + fn collect_abi_reg_uses(&mut self, inst: Inst, sig: SigRef, dfg: &DataFlowGraph) { + let fixed_args = dfg[inst].opcode().constraints().fixed_value_arguments(); + let args = dfg.inst_variable_args(inst); + for (idx, (abi, &arg)) in + dfg.signatures[sig] + .argument_types + .iter() + .zip(args) + .enumerate() { + if abi.location.is_reg() { + self.reg_uses.push(RegUse::new(arg, fixed_args + idx)); + } + } + } + + // Process multiple register uses to resolve potential conflicts. + // + // Look for multiple uses of the same value in `self.reg_uses` and insert copies as necessary. + // Trigger spilling if any of the temporaries cause the register pressure to become too high. + // + // Leave `self.reg_uses` empty. + fn process_reg_uses(&mut self, + inst: Inst, + pos: &mut Cursor, + dfg: &mut DataFlowGraph, + tracker: &LiveValueTracker) { + // We're looking for multiple uses of the same value, so start by sorting by value. The + // secondary `opidx` key makes it possible to use an unstable sort once that is available + // outside nightly Rust. + self.reg_uses.sort_by_key(|u| (u.value, u.opidx)); + + // We are assuming that `reg_uses` has an entry per fixed register operand, and that any + // non-fixed register operands are compatible with one of the fixed uses of the value. + for i in 1..self.reg_uses.len() { + let ru = self.reg_uses[i]; + if self.reg_uses[i - 1].value != ru.value { + continue; + } + + // We have two fixed uses of the same value. Make a copy. + let (copy, rc) = self.insert_copy(ru.value, pos, dfg); + dfg.inst_args_mut(inst)[ru.opidx as usize] = copy; + + // Make sure the new copy doesn't blow the register pressure. + while let Err(mask) = self.pressure.take_transient(rc) { + dbg!("Copy of {} reg causes spill", rc); + // Spill a live register that is *not* used by the current instruction. + // Spilling a use wouldn't help. + let args = dfg.inst_args(inst); + self.spill_from(mask, + tracker.live().iter().filter(|lv| !args.contains(&lv.value)), + dfg, + &pos.layout); + } + } + self.pressure.reset_transient(); + self.reg_uses.clear() + } // Spill a candidate from `candidates` whose top-level register class is in `mask`. fn spill_from<'ii, II>(&mut self, @@ -267,4 +389,57 @@ impl<'a> Context<'a> { self.spills.clear() } } + + /// Insert a `copy value` before `pos` and give it a live range extending to `pos`. + /// + /// Returns the new local value created and its register class. + fn insert_copy(&mut self, + value: Value, + pos: &mut Cursor, + dfg: &mut DataFlowGraph) + -> (Value, RegClass) { + let copy = dfg.ins(pos).copy(value); + let inst = dfg.value_def(copy).unwrap_inst(); + let ty = dfg.value_type(copy); + + // Give it an encoding. + let encoding = self.isa + .encode(dfg, &dfg[inst], ty) + .expect("Can't encode copy"); + *self.encodings.ensure(inst) = encoding; + + // Update live ranges. + let rc = self.encinfo + .operand_constraints(encoding) + .expect("Bad copy encoding") + .outs + [0] + .regclass; + self.liveness + .create_dead(copy, inst, Affinity::Reg(rc.into())); + self.liveness + .extend_locally(copy, + pos.layout.pp_ebb(inst), + pos.current_inst().expect("must be at an instruction"), + pos.layout); + + (copy, rc) + } +} + +// Struct representing a register use of a value. +// Used to detect multiple uses of the same value with incompatible register constraints. +#[derive(Clone, Copy)] +struct RegUse { + value: Value, + opidx: u16, +} + +impl RegUse { + fn new(value: Value, idx: usize) -> RegUse { + RegUse { + value, + opidx: idx as u16, + } + } } From 91d919c11a15dc00de2ce133357f5094f089ff88 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Jun 2017 10:35:01 -0700 Subject: [PATCH 802/968] Track stack slot kinds. Add a StackSlotKind enumeration to help keep track of the different kinds of stack slots supported: - Incoming and outgoing function arguments on the stack. - Spill slots and locals. Change the text format syntax for declaring a stack slot to use a kind keyword rather than just 'stack_slot'. --- docs/example.cton | 2 +- docs/langref.rst | 4 +- filetests/parser/tiny.cton | 12 +++-- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/ir/stackslot.rs | 79 ++++++++++++++++++++++++++++---- lib/cretonne/src/write.rs | 14 +++--- lib/reader/src/parser.rs | 29 +++++++----- lib/reader/src/sourcemap.rs | 2 +- misc/vim/syntax/cton.vim | 4 +- 9 files changed, 110 insertions(+), 38 deletions(-) diff --git a/docs/example.cton b/docs/example.cton index 8b9c43c6b9..493794da50 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -1,7 +1,7 @@ test verifier function %average(i32, i32) -> f32 { - ss1 = stack_slot 8 ; Stack slot for ``sum``. + ss1 = local 8 ; Stack slot for ``sum``. ebb1(v1: i32, v2: i32): v3 = f64const 0x0.0 diff --git a/docs/langref.rst b/docs/langref.rst index 24b5961ec3..ac20591c36 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -465,9 +465,9 @@ frame. The stack frame is divided into fixed-size stack slots that are allocated in the :term:`function preamble`. Stack slots are not typed, they simply represent a contiguous sequence of bytes in the stack frame. -.. inst:: SS = stack_slot Bytes, Flags... +.. inst:: SS = local Bytes, Flags... - Allocate a stack slot in the preamble. + Allocate a stack slot for a local variable in the preamble. If no alignment is specified, Cretonne will pick an appropriate alignment for the stack slot based on its size and access patterns. diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 1414668a52..704477e6c2 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -96,8 +96,10 @@ ebb0(v90: i32, v91: f32): ; Stack slot references function %stack() { - ss10 = stack_slot 8 - ss2 = stack_slot 4 + ss10 = spill_slot 8 + ss2 = local 4 + ss3 = incoming_arg 4 + ss4 = outgoing_arg 4 ebb0: v1 = stack_load.i32 ss10 @@ -106,8 +108,10 @@ ebb0: stack_store v2, ss2 } ; sameln: function %stack() { -; nextln: $ss10 = stack_slot 8 -; nextln: $ss2 = stack_slot 4 +; nextln: $ss10 = spill_slot 8 +; nextln: $ss2 = local 4 +; nextln: $ss3 = incoming_arg 4 +; nextln: $ss4 = outgoing_arg 4 ; check: ebb0: ; nextln: $v1 = stack_load.i32 $ss10 diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 49617165e9..56c972c429 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -22,7 +22,7 @@ pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ArgumentPurpos pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; -pub use ir::stackslot::StackSlotData; +pub use ir::stackslot::{StackSlotKind, StackSlotData}; pub use ir::jumptable::JumpTableData; pub use ir::valueloc::{ValueLoc, ArgumentLoc}; pub use ir::dfg::{DataFlowGraph, ValueDef}; diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index b22b82919c..a205f6d410 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -3,43 +3,104 @@ //! The `StackSlotData` struct keeps track of a single stack slot in a function. //! -use std::fmt::{self, Display, Formatter}; +use std::fmt; +use std::str::FromStr; + +/// The kind of a stack slot. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StackSlotKind { + /// A spill slot. This is a stack slot created by the register allocator. + SpillSlot, + + /// A local variable. This is a chunk of local stack memory for use by the `stack_load` and + /// `stack_store` instructions. + Local, + + /// An incoming function argument. + /// + /// If the current function has more arguments than fits in registers, the remaining arguments + /// are passed on the stack by the caller. These incoming arguments are represented as SSA + /// values assigned to incoming stack slots. + IncomingArg, + + /// An outgoing function argument. + /// + /// When preparing to call a function whose arguments don't fit in registers, outgoing argument + /// stack slots are used to represent individual arguments in the outgoing call frame. These + /// stack slots are only valid while setting up a call. + OutgoingArg, +} + +impl FromStr for StackSlotKind { + type Err = (); + + fn from_str(s: &str) -> Result { + use self::StackSlotKind::*; + match s { + "local" => Ok(Local), + "spill_slot" => Ok(SpillSlot), + "incoming_arg" => Ok(IncomingArg), + "outgoing_arg" => Ok(OutgoingArg), + _ => Err(()), + } + } +} + +impl fmt::Display for StackSlotKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::StackSlotKind::*; + f.write_str(match *self { + Local => "local", + SpillSlot => "spill_slot", + IncomingArg => "incoming_arg", + OutgoingArg => "outgoing_arg", + }) + } +} /// Contents of a stack slot. #[derive(Clone, Debug)] pub struct StackSlotData { + /// The kind of stack slot. + pub kind: StackSlotKind, + /// Size of stack slot in bytes. pub size: u32, } impl StackSlotData { /// Create a stack slot with the specified byte size. - pub fn new(size: u32) -> StackSlotData { - StackSlotData { size: size } + pub fn new(kind: StackSlotKind, size: u32) -> StackSlotData { + StackSlotData { kind, size } } } -impl Display for StackSlotData { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "stack_slot {}", self.size) +impl fmt::Display for StackSlotData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.kind, self.size) } } #[cfg(test)] mod tests { use ir::Function; - use super::StackSlotData; + use super::*; #[test] fn stack_slot() { let mut func = Function::new(); - let ss0 = func.stack_slots.push(StackSlotData::new(4)); - let ss1 = func.stack_slots.push(StackSlotData::new(8)); + let ss0 = func.stack_slots + .push(StackSlotData::new(StackSlotKind::IncomingArg, 4)); + let ss1 = func.stack_slots + .push(StackSlotData::new(StackSlotKind::SpillSlot, 8)); assert_eq!(ss0.to_string(), "ss0"); assert_eq!(ss1.to_string(), "ss1"); assert_eq!(func.stack_slots[ss0].size, 4); assert_eq!(func.stack_slots[ss1].size, 8); + + assert_eq!(func.stack_slots[ss0].to_string(), "incoming_arg 4"); + assert_eq!(func.stack_slots[ss1].to_string(), "spill_slot 8"); } } diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 818e4d9700..f015b7d6df 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -359,7 +359,7 @@ impl<'a> fmt::Display for DisplayValues<'a> { #[cfg(test)] mod tests { - use ir::{Function, FunctionName, StackSlotData}; + use ir::{Function, FunctionName, StackSlotData, StackSlotKind}; use ir::types; #[test] @@ -370,21 +370,21 @@ mod tests { f.name = FunctionName::new("foo"); assert_eq!(f.to_string(), "function %foo() {\n}\n"); - f.stack_slots.push(StackSlotData::new(4)); - assert_eq!(f.to_string(), - "function %foo() {\n ss0 = stack_slot 4\n}\n"); + f.stack_slots + .push(StackSlotData::new(StackSlotKind::Local, 4)); + assert_eq!(f.to_string(), "function %foo() {\n ss0 = local 4\n}\n"); let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); assert_eq!(f.to_string(), - "function %foo() {\n ss0 = stack_slot 4\n\nebb0:\n}\n"); + "function %foo() {\n ss0 = local 4\n\nebb0:\n}\n"); f.dfg.append_ebb_arg(ebb, types::I8); assert_eq!(f.to_string(), - "function %foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8):\n}\n"); + "function %foo() {\n ss0 = local 4\n\nebb0(v0: i8):\n}\n"); f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(f.to_string(), - "function %foo() {\n ss0 = stack_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); + "function %foo() {\n ss0 = local 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 3529bc95a3..bc8ebcfa0d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -896,13 +896,17 @@ impl<'a> Parser<'a> { // Parse a stack slot decl. // - // stack-slot-decl ::= * StackSlot(ss) "=" "stack_slot" Bytes {"," stack-slot-flag} + // stack-slot-decl ::= * StackSlot(ss) "=" stack-slot-kind Bytes {"," stack-slot-flag} + // stack-slot-kind ::= "local" + // | "spill_slot" + // | "incoming_arg" + // | "outgoing_arg" fn parse_stack_slot_decl(&mut self) -> Result<(u32, StackSlotData)> { let number = self.match_ss("expected stack slot number: ss«n»")?; - self.match_token(Token::Equal, "expected '=' in stack_slot decl")?; - self.match_identifier("stack_slot", "expected 'stack_slot'")?; + self.match_token(Token::Equal, "expected '=' in stack slot declaration")?; + let kind = self.match_enum("expected stack slot kind")?; - // stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" * Bytes {"," stack-slot-flag} + // stack-slot-decl ::= StackSlot(ss) "=" stack-slot-kind * Bytes {"," stack-slot-flag} let bytes: i64 = self.match_imm64("expected byte-size in stack_slot decl")? .into(); if bytes < 0 { @@ -911,9 +915,9 @@ impl<'a> Parser<'a> { if bytes > u32::MAX as i64 { return err!(self.loc, "stack slot too large"); } - let data = StackSlotData::new(bytes as u32); + let data = StackSlotData::new(kind, bytes as u32); - // TBD: stack-slot-decl ::= StackSlot(ss) "=" "stack_slot" Bytes * {"," stack-slot-flag} + // TBD: stack-slot-decl ::= StackSlot(ss) "=" stack-slot-kind Bytes * {"," stack-slot-flag} Ok((number, data)) } @@ -1719,6 +1723,7 @@ mod tests { use super::*; use cretonne::ir::{ArgumentExtension, ArgumentPurpose}; use cretonne::ir::types; + use cretonne::ir::StackSlotKind; use cretonne::ir::entities::AnyEntity; use testfile::{Details, Comment}; use isaspec::IsaSpec; @@ -1793,8 +1798,8 @@ mod tests { #[test] fn stack_slot_decl() { let (func, _) = Parser::new("function %foo() { - ss3 = stack_slot 13 - ss1 = stack_slot 1 + ss3 = incoming_arg 13 + ss1 = spill_slot 1 }") .parse_function(None) .unwrap(); @@ -1802,16 +1807,18 @@ mod tests { let mut iter = func.stack_slots.keys(); let ss0 = iter.next().unwrap(); assert_eq!(ss0.to_string(), "ss0"); + assert_eq!(func.stack_slots[ss0].kind, StackSlotKind::IncomingArg); assert_eq!(func.stack_slots[ss0].size, 13); let ss1 = iter.next().unwrap(); assert_eq!(ss1.to_string(), "ss1"); + assert_eq!(func.stack_slots[ss1].kind, StackSlotKind::SpillSlot); assert_eq!(func.stack_slots[ss1].size, 1); assert_eq!(iter.next(), None); // Catch duplicate definitions. assert_eq!(Parser::new("function %bar() { - ss1 = stack_slot 13 - ss1 = stack_slot 1 + ss1 = spill_slot 13 + ss1 = spill_slot 1 }") .parse_function(None) .unwrap_err() @@ -1844,7 +1851,7 @@ mod tests { fn comments() { let (func, Details { comments, .. }) = Parser::new("; before function %comment() { ; decl - ss10 = stack_slot 13 ; stackslot. + ss10 = outgoing_arg 13 ; stackslot. ; Still stackslot. jt10 = jump_table ebb0 ; Jumptable diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 824eb97621..d905cdb9ab 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -223,7 +223,7 @@ mod tests { #[test] fn details() { let tf = parse_test("function %detail() { - ss10 = stack_slot 13 + ss10 = incoming_arg 13 jt10 = jump_table ebb0 ebb0(v4: i32, v7: i32): v10 = iadd v4, v7 diff --git a/misc/vim/syntax/cton.vim b/misc/vim/syntax/cton.vim index 86162d8a50..003029e1bb 100644 --- a/misc/vim/syntax/cton.vim +++ b/misc/vim/syntax/cton.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Cretonne " Maintainer: Jakob Stoklund Olesen / From a4cc66cb4a83229d697af5fc27a93048d755d87e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Jun 2017 11:51:41 -0700 Subject: [PATCH 803/968] Add a stack frame manager. Use a new StackSlots struct to keep track of a function's stack slots instead of just an entity map. This let's us build more internal data structures for tracking the stack slots if necessary. Start by adding a make_spill_slot() function that will be used by the register allocator. --- lib/cretonne/src/ir/function.rs | 9 ++--- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/ir/stackslot.rs | 68 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 075ee7c6fd..3cb547fdff 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -5,8 +5,8 @@ use binemit::CodeOffset; use entity_map::{EntityMap, PrimaryEntityData}; -use ir::{FunctionName, Signature, Value, Inst, Ebb, StackSlot, StackSlotData, JumpTable, - JumpTableData, ValueLoc, DataFlowGraph, Layout}; +use ir::{FunctionName, Signature, Value, Inst, Ebb, StackSlots, JumpTable, JumpTableData, + ValueLoc, DataFlowGraph, Layout}; use isa::{TargetIsa, Encoding}; use std::fmt::{self, Display, Debug, Formatter}; use write::write_function; @@ -24,7 +24,7 @@ pub struct Function { pub signature: Signature, /// Stack slots allocated in this function. - pub stack_slots: EntityMap, + pub stack_slots: StackSlots, /// Jump tables used in this function. pub jump_tables: EntityMap, @@ -50,7 +50,6 @@ pub struct Function { pub offsets: EntityMap, } -impl PrimaryEntityData for StackSlotData {} impl PrimaryEntityData for JumpTableData {} impl Function { @@ -59,7 +58,7 @@ impl Function { Function { name, signature: sig, - stack_slots: EntityMap::new(), + stack_slots: StackSlots::new(), jump_tables: EntityMap::new(), dfg: DataFlowGraph::new(), layout: Layout::new(), diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 56c972c429..de5e1515b6 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -22,7 +22,7 @@ pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ArgumentPurpos pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; -pub use ir::stackslot::{StackSlotKind, StackSlotData}; +pub use ir::stackslot::{StackSlots, StackSlotKind, StackSlotData}; pub use ir::jumptable::JumpTableData; pub use ir::valueloc::{ValueLoc, ArgumentLoc}; pub use ir::dfg::{DataFlowGraph, ValueDef}; diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index a205f6d410..a1a5f0cb94 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -3,7 +3,10 @@ //! The `StackSlotData` struct keeps track of a single stack slot in a function. //! +use entity_map::{EntityMap, PrimaryEntityData, Keys}; +use ir::{Type, StackSlot}; use std::fmt; +use std::ops::Index; use std::str::FromStr; /// The kind of a stack slot. @@ -81,6 +84,71 @@ impl fmt::Display for StackSlotData { } } +impl PrimaryEntityData for StackSlotData {} + +/// Stack frame manager. +/// +/// Keep track of all the stack slots used by a function. +#[derive(Clone, Debug)] +pub struct StackSlots { + slots: EntityMap, +} + +/// Stack slot manager functions that behave mostly like an entity map. +impl StackSlots { + /// Create an empty stack slot manager. + pub fn new() -> StackSlots { + StackSlots { slots: EntityMap::new() } + } + + /// Clear out everything. + pub fn clear(&mut self) { + self.slots.clear(); + } + + /// Allocate a new stack slot. + /// + /// This function should be primarily used by the text format parser. There are more convenient + /// functions for creating specific kinds of stack slots below. + pub fn push(&mut self, data: StackSlotData) -> StackSlot { + self.slots.push(data) + } + + /// Check if `ss` is a valid stack slot reference. + pub fn is_valid(&self, ss: StackSlot) -> bool { + self.slots.is_valid(ss) + } + + /// Get an iterator over all the stack slot keys. + pub fn keys(&self) -> Keys { + self.slots.keys() + } + + /// Get a reference to the next stack slot that would be created by `push()`. + /// + /// This should just be used by the parser. + pub fn next_key(&self) -> StackSlot { + self.slots.next_key() + } +} + +/// Higher-level stack frame manipulation functions. +impl StackSlots { + /// Create a new spill slot for spilling values of type `ty`. + pub fn make_spill_slot(&mut self, ty: Type) -> StackSlot { + let bytes = (ty.bits() as u32 + 7) / 8; + self.push(StackSlotData::new(StackSlotKind::SpillSlot, bytes)) + } +} + +impl Index for StackSlots { + type Output = StackSlotData; + + fn index(&self, ss: StackSlot) -> &StackSlotData { + &self.slots[ss] + } +} + #[cfg(test)] mod tests { use ir::Function; From bae37e523f059637bbb716bbf0412942c1d56f0f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Jun 2017 13:08:13 -0700 Subject: [PATCH 804/968] Add typedefs for the common entity maps. The various entity maps in a function end up being referenced in multiple places, so create typedefs for them. --- lib/cretonne/src/binemit/relaxation.rs | 7 +++---- lib/cretonne/src/ir/function.rs | 29 +++++++++++++------------- lib/cretonne/src/ir/mod.rs | 16 ++++++++++++++ lib/cretonne/src/regalloc/coloring.rs | 18 ++++++++-------- lib/cretonne/src/regalloc/reload.rs | 7 +++---- 5 files changed, 45 insertions(+), 32 deletions(-) diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index 517867c725..53b7fd0112 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -28,9 +28,8 @@ //! ``` use binemit::CodeOffset; -use entity_map::EntityMap; -use ir::{Function, DataFlowGraph, Cursor, Inst, InstructionData, Opcode}; -use isa::{TargetIsa, EncInfo, Encoding}; +use ir::{Function, DataFlowGraph, Cursor, InstructionData, Opcode, InstEncodings}; +use isa::{TargetIsa, EncInfo}; use iterators::IteratorExtras; /// Relax branches and compute the final layout of EBB headers in `func`. @@ -127,7 +126,7 @@ fn fallthroughs(func: &mut Function) { /// Return the size of the replacement instructions up to and including the location where `pos` is /// left. fn relax_branch(dfg: &mut DataFlowGraph, - encodings: &mut EntityMap, + encodings: &mut InstEncodings, encinfo: &EncInfo, pos: &mut Cursor, offset: CodeOffset, diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 3cb547fdff..59213aff13 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -3,12 +3,11 @@ //! The `Function` struct defined in this module owns all of its extended basic blocks and //! instructions. -use binemit::CodeOffset; use entity_map::{EntityMap, PrimaryEntityData}; -use ir::{FunctionName, Signature, Value, Inst, Ebb, StackSlots, JumpTable, JumpTableData, - ValueLoc, DataFlowGraph, Layout}; -use isa::{TargetIsa, Encoding}; -use std::fmt::{self, Display, Debug, Formatter}; +use ir::{FunctionName, Signature, JumpTableData, DataFlowGraph, Layout}; +use ir::{JumpTables, InstEncodings, ValueLocations, StackSlots, EbbOffsets}; +use isa::TargetIsa; +use std::fmt; use write::write_function; /// A function. @@ -27,7 +26,7 @@ pub struct Function { pub stack_slots: StackSlots, /// Jump tables used in this function. - pub jump_tables: EntityMap, + pub jump_tables: JumpTables, /// Data flow graph containing the primary definition of all instructions, EBBs and values. pub dfg: DataFlowGraph, @@ -37,17 +36,17 @@ pub struct Function { /// Encoding recipe and bits for the legal instructions. /// Illegal instructions have the `Encoding::default()` value. - pub encodings: EntityMap, + pub encodings: InstEncodings, /// Location assigned to every value. - pub locations: EntityMap, + pub locations: ValueLocations, /// Code offsets of the EBB headers. /// /// This information is only transiently available after the `binemit::relax_branches` function /// computes it, and it can easily be recomputed by calling that function. It is not included /// in the textual IL format. - pub offsets: EntityMap, + pub offsets: EbbOffsets, } impl PrimaryEntityData for JumpTableData {} @@ -82,20 +81,20 @@ impl Function { /// Wrapper type capable of displaying a `Function` with correct ISA annotations. pub struct DisplayFunction<'a>(&'a Function, Option<&'a TargetIsa>); -impl<'a> Display for DisplayFunction<'a> { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { +impl<'a> fmt::Display for DisplayFunction<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write_function(fmt, self.0, self.1) } } -impl Display for Function { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { +impl fmt::Display for Function { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write_function(fmt, self, None) } } -impl Debug for Function { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { +impl fmt::Debug for Function { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write_function(fmt, self, None) } } diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index de5e1515b6..c79edfa4c6 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -31,3 +31,19 @@ pub use ir::function::Function; pub use ir::builder::InstBuilder; pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; pub use ir::memflags::MemFlags; + +use binemit; +use entity_map::EntityMap; +use isa; + +/// Map of value locations. +pub type ValueLocations = EntityMap; + +/// Map of jump tables. +pub type JumpTables = EntityMap; + +/// Map of instruction encodings. +pub type InstEncodings = EntityMap; + +/// Code offsets for EBBs. +pub type EbbOffsets = EntityMap; diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 53546de07f..c71218e7cc 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -33,7 +33,7 @@ use entity_map::EntityMap; use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph}; +use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, ValueLocations}; use ir::{InstBuilder, Signature, ArgumentType, ArgumentLoc}; use isa::{TargetIsa, Encoding, EncInfo, OperandConstraint, ConstraintKind}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; @@ -218,7 +218,7 @@ impl<'a> Context<'a> { fn color_entry_args(&self, sig: &Signature, args: &[LiveValue], - locations: &mut EntityMap) + locations: &mut ValueLocations) -> AllocatableSet { assert_eq!(sig.argument_types.len(), args.len()); @@ -271,7 +271,7 @@ impl<'a> Context<'a> { fn color_args(&self, args: &[LiveValue], mut regs: AllocatableSet, - locations: &mut EntityMap) + locations: &mut ValueLocations) -> AllocatableSet { // Available registers *after* filtering out the dead arguments. let mut live_regs = regs.clone(); @@ -309,7 +309,7 @@ impl<'a> Context<'a> { dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, - locations: &mut EntityMap, + locations: &mut ValueLocations, func_signature: &Signature) { dbg!("Coloring [{}] {}", self.encinfo.display(encoding), @@ -449,7 +449,7 @@ impl<'a> Context<'a> { // into the constraint solver. Convert them to solver variables so they can be diverted. fn divert_fixed_input_conflicts(&mut self, live: &[LiveValue], - locations: &mut EntityMap) { + locations: &mut ValueLocations) { for lv in live { if let Affinity::Reg(rci) = lv.affinity { let rc = self.reginfo.rc(rci); @@ -468,7 +468,7 @@ impl<'a> Context<'a> { constraints: &[OperandConstraint], defs: &[LiveValue], throughs: &[LiveValue], - locations: &mut EntityMap) { + locations: &mut ValueLocations) { for (op, lv) in constraints.iter().zip(defs) { if let ConstraintKind::FixedReg(reg) = op.kind { self.add_fixed_output(lv.value, op.regclass, reg, throughs, locations); @@ -483,7 +483,7 @@ impl<'a> Context<'a> { abi_types: &[ArgumentType], defs: &[LiveValue], throughs: &[LiveValue], - locations: &mut EntityMap) { + locations: &mut ValueLocations) { // It's technically possible for a call instruction to have fixed results before the // variable list of results, but we have no known instances of that. // Just assume all results are variable return values. @@ -506,7 +506,7 @@ impl<'a> Context<'a> { rc: RegClass, reg: RegUnit, throughs: &[LiveValue], - locations: &mut EntityMap) { + locations: &mut ValueLocations) { if !self.solver.add_fixed_output(rc, reg) { // The fixed output conflicts with some of the live-through registers. for lv in throughs { @@ -538,7 +538,7 @@ impl<'a> Context<'a> { constraints: &[OperandConstraint], defs: &[LiveValue], _dfg: &mut DataFlowGraph, - _locations: &mut EntityMap) { + _locations: &mut ValueLocations) { for (op, lv) in constraints.iter().zip(defs) { match op.kind { ConstraintKind::FixedReg(_) | diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index dd623de2b5..2226566799 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -10,8 +10,7 @@ //! pressure limits to be exceeded. use dominator_tree::DominatorTree; -use entity_map::EntityMap; -use ir::{Ebb, Inst, Value, Function, Signature, DataFlowGraph}; +use ir::{Ebb, Inst, Value, Function, Signature, DataFlowGraph, InstEncodings}; use ir::layout::{Cursor, CursorPosition}; use ir::{InstBuilder, Opcode, ArgumentType, ArgumentLoc}; use isa::RegClass; @@ -201,7 +200,7 @@ impl<'a> Context<'a> { encoding: Encoding, pos: &mut Cursor, dfg: &mut DataFlowGraph, - encodings: &mut EntityMap, + encodings: &mut InstEncodings, func_signature: &Signature, tracker: &mut LiveValueTracker) { // Get the operand constraints for `inst` that we are trying to satisfy. @@ -341,7 +340,7 @@ impl<'a> Context<'a> { stack: Value, reg: Value, pos: &mut Cursor, - encodings: &mut EntityMap, + encodings: &mut InstEncodings, dfg: &mut DataFlowGraph) { let ty = dfg.value_type(reg); From 342121aba089adba96948c9993d1275ae20c6ba8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 16 Jun 2017 13:32:21 -0700 Subject: [PATCH 805/968] Assign spill slots to spilled values. As soon as a value is spilled, also assign it to a spill slot. For now, create a new spill slot for each spilled value. In the future, values will be sharing spill slots of they are phi-related. --- filetests/regalloc/spill.cton | 11 ++++++++--- lib/cretonne/src/regalloc/spilling.rs | 25 +++++++++++++++++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index f163c478b3..23ddf7129b 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -10,6 +10,7 @@ test regalloc ; - %x10-%x15 are function arguments. ; ; regex: V=v\d+ +; regex: WS=\s+ isa riscv enable_e @@ -19,14 +20,18 @@ isa riscv enable_e ; 2. The link register. ; 3. The first computed value, v2 function %pyramid(i32) -> i32 { +; check: ss0 = spill_slot 4 +; check: ss1 = spill_slot 4 +; check: ss2 = spill_slot 4 +; not: spill_slot ebb0(v1: i32): ; check: $ebb0($(rv1=$V): i32, $(rlink=$V): i32) - ; check: $v1 = spill $rv1 - ; nextln: $(link=$V) = spill $rlink + ; check: ,ss0]$WS $v1 = spill $rv1 + ; nextln: ,ss1]$WS $(link=$V) = spill $rlink ; not: spill v2 = iadd_imm v1, 12 ; check: $(r1v2=$V) = iadd_imm - ; nextln: $v2 = spill $r1v2 + ; nextln: ,ss2]$WS $v2 = spill $r1v2 ; not: spill v3 = iadd_imm v2, 12 v4 = iadd_imm v3, 12 diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 23d4799dad..f21e6e6c1a 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -16,11 +16,11 @@ //! operands. use dominator_tree::DominatorTree; -use entity_map::EntityMap; use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; -use ir::{Function, Ebb, Inst, Value, SigRef}; +use ir::{Function, Ebb, Inst, Value, ValueLoc, SigRef}; +use ir::{InstEncodings, StackSlots, ValueLocations}; use isa::registers::{RegClass, RegClassMask}; -use isa::{TargetIsa, RegInfo, EncInfo, Encoding, RecipeConstraints, ConstraintKind}; +use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; @@ -41,7 +41,9 @@ struct Context<'a> { encinfo: EncInfo, // References to parts of the current function. - encodings: &'a mut EntityMap, + encodings: &'a mut InstEncodings, + stack_slots: &'a mut StackSlots, + locations: &'a mut ValueLocations, // References to contextual data structures we need. domtree: &'a DominatorTree, @@ -85,6 +87,8 @@ impl Spilling { reginfo: isa.register_info(), encinfo: isa.encoding_info(), encodings: &mut func.encodings, + stack_slots: &mut func.stack_slots, + locations: &mut func.locations, domtree, liveness, topo, @@ -209,7 +213,7 @@ impl<'a> Context<'a> { if call_sig.is_some() { for lv in throughs { if lv.affinity.is_reg() && !self.spills.contains(&lv.value) { - self.spill_reg(lv.value); + self.spill_reg(lv.value, dfg); } } } @@ -351,7 +355,7 @@ impl<'a> Context<'a> { if let Some(value) = best { // Found a spill candidate. - self.spill_reg(value); + self.spill_reg(value, dfg); } else { panic!("Ran out of registers for mask={}", mask); } @@ -365,12 +369,17 @@ impl<'a> Context<'a> { /// /// Note that this does not update the cached affinity in the live value tracker. Call /// `process_spills` to do that. - fn spill_reg(&mut self, value: Value) { + fn spill_reg(&mut self, value: Value, dfg: &DataFlowGraph) { if let Affinity::Reg(rci) = self.liveness.spill(value) { let rc = self.reginfo.rc(rci); self.pressure.free(rc); self.spills.push(value); - dbg!("Spilled {}:{} -> {}", value, rc, self.pressure); + + // Assign a spill slot. + // TODO: phi-related values should use the same spill slot. + let ss = self.stack_slots.make_spill_slot(dfg.value_type(value)); + *self.locations.ensure(value) = ValueLoc::Stack(ss); + dbg!("Spilled {}:{} to {} -> {}", value, rc, ss, self.pressure); } else { panic!("Cannot spill {} that was already on the stack", value); } From 1a480a257836e96b8ee2adcd9e335466b5cd7913 Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Mon, 19 Jun 2017 20:52:19 +0500 Subject: [PATCH 806/968] Implement an iterator over encodings (#96) * Implement an iterator over encodings * Implement TargetIsa::legal_encodings * Exclude non-boolean settings of isa flags bytes * Address flake8 long line error --- lib/cretonne/meta/gen_settings.py | 4 ++ lib/cretonne/src/isa/arm32/mod.rs | 26 +++---- lib/cretonne/src/isa/arm64/mod.rs | 26 +++---- lib/cretonne/src/isa/enc_tables.rs | 108 +++++++++++++++++++---------- lib/cretonne/src/isa/intel/mod.rs | 26 +++---- lib/cretonne/src/isa/mod.rs | 15 +++- lib/cretonne/src/isa/riscv/mod.rs | 26 +++---- 7 files changed, 139 insertions(+), 92 deletions(-) diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 23e9a5c7a9..ae16b337c0 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -87,6 +87,10 @@ def gen_getters(sgrp, fmt): """ fmt.doc_comment("User-defined settings.") with fmt.indented('impl Flags {', '}'): + fmt.doc_comment('Returns inner slice of bytes.') + fmt.doc_comment('The byte-sized settings are not included.') + with fmt.indented('pub fn predicate_bytes(&self) -> &[u8] {', '}'): + fmt.line('&self.bytes[{}..]'.format(sgrp.boolean_offset)) fmt.doc_comment('Dynamic numbered predicate getter.') with fmt.indented( 'pub fn numbered_predicate(&self, p: usize) -> bool {', '}'): diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 5657c7523b..d986b9e0e2 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -8,9 +8,9 @@ mod registers; use binemit::CodeSink; use super::super::settings as shared_settings; -use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; use ir; use regalloc; @@ -61,22 +61,22 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn encode(&self, - _dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result { + fn legal_encodings<'a, 'b>(&'a self, + _dfg: &'b ir::DataFlowGraph, + inst: &'b ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + Ok(Encodings::new(enclist_offset, + &enc_tables::ENCLISTS[..], + inst, + enc_tables::check_instp, + self.isa_flags.predicate_bytes())) + }) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index fe81ea8c09..c8c2de3cf8 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -8,9 +8,9 @@ mod registers; use binemit::CodeSink; use super::super::settings as shared_settings; -use isa::enc_tables::{lookup_enclist, general_encoding}; +use isa::enc_tables::{lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; use ir; use regalloc; @@ -54,22 +54,22 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn encode(&self, - _dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result { + fn legal_encodings<'a, 'b>(&'a self, + _dfg: &'b ir::DataFlowGraph, + inst: &'b ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), &enc_tables::LEVEL1_A64[..], &enc_tables::LEVEL2[..]) .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + Ok(Encodings::new(enclist_offset, + &enc_tables::ENCLISTS[..], + inst, + enc_tables::check_instp, + self.isa_flags.predicate_bytes())) + }) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index d7dde71eed..3467a2bdfd 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -2,7 +2,7 @@ //! //! This module contains types and functions for working with the encoding tables generated by //! `lib/cretonne/meta/gen_encoding.py`. -use ir::{Type, Opcode}; +use ir::{Type, Opcode, InstructionData}; use isa::{Encoding, Legalize}; use constant_hash::{Table, probe}; @@ -114,43 +114,75 @@ const CODE_ALWAYS: EncListEntry = PRED_MASK; /// The encoding list terminator. const CODE_FAIL: EncListEntry = 0xffff; -/// Find the first applicable general encoding of `inst`. -/// -/// Given an encoding list offset as returned by `lookup_enclist` above, search the encoding list -/// for the most first encoding that applies to `inst`. The encoding lists are laid out such that -/// this is the first valid entry in the list. -/// -/// This function takes two closures that are used to evaluate predicates: -/// - `instp` is passed an instruction predicate number to be evaluated on the current instruction. -/// - `isap` is passed an ISA predicate number to evaluate. -/// -/// Returns the corresponding encoding, or `None` if no list entries are satisfied by `inst`. -pub fn general_encoding(offset: usize, - enclist: &[EncListEntry], - instp: InstP, - isap: IsaP) - -> Option - where InstP: Fn(EncListEntry) -> bool, - IsaP: Fn(EncListEntry) -> bool -{ - let mut pos = offset; - while enclist[pos] != CODE_FAIL { - let pred = enclist[pos]; - if pred <= CODE_ALWAYS { - // This is an instruction predicate followed by recipe and encbits entries. - if pred == CODE_ALWAYS || instp(pred) { - return Some(Encoding::new(enclist[pos + 1], enclist[pos + 2])); - } - pos += 3; - } else { - // This is an ISA predicate entry. - pos += 1; - if !isap(pred & PRED_MASK) { - // ISA predicate failed, skip the next N entries. - pos += 3 * (pred >> PRED_BITS) as usize; - } +/// An iterator over legal encodings for the instruction. +pub struct Encodings<'a, 'b> { + offset: usize, + enclist: &'b [EncListEntry], + inst: &'b InstructionData, + instp: fn(&InstructionData, EncListEntry) -> bool, + isa_predicate_bytes: &'a [u8], +} + +impl<'a, 'b> Encodings<'a, 'b> { + /// Creates a new instance of `Encodings`. + /// + /// # Parameters + /// + /// - `offset` an offset into encoding list returned by `lookup_enclist` function. + /// - `inst` the current instruction. + /// - `enclist` a list of encoding entries. + /// - `instp` an instruction predicate number to be evaluated on the current instruction. + /// - `isa_predicate_bytes` an ISA flags as a slice of bytes to evaluate an ISA predicate number + /// on the current instruction. + /// + /// This iterator provides search for encodings that applies to the given instruction. The + /// encoding lists are laid out such that first call to `next` returns valid entry in the list + /// or `None`. + pub fn new(offset: usize, + enclist: &'b [EncListEntry], + inst: &'b InstructionData, + instp: fn(&InstructionData, EncListEntry) -> bool, + isa_predicate_bytes: &'a [u8]) + -> Self { + Encodings { + offset, + enclist, + inst, + instp, + isa_predicate_bytes, } } - - None +} + +impl<'a, 'b> Iterator for Encodings<'a, 'b> { + type Item = Encoding; + + fn next(&mut self) -> Option { + fn numbered_predicate(bytes: &[u8], p: usize) -> bool { + bytes[p / 8] & (1 << (p % 8)) != 0 + } + + while self.enclist[self.offset] != CODE_FAIL { + let pred = self.enclist[self.offset]; + if pred <= CODE_ALWAYS { + // This is an instruction predicate followed by recipe and encbits entries. + if pred == CODE_ALWAYS || (self.instp)(self.inst, pred) { + let encoding = Encoding::new(self.enclist[self.offset + 1], + self.enclist[self.offset + 2]); + self.offset += 3; + return Some(encoding); + } else { + self.offset += 3; + } + } else { + // This is an ISA predicate entry. + self.offset += 1; + if !numbered_predicate(self.isa_predicate_bytes, (pred & PRED_MASK) as usize) { + // ISA predicate failed, skip the next N entries. + self.offset += 3 * (pred >> PRED_BITS) as usize; + } + } + } + None + } } diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 1711be3166..6cfd0b4b85 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -8,9 +8,9 @@ mod registers; use binemit::CodeSink; use super::super::settings as shared_settings; -use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; use ir; use regalloc; @@ -61,22 +61,22 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn encode(&self, - _dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result { + fn legal_encodings<'a, 'b>(&'a self, + _dfg: &'b ir::DataFlowGraph, + inst: &'b ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + Ok(Encodings::new(enclist_offset, + &enc_tables::ENCLISTS[..], + inst, + enc_tables::check_instp, + self.isa_flags.predicate_bytes())) + }) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index edc0034217..1ec80e790c 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -48,6 +48,7 @@ use binemit::CodeSink; use settings; use ir; use regalloc; +use isa::enc_tables::Encodings; pub mod riscv; pub mod intel; @@ -136,17 +137,27 @@ pub trait TargetIsa { /// Get a data structure describing the registers in this ISA. fn register_info(&self) -> RegInfo; + /// Returns an iterartor over legal encodings for the instruction. + fn legal_encodings<'a, 'b>(&'a self, + dfg: &'b ir::DataFlowGraph, + inst: &'b ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize>; + /// Encode an instruction after determining it is legal. /// /// If `inst` can legally be encoded in this ISA, produce the corresponding `Encoding` object. - /// Otherwise, return `None`. + /// Otherwise, return `Legalize` action. /// /// This is also the main entry point for determining if an instruction is legal. fn encode(&self, dfg: &ir::DataFlowGraph, inst: &ir::InstructionData, ctrl_typevar: ir::Type) - -> Result; + -> Result { + self.legal_encodings(dfg, inst, ctrl_typevar) + .and_then(|mut iter| iter.next().ok_or(Legalize::Expand)) + } /// Get a data structure describing the instruction encodings in this ISA. fn encoding_info(&self) -> EncInfo; diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index ad64e4fd29..5f9cd771a2 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -8,9 +8,9 @@ mod registers; use super::super::settings as shared_settings; use binemit::CodeSink; -use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; use ir; use regalloc; @@ -61,22 +61,22 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn encode(&self, - _dfg: &ir::DataFlowGraph, - inst: &ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result { + fn legal_encodings<'a, 'b>(&'a self, + _dfg: &'b ir::DataFlowGraph, + inst: &'b ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, &enc_tables::LEVEL2[..]) .and_then(|enclist_offset| { - general_encoding(enclist_offset, - &enc_tables::ENCLISTS[..], - |instp| enc_tables::check_instp(inst, instp), - |isap| self.isa_flags.numbered_predicate(isap as usize)) - .ok_or(Legalize::Expand) - }) + Ok(Encodings::new(enclist_offset, + &enc_tables::ENCLISTS[..], + inst, + enc_tables::check_instp, + self.isa_flags.predicate_bytes())) + }) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { From 61a0844b24383bb34364e44a876217c4e5858b0f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 19 Jun 2017 16:24:10 -0700 Subject: [PATCH 807/968] Lint fixes (#99) * Replace a single-character string literal with a character literal. * Use is_some() instead of comparing with Some(_). * Add code-quotes around type names in comments. * Use !...is_empty() instead of len() != 0. * Tidy up redundant returns. * Remove redundant .clone() calls. * Remove unnecessary explicit lifetime parameters. * Tidy up unnecessary '&'s. * Add parens to make operator precedence explicit. * Use debug_assert_eq instead of debug_assert with ==. * Replace a &Vec argument with a &[...]. * Replace `a = a op b` with `a op= b`. * Avoid unnecessary closures. * Avoid .iter() and .iter_mut() for iterating over containers. * Remove unneeded qualification. --- lib/cretonne/src/context.rs | 2 +- lib/cretonne/src/ir/condcodes.rs | 52 ++++++++-------- lib/cretonne/src/ir/dfg.rs | 13 ++-- lib/cretonne/src/ir/extfunc.rs | 2 +- lib/cretonne/src/ir/immediates.rs | 4 +- lib/cretonne/src/ir/instructions.rs | 28 ++++----- lib/cretonne/src/ir/jumptable.rs | 2 +- lib/cretonne/src/ir/valueloc.rs | 8 +-- lib/cretonne/src/isa/intel/binemit.rs | 6 +- lib/cretonne/src/licm.rs | 29 ++++----- lib/cretonne/src/regalloc/allocatable_set.rs | 6 +- lib/cretonne/src/regalloc/pressure.rs | 4 +- lib/cretonne/src/regalloc/reload.rs | 2 +- lib/cretonne/src/regalloc/spilling.rs | 2 +- lib/cretonne/src/result.rs | 3 +- lib/cretonne/src/simple_gvn.rs | 4 +- lib/cretonne/src/topo_order.rs | 2 +- lib/cretonne/src/verifier/mod.rs | 64 ++++++++++---------- lib/filecheck/src/checker.rs | 6 +- lib/filecheck/src/lib.rs | 4 +- lib/reader/src/isaspec.rs | 2 +- lib/reader/src/lexer.rs | 16 ++--- lib/reader/src/parser.rs | 6 +- lib/reader/src/sourcemap.rs | 2 +- 24 files changed, 132 insertions(+), 137 deletions(-) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 90c4948b40..5e23ed3efc 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -60,7 +60,7 @@ impl Context { /// /// The `isa` argument is currently unused, but the verifier will soon be able to also /// check ISA-dependent constraints. - pub fn verify<'a>(&self, isa: Option<&TargetIsa>) -> verifier::Result { + pub fn verify(&self, isa: Option<&TargetIsa>) -> verifier::Result { verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa) } diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs index 520014471d..f63bd60938 100644 --- a/lib/cretonne/src/ir/condcodes.rs +++ b/lib/cretonne/src/ir/condcodes.rs @@ -88,17 +88,17 @@ impl CondCode for IntCC { impl Display for IntCC { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use self::IntCC::*; - f.write_str(match self { - &Equal => "eq", - &NotEqual => "ne", - &SignedGreaterThan => "sgt", - &SignedGreaterThanOrEqual => "sge", - &SignedLessThan => "slt", - &SignedLessThanOrEqual => "sle", - &UnsignedGreaterThan => "ugt", - &UnsignedGreaterThanOrEqual => "uge", - &UnsignedLessThan => "ult", - &UnsignedLessThanOrEqual => "ule", + f.write_str(match *self { + Equal => "eq", + NotEqual => "ne", + SignedGreaterThan => "sgt", + SignedGreaterThanOrEqual => "sge", + SignedLessThan => "slt", + SignedLessThanOrEqual => "sle", + UnsignedGreaterThan => "ugt", + UnsignedGreaterThanOrEqual => "uge", + UnsignedLessThan => "ult", + UnsignedLessThanOrEqual => "ule", }) } } @@ -219,21 +219,21 @@ impl CondCode for FloatCC { impl Display for FloatCC { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use self::FloatCC::*; - f.write_str(match self { - &Ordered => "ord", - &Unordered => "uno", - &Equal => "eq", - &NotEqual => "ne", - &OrderedNotEqual => "one", - &UnorderedOrEqual => "ueq", - &LessThan => "lt", - &LessThanOrEqual => "le", - &GreaterThan => "gt", - &GreaterThanOrEqual => "ge", - &UnorderedOrLessThan => "ult", - &UnorderedOrLessThanOrEqual => "ule", - &UnorderedOrGreaterThan => "ugt", - &UnorderedOrGreaterThanOrEqual => "uge", + f.write_str(match *self { + Ordered => "ord", + Unordered => "uno", + Equal => "eq", + NotEqual => "ne", + OrderedNotEqual => "one", + UnorderedOrEqual => "ueq", + LessThan => "lt", + LessThanOrEqual => "le", + GreaterThan => "gt", + GreaterThanOrEqual => "ge", + UnorderedOrLessThan => "ult", + UnorderedOrLessThanOrEqual => "ule", + UnorderedOrGreaterThan => "ugt", + UnorderedOrGreaterThanOrEqual => "uge", }) } } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 36607e76f1..40f209a49b 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -247,10 +247,11 @@ impl DataFlowGraph { // Try to create short alias chains by finding the original source value. // This also avoids the creation of loops. let original = self.resolve_aliases(src); - assert!(dest != original, - "Aliasing {} to {} would create a loop", - dest, - src); + assert_ne!(dest, + original, + "Aliasing {} to {} would create a loop", + dest, + src); let ty = self.value_type(original); assert_eq!(self.value_type(dest), ty, @@ -326,8 +327,8 @@ pub enum ValueDef { impl ValueDef { /// Unwrap the instruction where the value was defined, or panic. pub fn unwrap_inst(&self) -> Inst { - match self { - &ValueDef::Res(inst, _) => inst, + match *self { + ValueDef::Res(inst, _) => inst, _ => panic!("Value is not an instruction result"), } } diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index e0863f8d1d..57346df5f2 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -70,7 +70,7 @@ impl Signature { pub struct DisplaySignature<'a>(&'a Signature, Option<&'a RegInfo>); fn write_list(f: &mut fmt::Formatter, - args: &Vec, + args: &[ArgumentType], regs: Option<&RegInfo>) -> fmt::Result { match args.split_first() { diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 44c4cd3fc3..fa6185cb8b 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -314,7 +314,7 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { let max_e_bits = (1u64 << w) - 1; let t_bits = bits & ((1u64 << t) - 1); // Trailing significand. let e_bits = (bits >> t) & max_e_bits; // Biased exponent. - let sign_bit = (bits >> w + t) & 1; + let sign_bit = (bits >> (w + t)) & 1; let bias: i32 = (1 << (w - 1)) - 1; let e = e_bits as i32 - bias; // Unbiased exponent. @@ -381,7 +381,7 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size"); let (sign_bit, s2) = if s.starts_with('-') { - (1u64 << t + w, &s[1..]) + (1u64 << (t + w), &s[1..]) } else if s.starts_with('+') { (0, &s[1..]) } else { diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 66c6d039b0..ad4dd6fa21 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -286,23 +286,23 @@ impl InstructionData { /// Any instruction that can transfer control to another EBB reveals its possible destinations /// here. pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> { - match self { - &InstructionData::Jump { + match *self { + InstructionData::Jump { destination, ref args, .. - } => BranchInfo::SingleDest(destination, &args.as_slice(pool)), - &InstructionData::Branch { + } => BranchInfo::SingleDest(destination, args.as_slice(pool)), + InstructionData::Branch { destination, ref args, .. } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]), - &InstructionData::BranchIcmp { + InstructionData::BranchIcmp { destination, ref args, .. } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]), - &InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), + InstructionData::BranchTable { table, .. } => BranchInfo::Table(table), _ => BranchInfo::NotABranch, } } @@ -312,10 +312,10 @@ impl InstructionData { /// /// Multi-destination branches like `br_table` return `None`. pub fn branch_destination(&self) -> Option { - match self { - &InstructionData::Jump { destination, .. } => Some(destination), - &InstructionData::Branch { destination, .. } => Some(destination), - &InstructionData::BranchIcmp { destination, .. } => Some(destination), + match *self { + InstructionData::Jump { destination, .. } => Some(destination), + InstructionData::Branch { destination, .. } => Some(destination), + InstructionData::BranchIcmp { destination, .. } => Some(destination), _ => None, } } @@ -337,11 +337,11 @@ impl InstructionData { /// /// Any instruction that can call another function reveals its call signature here. pub fn analyze_call<'a>(&'a self, pool: &'a ValueListPool) -> CallInfo<'a> { - match self { - &InstructionData::Call { func_ref, ref args, .. } => { - CallInfo::Direct(func_ref, &args.as_slice(pool)) + match *self { + InstructionData::Call { func_ref, ref args, .. } => { + CallInfo::Direct(func_ref, args.as_slice(pool)) } - &InstructionData::IndirectCall { sig_ref, ref args, .. } => { + InstructionData::IndirectCall { sig_ref, ref args, .. } => { CallInfo::Indirect(sig_ref, &args.as_slice(pool)[1..]) } _ => CallInfo::NotACall, diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index dd9bc5c187..24dad02056 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -65,7 +65,7 @@ impl JumpTableData { /// Enumerate over all `(idx, dest)` pairs in the table in order. /// /// This returns an iterator that skips any empty slots in the table. - pub fn entries<'a>(&'a self) -> Entries { + pub fn entries(&self) -> Entries { Entries(self.table.iter().cloned().enumerate()) } diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index b664fe70e5..8c5ea50dfa 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -98,16 +98,16 @@ impl Default for ArgumentLoc { impl ArgumentLoc { /// Is this an assigned location? (That is, not `Unassigned`). pub fn is_assigned(&self) -> bool { - match self { - &ArgumentLoc::Unassigned => false, + match *self { + ArgumentLoc::Unassigned => false, _ => true, } } /// Is this a register location? pub fn is_reg(&self) -> bool { - match self { - &ArgumentLoc::Reg(_) => true, + match *self { + ArgumentLoc::Reg(_) => true, _ => false, } } diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 834a9848c3..87587463ca 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -10,13 +10,13 @@ pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; // Emit single-byte opcode. fn put_op1(bits: u16, sink: &mut CS) { - debug_assert!(bits & 0x0f00 == 0, "Invalid encoding bits for Op1*"); + debug_assert_eq!(bits & 0x0f00, 0, "Invalid encoding bits for Op1*"); sink.put1(bits as u8); } // Emit two-byte opcode: 0F XX fn put_op2(bits: u16, sink: &mut CS) { - debug_assert!(bits & 0x0f00 == 0x0400, "Invalid encoding bits for Op2*"); + debug_assert_eq!(bits & 0x0f00, 0x0400, "Invalid encoding bits for Op2*"); sink.put1(0x0f); sink.put1(bits as u8); } @@ -26,7 +26,7 @@ const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2]; // Emit single-byte opcode with mandatory prefix. fn put_mp1(bits: u16, sink: &mut CS) { - debug_assert!(bits & 0x0c00 == 0, "Invalid encoding bits for Mp1*"); + debug_assert_eq!(bits & 0x0c00, 0, "Invalid encoding bits for Mp1*"); let pp = (bits >> 8) & 3; sink.put1(PREFIX[(pp - 1) as usize]); sink.put1(bits as u8); diff --git a/lib/cretonne/src/licm.rs b/lib/cretonne/src/licm.rs index 0fc8212460..10e0558999 100644 --- a/lib/cretonne/src/licm.rs +++ b/lib/cretonne/src/licm.rs @@ -21,18 +21,13 @@ pub fn do_licm(func: &mut Function, let invariant_inst = remove_loop_invariant_instructions(lp, func, cfg, loop_analysis); // Then we create the loop's pre-header and fill it with the invariant instructions // Then we remove the invariant instructions from the loop body - if invariant_inst.len() > 0 { + if !invariant_inst.is_empty() { // If the loop has a natural pre-header we use it, otherwise we create it. let mut pos; - match has_pre_header(&func.layout, - cfg, - domtree, - loop_analysis.loop_header(lp).clone()) { + match has_pre_header(&func.layout, cfg, domtree, loop_analysis.loop_header(lp)) { None => { - let pre_header = create_pre_header(loop_analysis.loop_header(lp).clone(), - func, - cfg, - domtree); + let pre_header = + create_pre_header(loop_analysis.loop_header(lp), func, cfg, domtree); pos = Cursor::new(&mut func.layout); pos.goto_bottom(pre_header); pos.prev_inst(); @@ -47,7 +42,7 @@ pub fn do_licm(func: &mut Function, // The last instruction of the pre-header is the termination instruction (usually // a jump) so we need to insert just before this. for inst in invariant_inst { - pos.insert_inst(inst.clone()); + pos.insert_inst(inst); } } } @@ -146,7 +141,7 @@ fn remove_loop_invariant_instructions(lp: Loop, for ebb in postorder_ebbs_loop(loop_analysis, cfg, lp).iter().rev() { // Arguments of the EBB are loop values for val in func.dfg.ebb_args(*ebb) { - loop_values.insert(val.clone()); + loop_values.insert(*val); } pos.goto_top(*ebb); while let Some(inst) = pos.next_inst() { @@ -164,7 +159,7 @@ fn remove_loop_invariant_instructions(lp: Loop, // If the instruction is not loop-invariant we push its results in the set of // loop values for out in func.dfg.inst_results(inst) { - loop_values.insert(out.clone()); + loop_values.insert(*out); } } } @@ -176,7 +171,7 @@ fn remove_loop_invariant_instructions(lp: Loop, fn postorder_ebbs_loop(loop_analysis: &LoopAnalysis, cfg: &ControlFlowGraph, lp: Loop) -> Vec { let mut grey = HashSet::new(); let mut black = HashSet::new(); - let mut stack = vec![loop_analysis.loop_header(lp).clone()]; + let mut stack = vec![loop_analysis.loop_header(lp)]; let mut postorder = Vec::new(); while !stack.is_empty() { @@ -187,13 +182,13 @@ fn postorder_ebbs_loop(loop_analysis: &LoopAnalysis, cfg: &ControlFlowGraph, lp: stack.push(node); // Get any children we've never seen before. for child in cfg.get_successors(node) { - if loop_analysis.is_in_loop(child.clone(), lp) && !grey.contains(child) { - stack.push(child.clone()); + if loop_analysis.is_in_loop(*child, lp) && !grey.contains(child) { + stack.push(*child); } } } else if !black.contains(&node) { - postorder.push(node.clone()); - black.insert(node.clone()); + postorder.push(node); + black.insert(node); } } postorder diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index 6ad8019ba9..aecd338e7f 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -50,14 +50,14 @@ impl AllocatableSet { /// It is an error to take a register that doesn't have all of its register units available. pub fn take(&mut self, rc: RegClass, reg: RegUnit) { let (idx, bits) = bitmask(rc, reg); - debug_assert!((self.avail[idx] & bits) == bits, "Not available"); + debug_assert_eq!(self.avail[idx] & bits, bits, "Not available"); self.avail[idx] &= !bits; } /// Make `reg` available for allocation again. pub fn free(&mut self, rc: RegClass, reg: RegUnit) { let (idx, bits) = bitmask(rc, reg); - debug_assert!((self.avail[idx] & bits) == 0, "Not allocated"); + debug_assert_eq!(self.avail[idx] & bits, 0, "Not allocated"); self.avail[idx] |= bits; } @@ -118,7 +118,7 @@ impl Iterator for RegSetIter { let unit = unit_offset + word.trailing_zeros() as RegUnit; // Clear that lowest bit so we won't find it again. - *word = *word & (*word - 1); + *word &= *word - 1; return Some(unit); } diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index 1398bff8d6..4839c7451a 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -199,7 +199,7 @@ impl Pressure { /// Reset all counts to 0, both base and transient. pub fn reset(&mut self) { - for e in self.toprc.iter_mut() { + for e in &mut self.toprc { e.base_count = 0; e.transient_count = 0; } @@ -220,7 +220,7 @@ impl Pressure { /// Reset all transient counts to 0. pub fn reset_transient(&mut self) { - for e in self.toprc.iter_mut() { + for e in &mut self.toprc { e.transient_count = 0; } } diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 2226566799..e8327b5a73 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -233,7 +233,7 @@ impl<'a> Context<'a> { // Create a live range for the new reload. let affinity = Affinity::Reg(cand.regclass.into()); self.liveness.create_dead(reg, dfg.value_def(reg), affinity); - self.liveness.extend_locally(reg, ebb, inst, &pos.layout); + self.liveness.extend_locally(reg, ebb, inst, pos.layout); } // Rewrite arguments. diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index f21e6e6c1a..7311de5d5d 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -226,7 +226,7 @@ impl<'a> Context<'a> { // Add register def to pressure, spill if needed. while let Err(mask) = self.pressure.take_transient(op.regclass) { dbg!("Need {} reg from {} throughs", op.regclass, throughs.len()); - self.spill_from(mask, throughs, dfg, &pos.layout); + self.spill_from(mask, throughs, dfg, pos.layout); } } } diff --git a/lib/cretonne/src/result.rs b/lib/cretonne/src/result.rs index 6605f1daff..960bd021d4 100644 --- a/lib/cretonne/src/result.rs +++ b/lib/cretonne/src/result.rs @@ -3,7 +3,6 @@ use verifier; use std::error::Error as StdError; use std::fmt; -use std::result; /// A compilation error. /// @@ -32,7 +31,7 @@ pub enum CtonError { } /// A Cretonne compilation result. -pub type CtonResult = result::Result<(), CtonError>; +pub type CtonResult = Result<(), CtonError>; impl fmt::Display for CtonError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs index e8f89f4a1b..bb339fbd4d 100644 --- a/lib/cretonne/src/simple_gvn.rs +++ b/lib/cretonne/src/simple_gvn.rs @@ -16,7 +16,7 @@ fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool { pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { let mut visible_values: HashMap = HashMap::new(); - let domtree = DominatorTree::with_function(func, &cfg); + let domtree = DominatorTree::with_function(func, cfg); // Visit EBBs in a reverse post-order. let mut pos = Cursor::new(&mut func.layout); @@ -47,7 +47,7 @@ pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph) { use std::collections::hash_map::Entry::*; match entry { Occupied(mut entry) => { - if domtree.dominates(*entry.get(), inst, &pos.layout) { + if domtree.dominates(*entry.get(), inst, pos.layout) { func.dfg.replace_with_aliases(inst, *entry.get()); pos.remove_inst_and_step_back(); } else { diff --git a/lib/cretonne/src/topo_order.rs b/lib/cretonne/src/topo_order.rs index 55cece2c2f..323df99ef0 100644 --- a/lib/cretonne/src/topo_order.rs +++ b/lib/cretonne/src/topo_order.rs @@ -73,7 +73,7 @@ impl TopoOrder { } } } - return self.stack.pop(); + self.stack.pop() } } diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index a2f806a447..08aeb9249f 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -229,21 +229,21 @@ impl<'a> Verifier<'a> { self.verify_value(inst, res)?; } - match &self.func.dfg[inst] { - &MultiAry { ref args, .. } => { + match self.func.dfg[inst] { + MultiAry { ref args, .. } => { self.verify_value_list(inst, args)?; } - &Jump { + Jump { destination, ref args, .. } | - &Branch { + Branch { destination, ref args, .. } | - &BranchIcmp { + BranchIcmp { destination, ref args, .. @@ -251,41 +251,41 @@ impl<'a> Verifier<'a> { self.verify_ebb(inst, destination)?; self.verify_value_list(inst, args)?; } - &BranchTable { table, .. } => { + BranchTable { table, .. } => { self.verify_jump_table(inst, table)?; } - &Call { func_ref, ref args, .. } => { + Call { func_ref, ref args, .. } => { self.verify_func_ref(inst, func_ref)?; self.verify_value_list(inst, args)?; } - &IndirectCall { sig_ref, ref args, .. } => { + IndirectCall { sig_ref, ref args, .. } => { self.verify_sig_ref(inst, sig_ref)?; self.verify_value_list(inst, args)?; } - &StackLoad { stack_slot, .. } | - &StackStore { stack_slot, .. } => { + StackLoad { stack_slot, .. } | + StackStore { stack_slot, .. } => { self.verify_stack_slot(inst, stack_slot)?; } // Exhaustive list so we can't forget to add new formats - &Nullary { .. } | - &Unary { .. } | - &UnaryImm { .. } | - &UnaryIeee32 { .. } | - &UnaryIeee64 { .. } | - &Binary { .. } | - &BinaryImm { .. } | - &Ternary { .. } | - &InsertLane { .. } | - &ExtractLane { .. } | - &IntCompare { .. } | - &IntCompareImm { .. } | - &FloatCompare { .. } | - &HeapLoad { .. } | - &HeapStore { .. } | - &Load { .. } | - &Store { .. } | - &RegMove { .. } => {} + Nullary { .. } | + Unary { .. } | + UnaryImm { .. } | + UnaryIeee32 { .. } | + UnaryIeee64 { .. } | + Binary { .. } | + BinaryImm { .. } | + Ternary { .. } | + InsertLane { .. } | + ExtractLane { .. } | + IntCompare { .. } | + IntCompareImm { .. } | + FloatCompare { .. } | + HeapLoad { .. } | + HeapStore { .. } | + Load { .. } | + Store { .. } | + RegMove { .. } => {} } Ok(()) @@ -627,14 +627,14 @@ impl<'a> Verifier<'a> { got_succs.extend(cfg.get_successors(ebb)); let missing_succs: Vec = expected_succs.difference(&got_succs).cloned().collect(); - if missing_succs.len() != 0 { + if !missing_succs.is_empty() { return err!(ebb, "cfg lacked the following successor(s) {:?}", missing_succs); } let excess_succs: Vec = got_succs.difference(&expected_succs).cloned().collect(); - if excess_succs.len() != 0 { + if !excess_succs.is_empty() { return err!(ebb, "cfg had unexpected successor(s) {:?}", excess_succs); } @@ -642,14 +642,14 @@ impl<'a> Verifier<'a> { got_preds.extend(cfg.get_predecessors(ebb).iter().map(|&(_, inst)| inst)); let missing_preds: Vec = expected_preds.difference(&got_preds).cloned().collect(); - if missing_preds.len() != 0 { + if !missing_preds.is_empty() { return err!(ebb, "cfg lacked the following predecessor(s) {:?}", missing_preds); } let excess_preds: Vec = got_preds.difference(&expected_preds).cloned().collect(); - if excess_preds.len() != 0 { + if !excess_preds.is_empty() { return err!(ebb, "cfg had unexpected predecessor(s) {:?}", excess_preds); } diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs index 6dcbdd2620..21d9049fee 100644 --- a/lib/filecheck/src/checker.rs +++ b/lib/filecheck/src/checker.rs @@ -66,7 +66,7 @@ impl Directive { return Err(Error::Syntax(format!("invalid variable name in regex: {}", rest))); } let var = rest[0..varlen].to_string(); - if !rest[varlen..].starts_with("=") { + if !rest[varlen..].starts_with('=') { return Err(Error::Syntax(format!("expected '=' after variable '{}' in regex: {}", var, rest))); @@ -196,7 +196,7 @@ impl Checker { // Check if `pat` matches in `range`. state.recorder.directive(dct_idx); if let Some((match_begin, match_end)) = state.match_positive(pat, range)? { - if let &Directive::Unordered(_) = dct { + if let Directive::Unordered(_) = *dct { // This was an unordered unordered match. // Keep track of the largest matched position, but leave `last_ordered` alone. state.max_match = max(state.max_match, match_end); @@ -231,7 +231,7 @@ impl Checker { // Verify any pending `not:` directives after the last ordered directive. for (not_idx, not_begin, rx) in nots.drain(..) { state.recorder.directive(not_idx); - if let Some(_) = rx.find(&text[not_begin..]) { + if rx.find(&text[not_begin..]).is_some() { // Matched `not:` pattern. // TODO: Use matched range for an error message. return Ok(false); diff --git a/lib/filecheck/src/lib.rs b/lib/filecheck/src/lib.rs index 66fe6036ab..8cca73736b 100644 --- a/lib/filecheck/src/lib.rs +++ b/lib/filecheck/src/lib.rs @@ -4,8 +4,8 @@ //! A list of directives is typically extracted from a file containing a test case. The test case //! is then run through the program under test, and its output matched against the directives. //! -//! See the [CheckerBuilder](struct.CheckerBuilder.html) and [Checker](struct.Checker.html) types -//! for the main library API. +//! See the [`CheckerBuilder`](struct.CheckerBuilder.html) and [`Checker`](struct.Checker.html) +//! types for the main library API. //! //! # Directives //! diff --git a/lib/reader/src/isaspec.rs b/lib/reader/src/isaspec.rs index b7884bc42c..3715f4ed6b 100644 --- a/lib/reader/src/isaspec.rs +++ b/lib/reader/src/isaspec.rs @@ -25,7 +25,7 @@ pub enum IsaSpec { impl IsaSpec { /// If the `IsaSpec` contains exactly 1 `TargetIsa` we return a reference to it pub fn unique_isa(&self) -> Option<&TargetIsa> { - if let &IsaSpec::Some(ref isa_vec) = self { + if let IsaSpec::Some(ref isa_vec) = *self { if isa_vec.len() == 1 { return Some(&*isa_vec[0]); } diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index bbbb023490..d4392a5bd9 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -171,7 +171,7 @@ impl<'a> Lexer<'a> { // Scan a single-char token. fn scan_char(&mut self, tok: Token<'a>) -> Result, LocatedError> { - assert!(self.lookahead != None); + assert_ne!(self.lookahead, None); let loc = self.loc(); self.next_ch(); token(tok, loc) @@ -184,7 +184,7 @@ impl<'a> Lexer<'a> { -> Result, LocatedError> { let loc = self.loc(); for _ in 0..count { - assert!(self.lookahead != None); + assert_ne!(self.lookahead, None); self.next_ch(); } token(tok, loc) @@ -206,7 +206,7 @@ impl<'a> Lexer<'a> { fn scan_comment(&mut self) -> Result, LocatedError> { let loc = self.loc(); let text = self.rest_of_line(); - return token(Token::Comment(text), loc); + token(Token::Comment(text), loc) } // Scan a number token which can represent either an integer or floating point number. @@ -305,8 +305,8 @@ impl<'a> Lexer<'a> { // decoded token. fn numbered_entity(prefix: &str, number: u32) -> Option> { match prefix { - "v" => Value::with_number(number).map(|v| Token::Value(v)), - "ebb" => Ebb::with_number(number).map(|ebb| Token::Ebb(ebb)), + "v" => Value::with_number(number).map(Token::Value), + "ebb" => Ebb::with_number(number).map(Token::Ebb), "ss" => Some(Token::StackSlot(number)), "jt" => Some(Token::JumpTable(number)), "fn" => Some(Token::FuncRef(number)), @@ -339,7 +339,7 @@ impl<'a> Lexer<'a> { }; if is_vector { if number <= u16::MAX as u32 { - base_type.by(number as u16).map(|t| Token::Type(t)) + base_type.by(number as u16).map(Token::Type) } else { None } @@ -352,7 +352,7 @@ impl<'a> Lexer<'a> { let loc = self.loc(); let begin = self.pos + 1; - assert!(self.lookahead == Some('%')); + assert_eq!(self.lookahead, Some('%')); while let Some(c) = self.next_ch() { if !(c.is_ascii() && c.is_alphanumeric() || c == '_') { @@ -368,7 +368,7 @@ impl<'a> Lexer<'a> { let loc = self.loc(); let begin = self.pos + 1; - assert!(self.lookahead == Some('#')); + assert_eq!(self.lookahead, Some('#')); while let Some(c) = self.next_ch() { if !char::is_digit(c, 16) { diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index bc8ebcfa0d..3745bd849e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -282,7 +282,7 @@ impl<'a> Parser<'a> { None => break, } } - return self.lookahead; + self.lookahead } // Begin gathering comments associated with `entity`. @@ -397,7 +397,7 @@ impl<'a> Parser<'a> { fn error(&self, message: &str) -> Error { Error { - location: self.loc.clone(), + location: self.loc, message: message.to_string(), } } @@ -1066,7 +1066,7 @@ impl<'a> Parser<'a> { self.consume(); self.parse_instruction(results, encoding, result_locations, ctx, ebb)?; } - _ if results.len() != 0 => return err!(self.loc, "expected -> or ="), + _ if !results.is_empty() => return err!(self.loc, "expected -> or ="), _ => self.parse_instruction(results, encoding, result_locations, ctx, ebb)?, } } diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index d905cdb9ab..d590a5220f 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -208,7 +208,7 @@ impl MutableSourceMap for SourceMap { } fn def_entity(&mut self, entity: AnyEntity, loc: &Location) -> Result<()> { - if self.locations.insert(entity, loc.clone()).is_some() { + if self.locations.insert(entity, *loc).is_some() { err!(loc, "duplicate entity: {}", entity) } else { Ok(()) From b4e785d0f57ddb12d17d22d667a42c53461cb613 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Jun 2017 10:07:23 -0700 Subject: [PATCH 808/968] Move EntityRef and entity_impl! into a new module. The EntityRef trait is used by more than just the EntityMap now, so it should live in its own module. Also move the entity_impl! macro into the new module so it can be used for defining new entity references anywhere. --- lib/cretonne/src/entity_list.rs | 5 +-- lib/cretonne/src/entity_map.rs | 19 ++------- lib/cretonne/src/entity_ref.rs | 51 ++++++++++++++++++++++ lib/cretonne/src/ir/entities.rs | 59 +++++--------------------- lib/cretonne/src/ir/jumptable.rs | 2 +- lib/cretonne/src/ir/layout.rs | 2 +- lib/cretonne/src/ir/progpoint.rs | 4 +- lib/cretonne/src/isa/registers.rs | 2 +- lib/cretonne/src/lib.rs | 2 + lib/cretonne/src/loop_analysis.rs | 28 +++--------- lib/cretonne/src/regalloc/diversion.rs | 2 +- lib/cretonne/src/regalloc/liverange.rs | 2 +- lib/cretonne/src/regalloc/solver.rs | 2 +- lib/cretonne/src/sparse_map.rs | 5 ++- 14 files changed, 85 insertions(+), 100 deletions(-) create mode 100644 lib/cretonne/src/entity_ref.rs diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs index 1ccddc3aed..4b535e3f56 100644 --- a/lib/cretonne/src/entity_list.rs +++ b/lib/cretonne/src/entity_list.rs @@ -46,12 +46,11 @@ //! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is //! reserved for the empty list which isn't allocated in the vector. +use entity_ref::EntityRef; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::mem; -use entity_map::EntityRef; - /// A small list of entity references allocated from a pool. /// /// All of the list methods that take a pool reference must be given the same pool reference every @@ -484,7 +483,7 @@ mod tests { use super::*; use super::{sclass_size, sclass_for_length}; use ir::Inst; - use entity_map::EntityRef; + use entity_ref::EntityRef; #[test] fn size_classes() { diff --git a/lib/cretonne/src/entity_map.rs b/lib/cretonne/src/entity_map.rs index 8227a19fb6..be73b790e1 100644 --- a/lib/cretonne/src/entity_map.rs +++ b/lib/cretonne/src/entity_map.rs @@ -1,8 +1,7 @@ //! Densely numbered entity references as mapping keys. //! -//! This module defines an `EntityRef` trait that should be implemented by reference types wrapping -//! a small integer index. The `EntityMap` data structure uses the dense index space to implement a -//! map with a vector. There are primary and secondary entity maps: +//! The `EntityMap` data structure uses the dense index space to implement a map with a vector. +//! There are primary and secondary entity maps: //! //! - A *primary* `EntityMap` contains the main definition of an entity, and it can be used to //! allocate new entity references with the `push` method. The values stores in a primary map @@ -10,22 +9,10 @@ //! - A *secondary* `EntityMap` contains additional data about entities kept in a primary map. The //! values need to implement `Clone + Default` traits so the map can be grown with `ensure`. -use std::vec::Vec; -use std::default::Default; +use entity_ref::EntityRef; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; -/// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key -/// of an `EntityMap`. -pub trait EntityRef: Copy + Eq { - /// Create a new entity reference from a small integer. - /// This should crash if the requested index is not representable. - fn new(usize) -> Self; - - /// Get the index that was used to create this entity reference. - fn index(self) -> usize; -} - /// A mapping `K -> V` for densely indexed entity references. #[derive(Debug, Clone)] pub struct EntityMap diff --git a/lib/cretonne/src/entity_ref.rs b/lib/cretonne/src/entity_ref.rs new file mode 100644 index 0000000000..9990b521f0 --- /dev/null +++ b/lib/cretonne/src/entity_ref.rs @@ -0,0 +1,51 @@ +//! Densely numbered entity references as mapping keys. +//! +//! This module defines an `EntityRef` trait that should be implemented by reference types wrapping +//! a small integer index. + +/// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key +/// of an `EntityMap` or `SparseMap`. +pub trait EntityRef: Copy + Eq { + /// Create a new entity reference from a small integer. + /// This should crash if the requested index is not representable. + fn new(usize) -> Self; + + /// Get the index that was used to create this entity reference. + fn index(self) -> usize; +} + +/// Macro which provides the common implementation of a 32-bit entity reference. +#[macro_export] +macro_rules! entity_impl { + // Basic traits. + ($entity:ident) => { + impl $crate::entity_ref::EntityRef for $entity { + fn new(index: usize) -> Self { + assert!(index < (::std::u32::MAX as usize)); + $entity(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } + } + + impl $crate::packed_option::ReservedValue for $entity { + fn reserved_value() -> $entity { + $entity(::std::u32::MAX) + } + } + }; + + // Include basic `Display` impl using the given display prefix. + // Display an `Ebb` reference as "ebb12". + ($entity:ident, $display_prefix:expr) => { + entity_impl!($entity); + + impl ::std::fmt::Display for $entity { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{}{}", $display_prefix, self.0) + } + } + } +} diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 04b54e0488..98a29c7e61 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -19,46 +19,9 @@ //! The entity references all implement the `Display` trait in a way that matches the textual IL //! format. -use entity_map::EntityRef; -use packed_option::ReservedValue; -use std::fmt::{self, Display, Formatter}; +use std::fmt; use std::u32; -// Implement the common traits for a 32-bit entity reference. -macro_rules! entity_impl { - // Basic traits. - ($entity:ident) => { - impl EntityRef for $entity { - fn new(index: usize) -> Self { - assert!(index < (u32::MAX as usize)); - $entity(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } - } - - impl ReservedValue for $entity { - fn reserved_value() -> $entity { - $entity(u32::MAX) - } - } - }; - - // Include basic `Display` impl using the given display prefix. - // Display an `Ebb` reference as "ebb12". - ($entity:ident, $display_prefix:expr) => { - entity_impl!($entity); - - impl Display for $entity { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "{}{}", $display_prefix, self.0) - } - } - } -} - /// An opaque reference to an extended basic block in a function. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct Ebb(u32); @@ -138,17 +101,17 @@ pub enum AnyEntity { SigRef(SigRef), } -impl Display for AnyEntity { - fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { +impl fmt::Display for AnyEntity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - AnyEntity::Function => write!(fmt, "function"), - AnyEntity::Ebb(r) => r.fmt(fmt), - AnyEntity::Inst(r) => r.fmt(fmt), - AnyEntity::Value(r) => r.fmt(fmt), - AnyEntity::StackSlot(r) => r.fmt(fmt), - AnyEntity::JumpTable(r) => r.fmt(fmt), - AnyEntity::FuncRef(r) => r.fmt(fmt), - AnyEntity::SigRef(r) => r.fmt(fmt), + AnyEntity::Function => write!(f, "function"), + AnyEntity::Ebb(r) => r.fmt(f), + AnyEntity::Inst(r) => r.fmt(f), + AnyEntity::Value(r) => r.fmt(f), + AnyEntity::StackSlot(r) => r.fmt(f), + AnyEntity::JumpTable(r) => r.fmt(f), + AnyEntity::FuncRef(r) => r.fmt(f), + AnyEntity::SigRef(r) => r.fmt(f), } } } diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index 24dad02056..aaaa694a50 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -122,7 +122,7 @@ impl Display for JumpTableData { mod tests { use super::JumpTableData; use ir::Ebb; - use entity_map::EntityRef; + use entity_ref::EntityRef; #[test] fn empty() { diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 3d569adcc6..0fa48edc6f 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -982,7 +982,7 @@ impl<'f> Cursor<'f> { #[cfg(test)] mod tests { use super::{Layout, Cursor, CursorPosition}; - use entity_map::EntityRef; + use entity_ref::EntityRef; use ir::{Ebb, Inst, ProgramOrder}; use std::cmp::Ordering; diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index 3134a53603..373bc382ab 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -1,6 +1,6 @@ //! Program points. -use entity_map::EntityRef; +use entity_ref::EntityRef; use ir::{Ebb, Inst, ValueDef}; use std::fmt; use std::u32; @@ -122,7 +122,7 @@ pub trait ProgramOrder { #[cfg(test)] mod tests { use super::*; - use entity_map::EntityRef; + use entity_ref::EntityRef; use ir::{Inst, Ebb}; #[test] diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs index 5549e98157..2823fbea37 100644 --- a/lib/cretonne/src/isa/registers.rs +++ b/lib/cretonne/src/isa/registers.rs @@ -1,6 +1,6 @@ //! Data structures describing the registers in an ISA. -use entity_map::EntityRef; +use entity_ref::EntityRef; use std::fmt; /// Register units are the smallest units of register allocation. diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 6ac2f7993b..09bdfc9302 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -12,6 +12,8 @@ pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); #[macro_use] pub mod dbg; +#[macro_use] +pub mod entity_ref; pub mod binemit; pub mod dominator_tree; diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs index 5ed5f8a4d8..d0a9d505bd 100644 --- a/lib/cretonne/src/loop_analysis.rs +++ b/lib/cretonne/src/loop_analysis.rs @@ -1,34 +1,16 @@ //! A loop analysis represented as mappings of loops to their header Ebb //! and parent in the loop tree. -use ir::{Function, Ebb, Layout}; -use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; -use entity_map::{EntityMap, PrimaryEntityData}; -use packed_option::{PackedOption, ReservedValue}; -use entity_map::{EntityRef, Keys}; -use std::u32; +use entity_map::{EntityMap, PrimaryEntityData, Keys}; +use flowgraph::ControlFlowGraph; +use ir::{Function, Ebb, Layout}; +use packed_option::PackedOption; /// A opaque reference to a code loop. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct Loop(u32); -impl EntityRef for Loop { - fn new(index: usize) -> Self { - assert!(index < (u32::MAX as usize)); - Loop(index as u32) - } - - fn index(self) -> usize { - self.0 as usize - } -} - -impl ReservedValue for Loop { - fn reserved_value() -> Loop { - Loop(u32::MAX) - } -} - +entity_impl!(Loop, "loop"); /// Loop tree information for a single function. /// diff --git a/lib/cretonne/src/regalloc/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs index 89decad82f..e8e8d3d190 100644 --- a/lib/cretonne/src/regalloc/diversion.rs +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -89,7 +89,7 @@ impl RegDiversions { mod tests { use super::*; use ir::Value; - use entity_map::EntityRef; + use entity_ref::EntityRef; #[test] fn inserts() { diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 1cabd113dd..9866fabb20 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -394,7 +394,7 @@ impl SparseMapValue for LiveRange { mod tests { use super::LiveRange; use ir::{Inst, Ebb, Value}; - use entity_map::EntityRef; + use entity_ref::EntityRef; use ir::{ProgramOrder, ExpandedProgramPoint}; use std::cmp::Ordering; diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index d94cf3e72d..b2e73bf91f 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -675,7 +675,7 @@ impl Solver { #[cfg(test)] mod tests { - use entity_map::EntityRef; + use entity_ref::EntityRef; use ir::Value; use isa::{TargetIsa, RegClass, RegUnit}; use regalloc::AllocatableSet; diff --git a/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs index ad49ac49fa..b1e844b618 100644 --- a/lib/cretonne/src/sparse_map.rs +++ b/lib/cretonne/src/sparse_map.rs @@ -35,7 +35,8 @@ //! - `SparseMap` requires the values to implement `SparseMapValue` which means that they must //! contain their own key. -use entity_map::{EntityRef, EntityMap}; +use entity_map::EntityMap; +use entity_ref::EntityRef; use std::mem; use std::slice; use std::u32; @@ -215,7 +216,7 @@ pub type SparseSet = SparseMap; #[cfg(test)] mod tests { use super::*; - use entity_map::EntityRef; + use entity_ref::EntityRef; use ir::Inst; // Mock key-value object for testing. From e83e2ccf173ed6b007049e888bbaee45d2af9cd3 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 22 Jun 2017 12:01:32 -0700 Subject: [PATCH 809/968] Documentation fixes (#103) * Clarify that extended basic blocks are abbreviated as EBB. * Fix typo. * Fix a typo. * Fix typos. * Use the same phrase to indicate scalar-only as other places in the doc. * Mention that `band_imm` and friends are scalar-only. And mention that they're equivalent to their respective non-immediate-form counterparts. --- docs/compare-llvm.rst | 4 ++-- docs/langref.rst | 8 ++++---- docs/regalloc.rst | 4 ++-- docs/testing.rst | 2 +- lib/cretonne/meta/base/instructions.py | 18 +++++++++++++++++- lib/reader/src/parser.rs | 2 +- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/compare-llvm.rst b/docs/compare-llvm.rst index dc19a64983..03b82dc7f7 100644 --- a/docs/compare-llvm.rst +++ b/docs/compare-llvm.rst @@ -152,7 +152,7 @@ can hold. address type too. - SIMD vector types are limited to a power-of-two number of vector lanes up to 256. LLVM allows an arbitrary number of SIMD lanes. -- Cretonne has no aggregrate types. LLVM has named and anonymous struct types as +- Cretonne has no aggregate types. LLVM has named and anonymous struct types as well as array types. Cretonne has multiple boolean types, whereas LLVM simply uses `i1`. The sized @@ -160,7 +160,7 @@ Cretonne boolean types are used to represent SIMD vector masks like ``b32x4`` where each lane is either all 0 or all 1 bits. Cretonne instructions and function calls can return multiple result values. LLVM -instead models this by returning a single value of an aggregrate type. +instead models this by returning a single value of an aggregate type. Instruction set --------------- diff --git a/docs/langref.rst b/docs/langref.rst index ac20591c36..7f8d3e038f 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -40,9 +40,9 @@ that can be referenced inside the function. In the example above, the preamble declares a single local variable, ``ss1``. After the preamble follows the :term:`function body` which consists of -:term:`extended basic block`\s, the first of which is the :term:`entry block`. -Every EBB ends with a :term:`terminator instruction`, so execution can never -fall through to the next EBB without an explicit branch. +:term:`extended basic block`\s (EBBs), the first of which is the +:term:`entry block`. Every EBB ends with a :term:`terminator instruction`, so +execution can never fall through to the next EBB without an explicit branch. A ``.cton`` file consists of a sequence of independent function definitions: @@ -253,7 +253,7 @@ indicate the different kinds of immediate operands on an instruction. A signed 32-bit immediate address offset. In the textual format, :type:`offset32` immediates always have an explicit - sign, and a 0 offset may beomitted. + sign, and a 0 offset may be omitted. .. type:: ieee32 diff --git a/docs/regalloc.rst b/docs/regalloc.rst index 75477e7375..8c73df41ec 100644 --- a/docs/regalloc.rst +++ b/docs/regalloc.rst @@ -55,12 +55,12 @@ EBB argument fixup The contract between the spilling and coloring phases is that the number of values in registers never exceeds the number of available registers. This -sounds simple enough in theory, but in pratice there are some complications. +sounds simple enough in theory, but in practice there are some complications. Real-world complications to SSA coloring ---------------------------------------- -In practice, instruction set architectures don't have "K interchangable +In practice, instruction set architectures don't have "K interchangeable registers", and register pressure can't be measured with a single number. There are complications: diff --git a/docs/testing.rst b/docs/testing.rst index b72ace08d3..edc8ee6570 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -100,7 +100,7 @@ of input functions in the :doc:`Cretonne textual intermediate language The available test commands are described below. -Many test comands only make sense in the context of a target instruction set +Many test commands only make sense in the context of a target instruction set architecture. These tests require one or more ISA specifications in the test header: diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index b4458ec585..dc9accf1cc 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -713,7 +713,8 @@ imul_imm = Instruction( 'imul_imm', """ Integer multiplication by immediate constant. - Polymorphic over all scalar integer types. + Polymorphic over all scalar integer types, but does not support vector + types. """, ins=(x, Y), outs=a) @@ -912,18 +913,33 @@ a = Operand('a', iB) band_imm = Instruction( 'band_imm', """ Bitwise and with immediate. + + Same as :inst:`band`, but one operand is an immediate constant. + + Polymorphic over all scalar integer types, but does not support vector + types. """, ins=(x, Y), outs=a) bor_imm = Instruction( 'bor_imm', """ Bitwise or with immediate. + + Same as :inst:`bor`, but one operand is an immediate constant. + + Polymorphic over all scalar integer types, but does not support vector + types. """, ins=(x, Y), outs=a) bxor_imm = Instruction( 'bxor_imm', """ Bitwise xor with immediate. + + Same as :inst:`bxor`, but one operand is an immediate constant. + + Polymorphic over all scalar integer types, but does not support vector + types. """, ins=(x, Y), outs=a) diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 3745bd849e..ae88e09873 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -625,7 +625,7 @@ impl<'a> Parser<'a> { // Apply the ISA-specific settings to `isa_builder`. isaspec::parse_options(words, &mut isa_builder, &self.loc)?; - // Construct a trait object with the aggregrate settings. + // Construct a trait object with the aggregate settings. isas.push(isa_builder.finish(settings::Flags::new(&flag_builder))); } _ => break, From 9e02b9818f8300fdffd311caaefcd0ac116d1cb1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 22 Jun 2017 12:11:54 -0700 Subject: [PATCH 810/968] Encode iconst.i32 for RISC-V. For large constants with the low 12 bits clear, we already have the "lui" encoding. Add "addi %x0" encodings for signed 12-bit constants. --- filetests/isa/riscv/binary32.cton | 3 +++ filetests/isa/riscv/verify-encoding.cton | 2 +- lib/cretonne/meta/isa/riscv/encodings.py | 7 ++++++- lib/cretonne/meta/isa/riscv/recipes.py | 5 +++++ lib/cretonne/src/isa/riscv/binemit.rs | 12 ++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 209d9922c0..6575609267 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -77,6 +77,9 @@ ebb0(v9999: i32): ; lui [-,%x7] v140 = iconst.i32 0x12345000 ; bin: 123453b7 [-,%x16] v141 = iconst.i32 0xffffffff_fedcb000 ; bin: fedcb837 + ; addi + [-,%x7] v142 = iconst.i32 1000 ; bin: 3e800393 + [-,%x16] v143 = iconst.i32 -905 ; bin: c7700813 ; Copies alias to iadd_imm. [-,%x7] v150 = copy v1 ; bin: 00050393 diff --git a/filetests/isa/riscv/verify-encoding.cton b/filetests/isa/riscv/verify-encoding.cton index 52b8d6d79c..b88fdc6402 100644 --- a/filetests/isa/riscv/verify-encoding.cton +++ b/filetests/isa/riscv/verify-encoding.cton @@ -6,7 +6,7 @@ function %RV32I(i32 link [%x1]) -> i32 link [%x1] { ebb0(v9999: i32): ; iconst.i32 needs legalizing, so it should throw a - [R#0,-] v1 = iconst.i32 1 ; error: Instruction failed to re-encode + [R#0,-] v1 = iconst.i32 0xf0f0f0f0f0 ; error: Instruction failed to re-encode return v9999 } diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 374cc951af..d8afbab2cf 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -7,7 +7,7 @@ from base.immediates import intcc from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL from .recipes import LOAD, STORE -from .recipes import R, Rshamt, Ricmp, I, Iicmp, Iret, Icall, Icopy +from .recipes import R, Rshamt, Ricmp, I, Iz, Iicmp, Iret, Icall, Icopy from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi from .settings import use_m from cdsl.ast import Var @@ -40,6 +40,11 @@ RV64.enc(base.isub.i32, R, OP32(0b000, 0b0100000)) # There are no andiw/oriw/xoriw variations. RV64.enc(base.iadd_imm.i32, I, OPIMM32(0b000)) +# Use iadd_imm with %x0 to materialize constants. +RV32.enc(base.iconst.i32, Iz, OPIMM(0b000)) +RV64.enc(base.iconst.i32, Iz, OPIMM(0b000)) +RV64.enc(base.iconst.i64, Iz, OPIMM(0b000)) + # Dynamic shifts have the same masking semantics as the cton base instructions. for inst, inst_imm, f3, f7 in [ (base.ishl, base.ishl_imm, 0b001, 0b0000000), diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 60a67fbc00..2ea7f35597 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -104,6 +104,11 @@ I = EncRecipe( 'I', BinaryImm, size=4, ins=GPR, outs=GPR, instp=IsSignedInt(BinaryImm.imm, 12)) +# I-type instruction with a hardcoded %x0 rs1. +Iz = EncRecipe( + 'Iz', UnaryImm, size=4, ins=(), outs=GPR, + instp=IsSignedInt(UnaryImm.imm, 12)) + # I-type encoding of an integer comparison. Iicmp = EncRecipe( 'Iicmp', IntCompareImm, size=4, ins=GPR, outs=GPR, diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 0c25f9ec85..21638980b2 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -160,6 +160,18 @@ fn recipe_i(func: &Function, inst: Inst, sink: &mut CS) { } } +fn recipe_iz(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::UnaryImm { imm, .. } = func.dfg[inst] { + put_i(func.encodings[inst].bits(), + 0, + imm.into(), + func.locations[func.dfg.first_result(inst)].unwrap_reg(), + sink); + } else { + panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); + } +} + fn recipe_iicmp(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::IntCompareImm { arg, imm, .. } = func.dfg[inst] { put_i(func.encodings[inst].bits(), From e094389f121a5ce87dd30c8eec8b8ae824835285 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 22 Jun 2017 14:34:21 -0700 Subject: [PATCH 811/968] Add a simple_gvn test that includes some basic control flow. --- filetests/simple_gvn/basic.cton | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/filetests/simple_gvn/basic.cton b/filetests/simple_gvn/basic.cton index c76ec12b88..6ff45d1aef 100644 --- a/filetests/simple_gvn/basic.cton +++ b/filetests/simple_gvn/basic.cton @@ -19,3 +19,23 @@ ebb0(v0: i32, v1: i32): ; check: v6 = iadd $v4, $v4 return v6 } + +function %redundancies_on_some_paths(i32, i32, i32) -> i32 { +ebb0(v0: i32, v1: i32, v2: i32): + v3 = iadd v0, v1 + brz v3, ebb1 + v4 = iadd v0, v1 + jump ebb2(v4) +; check: jump ebb2(v3) + +ebb1: + v5 = iadd v0, v1 + jump ebb2(v5) +; check: jump ebb2(v3) + +ebb2(v6: i32): + v7 = iadd v0, v1 + v8 = iadd v6, v7 +; check: v8 = iadd v6, v3 + return v8 +} From 268e8e3114b5a1b3ca66ec8f2b64c0a44e7fbe22 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Jun 2017 10:03:39 -0700 Subject: [PATCH 812/968] Add two interference checking methods to LiveInterval. The overlaps_def() method tests if a definition would conflict with the live range. The reaches_use() method tests if a live range is live at an instruction. --- lib/cretonne/src/regalloc/liverange.rs | 36 +++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 9866fabb20..95dfb49f2e 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -108,7 +108,7 @@ //! use std::cmp::Ordering; -use ir::{Inst, Ebb, Value, ProgramPoint, ProgramOrder}; +use ir::{Inst, Ebb, Value, ProgramPoint, ExpandedProgramPoint, ProgramOrder}; use regalloc::affinity::Affinity; use sparse_map::SparseMapValue; @@ -381,6 +381,40 @@ impl LiveRange { pub fn liveins(&self) -> &[Interval] { &self.liveins } + + /// Check if this live range overlaps a definition in `ebb`. + pub fn overlaps_def(&self, def: ExpandedProgramPoint, ebb: Ebb, order: &PO) -> bool + where PO: ProgramOrder + { + // Check for an overlap with the local range. + if order.cmp(def, self.def_begin) != Ordering::Less && + order.cmp(def, self.def_end) == Ordering::Less { + return true; + } + + // Check for an overlap with a live-in range. + match self.livein_local_end(ebb, order) { + Some(inst) => order.cmp(def, inst) == Ordering::Less, + None => false, + } + } + + /// Check if this live range reaches a use at `inst` in `ebb`. + pub fn reaches_use(&self, user: Inst, ebb: Ebb, order: &PO) -> bool + where PO: ProgramOrder + { + // Check for an overlap with the local range. + if order.cmp(user, self.def_begin) == Ordering::Greater && + order.cmp(user, self.def_end) != Ordering::Greater { + return true; + } + + // Check for an overlap with a live-in range. + match self.livein_local_end(ebb, order) { + Some(inst) => order.cmp(user, inst) != Ordering::Greater, + None => false, + } + } } /// Allow a `LiveRange` to be stored in a `SparseMap` indexed by values. From 2a9b8162c8bbacdf01af68de79f9b7102e9e6244 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 22 Jun 2017 08:44:08 -0700 Subject: [PATCH 813/968] Implement Display and Debug for the program point types. --- lib/cretonne/src/ir/progpoint.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index 373bc382ab..7adeb43c5c 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -82,15 +82,28 @@ impl From for ExpandedProgramPoint { } } -impl fmt::Display for ProgramPoint { +impl fmt::Display for ExpandedProgramPoint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match (*self).into() { + match *self { ExpandedProgramPoint::Inst(x) => write!(f, "{}", x), ExpandedProgramPoint::Ebb(x) => write!(f, "{}", x), } } } +impl fmt::Display for ProgramPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let epp: ExpandedProgramPoint = (*self).into(); + epp.fmt(f) + } +} + +impl fmt::Debug for ExpandedProgramPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ExpandedProgramPoint({})", self) + } +} + impl fmt::Debug for ProgramPoint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ProgramPoint({})", self) From 0f2459dd21bd8941158b76da47d7e58e33ea9868 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 22 Jun 2017 09:31:32 -0700 Subject: [PATCH 814/968] Skip ghost instructions when coloring. Ghost instructions don't have an encoding, and don't appear in the output. The values they define do not need to be assigned to registers, so they can be skipped. --- lib/cretonne/src/regalloc/coloring.rs | 40 +++++++++++---------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index c71218e7cc..56244bcc3c 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -31,18 +31,18 @@ //! defined by the instruction and only consider the colors of other values that are live at the //! instruction. -use entity_map::EntityMap; use dominator_tree::DominatorTree; +use entity_map::EntityMap; use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, ValueLocations}; use ir::{InstBuilder, Signature, ArgumentType, ArgumentLoc}; -use isa::{TargetIsa, Encoding, EncInfo, OperandConstraint, ConstraintKind}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; +use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind}; +use regalloc::RegDiversions; use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; use regalloc::solver::Solver; -use regalloc::RegDiversions; use topo_order::TopoOrder; @@ -138,19 +138,18 @@ impl<'a> Context<'a> { let mut pos = Cursor::new(&mut func.layout); pos.goto_top(ebb); while let Some(inst) = pos.next_inst() { - let encoding = func.encodings[inst]; - assert!(encoding.is_legal(), "Illegal: {}", func.dfg[inst].opcode()); - self.visit_inst(inst, - encoding, - &mut pos, - &mut func.dfg, - tracker, - &mut regs, - &mut func.locations, - &func.signature); - tracker.drop_dead(inst); + if let Some(constraints) = self.encinfo.operand_constraints(func.encodings[inst]) { + self.visit_inst(inst, + constraints, + &mut pos, + &mut func.dfg, + tracker, + &mut regs, + &mut func.locations, + &func.signature); + tracker.drop_dead(inst); + } } - } /// Visit the `ebb` header. @@ -304,21 +303,14 @@ impl<'a> Context<'a> { /// or killed values from the set. fn visit_inst(&mut self, inst: Inst, - encoding: Encoding, + constraints: &RecipeConstraints, pos: &mut Cursor, dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, locations: &mut ValueLocations, func_signature: &Signature) { - dbg!("Coloring [{}] {}", - self.encinfo.display(encoding), - dfg.display_inst(inst)); - - // Get the operand constraints for `inst` that we are trying to satisfy. - let constraints = self.encinfo - .operand_constraints(encoding) - .expect("Missing instruction encoding"); + dbg!("Coloring {}", dfg.display_inst(inst)); // Program the solver with register constraints for the input side. self.solver.reset(regs); From 719fc02c79e3324cd5abe88b692c68e22b4c5d12 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 20 Jun 2017 15:17:19 -0700 Subject: [PATCH 815/968] Virtual registers. Add a VirtRegs collection which tracks virtual registers. A virtual register is a set of related SSA values whose live ranges don't interfere. It is advantageous to use the same register or spill slot for al the values in a virtual register. It reduces copies for EBB arguments. --- lib/cretonne/src/regalloc/context.rs | 5 + lib/cretonne/src/regalloc/mod.rs | 1 + lib/cretonne/src/regalloc/virtregs.rs | 135 ++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 lib/cretonne/src/regalloc/virtregs.rs diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index b404602a34..ea2cdcd924 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -13,6 +13,7 @@ use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; use regalloc::reload::Reload; use regalloc::spilling::Spilling; +use regalloc::virtregs::VirtRegs; use result::CtonResult; use topo_order::TopoOrder; use verifier::{verify_context, verify_liveness}; @@ -20,6 +21,7 @@ use verifier::{verify_context, verify_liveness}; /// Persistent memory allocations for register allocation. pub struct Context { liveness: Liveness, + virtregs: VirtRegs, topo: TopoOrder, tracker: LiveValueTracker, spilling: Spilling, @@ -35,6 +37,7 @@ impl Context { pub fn new() -> Context { Context { liveness: Liveness::new(), + virtregs: VirtRegs::new(), topo: TopoOrder::new(), tracker: LiveValueTracker::new(), spilling: Spilling::new(), @@ -54,6 +57,8 @@ impl Context { domtree: &DominatorTree) -> CtonResult { // `Liveness` and `Coloring` are self-clearing. + self.virtregs.clear(); + // Tracker state (dominator live sets) is actually reused between the spilling and coloring // phases. self.tracker.clear(); diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 59eb341eb0..ca4624b45b 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -15,6 +15,7 @@ mod pressure; mod reload; mod solver; mod spilling; +mod virtregs; pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; diff --git a/lib/cretonne/src/regalloc/virtregs.rs b/lib/cretonne/src/regalloc/virtregs.rs new file mode 100644 index 0000000000..6efe196805 --- /dev/null +++ b/lib/cretonne/src/regalloc/virtregs.rs @@ -0,0 +1,135 @@ +//! Virtual registers. +//! +//! A virtual register is a set of related SSA values whose live ranges don't interfere. If all the +//! values in a virtual register are assigned to the same location, fewer copies will result in the +//! output. +//! +//! A virtual register is typically built by merging together SSA values that are "phi-related" - +//! that is, one value is passed as an EBB argument to a branch and the other is the EBB parameter +//! value itself. +//! +//! If any values in a virtual register are spilled, they will use the same stack slot. This avoids +//! memory-to-memory copies when a spilled value is passed as an EBB argument. + +use entity_list::{EntityList, ListPool}; +use entity_map::{EntityMap, PrimaryEntityData}; +use ir::Value; +use packed_option::PackedOption; +use ref_slice::ref_slice; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] +pub struct VirtReg(u32); +entity_impl!(VirtReg, "vreg"); + +type ValueList = EntityList; +impl PrimaryEntityData for ValueList {} + +/// Collection of virtual registers. +/// +/// Each virtual register is a list of values. Also maintain a map from values to their unique +/// virtual register, if any. +pub struct VirtRegs { + /// Memory pool for the value lists. + pool: ListPool, + + /// The primary table of virtual registers. + /// + /// The list of values ion a virtual register is kept sorted according to the dominator tree's + /// RPO of the value defs. + vregs: EntityMap, + + /// Each value belongs to at most one virtual register. + value_vregs: EntityMap>, +} + +#[allow(dead_code)] +impl VirtRegs { + /// Create a new virtual register collection. + pub fn new() -> VirtRegs { + VirtRegs { + pool: ListPool::new(), + vregs: EntityMap::new(), + value_vregs: EntityMap::new(), + } + } + + /// Clear all virtual registers. + pub fn clear(&mut self) { + self.vregs.clear(); + self.value_vregs.clear(); + self.pool.clear(); + } + + /// Get the virtual register containing `value`, if any. + pub fn get(&self, value: Value) -> Option { + self.value_vregs.get_or_default(value).into() + } + + /// Get the list of values in `vreg`. The values are ordered according to `DomTree::rpo_cmp` of + /// their definition points. + pub fn values(&self, vreg: VirtReg) -> &[Value] { + self.vregs[vreg].as_slice(&self.pool) + } + + /// Get the congruence class of `value`. + /// + /// If `value` belongs to a virtual register, the congruence class is the values of the virtual + /// register. Otherwise it is just the value itself. + pub fn congruence_class<'a, 'b>(&'a self, value: &'b Value) -> &'b [Value] + where 'a: 'b + { + self.get(*value) + .map(|vr| self.values(vr)) + .unwrap_or(ref_slice(value)) + } + + /// Check if `a` and `b` belong to the same congruence class. + pub fn same_class(&self, a: Value, b: Value) -> bool { + match (self.get(a), self.get(b)) { + (Some(va), Some(vb)) => va == vb, + _ => a == b, + } + } + + /// Unify `values` into a single virtual register. + /// + /// The values in the slice can be singletons or they can belong to a virtual register already. + /// If a value belongs to a virtual register, all of the values in that register must be + /// present. + /// + /// The values are assumed to already be in RPO order. + pub fn unify(&mut self, values: &[Value]) -> VirtReg { + // Start by clearing all virtual registers involved. + // Pick a virtual register to reuse (the smallest number) or allocate a new one. + let mut singletons = 0; + let mut cleared = 0; + let vreg = values + .iter() + .filter_map(|&v| { + let vr = self.get(v); + match vr { + None => singletons += 1, + Some(vr) => { + if !self.vregs[vr].is_empty() { + cleared += self.vregs[vr].len(&self.pool); + self.vregs[vr].clear(&mut self.pool); + } + } + } + vr + }) + .min() + .unwrap_or_else(|| self.vregs.push(Default::default())); + + assert_eq!(values.len(), + singletons + cleared, + "Can't unify partial virtual registers"); + + self.vregs[vreg].extend(values.iter().cloned(), &mut self.pool); + for &v in values { + *self.value_vregs.ensure(v) = vreg.into(); + } + + vreg + } +} From cf967642a3ebf099077392367401a72e550d0066 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 21 Jun 2017 09:24:12 -0700 Subject: [PATCH 816/968] Add a coalescing pass to the register allocator. Coalescing means creating virtual registers and transforming the code into conventional SSA form. This means that every value used as a branch argument will belong to the same virtual register as the corresponding EBB argument value. Conventional SSA form makes it easy to avoid memory-memory copies when spilling values, and the virtual registers can be used as hints when picking registers too. This reduces the number of register moves needed for EBB arguments. --- filetests/regalloc/coalesce.cton | 91 ++++ lib/cretonne/src/dbg.rs | 21 + lib/cretonne/src/regalloc/coalescing.rs | 530 ++++++++++++++++++++++++ lib/cretonne/src/regalloc/context.rs | 18 + lib/cretonne/src/regalloc/mod.rs | 1 + 5 files changed, 661 insertions(+) create mode 100644 filetests/regalloc/coalesce.cton create mode 100644 lib/cretonne/src/regalloc/coalescing.rs diff --git a/filetests/regalloc/coalesce.cton b/filetests/regalloc/coalesce.cton new file mode 100644 index 0000000000..8b76c8db6b --- /dev/null +++ b/filetests/regalloc/coalesce.cton @@ -0,0 +1,91 @@ +test regalloc +isa riscv + +; Test the coalescer. +; regex: V=v\d+ +; regex: WS=\s+ + +; This function is already CSSA, so no copies should be inserted. +function %cssa(i32) -> i32 { +ebb0(v0: i32): + ; not: copy + ; v0 is used by the branch and passed as an arg - that's no conflict. + brnz v0, ebb1(v0) + ; v0 is live across the branch above. That's no conflict. + v1 = iadd_imm v0, 7 + jump ebb1(v1) + +ebb1(v10: i32): + v11 = iadd_imm v10, 7 + return v11 +} + +function %trivial(i32) -> i32 { +ebb0(v0: i32): + ; check: $(cp1=$V) = copy $v0 + ; nextln: brnz $v0, $ebb1($cp1) + brnz v0, ebb1(v0) + ; not: copy + v1 = iadd_imm v0, 7 + jump ebb1(v1) + +ebb1(v10: i32): + ; Use v0 in the destination EBB causes a conflict. + v11 = iadd v10, v0 + return v11 +} + +; A value is used as an SSA argument twice in the same branch. +function %dualuse(i32) -> i32 { +ebb0(v0: i32): + ; check: $(cp1=$V) = copy $v0 + ; nextln: brnz $v0, $ebb1($v0, $cp1) + brnz v0, ebb1(v0, v0) + ; not: copy + v1 = iadd_imm v0, 7 + v2 = iadd_imm v1, 56 + jump ebb1(v1, v2) + +ebb1(v10: i32, v11: i32): + v12 = iadd v10, v11 + return v12 +} + +; Interference away from the branch +; The interference can be broken with a copy at either branch. +function %interference(i32) -> i32 { +ebb0(v0: i32): + ; not: copy + brnz v0, ebb1(v0) + v1 = iadd_imm v0, 7 + ; v1 and v0 interfere here: + trapnz v0 + ; check: $(cp1=$V) = copy $v1 + ; nextln: jump $ebb1($cp1) + jump ebb1(v1) + +ebb1(v10: i32): + ; not: copy + v11 = iadd_imm v10, 7 + return v11 +} + +; A loop where one induction variable is used as a backedge argument. +function %fibonacci(i32) -> i32 { +ebb0(v0: i32): + ; not: copy + v1 = iconst.i32 1 + v2 = iconst.i32 2 + jump ebb1(v1, v2) + +ebb1(v10: i32, v11: i32): + ; v11 needs to be isolated because it interferes with v10. + ; check: $ebb1($v10: i32, $(nv11a=$V): i32) + ; check: $v11 = copy $nv11a + v12 = iadd v10, v11 + v13 = icmp ult v12, v0 + ; check: $(nv11b=$V) = copy $v11 + ; nextln: brnz $v13, $ebb1($nv11b, $v12) + brnz v13, ebb1(v11, v12) + return v12 +} diff --git a/lib/cretonne/src/dbg.rs b/lib/cretonne/src/dbg.rs index 06723fa018..dc7793f3d9 100644 --- a/lib/cretonne/src/dbg.rs +++ b/lib/cretonne/src/dbg.rs @@ -13,6 +13,7 @@ use std::ascii::AsciiExt; use std::cell::RefCell; use std::env; use std::ffi::OsStr; +use std::fmt; use std::fs::File; use std::io::{Write, BufWriter}; use std::sync::atomic; @@ -98,3 +99,23 @@ macro_rules! dbg { } } } + +/// Helper for printing lists. +pub struct DisplayList<'a, T>(pub &'a [T]) where T: 'a + fmt::Display; + +impl<'a, T> fmt::Display for DisplayList<'a, T> + where T: 'a + fmt::Display +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0.split_first() { + None => write!(f, "[]"), + Some((first, rest)) => { + write!(f, "[{}", first)?; + for x in rest { + write!(f, ", {}", x)?; + } + write!(f, "]") + } + } + } +} diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs new file mode 100644 index 0000000000..70a20e3897 --- /dev/null +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -0,0 +1,530 @@ +//! Constructing conventional SSA form. +//! +//! Conventional SSA form is a subset of SSA form where any (transitively) phi-related values do +//! not interfere. We construct CSSA by building virtual registers that are as large as possible +//! and inserting copies where necessary such that all values passed to an EBB argument will belong +//! to the same virtual register as the EBB argument value itself. + +use dbg::DisplayList; +use dominator_tree::DominatorTree; +use flowgraph::{ControlFlowGraph, BasicBlock}; +use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; +use ir::{Function, Ebb, Inst, Value, ExpandedProgramPoint}; +use regalloc::affinity::Affinity; +use regalloc::liveness::Liveness; +use regalloc::virtregs::VirtRegs; +use std::cmp::Ordering; +use std::iter::Peekable; +use std::mem; +use isa::{TargetIsa, EncInfo}; + +/// Dominator forest. +/// +/// This is a utility type used for merging virtual registers, where each virtual register is a +/// list of values ordered according to `DomTree::rpo_cmp`. +/// +/// A `DomForest` object is used as a buffer for building virtual registers. It lets you merge two +/// sorted lists of values while checking for interference only whee necessary. +/// +/// The idea of a dominator forest was introduced here: +/// +/// Budimlic, Z., Budimlic, Z., Cooper, K. D., Cooper, K. D., Harvey, T. J., Harvey, T. J., et al. +/// (2002). Fast copy coalescing and live-range identification (Vol. 37, pp. 25–32). ACM. +/// http://doi.org/10.1145/543552.512534 +/// +/// The linear stack representation here: +/// +/// Boissinot, B., Darte, A., & Rastello, F. (2009). Revisiting out-of-SSA translation for +/// correctness, code quality and efficiency. Presented at the Proceedings of the 7th …. +struct DomForest { + // The sequence of values that have been merged so far. In RPO order of their defs. + values: Vec, + + // Stack representing the rightmost edge of the dominator forest so far, ending in the last + // element of `values`. At all times, each element in the stack dominates the next one, and all + // elements dominating the end of `values` are on the stack. + stack: Vec, +} + +/// A node in the dominator forest. +#[derive(Clone, Copy, Debug)] +struct Node { + value: Value, + /// Set identifier. Values in the same set are assumed to be non-interfering. + set: u8, + /// The program point where `value` is defined. + def: ExpandedProgramPoint, +} + +impl Node { + /// Create a node for `value`. + pub fn new(value: Value, set: u8, dfg: &DataFlowGraph) -> Node { + Node { + value, + set, + def: dfg.value_def(value).into(), + } + } +} + +/// Push a node to `stack` and update `stack` so it contains all dominator forest ancestors of +/// the pushed value. +/// + +impl DomForest { + /// Create a new empty dominator forest. + pub fn new() -> DomForest { + DomForest { + values: Vec::new(), + stack: Vec::new(), + } + } + + /// Swap the merged list with `buffer`, leaving the dominator forest empty. + /// + /// This is typically called after a successful merge to extract the merged value list. + pub fn swap(&mut self, buffer: &mut Vec) { + buffer.clear(); + mem::swap(&mut self.values, buffer); + } + + /// Add a single node to the forest. + /// + /// Update the stack so its dominance invariants are preserved. Detect a parent node on the + /// stack which is the closest one dominating the new node. + /// + /// If the pushed node's parent in the dominator forest belongs to a different set, returns + /// `Some(parent)`. + fn push_node(&mut self, node: Node, layout: &Layout, domtree: &DominatorTree) -> Option { + self.values.push(node.value); + + // The stack contains the current sequence of dominating defs. Pop elements until we + // find one that dominates `node`. + while let Some(top) = self.stack.pop() { + if domtree.dominates(top.def, node.def, layout) { + // This is the right insertion spot for `node`. + self.stack.push(top); + self.stack.push(node); + // If the parent value comes from a different set, return it for interference + // checking. If the sets are equal, assume that interference is already handled. + if top.set != node.set { + return Some(top.value); + } else { + return None; + } + } + } + + // No dominators, start a new tree in the forest. + self.stack.push(node); + None + } + + /// Try to merge two sorted sets of values. Each slice must already be sorted and free of any + /// interference. + /// + /// It is permitted for a value to appear in both lists. The merged sequence will only have one + /// copy of the value. + /// + /// If an interference is detected, returns `Err((a, b))` with the two conflicting values form + /// `va` and `vb` respectively. + /// + /// If the merge succeeds, returns `Ok(())`. The merged sequence can be extracted with + /// `swap()`. + pub fn try_merge(&mut self, + va: &[Value], + vb: &[Value], + dfg: &DataFlowGraph, + layout: &Layout, + domtree: &DominatorTree, + liveness: &Liveness) + -> Result<(), (Value, Value)> { + self.stack.clear(); + self.values.clear(); + self.values.reserve(va.len() + vb.len()); + + // Convert the two value lists into a merged sequence of nodes. + let merged = MergedNodes { + a: va.iter().map(|&value| Node::new(value, 0, dfg)).peekable(), + b: vb.iter().map(|&value| Node::new(value, 1, dfg)).peekable(), + layout, + domtree, + }; + for node in merged { + if let Some(parent) = self.push_node(node, layout, domtree) { + // Check if `parent` live range contains `node.def`. + let lr = liveness + .get(parent) + .expect("No live range for parent value"); + if lr.overlaps_def(node.def, layout.pp_ebb(node.def), layout) { + // Interference detected. Get the `(a, b)` order right in the error. + return Err(if node.set == 0 { + (node.value, parent) + } else { + (parent, node.value) + }); + } + } + } + + Ok(()) + } +} + +/// Node-merging iterator. +/// +/// Given two ordered sequences of nodes, yield an ordered sequence containing all of them. +/// Duplicates are removed. +struct MergedNodes<'a, IA, IB> + where IA: Iterator, + IB: Iterator +{ + a: Peekable, + b: Peekable, + layout: &'a Layout, + domtree: &'a DominatorTree, +} + +impl<'a, IA, IB> Iterator for MergedNodes<'a, IA, IB> + where IA: Iterator, + IB: Iterator +{ + type Item = Node; + + fn next(&mut self) -> Option { + let ord = match (self.a.peek(), self.b.peek()) { + (Some(a), Some(b)) => { + // If the two values are defined at the same point, compare value numbers instead + // this is going to cause an interference conflict unless its actually the same + // value appearing in both streams. + self.domtree + .rpo_cmp(a.def, b.def, self.layout) + .then(Ord::cmp(&a.value, &b.value)) + } + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (None, None) => return None, + }; + match ord { + Ordering::Equal => { + // The two iterators produced the same value. Just return the first one. + self.b.next(); + self.a.next() + } + Ordering::Less => self.a.next(), + Ordering::Greater => self.b.next(), + } + } +} + +/// Data structures to be used by the coalescing pass. +pub struct Coalescing { + forest: DomForest, + + // Current set of coalesced values. Kept sorted and interference free. + values: Vec, + + // New values that were created when splitting interferences. + split_values: Vec, +} + +/// One-shot context created once per invocation. +struct Context<'a> { + isa: &'a TargetIsa, + encinfo: EncInfo, + + func: &'a mut Function, + domtree: &'a DominatorTree, + liveness: &'a mut Liveness, + virtregs: &'a mut VirtRegs, + + forest: &'a mut DomForest, + values: &'a mut Vec, + split_values: &'a mut Vec, +} + +impl Coalescing { + /// Create a new coalescing pass. + pub fn new() -> Coalescing { + Coalescing { + forest: DomForest::new(), + values: Vec::new(), + split_values: Vec::new(), + } + + } + + /// Convert `func` to conventional SSA form and build virtual registers in the process. + pub fn conventional_ssa(&mut self, + isa: &TargetIsa, + func: &mut Function, + cfg: &ControlFlowGraph, + domtree: &DominatorTree, + liveness: &mut Liveness, + virtregs: &mut VirtRegs) { + dbg!("Coalescing for:\n{}", func.display(isa)); + let mut context = Context { + isa, + encinfo: isa.encoding_info(), + func, + domtree, + liveness, + virtregs, + forest: &mut self.forest, + values: &mut self.values, + split_values: &mut self.split_values, + }; + + // TODO: The iteration order matters here. We should coalesce in the most important blocks + // first, so they get first pick at forming virtual registers. + for &ebb in domtree.cfg_postorder() { + let preds = cfg.get_predecessors(ebb); + if !preds.is_empty() { + for argnum in 0..context.func.dfg.num_ebb_args(ebb) { + context.coalesce_ebb_arg(ebb, argnum, preds) + } + } + } + } +} + +impl<'a> Context<'a> { + /// Coalesce the `argnum`'th argument to `ebb`. + fn coalesce_ebb_arg(&mut self, ebb: Ebb, argnum: usize, preds: &[BasicBlock]) { + self.split_values.clear(); + let mut succ_val = self.func.dfg.ebb_args(ebb)[argnum]; + dbg!("Processing {}/{}: {}", ebb, argnum, succ_val); + + // We want to merge the virtual register for `succ_val` with the virtual registers for + // the branch arguments in the predecessors. This may not be possible if any live + // ranges interfere, so we can insert copies to break interferences: + // + // pred: + // jump ebb1(v1) + // + // ebb1(v10: i32): + // ... + // + // In the predecessor: + // + // v2 = copy v1 + // jump ebb(v2) + // + // A predecessor copy is always required if the branch argument virtual register is + // live into the successor. + // + // In the successor: + // + // ebb1(v11: i32): + // v10 = copy v11 + // + // A successor copy is always required if the `succ_val` virtual register is live at + // any predecessor branch. + + while let Some(bad_value) = self.try_coalesce(argnum, succ_val, preds) { + dbg!("Isolating interfering value {}", bad_value); + // The bad value has some conflict that can only be reconciled by excluding its + // congruence class from the new virtual register. + // + // Try to catch infinite splitting loops. The values created by splitting should never + // have irreconcilable interferences. + assert!(!self.split_values.contains(&bad_value), + "{} was already isolated", + bad_value); + let split_len = self.split_values.len(); + + // The bad value can be both the successor value and a predecessor value at the same + // time. + if self.virtregs.same_class(bad_value, succ_val) { + succ_val = self.split_succ(ebb, succ_val); + } + + // Check the predecessors. + for &(pred_ebb, pred_inst) in preds { + let pred_val = self.func.dfg.inst_variable_args(pred_inst)[argnum]; + if self.virtregs.same_class(bad_value, pred_val) { + self.split_pred(pred_inst, pred_ebb, argnum, pred_val); + } + } + + // Second loop check. + assert_ne!(split_len, + self.split_values.len(), + "Couldn't isolate {}", + bad_value); + } + + let vreg = self.virtregs.unify(self.values); + dbg!("Coalesced {} arg {} into {} = {}", + ebb, + argnum, + vreg, + DisplayList(self.virtregs.values(vreg))); + } + + /// Reset `self.values` to just the set of split values. + fn reset_values(&mut self) { + self.values.clear(); + self.values.extend_from_slice(self.split_values); + let domtree = &self.domtree; + let func = &self.func; + self.values + .sort_by(|&a, &b| { + domtree.rpo_cmp(func.dfg.value_def(a), func.dfg.value_def(b), &func.layout) + }); + } + + /// Try coalescing predecessors with `succ_val`. + /// + /// Returns a value from a congruence class that needs to be split before starting over, or + /// `None` if everything was successfully coalesced into `self.values`. + fn try_coalesce(&mut self, + argnum: usize, + succ_val: Value, + preds: &[BasicBlock]) + -> Option { + /// Initialize the value list with the split values. These are guaranteed to be + /// interference free, and anything that interferes with them must be split away. + self.reset_values(); + dbg!("Trying {} with split values: {:?}", succ_val, self.values); + + // Start by adding `succ_val` so we can determine if it interferes with any of the new + // split values. If it does, we must split it. + if self.add_class(succ_val).is_err() { + return Some(succ_val); + } + + for &(pred_ebb, pred_inst) in preds { + let pred_val = self.func.dfg.inst_variable_args(pred_inst)[argnum]; + dbg!("Checking {}: {}: {}", + pred_val, + pred_ebb, + self.func.dfg.display_inst(pred_inst)); + if let Err((a, b)) = self.add_class(pred_val) { + dbg!("Found conflict between {} and {}", a, b); + // We have a conflict between the already merged value `a` and one of the new + // values `b`. + // + // Check if the `a` live range is fundamentally incompatible with `pred_inst`. + if self.liveness + .get(a) + .expect("No live range for interfering value") + .reaches_use(pred_inst, pred_ebb, &self.func.layout) { + // Splitting at `pred_inst` wouldn't resolve the interference, so we need to + // start over. + return Some(a); + } + + // The local conflict could be avoided by splitting at this predecessor, so try + // that. This split is not necessarily required, but it allows us to make progress. + let new_val = self.split_pred(pred_inst, pred_ebb, argnum, pred_val); + assert!(self.add_class(new_val).is_ok(), + "Splitting didn't resolve conflict."); + } + } + + None + } + + /// Try merging the congruence class for `value` into `self.values`. + /// + /// Leave `self.values` unchanged on failure. + fn add_class(&mut self, value: Value) -> Result<(), (Value, Value)> { + self.forest + .try_merge(&self.values, + self.virtregs.congruence_class(&value), + &self.func.dfg, + &self.func.layout, + self.domtree, + self.liveness)?; + self.forest.swap(&mut self.values); + Ok(()) + } + + /// Split the congruence class for the `argnum` argument to `pred_inst` by inserting a copy. + fn split_pred(&mut self, + pred_inst: Inst, + pred_ebb: Ebb, + argnum: usize, + pred_val: Value) + -> Value { + let copy; + { + let mut pos = Cursor::new(&mut self.func.layout); + pos.goto_inst(pred_inst); + copy = self.func.dfg.ins(&mut pos).copy(pred_val); + } + let inst = self.func.dfg.value_def(copy).unwrap_inst(); + let ty = self.func.dfg.value_type(copy); + + dbg!("Inserted {}, before {}: {}", + self.func.dfg.display_inst(inst), + pred_ebb, + self.func.dfg.display_inst(pred_inst)); + + // Give it an encoding. + let encoding = self.isa + .encode(&self.func.dfg, &self.func.dfg[inst], ty) + .expect("Can't encode copy"); + *self.func.encodings.ensure(inst) = encoding; + + // Create a live range for the new value. + let affinity = Affinity::new(&self.encinfo + .operand_constraints(encoding) + .expect("Bad copy encoding") + .outs + [0]); + self.liveness.create_dead(copy, inst, affinity); + self.liveness + .extend_locally(copy, pred_ebb, pred_inst, &self.func.layout); + + self.func.dfg.inst_variable_args_mut(pred_inst)[argnum] = copy; + self.split_values.push(copy); + copy + } + + /// Split the congruence class for the successor EBB value itself. + fn split_succ(&mut self, ebb: Ebb, succ_val: Value) -> Value { + let ty = self.func.dfg.value_type(succ_val); + let new_val = self.func.dfg.replace_ebb_arg(succ_val, ty); + + // Insert a copy instruction at the top of ebb. + { + let mut pos = Cursor::new(&mut self.func.layout); + pos.goto_top(ebb); + pos.next_inst(); + self.func + .dfg + .ins(&mut pos) + .with_result(succ_val) + .copy(new_val); + } + let inst = self.func.dfg.value_def(succ_val).unwrap_inst(); + self.liveness.move_def_locally(succ_val, inst); + + dbg!("Inserted {}, following {}({}: {})", + self.func.dfg.display_inst(inst), + ebb, + new_val, + ty); + + // Give it an encoding. + let encoding = self.isa + .encode(&self.func.dfg, &self.func.dfg[inst], ty) + .expect("Can't encode copy"); + *self.func.encodings.ensure(inst) = encoding; + + // Create a live range for the new value. + let affinity = Affinity::new(&self.encinfo + .operand_constraints(encoding) + .expect("Bad copy encoding") + .outs + [0]); + self.liveness.create_dead(new_val, ebb, affinity); + self.liveness + .extend_locally(new_val, ebb, inst, &self.func.layout); + + self.split_values.push(new_val); + new_val + } +} diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index ea2cdcd924..78e1595747 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -8,6 +8,7 @@ use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; use isa::TargetIsa; +use regalloc::coalescing::Coalescing; use regalloc::coloring::Coloring; use regalloc::live_value_tracker::LiveValueTracker; use regalloc::liveness::Liveness; @@ -22,6 +23,7 @@ use verifier::{verify_context, verify_liveness}; pub struct Context { liveness: Liveness, virtregs: VirtRegs, + coalescing: Coalescing, topo: TopoOrder, tracker: LiveValueTracker, spilling: Spilling, @@ -38,6 +40,7 @@ impl Context { Context { liveness: Liveness::new(), virtregs: VirtRegs::new(), + coalescing: Coalescing::new(), topo: TopoOrder::new(), tracker: LiveValueTracker::new(), spilling: Spilling::new(), @@ -70,6 +73,21 @@ impl Context { verify_liveness(isa, func, cfg, &self.liveness)?; } + // Coalesce and create conventional SSA form. + self.coalescing + .conventional_ssa(isa, + func, + cfg, + domtree, + &mut self.liveness, + &mut self.virtregs); + + if isa.flags().enable_verifier() { + verify_context(func, cfg, domtree, Some(isa))?; + verify_liveness(isa, func, cfg, &self.liveness)?; + } + + // Second pass: Spilling. self.spilling .run(isa, diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index ca4624b45b..f689503c03 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -9,6 +9,7 @@ pub mod live_value_tracker; pub mod coloring; mod affinity; +mod coalescing; mod context; mod diversion; mod pressure; From 4ebc0e85873bebf5762496fb436be391b04a39da Mon Sep 17 00:00:00 2001 From: d1m0 Date: Thu, 22 Jun 2017 16:47:14 -0700 Subject: [PATCH 817/968] Convert interval sets inside TypeSet/ValueTypeSet in general sets (#102) * Convert TypeSet fields to sets; Add BitSet type to rust; Encode ValueTypeSets using BitSet; (still need mypy cleanup) * nits * cleanup nits * forgot mypy type annotations * rustfmt fixes * Round 1 comments: filer b2, b4; doc comments in python; move bitset in its own toplevel module; Use Into * fixes * Revert comment to appease rustfmt --- lib/cretonne/meta/cdsl/test_typevar.py | 22 ++-- lib/cretonne/meta/cdsl/typevar.py | 160 +++++++++++++++---------- lib/cretonne/src/bitset.rs | 147 +++++++++++++++++++++++ lib/cretonne/src/ir/instructions.rs | 93 ++++++-------- lib/cretonne/src/lib.rs | 1 + 5 files changed, 292 insertions(+), 131 deletions(-) create mode 100644 lib/cretonne/src/bitset.rs diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index 29db26f583..97793f71a5 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -40,7 +40,7 @@ class TestTypeSet(TestCase): a = TypeSet(lanes=True, ints=True, floats=True) s = set() s.add(a) - a.max_int = 32 + a.ints.remove(64) # Can't rehash after modification. with self.assertRaises(AssertionError): a in s @@ -71,14 +71,18 @@ class TestTypeVar(TestCase): def test_singleton(self): x = TypeVar.singleton(i32) self.assertEqual(str(x), '`i32`') - self.assertEqual(x.type_set.min_int, 32) - self.assertEqual(x.type_set.max_int, 32) - self.assertEqual(x.type_set.min_lanes, 1) - self.assertEqual(x.type_set.max_lanes, 1) + self.assertEqual(min(x.type_set.ints), 32) + self.assertEqual(max(x.type_set.ints), 32) + self.assertEqual(min(x.type_set.lanes), 1) + self.assertEqual(max(x.type_set.lanes), 1) + self.assertEqual(len(x.type_set.floats), 0) + self.assertEqual(len(x.type_set.bools), 0) x = TypeVar.singleton(i32.by(4)) self.assertEqual(str(x), '`i32x4`') - self.assertEqual(x.type_set.min_int, 32) - self.assertEqual(x.type_set.max_int, 32) - self.assertEqual(x.type_set.min_lanes, 4) - self.assertEqual(x.type_set.max_lanes, 4) + self.assertEqual(min(x.type_set.ints), 32) + self.assertEqual(max(x.type_set.ints), 32) + self.assertEqual(min(x.type_set.lanes), 4) + self.assertEqual(max(x.type_set.lanes), 4) + self.assertEqual(len(x.type_set.floats), 0) + self.assertEqual(len(x.type_set.bools), 0) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 1dc8630f5f..119c6bdf01 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -9,7 +9,7 @@ import math from . import types, is_power_of_two try: - from typing import Tuple, Union, TYPE_CHECKING # noqa + from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa if TYPE_CHECKING: from srcgen import Formatter # noqa Interval = Tuple[int, int] @@ -46,6 +46,32 @@ def intersect(a, b): return (None, None) +def is_empty(intv): + # type: (Interval) -> bool + return intv is None or intv is False or intv == (None, None) + + +def encode_bitset(vals, size): + # type: (Iterable[int], int) -> int + """ + Encode a set of values (each between 0 and size) as a bitset of width size. + """ + res = 0 + assert is_power_of_two(size) and size <= 64 + for v in vals: + assert 0 <= v and v < size + res |= 1 << v + return res + + +def pp_set(s): + # type: (Iterable[Any]) -> str + """ + Return a consistent string representation of a set (ordering is fixed) + """ + return '{' + ', '.join([repr(x) for x in sorted(s)]) + '}' + + def decode_interval(intv, full_range, default=None): # type: (BoolInterval, Interval, int) -> Interval """ @@ -74,6 +100,18 @@ def decode_interval(intv, full_range, default=None): return (default, default) +def interval_to_set(intv): + # type: (Interval) -> Set + if is_empty(intv): + return set() + + (lo, hi) = intv + assert is_power_of_two(lo) + assert is_power_of_two(hi) + assert lo <= hi + return set([2**i for i in range(int_log2(lo), int_log2(hi)+1)]) + + class TypeSet(object): """ A set of types. @@ -95,22 +133,22 @@ class TypeSet(object): A typeset representing scalar integer types `i8` through `i32`: >>> TypeSet(ints=(8, 32)) - TypeSet(lanes=(1, 1), ints=(8, 32)) + TypeSet(lanes={1}, ints={8, 16, 32}) Passing `True` instead of a range selects all available scalar types: >>> TypeSet(ints=True) - TypeSet(lanes=(1, 1), ints=(8, 64)) + TypeSet(lanes={1}, ints={8, 16, 32, 64}) >>> TypeSet(floats=True) - TypeSet(lanes=(1, 1), floats=(32, 64)) + TypeSet(lanes={1}, floats={32, 64}) >>> TypeSet(bools=True) - TypeSet(lanes=(1, 1), bools=(1, 64)) + TypeSet(lanes={1}, bools={1, 8, 16, 32, 64}) Similarly, passing `True` for the lanes selects all possible scalar and vector types: >>> TypeSet(lanes=True, ints=True) - TypeSet(lanes=(1, 256), ints=(8, 64)) + TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256}, ints={8, 16, 32, 64}) :param lanes: `(min, max)` inclusive range of permitted vector lane counts. :param ints: `(min, max)` inclusive range of permitted scalar integer @@ -123,19 +161,19 @@ class TypeSet(object): def __init__(self, lanes=None, ints=None, floats=None, bools=None): # type: (BoolInterval, BoolInterval, BoolInterval, BoolInterval) -> None # noqa - self.min_lanes, self.max_lanes = decode_interval( - lanes, (1, MAX_LANES), 1) - self.min_int, self.max_int = decode_interval(ints, (8, MAX_BITS)) - self.min_float, self.max_float = decode_interval(floats, (32, 64)) - self.min_bool, self.max_bool = decode_interval(bools, (1, MAX_BITS)) + self.lanes = interval_to_set(decode_interval(lanes, (1, MAX_LANES), 1)) + self.ints = interval_to_set(decode_interval(ints, (8, MAX_BITS))) + self.floats = interval_to_set(decode_interval(floats, (32, 64))) + self.bools = interval_to_set(decode_interval(bools, (1, MAX_BITS))) + self.bools = set(filter(lambda x: x == 1 or x >= 8, self.bools)) def typeset_key(self): - # type: () -> Tuple[int, int, int, int, int, int, int, int] + # type: () -> Tuple[Tuple, Tuple, Tuple, Tuple] """Key tuple used for hashing and equality.""" - return (self.min_lanes, self.max_lanes, - self.min_int, self.max_int, - self.min_float, self.max_float, - self.min_bool, self.max_bool) + return (tuple(sorted(list(self.lanes))), + tuple(sorted(list(self.ints))), + tuple(sorted(list(self.floats))), + tuple(sorted(list(self.bools)))) def __hash__(self): # type: () -> int @@ -153,31 +191,29 @@ class TypeSet(object): def __repr__(self): # type: () -> str - s = 'TypeSet(lanes=({}, {})'.format(self.min_lanes, self.max_lanes) - if self.min_int is not None: - s += ', ints=({}, {})'.format(self.min_int, self.max_int) - if self.min_float is not None: - s += ', floats=({}, {})'.format(self.min_float, self.max_float) - if self.min_bool is not None: - s += ', bools=({}, {})'.format(self.min_bool, self.max_bool) + s = 'TypeSet(lanes={}'.format(pp_set(self.lanes)) + if len(self.ints) > 0: + s += ', ints={}'.format(pp_set(self.ints)) + if len(self.floats) > 0: + s += ', floats={}'.format(pp_set(self.floats)) + if len(self.bools) > 0: + s += ', bools={}'.format(pp_set(self.bools)) return s + ')' def emit_fields(self, fmt): # type: (Formatter) -> None """Emit field initializers for this typeset.""" fmt.comment(repr(self)) - fields = ('lanes', 'int', 'float', 'bool') - for field in fields: - min_val = getattr(self, 'min_' + field) - max_val = getattr(self, 'max_' + field) - if min_val is None: - fmt.line('min_{}: 0,'.format(field)) - fmt.line('max_{}: 0,'.format(field)) - else: - fmt.line('min_{}: {},'.format( - field, int_log2(min_val))) - fmt.line('max_{}: {},'.format( - field, int_log2(max_val) + 1)) + + fields = (('lanes', 16), + ('ints', 8), + ('floats', 8), + ('bools', 8)) + + for (field, bits) in fields: + vals = [int_log2(x) for x in getattr(self, field)] + fmt.line('{}: BitSet::({}),' + .format(field, bits, encode_bitset(vals, bits))) def __iand__(self, other): # type: (TypeSet) -> TypeSet @@ -186,32 +222,22 @@ class TypeSet(object): >>> a = TypeSet(lanes=True, ints=(16, 32)) >>> a - TypeSet(lanes=(1, 256), ints=(16, 32)) + TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256}, ints={16, 32}) >>> b = TypeSet(lanes=(4, 16), ints=True) >>> a &= b >>> a - TypeSet(lanes=(4, 16), ints=(16, 32)) + TypeSet(lanes={4, 8, 16}, ints={16, 32}) >>> a = TypeSet(lanes=True, bools=(1, 8)) >>> b = TypeSet(lanes=True, bools=(16, 32)) >>> a &= b >>> a - TypeSet(lanes=(1, 256)) + TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256}) """ - self.min_lanes = max(self.min_lanes, other.min_lanes) - self.max_lanes = min(self.max_lanes, other.max_lanes) - - self.min_int, self.max_int = intersect( - (self.min_int, self.max_int), - (other.min_int, other.max_int)) - - self.min_float, self.max_float = intersect( - (self.min_float, self.max_float), - (other.min_float, other.max_float)) - - self.min_bool, self.max_bool = intersect( - (self.min_bool, self.max_bool), - (other.min_bool, other.max_bool)) + self.lanes.intersection_update(other.lanes) + self.ints.intersection_update(other.ints) + self.floats.intersection_update(other.floats) + self.bools.intersection_update(other.bools) return self @@ -382,12 +408,12 @@ class TypeVar(object): """ if not self.is_derived: ts = self.type_set - if ts.min_int: - assert ts.min_int > 8, "Can't halve all integer types" - if ts.min_float: - assert ts.min_float > 32, "Can't halve all float types" - if ts.min_bool: - assert ts.min_bool > 8, "Can't halve all boolean types" + if len(ts.ints) > 0: + assert min(ts.ints) > 8, "Can't halve all integer types" + if len(ts.floats) > 0: + assert min(ts.floats) > 32, "Can't halve all float types" + if len(ts.bools) > 0: + assert min(ts.bools) > 8, "Can't halve all boolean types" return TypeVar.derived(self, self.HALFWIDTH) @@ -399,12 +425,14 @@ class TypeVar(object): """ if not self.is_derived: ts = self.type_set - if ts.max_int: - assert ts.max_int < MAX_BITS, "Can't double all integer types." - if ts.max_float: - assert ts.max_float < MAX_BITS, "Can't double all float types." - if ts.max_bool: - assert ts.max_bool < MAX_BITS, "Can't double all bool types." + if len(ts.ints) > 0: + assert max(ts.ints) < MAX_BITS,\ + "Can't double all integer types." + if len(ts.floats) > 0: + assert max(ts.floats) < MAX_BITS,\ + "Can't double all float types." + if len(ts.bools) > 0: + assert max(ts.bools) < MAX_BITS, "Can't double all bool types." return TypeVar.derived(self, self.DOUBLEWIDTH) @@ -416,7 +444,7 @@ class TypeVar(object): """ if not self.is_derived: ts = self.type_set - assert ts.min_lanes > 1, "Can't halve a scalar type" + assert min(ts.lanes) > 1, "Can't halve a scalar type" return TypeVar.derived(self, self.HALFVECTOR) @@ -428,7 +456,7 @@ class TypeVar(object): """ if not self.is_derived: ts = self.type_set - assert ts.max_lanes < 256, "Can't double 256 lanes." + assert max(ts.lanes) < MAX_LANES, "Can't double 256 lanes." return TypeVar.derived(self, self.DOUBLEVECTOR) diff --git a/lib/cretonne/src/bitset.rs b/lib/cretonne/src/bitset.rs new file mode 100644 index 0000000000..cfca8371fb --- /dev/null +++ b/lib/cretonne/src/bitset.rs @@ -0,0 +1,147 @@ +//! Small Bitset +//! +//! This module defines a struct BitSet encapsulating a bitset built over the type T. +//! T is intended to be a primitive unsigned type. Currently it can be any type between u8 and u32 +//! +//! If you would like to add support for larger bitsets in the future, you need to change the trait +//! bound Into and the u32 in the implementation of max_bits() +use std::mem::size_of; +use std::ops::{Shl, BitOr, Sub, Add}; +use std::convert::{Into, From}; + +/// A small bitset built on a single primitive integer type +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BitSet(pub T); + +impl BitSet + where T: Into + From + BitOr + Shl + Sub + + Add + PartialEq + Copy +{ +/// Maximum number of bits supported by this BitSet instance + pub fn bits() -> usize { + size_of::() * 8 + } + +/// Maximum number of bits supported by any bitset instance atm. + pub fn max_bits() -> usize { + size_of::() * 8 + } + +/// Check if this BitSet contains the number num + pub fn contains(&self, num: u8) -> bool { + assert!((num as usize) < Self::bits()); + assert!((num as usize) < Self::max_bits()); + return self.0.into() & (1 << num) != 0; + } + +/// Return the smallest number contained in the bitset or None if empty + pub fn min(&self) -> Option { + if self.0.into() == 0 { + None + } else { + Some(self.0.into().trailing_zeros() as u8) + } + } + +/// Return the largest number contained in the bitset or None if empty + pub fn max(&self) -> Option { + if self.0.into() == 0 { + None + } else { + let leading_zeroes = self.0.into().leading_zeros() as usize; + Some((Self::max_bits() - leading_zeroes - 1) as u8) + } + } + +/// Construct a BitSet with the half-open range [lo,hi) filled in + pub fn from_range(lo: u8, hi: u8) -> BitSet { + assert!(lo <= hi); + assert!((hi as usize) <= Self::bits()); + let one : T = T::from(1); +// I can't just do (one << hi) - one here as the shift may overflow + let hi_rng = if hi >= 1 { + (one << (hi-1)) + ((one << (hi-1)) - one) + } else { + T::from(0) + }; + + let lo_rng = (one << lo) - one; + + BitSet(hi_rng - lo_rng) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn contains() { + let s = BitSet::(255); + for i in 0..7 { + assert!(s.contains(i)); + } + + let s1 = BitSet::(0); + for i in 0..7 { + assert!(!s1.contains(i)); + } + + let s2 = BitSet::(127); + for i in 0..6 { + assert!(s2.contains(i)); + } + assert!(!s2.contains(7)); + + let s3 = BitSet::(2 | 4 | 64); + assert!(!s3.contains(0) && !s3.contains(3) && !s3.contains(4) && !s3.contains(5) && + !s3.contains(7)); + assert!(s3.contains(1) && s3.contains(2) && s3.contains(6)); + + let s4 = BitSet::(4 | 8 | 256 | 1024); + assert!(!s4.contains(0) && !s4.contains(1) && !s4.contains(4) && !s4.contains(5) && + !s4.contains(6) && !s4.contains(7) && + !s4.contains(9) && !s4.contains(11)); + assert!(s4.contains(2) && s4.contains(3) && s4.contains(8) && s4.contains(10)); + } + + #[test] + fn minmax() { + let s = BitSet::(255); + assert_eq!(s.min(), Some(0)); + assert_eq!(s.max(), Some(7)); + assert!(s.min() == Some(0) && s.max() == Some(7)); + let s1 = BitSet::(0); + assert!(s1.min() == None && s1.max() == None); + let s2 = BitSet::(127); + assert!(s2.min() == Some(0) && s2.max() == Some(6)); + let s3 = BitSet::(2 | 4 | 64); + assert!(s3.min() == Some(1) && s3.max() == Some(6)); + let s4 = BitSet::(4 | 8 | 256 | 1024); + assert!(s4.min() == Some(2) && s4.max() == Some(10)); + } + + #[test] + fn from_range() { + let s = BitSet::::from_range(5, 5); + assert!(s.0 == 0); + + let s = BitSet::::from_range(0, 8); + assert!(s.0 == 255); + + let s = BitSet::::from_range(0, 8); + assert!(s.0 == 255u16); + + let s = BitSet::::from_range(0, 16); + assert!(s.0 == 65535u16); + + let s = BitSet::::from_range(5, 6); + assert!(s.0 == 32u8); + + let s = BitSet::::from_range(3, 7); + assert!(s.0 == 8 | 16 | 32 | 64); + + let s = BitSet::::from_range(5, 11); + assert!(s.0 == 32 | 64 | 128 | 256 | 512 | 1024); + } +} diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index ad4dd6fa21..35a200de08 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -17,6 +17,7 @@ use ir::types; use isa::RegUnit; use entity_list; +use bitset::BitSet; use ref_slice::{ref_slice, ref_slice_mut}; /// Some instructions use an external list of argument values because there is not enough space in @@ -499,17 +500,16 @@ impl OpcodeConstraints { } } +type BitSet8 = BitSet; +type BitSet16 = BitSet; + /// A value type set describes the permitted set of types for a type variable. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ValueTypeSet { - min_lanes: u8, - max_lanes: u8, - min_int: u8, - max_int: u8, - min_float: u8, - max_float: u8, - min_bool: u8, - max_bool: u8, + lanes: BitSet16, + ints: BitSet8, + floats: BitSet8, + bools: BitSet8, } impl ValueTypeSet { @@ -519,11 +519,11 @@ impl ValueTypeSet { fn is_base_type(&self, scalar: Type) -> bool { let l2b = scalar.log2_lane_bits(); if scalar.is_int() { - self.min_int <= l2b && l2b < self.max_int + self.ints.contains(l2b) } else if scalar.is_float() { - self.min_float <= l2b && l2b < self.max_float + self.floats.contains(l2b) } else if scalar.is_bool() { - self.min_bool <= l2b && l2b < self.max_bool + self.bools.contains(l2b) } else { false } @@ -532,23 +532,23 @@ impl ValueTypeSet { /// Does `typ` belong to this set? pub fn contains(&self, typ: Type) -> bool { let l2l = typ.log2_lane_count(); - self.min_lanes <= l2l && l2l < self.max_lanes && self.is_base_type(typ.lane_type()) + self.lanes.contains(l2l) && self.is_base_type(typ.lane_type()) } /// Get an example member of this type set. /// /// This is used for error messages to avoid suggesting invalid types. pub fn example(&self) -> Type { - let t = if self.max_int > 5 { + let t = if self.ints.max().unwrap_or(0) > 5 { types::I32 - } else if self.max_float > 5 { + } else if self.floats.max().unwrap_or(0) > 5 { types::F32 - } else if self.max_bool > 5 { + } else if self.bools.max().unwrap_or(0) > 5 { types::B32 } else { types::B1 }; - t.by(1 << self.min_lanes).unwrap() + t.by(1 << self.lanes.min().unwrap()).unwrap() } } @@ -709,15 +709,12 @@ mod tests { use ir::types::*; let vts = ValueTypeSet { - min_lanes: 0, - max_lanes: 8, - min_int: 3, - max_int: 7, - min_float: 0, - max_float: 0, - min_bool: 3, - max_bool: 7, + lanes: BitSet16::from_range(0, 8), + ints: BitSet8::from_range(4, 7), + floats: BitSet8::from_range(0, 0), + bools: BitSet8::from_range(3, 7), }; + assert!(!vts.contains(I8)); assert!(vts.contains(I32)); assert!(vts.contains(I64)); assert!(vts.contains(I32X4)); @@ -728,38 +725,26 @@ mod tests { assert_eq!(vts.example().to_string(), "i32"); let vts = ValueTypeSet { - min_lanes: 0, - max_lanes: 8, - min_int: 0, - max_int: 0, - min_float: 5, - max_float: 7, - min_bool: 3, - max_bool: 7, + lanes: BitSet16::from_range(0, 8), + ints: BitSet8::from_range(0, 0), + floats: BitSet8::from_range(5, 7), + bools: BitSet8::from_range(3, 7), }; assert_eq!(vts.example().to_string(), "f32"); let vts = ValueTypeSet { - min_lanes: 1, - max_lanes: 8, - min_int: 0, - max_int: 0, - min_float: 5, - max_float: 7, - min_bool: 3, - max_bool: 7, + lanes: BitSet16::from_range(1, 8), + ints: BitSet8::from_range(0, 0), + floats: BitSet8::from_range(5, 7), + bools: BitSet8::from_range(3, 7), }; assert_eq!(vts.example().to_string(), "f32x2"); let vts = ValueTypeSet { - min_lanes: 2, - max_lanes: 8, - min_int: 0, - max_int: 0, - min_float: 0, - max_float: 0, - min_bool: 3, - max_bool: 7, + lanes: BitSet16::from_range(2, 8), + ints: BitSet8::from_range(0, 0), + floats: BitSet8::from_range(0, 0), + bools: BitSet8::from_range(3, 7), }; assert!(!vts.contains(B32X2)); assert!(vts.contains(B32X4)); @@ -767,14 +752,10 @@ mod tests { let vts = ValueTypeSet { // TypeSet(lanes=(1, 256), ints=(8, 64)) - min_lanes: 0, - max_lanes: 9, - min_int: 3, - max_int: 7, - min_float: 0, - max_float: 0, - min_bool: 0, - max_bool: 0, + lanes: BitSet16::from_range(0, 9), + ints: BitSet8::from_range(3, 7), + floats: BitSet8::from_range(0, 0), + bools: BitSet8::from_range(0, 0), }; assert!(vts.contains(I32)); assert!(vts.contains(I32X4)); diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 09bdfc9302..08c1c4354b 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -16,6 +16,7 @@ pub mod dbg; pub mod entity_ref; pub mod binemit; +pub mod bitset; pub mod dominator_tree; pub mod entity_list; pub mod entity_map; From 83cc08a457fcf1c03f3330aa63c2ecc27788a601 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 22 Jun 2017 14:35:05 -0700 Subject: [PATCH 818/968] Add rusty-tags.* to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a230556d47..14318728b6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Cargo.lock .*.rustfmt cretonne.dbg* .mypy_cache +rusty-tags.* From 9487b885da866e7dbe45b18cfd11516c9fb4dd5c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 23 Jun 2017 10:43:59 -0700 Subject: [PATCH 819/968] Spill whole virtual registers at a time. When the spiller decides to spill a value, bring along all of the values in its virtual register. This ensures that we won't have problems with computing register pressure around EBB arguments. They will always be register-to-register or stack-to-stack with related values using the same stack slot. This also means that the reloading pass won't have to deal with spilled EBB arguments. --- lib/cretonne/src/regalloc/context.rs | 11 ++++++----- lib/cretonne/src/regalloc/spilling.rs | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 78e1595747..0908b02a1c 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -66,14 +66,14 @@ impl Context { // phases. self.tracker.clear(); - // First pass: Liveness analysis. + // Pass: Liveness analysis. self.liveness.compute(isa, func, cfg); if isa.flags().enable_verifier() { verify_liveness(isa, func, cfg, &self.liveness)?; } - // Coalesce and create conventional SSA form. + // Pass: Coalesce and create conventional SSA form. self.coalescing .conventional_ssa(isa, func, @@ -88,12 +88,13 @@ impl Context { } - // Second pass: Spilling. + // Pass: Spilling. self.spilling .run(isa, func, domtree, &mut self.liveness, + &self.virtregs, &mut self.topo, &mut self.tracker); @@ -102,7 +103,7 @@ impl Context { verify_liveness(isa, func, cfg, &self.liveness)?; } - // Third pass: Reload. + // Pass: Reload. self.reload .run(isa, func, @@ -116,7 +117,7 @@ impl Context { verify_liveness(isa, func, cfg, &self.liveness)?; } - // Fourth pass: Coloring. + // Pass: Coloring. self.coloring .run(isa, func, diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 7311de5d5d..f1feff3f4b 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -25,6 +25,7 @@ use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; use regalloc::pressure::Pressure; +use regalloc::virtregs::VirtRegs; use topo_order::TopoOrder; /// Persistent data structures for the spilling pass. @@ -48,6 +49,7 @@ struct Context<'a> { // References to contextual data structures we need. domtree: &'a DominatorTree, liveness: &'a mut Liveness, + virtregs: &'a VirtRegs, topo: &'a mut TopoOrder, // Current register pressure. @@ -77,6 +79,7 @@ impl Spilling { func: &mut Function, domtree: &DominatorTree, liveness: &mut Liveness, + virtregs: &VirtRegs, topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { dbg!("Spilling for:\n{}", func.display(isa)); @@ -91,6 +94,7 @@ impl Spilling { locations: &mut func.locations, domtree, liveness, + virtregs, topo, pressure: Pressure::new(®info, &usable_regs), spills: &mut self.spills, @@ -374,15 +378,17 @@ impl<'a> Context<'a> { let rc = self.reginfo.rc(rci); self.pressure.free(rc); self.spills.push(value); - - // Assign a spill slot. - // TODO: phi-related values should use the same spill slot. - let ss = self.stack_slots.make_spill_slot(dfg.value_type(value)); - *self.locations.ensure(value) = ValueLoc::Stack(ss); - dbg!("Spilled {}:{} to {} -> {}", value, rc, ss, self.pressure); + dbg!("Spilled {}:{} -> {}", value, rc, self.pressure); } else { panic!("Cannot spill {} that was already on the stack", value); } + + // Assign a spill slot for the whole virtual register. + let ss = self.stack_slots.make_spill_slot(dfg.value_type(value)); + for &v in self.virtregs.congruence_class(&value) { + self.liveness.spill(v); + *self.locations.ensure(v) = ValueLoc::Stack(ss); + } } /// Process any pending spills in the `self.spills` vector. From 6a9438d274fb01357cfa5d5a041aaf431348569a Mon Sep 17 00:00:00 2001 From: d1m0 Date: Fri, 23 Jun 2017 11:57:24 -0700 Subject: [PATCH 820/968] Add image computation of typesets; Remove TypeVar.singleton_type - instead derive singleton type from typeset; (#104) --- lib/cretonne/meta/base/types.py | 9 +- lib/cretonne/meta/cdsl/instructions.py | 2 +- lib/cretonne/meta/cdsl/test_typevar.py | 80 ++++++++- lib/cretonne/meta/cdsl/types.py | 28 +++- lib/cretonne/meta/cdsl/typevar.py | 218 +++++++++++++++++++++---- lib/cretonne/meta/cdsl/xform.py | 2 +- lib/cretonne/meta/gen_instr.py | 4 +- 7 files changed, 294 insertions(+), 49 deletions(-) diff --git a/lib/cretonne/meta/base/types.py b/lib/cretonne/meta/base/types.py index a2eb1054b9..7111626009 100644 --- a/lib/cretonne/meta/base/types.py +++ b/lib/cretonne/meta/base/types.py @@ -2,15 +2,10 @@ The base.types module predefines all the Cretonne scalar types. """ from __future__ import absolute_import -from cdsl.types import ScalarType, IntType, FloatType, BoolType +from cdsl.types import IntType, FloatType, BoolType #: Boolean. -b1 = ScalarType( - 'b1', 0, - """ - A boolean value that is either true or false. - """) - +b1 = BoolType(1) #: 1-bit bool. Type is abstract (can't be stored in mem) b8 = BoolType(8) #: 8-bit bool. b16 = BoolType(16) #: 16-bit bool. b32 = BoolType(32) #: 32-bit bool. diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index d8e9d24e5f..22c989bd65 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -186,7 +186,7 @@ class Instruction(object): try: opnum = self.value_opnums[self.format.typevar_operand] tv = self.ins[opnum].typevar - if tv is tv.free_typevar(): + if tv is tv.free_typevar() or tv.singleton_type() is not None: self.other_typevars = self._verify_ctrl_typevar(tv) self.ctrl_typevar = tv self.use_typevar_operand = True diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index 97793f71a5..5da081eabe 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -3,7 +3,7 @@ from unittest import TestCase from doctest import DocTestSuite from . import typevar from .typevar import TypeSet, TypeVar -from base.types import i32 +from base.types import i32, i16, b1, f64 def load_tests(loader, tests, ignore): @@ -45,6 +45,84 @@ class TestTypeSet(TestCase): with self.assertRaises(AssertionError): a in s + def test_forward_images(self): + a = TypeSet(lanes=(2, 8), ints=(8, 8), floats=(32, 32)) + b = TypeSet(lanes=(1, 8), ints=(8, 8), floats=(32, 32)) + self.assertEqual(a.lane_of(), TypeSet(ints=(8, 8), floats=(32, 32))) + + c = TypeSet(lanes=(2, 8)) + c.bools = set([8, 32]) + + # Test case with disjoint intervals + self.assertEqual(a.as_bool(), c) + + # For as_bool check b1 is present when 1 \in lanes + d = TypeSet(lanes=(1, 8)) + d.bools = set([1, 8, 32]) + self.assertEqual(b.as_bool(), d) + + self.assertEqual(TypeSet(lanes=(1, 32)).half_vector(), + TypeSet(lanes=(1, 16))) + + self.assertEqual(TypeSet(lanes=(1, 32)).double_vector(), + TypeSet(lanes=(2, 64))) + + self.assertEqual(TypeSet(lanes=(128, 256)).double_vector(), + TypeSet(lanes=(256, 256))) + + self.assertEqual(TypeSet(ints=(8, 32)).half_width(), + TypeSet(ints=(8, 16))) + + self.assertEqual(TypeSet(ints=(8, 32)).double_width(), + TypeSet(ints=(16, 64))) + + self.assertEqual(TypeSet(ints=(32, 64)).double_width(), + TypeSet(ints=(64, 64))) + + # Should produce an empty ts + self.assertEqual(TypeSet(floats=(32, 32)).half_width(), + TypeSet()) + + self.assertEqual(TypeSet(floats=(32, 64)).half_width(), + TypeSet(floats=(32, 32))) + + self.assertEqual(TypeSet(floats=(32, 32)).double_width(), + TypeSet(floats=(64, 64))) + + self.assertEqual(TypeSet(floats=(32, 64)).double_width(), + TypeSet(floats=(64, 64))) + + # Bools have trickier behavior around b1 (since b2, b4 don't exist) + self.assertEqual(TypeSet(bools=(1, 8)).half_width(), + TypeSet()) + + t = TypeSet() + t.bools = set([8, 16]) + self.assertEqual(TypeSet(bools=(1, 32)).half_width(), t) + + # double_width() of bools={1, 8, 16} must not include 2 or 8 + t.bools = set([16, 32]) + self.assertEqual(TypeSet(bools=(1, 16)).double_width(), t) + + self.assertEqual(TypeSet(bools=(32, 64)).double_width(), + TypeSet(bools=(64, 64))) + + def test_get_singleton(self): + # Raise error when calling get_singleton() on non-singleton TS + t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32)) + with self.assertRaises(AssertionError): + t.get_singleton() + t = TypeSet(lanes=(1, 2), floats=(32, 32)) + + with self.assertRaises(AssertionError): + t.get_singleton() + + self.assertEqual(TypeSet(ints=(16, 16)).get_singleton(), i16) + self.assertEqual(TypeSet(floats=(64, 64)).get_singleton(), f64) + self.assertEqual(TypeSet(bools=(1, 1)).get_singleton(), b1) + self.assertEqual(TypeSet(lanes=(4, 4), ints=(32, 32)).get_singleton(), + i32.by(4)) + class TestTypeVar(TestCase): def test_functions(self): diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index e42460af28..6feb2112d8 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -97,7 +97,7 @@ class VectorType(ValueType): # type: (ScalarType, int) -> None assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' super(VectorType, self).__init__( - name='{}x{}'.format(base.name, lanes), + name=VectorType.get_name(base, lanes), membytes=lanes*base.membytes, doc=""" A SIMD vector with {} lanes containing a `{}` each. @@ -111,6 +111,11 @@ class VectorType(ValueType): return ('VectorType(base={}, lanes={})' .format(self.base.name, self.lanes)) + @staticmethod + def get_name(base, lanes): + # type: (ValueType, int) -> str + return '{}x{}'.format(base.name, lanes) + class IntType(ScalarType): """A concrete scalar integer type.""" @@ -119,7 +124,7 @@ class IntType(ScalarType): # type: (int) -> None assert bits > 0, 'IntType must have positive number of bits' super(IntType, self).__init__( - name='i{:d}'.format(bits), + name=IntType.get_name(bits), membytes=bits // 8, doc="An integer type with {} bits.".format(bits)) self.bits = bits @@ -128,6 +133,11 @@ class IntType(ScalarType): # type: () -> str return 'IntType(bits={})'.format(self.bits) + @staticmethod + def get_name(bits): + # type: (int) -> str + return 'i{:d}'.format(bits) + class FloatType(ScalarType): """A concrete scalar floating point type.""" @@ -136,7 +146,7 @@ class FloatType(ScalarType): # type: (int, str) -> None assert bits > 0, 'FloatType must have positive number of bits' super(FloatType, self).__init__( - name='f{:d}'.format(bits), + name=FloatType.get_name(bits), membytes=bits // 8, doc=doc) self.bits = bits @@ -145,6 +155,11 @@ class FloatType(ScalarType): # type: () -> str return 'FloatType(bits={})'.format(self.bits) + @staticmethod + def get_name(bits): + # type: (int) -> str + return 'f{:d}'.format(bits) + class BoolType(ScalarType): """A concrete scalar boolean type.""" @@ -153,7 +168,7 @@ class BoolType(ScalarType): # type: (int) -> None assert bits > 0, 'BoolType must have positive number of bits' super(BoolType, self).__init__( - name='b{:d}'.format(bits), + name=BoolType.get_name(bits), membytes=bits // 8, doc="A boolean type with {} bits.".format(bits)) self.bits = bits @@ -161,3 +176,8 @@ class BoolType(ScalarType): def __repr__(self): # type: () -> str return 'BoolType(bits={})'.format(self.bits) + + @staticmethod + def get_name(bits): + # type: (int) -> str + return 'b{:d}'.format(bits) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 119c6bdf01..6ed3ffc86c 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -7,15 +7,19 @@ polymorphic by using type variables. from __future__ import absolute_import import math from . import types, is_power_of_two +from copy import deepcopy +from .types import ValueType, IntType, FloatType, BoolType try: from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa + from typing import cast if TYPE_CHECKING: from srcgen import Formatter # noqa Interval = Tuple[int, int] # An Interval where `True` means 'everything' BoolInterval = Union[bool, Interval] except ImportError: + TYPE_CHECKING = False pass MAX_LANES = 256 @@ -112,6 +116,16 @@ def interval_to_set(intv): return set([2**i for i in range(int_log2(lo), int_log2(hi)+1)]) +def legal_bool(bits): + # type: (int) -> bool + """ + True iff bits is a legal bit width for a bool type. + bits == 1 || bits \in { 8, 16, .. MAX_BITS } + """ + return bits == 1 or \ + (bits >= 8 and bits <= MAX_BITS and is_power_of_two(bits)) + + class TypeSet(object): """ A set of types. @@ -165,7 +179,15 @@ class TypeSet(object): self.ints = interval_to_set(decode_interval(ints, (8, MAX_BITS))) self.floats = interval_to_set(decode_interval(floats, (32, 64))) self.bools = interval_to_set(decode_interval(bools, (1, MAX_BITS))) - self.bools = set(filter(lambda x: x == 1 or x >= 8, self.bools)) + self.bools = set(filter(legal_bool, self.bools)) + + def copy(self): + # type: (TypeSet) -> TypeSet + """ + Return a copy of our self. deepcopy is sufficient and safe here, since + TypeSet contains only sets of numbers. + """ + return deepcopy(self) def typeset_key(self): # type: () -> Tuple[Tuple, Tuple, Tuple, Tuple] @@ -241,6 +263,109 @@ class TypeSet(object): return self + def lane_of(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across lane_of + """ + new = self.copy() + new.lanes = set([1]) + return new + + def as_bool(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across as_bool + """ + new = self.copy() + new.ints = set() + new.floats = set() + new.bools = self.ints.union(self.floats).union(self.bools) + + if 1 in self.lanes: + new.bools.add(1) + return new + + def half_width(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across halfwidth + """ + new = self.copy() + new.ints = set([x/2 for x in self.ints if x > 8]) + new.floats = set([x/2 for x in self.floats if x > 32]) + new.bools = set([x/2 for x in self.bools if x > 8]) + + return new + + def double_width(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across doublewidth + """ + new = self.copy() + new.ints = set([x*2 for x in self.ints if x < MAX_BITS]) + new.floats = set([x*2 for x in self.floats if x < MAX_BITS]) + new.bools = set(filter(legal_bool, + set([x*2 for x in self.bools if x < MAX_BITS]))) + + return new + + def half_vector(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across halfvector + """ + new = self.copy() + new.lanes = set([x/2 for x in self.lanes if x > 1]) + + return new + + def double_vector(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across doublevector + """ + new = self.copy() + new.lanes = set([x*2 for x in self.lanes if x < MAX_LANES]) + + return new + + def size(self): + # type: () -> int + """ + Return the number of concrete types represented by this typeset + """ + return len(self.lanes) * (len(self.ints) + len(self.floats) + + len(self.bools)) + + def get_singleton(self): + # type: () -> types.ValueType + """ + Return the singleton type represented by self. Can only call on + typesets containing 1 type. + """ + assert self.size() == 1 + if len(self.ints) > 0: + bits = tuple(self.ints)[0] + scalar_type = ValueType.by_name(IntType.get_name(bits)) + elif len(self.floats) > 0: + bits = tuple(self.floats)[0] + scalar_type = ValueType.by_name(FloatType.get_name(bits)) + else: + bits = tuple(self.bools)[0] + scalar_type = ValueType.by_name(BoolType.get_name(bits)) + + nlanes = tuple(self.lanes)[0] + + if nlanes == 1: + return scalar_type + else: + if TYPE_CHECKING: + return cast(types.ScalarType, scalar_type).by(nlanes) + else: + return scalar_type.by(nlanes) + class TypeVar(object): """ @@ -271,7 +396,6 @@ class TypeVar(object): # type: (str, str, BoolInterval, BoolInterval, BoolInterval, bool, BoolInterval, TypeVar, str) -> None # noqa self.name = name self.__doc__ = doc - self.singleton_type = None # type: types.ValueType self.is_derived = isinstance(base, TypeVar) if base: assert self.is_derived @@ -313,7 +437,6 @@ class TypeVar(object): tv = TypeVar( typ.name, typ.__doc__, ints, floats, bools, simd=lanes) - tv.singleton_type = typ return tv def __str__(self): @@ -406,14 +529,13 @@ class TypeVar(object): Return a derived type variable that has the same number of vector lanes as this one, but the lanes are half the width. """ - if not self.is_derived: - ts = self.type_set - if len(ts.ints) > 0: - assert min(ts.ints) > 8, "Can't halve all integer types" - if len(ts.floats) > 0: - assert min(ts.floats) > 32, "Can't halve all float types" - if len(ts.bools) > 0: - assert min(ts.bools) > 8, "Can't halve all boolean types" + ts = self.get_typeset() + if len(ts.ints) > 0: + assert min(ts.ints) > 8, "Can't halve all integer types" + if len(ts.floats) > 0: + assert min(ts.floats) > 32, "Can't halve all float types" + if len(ts.bools) > 0: + assert min(ts.bools) > 8, "Can't halve all boolean types" return TypeVar.derived(self, self.HALFWIDTH) @@ -423,16 +545,13 @@ class TypeVar(object): Return a derived type variable that has the same number of vector lanes as this one, but the lanes are double the width. """ - if not self.is_derived: - ts = self.type_set - if len(ts.ints) > 0: - assert max(ts.ints) < MAX_BITS,\ - "Can't double all integer types." - if len(ts.floats) > 0: - assert max(ts.floats) < MAX_BITS,\ - "Can't double all float types." - if len(ts.bools) > 0: - assert max(ts.bools) < MAX_BITS, "Can't double all bool types." + ts = self.get_typeset() + if len(ts.ints) > 0: + assert max(ts.ints) < MAX_BITS, "Can't double all integer types." + if len(ts.floats) > 0: + assert max(ts.floats) < MAX_BITS, "Can't double all float types." + if len(ts.bools) > 0: + assert max(ts.bools) < MAX_BITS, "Can't double all bool types." return TypeVar.derived(self, self.DOUBLEWIDTH) @@ -442,9 +561,8 @@ class TypeVar(object): Return a derived type variable that has half the number of vector lanes as this one, with the same lane type. """ - if not self.is_derived: - ts = self.type_set - assert min(ts.lanes) > 1, "Can't halve a scalar type" + ts = self.get_typeset() + assert min(ts.lanes) > 1, "Can't halve a scalar type" return TypeVar.derived(self, self.HALFVECTOR) @@ -454,12 +572,23 @@ class TypeVar(object): Return a derived type variable that has twice the number of vector lanes as this one, with the same lane type. """ - if not self.is_derived: - ts = self.type_set - assert max(ts.lanes) < MAX_LANES, "Can't double 256 lanes." + ts = self.get_typeset() + assert max(ts.lanes) < MAX_LANES, "Can't double 256 lanes." return TypeVar.derived(self, self.DOUBLEVECTOR) + def singleton_type(self): + # type: () -> ValueType + """ + If the associated typeset has a single type return it. Otherwise return + None + """ + ts = self.get_typeset() + if ts.size() != 1: + return None + + return ts.get_singleton() + def free_typevar(self): # type: () -> TypeVar """ @@ -467,7 +596,7 @@ class TypeVar(object): """ if self.is_derived: return self.base - elif self.singleton_type: + elif self.singleton_type() is not None: # A singleton type variable is not a proper free variable. return None else: @@ -481,8 +610,8 @@ class TypeVar(object): if self.is_derived: return '{}.{}()'.format( self.base.rust_expr(), self.derived_func) - elif self.singleton_type: - return self.singleton_type.rust_name() + elif self.singleton_type(): + return self.singleton_type().rust_name() else: return self.name @@ -501,9 +630,6 @@ class TypeVar(object): if not a.is_derived and not b.is_derived: a.type_set &= b.type_set - # TODO: What if a.type_set becomes empty? - if not a.singleton_type: - a.singleton_type = b.singleton_type return # TODO: Implement constraints for derived type variables. @@ -514,3 +640,29 @@ class TypeVar(object): # # For the fully general case, we would need to compute an image typeset # for `b` and propagate a `a.derived_func` pre-image to `a.base`. + + def get_typeset(self): + # type: () -> TypeSet + """ + Returns the typeset for this TV. If the TV is derived, computes it + recursively from the derived function and the base's typeset. + """ + if not self.is_derived: + return self.type_set + else: + if (self.derived_func == TypeVar.SAMEAS): + return self.base.get_typeset() + elif (self.derived_func == TypeVar.LANEOF): + return self.base.get_typeset().lane_of() + elif (self.derived_func == TypeVar.ASBOOL): + return self.base.get_typeset().as_bool() + elif (self.derived_func == TypeVar.HALFWIDTH): + return self.base.get_typeset().half_width() + elif (self.derived_func == TypeVar.DOUBLEWIDTH): + return self.base.get_typeset().double_width() + elif (self.derived_func == TypeVar.HALFVECTOR): + return self.base.get_typeset().half_vector() + elif (self.derived_func == TypeVar.DOUBLEVECTOR): + return self.base.get_typeset().double_vector() + else: + assert False, "Unknown derived function: " + self.derived_func diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 9d284bc1e2..9ce93c9ed9 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -254,7 +254,7 @@ class XForm(object): # Some variables have a fixed type which appears as a type variable # with a singleton_type field set. That's allowed for temps too. for v in fvars: - if v.is_temp() and not v.typevar.singleton_type: + if v.is_temp() and not v.typevar.singleton_type(): raise AssertionError( "Cannot determine type of temp '{}' in xform:\n{}" .format(v, self)) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 548cd45a29..2d67311ae2 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -321,8 +321,8 @@ def get_constraint(op, ctrl_typevar, type_sets): tv = op.typevar # A concrete value type. - if tv.singleton_type: - return 'Concrete({})'.format(tv.singleton_type.rust_name()) + if tv.singleton_type(): + return 'Concrete({})'.format(tv.singleton_type().rust_name()) if tv.free_typevar() is not ctrl_typevar: assert not tv.is_derived From 1fa88991924395d748c425ef0a4d0f74e8d21437 Mon Sep 17 00:00:00 2001 From: Dimo Date: Fri, 23 Jun 2017 17:46:05 -0700 Subject: [PATCH 821/968] Cleanup ValueType.get_names to with_bits form previous PR; Add computation of inverse image of typeset across a derived function - TypeSet.map_inverse; Change TypeVar.constrain_type to perform a more-general computation using inverse images of TypeSets; Tests for map_inverse; --- lib/cretonne/meta/cdsl/test_typevar.py | 108 ++++++++++++++++++++ lib/cretonne/meta/cdsl/types.py | 46 +++++---- lib/cretonne/meta/cdsl/typevar.py | 131 +++++++++++++++++++------ 3 files changed, 237 insertions(+), 48 deletions(-) diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index 5da081eabe..f655c8966d 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -4,6 +4,8 @@ from doctest import DocTestSuite from . import typevar from .typevar import TypeSet, TypeVar from base.types import i32, i16, b1, f64 +from itertools import product +from functools import reduce def load_tests(loader, tests, ignore): @@ -123,6 +125,58 @@ class TestTypeSet(TestCase): self.assertEqual(TypeSet(lanes=(4, 4), ints=(32, 32)).get_singleton(), i32.by(4)) + def test_map_inverse(self): + t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32)) + self.assertEqual(t, t.map_inverse(TypeVar.SAMEAS)) + + # LANEOF + self.assertEqual(TypeSet(lanes=True, ints=(8, 8), floats=(32, 32)), + t.map_inverse(TypeVar.LANEOF)) + # Inverse of empty set is still empty across LANEOF + self.assertEqual(TypeSet(), + TypeSet().map_inverse(TypeVar.LANEOF)) + + # ASBOOL + t = TypeSet(lanes=(1, 4), bools=(1, 64)) + self.assertEqual(t.map_inverse(TypeVar.ASBOOL), + TypeSet(lanes=(1, 4), ints=True, bools=True, + floats=True)) + + # Inverse image across ASBOOL of TS not involving b1 cannot have + # lanes=1 + t = TypeSet(lanes=(1, 4), bools=(16, 32)) + self.assertEqual(t.map_inverse(TypeVar.ASBOOL), + TypeSet(lanes=(2, 4), ints=(16, 32), bools=(16, 32), + floats=(32, 32))) + + # Half/Double Vector + t = TypeSet(lanes=(1, 1), ints=(8, 8)) + t1 = TypeSet(lanes=(256, 256), ints=(8, 8)) + self.assertEqual(t.map_inverse(TypeVar.DOUBLEVECTOR).size(), 0) + self.assertEqual(t1.map_inverse(TypeVar.HALFVECTOR).size(), 0) + + t = TypeSet(lanes=(1, 16), ints=(8, 16), floats=(32, 32)) + t1 = TypeSet(lanes=(64, 256), bools=(1, 32)) + + self.assertEqual(t.map_inverse(TypeVar.DOUBLEVECTOR), + TypeSet(lanes=(1, 8), ints=(8, 16), floats=(32, 32))) + self.assertEqual(t1.map_inverse(TypeVar.HALFVECTOR), + TypeSet(lanes=(128, 256), bools=(1, 32))) + + # Half/Double Width + t = TypeSet(ints=(8, 8), floats=(32, 32), bools=(1, 8)) + t1 = TypeSet(ints=(64, 64), floats=(64, 64), bools=(64, 64)) + self.assertEqual(t.map_inverse(TypeVar.DOUBLEWIDTH).size(), 0) + self.assertEqual(t1.map_inverse(TypeVar.HALFWIDTH).size(), 0) + + t = TypeSet(lanes=(1, 16), ints=(8, 16), floats=(32, 64)) + t1 = TypeSet(lanes=(64, 256), bools=(1, 64)) + + self.assertEqual(t.map_inverse(TypeVar.DOUBLEWIDTH), + TypeSet(lanes=(1, 16), ints=(8, 8), floats=(32, 32))) + self.assertEqual(t1.map_inverse(TypeVar.HALFWIDTH), + TypeSet(lanes=(64, 256), bools=(16, 64))) + class TestTypeVar(TestCase): def test_functions(self): @@ -164,3 +218,57 @@ class TestTypeVar(TestCase): self.assertEqual(max(x.type_set.lanes), 4) self.assertEqual(len(x.type_set.floats), 0) self.assertEqual(len(x.type_set.bools), 0) + + def test_stress_constrain_types(self): + # Get all 49 possible derived vars of lentgh 2. Since we have SAMEAS + # this includes singly derived and non-derived vars + funcs = [TypeVar.SAMEAS, TypeVar.LANEOF, + TypeVar.ASBOOL, TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR, + TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH] + v = list(product(*[funcs, funcs])) + + # For each pair of derived variables + for (i1, i2) in product(v, v): + # Compute the derived sets for each starting with a full typeset + full_ts = TypeSet(lanes=True, floats=True, ints=True, bools=True) + ts1 = reduce(lambda ts, func: ts.map(func), i1, full_ts) + ts2 = reduce(lambda ts, func: ts.map(func), i2, full_ts) + + # Compute intersection + intersect = ts1.copy() + intersect &= ts2 + + # Propagate instersections backward + ts1_src = reduce(lambda ts, func: ts.map_inverse(func), + reversed(i1), + intersect) + ts2_src = reduce(lambda ts, func: ts.map_inverse(func), + reversed(i2), + intersect) + + # If the intersection or its propagated forms are empty, then these + # two variables can never overlap. For example x.double_vector and + # x.lane_of. + if (intersect.size() == 0 or ts1_src.size() == 0 or + ts2_src.size() == 0): + continue + + # Should be safe to create derived tvs from ts1_src and ts2_src + tv1 = reduce(lambda tv, func: TypeVar.derived(tv, func), + i1, + TypeVar.from_typeset(ts1_src)) + + tv2 = reduce(lambda tv, func: TypeVar.derived(tv, func), + i2, + TypeVar.from_typeset(ts2_src)) + + # The typesets of the two derived variables should be subsets of + # the intersection we computed originally + assert tv1.get_typeset().issubset(intersect) + assert tv2.get_typeset().issubset(intersect) + + # In the absence of AS_BOOL map(map_inverse(f)) == f so the + # typesets of tv1 and tv2 should be exactly intersection + assert (tv1.get_typeset() == tv2.get_typeset() and + tv1.get_typeset() == intersect) or\ + TypeVar.ASBOOL in set(i1 + i2) diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index 6feb2112d8..eeb5325ae8 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -3,8 +3,9 @@ from __future__ import absolute_import import math try: - from typing import Dict, List # noqa + from typing import Dict, List, cast, TYPE_CHECKING # noqa except ImportError: + TYPE_CHECKING = False pass @@ -97,7 +98,7 @@ class VectorType(ValueType): # type: (ScalarType, int) -> None assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types' super(VectorType, self).__init__( - name=VectorType.get_name(base, lanes), + name='{}x{}'.format(base.name, lanes), membytes=lanes*base.membytes, doc=""" A SIMD vector with {} lanes containing a `{}` each. @@ -111,11 +112,6 @@ class VectorType(ValueType): return ('VectorType(base={}, lanes={})' .format(self.base.name, self.lanes)) - @staticmethod - def get_name(base, lanes): - # type: (ValueType, int) -> str - return '{}x{}'.format(base.name, lanes) - class IntType(ScalarType): """A concrete scalar integer type.""" @@ -124,7 +120,7 @@ class IntType(ScalarType): # type: (int) -> None assert bits > 0, 'IntType must have positive number of bits' super(IntType, self).__init__( - name=IntType.get_name(bits), + name='i{:d}'.format(bits), membytes=bits // 8, doc="An integer type with {} bits.".format(bits)) self.bits = bits @@ -134,9 +130,13 @@ class IntType(ScalarType): return 'IntType(bits={})'.format(self.bits) @staticmethod - def get_name(bits): - # type: (int) -> str - return 'i{:d}'.format(bits) + def with_bits(bits): + # type: (int) -> IntType + typ = ValueType.by_name('i{:d}'.format(bits)) + if TYPE_CHECKING: + return cast(IntType, typ) + else: + return typ class FloatType(ScalarType): @@ -146,7 +146,7 @@ class FloatType(ScalarType): # type: (int, str) -> None assert bits > 0, 'FloatType must have positive number of bits' super(FloatType, self).__init__( - name=FloatType.get_name(bits), + name='f{:d}'.format(bits), membytes=bits // 8, doc=doc) self.bits = bits @@ -156,9 +156,13 @@ class FloatType(ScalarType): return 'FloatType(bits={})'.format(self.bits) @staticmethod - def get_name(bits): - # type: (int) -> str - return 'f{:d}'.format(bits) + def with_bits(bits): + # type: (int) -> FloatType + typ = ValueType.by_name('f{:d}'.format(bits)) + if TYPE_CHECKING: + return cast(FloatType, typ) + else: + return typ class BoolType(ScalarType): @@ -168,7 +172,7 @@ class BoolType(ScalarType): # type: (int) -> None assert bits > 0, 'BoolType must have positive number of bits' super(BoolType, self).__init__( - name=BoolType.get_name(bits), + name='b{:d}'.format(bits), membytes=bits // 8, doc="A boolean type with {} bits.".format(bits)) self.bits = bits @@ -178,6 +182,10 @@ class BoolType(ScalarType): return 'BoolType(bits={})'.format(self.bits) @staticmethod - def get_name(bits): - # type: (int) -> str - return 'b{:d}'.format(bits) + def with_bits(bits): + # type: (int) -> BoolType + typ = ValueType.by_name('b{:d}'.format(bits)) + if TYPE_CHECKING: + return cast(BoolType, typ) + else: + return typ diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 6ed3ffc86c..69b419bfa3 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -8,18 +8,17 @@ from __future__ import absolute_import import math from . import types, is_power_of_two from copy import deepcopy -from .types import ValueType, IntType, FloatType, BoolType +from .types import IntType, FloatType, BoolType try: from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa - from typing import cast if TYPE_CHECKING: from srcgen import Formatter # noqa + from .types import ValueType # noqa Interval = Tuple[int, int] # An Interval where `True` means 'everything' BoolInterval = Union[bool, Interval] except ImportError: - TYPE_CHECKING = False pass MAX_LANES = 256 @@ -263,6 +262,16 @@ class TypeSet(object): return self + def issubset(self, other): + # type: (TypeSet) -> bool + """ + Return true iff self is a subset of other + """ + return self.lanes.issubset(other.lanes) and \ + self.ints.issubset(other.ints) and \ + self.floats.issubset(other.floats) and \ + self.bools.issubset(other.bools) + def lane_of(self): # type: () -> TypeSet """ @@ -292,9 +301,9 @@ class TypeSet(object): Return a TypeSet describing the image of self across halfwidth """ new = self.copy() - new.ints = set([x/2 for x in self.ints if x > 8]) - new.floats = set([x/2 for x in self.floats if x > 32]) - new.bools = set([x/2 for x in self.bools if x > 8]) + new.ints = set([x//2 for x in self.ints if x > 8]) + new.floats = set([x//2 for x in self.floats if x > 32]) + new.bools = set([x//2 for x in self.bools if x > 8]) return new @@ -317,7 +326,7 @@ class TypeSet(object): Return a TypeSet describing the image of self across halfvector """ new = self.copy() - new.lanes = set([x/2 for x in self.lanes if x > 1]) + new.lanes = set([x//2 for x in self.lanes if x > 1]) return new @@ -331,6 +340,67 @@ class TypeSet(object): return new + def map(self, func): + # type: (str) -> TypeSet + """ + Return the image of self across the derived function func + """ + if (func == TypeVar.SAMEAS): + return self + elif (func == TypeVar.LANEOF): + return self.lane_of() + elif (func == TypeVar.ASBOOL): + return self.as_bool() + elif (func == TypeVar.HALFWIDTH): + return self.half_width() + elif (func == TypeVar.DOUBLEWIDTH): + return self.double_width() + elif (func == TypeVar.HALFVECTOR): + return self.half_vector() + elif (func == TypeVar.DOUBLEVECTOR): + return self.double_vector() + else: + assert False, "Unknown derived function: " + func + + def map_inverse(self, func): + # type: (str) -> TypeSet + """ + Return the inverse image of self across the derived function func + """ + # The inverse of the empty set is always empty + if (self.size() == 0): + return self + + if (func == TypeVar.SAMEAS): + return self + elif (func == TypeVar.LANEOF): + new = self.copy() + new.lanes = set([2**i for i in range(0, int_log2(MAX_LANES)+1)]) + return new + elif (func == TypeVar.ASBOOL): + new = self.copy() + new.ints = self.bools.difference(set([1])) + new.floats = self.bools.intersection(set([32, 64])) + + if 1 not in self.bools: + try: + # If the range doesn't have b1, then the domain can't + # include scalars, as as_bool(scalar)=b1 + new.lanes.remove(1) + except KeyError: + pass + return new + elif (func == TypeVar.HALFWIDTH): + return self.double_width() + elif (func == TypeVar.DOUBLEWIDTH): + return self.half_width() + elif (func == TypeVar.HALFVECTOR): + return self.double_vector() + elif (func == TypeVar.DOUBLEVECTOR): + return self.half_vector() + else: + assert False, "Unknown derived function: " + func + def size(self): # type: () -> int """ @@ -346,25 +416,20 @@ class TypeSet(object): typesets containing 1 type. """ assert self.size() == 1 + scalar_type = None # type: types.ScalarType if len(self.ints) > 0: - bits = tuple(self.ints)[0] - scalar_type = ValueType.by_name(IntType.get_name(bits)) + scalar_type = IntType.with_bits(tuple(self.ints)[0]) elif len(self.floats) > 0: - bits = tuple(self.floats)[0] - scalar_type = ValueType.by_name(FloatType.get_name(bits)) + scalar_type = FloatType.with_bits(tuple(self.floats)[0]) else: - bits = tuple(self.bools)[0] - scalar_type = ValueType.by_name(BoolType.get_name(bits)) + scalar_type = BoolType.with_bits(tuple(self.bools)[0]) nlanes = tuple(self.lanes)[0] if nlanes == 1: return scalar_type else: - if TYPE_CHECKING: - return cast(types.ScalarType, scalar_type).by(nlanes) - else: - return scalar_type.by(nlanes) + return scalar_type.by(nlanes) class TypeVar(object): @@ -483,6 +548,14 @@ class TypeVar(object): """Create a type variable that is a function of another.""" return TypeVar(None, None, base=base, derived_func=derived_func) + @staticmethod + def from_typeset(ts): + # type: (TypeSet) -> TypeVar + """ Create a type variable from a type set.""" + tv = TypeVar(None, None) + tv.type_set = ts + return tv + def change_to_derived(self, base, derived_func): # type: (TypeVar, str) -> None """Change this type variable into a derived one.""" @@ -615,6 +688,17 @@ class TypeVar(object): else: return self.name + def constrain_types_by_ts(self, ts): + # type: (TypeSet) -> None + """ + Constrain the range of types this variable can assume to a subset of + those in the typeset ts. + """ + if not self.is_derived: + self.type_set &= ts + else: + self.base.constrain_types_by_ts(ts.map_inverse(self.derived_func)) + def constrain_types(self, other): # type: (TypeVar) -> None """ @@ -628,18 +712,7 @@ class TypeVar(object): if a is b: return - if not a.is_derived and not b.is_derived: - a.type_set &= b.type_set - return - - # TODO: Implement constraints for derived type variables. - # - # If a and b are both derived with the same derived_func, we could say - # `a.base.constrain_types(b.base)`, but unless the derived_func is - # injective, that may constrain `a.base` more than necessary. - # - # For the fully general case, we would need to compute an image typeset - # for `b` and propagate a `a.derived_func` pre-image to `a.base`. + a.constrain_types_by_ts(b.get_typeset()) def get_typeset(self): # type: () -> TypeSet From c24f64de3b93ecac85ca67cb46dc37303890b3b1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Jun 2017 16:04:50 -0700 Subject: [PATCH 822/968] Process ghost instruction kills during coloring. Ghost instructions don't generate code, but they can keep registers alive. The coloring pass needs to process values killed by ghost instructions so it knows when the registers are freed up. Also track register pressure changes from ghost kills in the spiller. --- lib/cretonne/src/regalloc/coloring.rs | 40 ++++++++++++++++++- lib/cretonne/src/regalloc/diversion.rs | 15 +++++++ .../src/regalloc/live_value_tracker.rs | 10 +++++ lib/cretonne/src/regalloc/spilling.rs | 7 +++- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 56244bcc3c..878656ae84 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -147,8 +147,11 @@ impl<'a> Context<'a> { &mut regs, &mut func.locations, &func.signature); - tracker.drop_dead(inst); + } else { + let (_throughs, kills) = tracker.process_ghost(inst); + self.process_ghost_kills(kills, &mut regs, &func.locations); } + tracker.drop_dead(inst); } } @@ -377,6 +380,9 @@ impl<'a> Context<'a> { } } } + + self.forget_diverted(kills); + *regs = output_regs; } @@ -569,4 +575,36 @@ impl<'a> Context<'a> { dfg.ins(pos).regmove(m.value, m.from, m.to); } } + + /// Forget about any register diversions in `kills`. + fn forget_diverted(&mut self, kills: &[LiveValue]) { + if self.divert.is_empty() { + return; + } + + for lv in kills { + if lv.affinity.is_reg() { + self.divert.remove(lv.value); + } + } + } + + /// Process kills on a ghost instruction. + /// - Forget diversions. + /// - Free killed registers. + fn process_ghost_kills(&mut self, + kills: &[LiveValue], + regs: &mut AllocatableSet, + locations: &ValueLocations) { + for lv in kills { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + let reg = match self.divert.remove(lv.value) { + Some(r) => r, + None => locations[lv.value].unwrap_reg(), + }; + regs.free(rc, reg); + } + } + } } diff --git a/lib/cretonne/src/regalloc/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs index e8e8d3d190..e07b694ab9 100644 --- a/lib/cretonne/src/regalloc/diversion.rs +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -51,6 +51,11 @@ impl RegDiversions { self.current.clear() } + /// Are there any diversions? + pub fn is_empty(&self) -> bool { + self.current.is_empty() + } + /// Get the current diversion of `value`, if any. pub fn diversion(&self, value: Value) -> Option<&Diversion> { self.current.iter().find(|d| d.value == value) @@ -83,6 +88,16 @@ impl RegDiversions { self.current.push(Diversion::new(value, from, to)); } } + + /// Drop any recorded register move for `value`. + /// + /// Returns the `to` register of the removed diversion. + pub fn remove(&mut self, value: Value) -> Option { + self.current + .iter() + .position(|d| d.value == value) + .map(|i| self.current.swap_remove(i).to) + } } #[cfg(test)] diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index ccaf6c9209..b243b93923 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -280,6 +280,16 @@ impl LiveValueTracker { &self.live.values[first_def..]) } + /// Prepare to move past a ghost instruction. + /// + /// This is like `process_inst`, except any defs are ignored. + /// + /// Returns `(throughs, kills)`. + pub fn process_ghost(&mut self, inst: Inst) -> (&[LiveValue], &[LiveValue]) { + let first_kill = self.live.live_after(inst); + self.live.values.as_slice().split_at(first_kill) + } + /// Drop the values that are now dead after moving past `inst`. /// /// This removes both live values that were killed by `inst` and dead defines on `inst` itself. diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index f1feff3f4b..09559c9d26 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -129,9 +129,12 @@ impl<'a> Context<'a> { while let Some(inst) = pos.next_inst() { if let Some(constraints) = self.encinfo.operand_constraints(self.encodings[inst]) { self.visit_inst(inst, constraints, &mut pos, dfg, tracker); - tracker.drop_dead(inst); - self.process_spills(tracker); + } else { + let (_throughs, kills) = tracker.process_ghost(inst); + self.free_regs(kills); } + tracker.drop_dead(inst); + self.process_spills(tracker); } } From 1d20c92ffe003ba18dfbfb28677e9a7186ded38a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Jun 2017 12:59:23 -0700 Subject: [PATCH 823/968] Color EBB arguments. When coloring registers for a branch instruction, also make sure that the values passed as EBB arguments are in the registers expected by the EBB. The first time a branch to an EBB is processed, assign the EBB arguments to the registers where the branch arguments already reside so no regmoves are needed. --- filetests/regalloc/coalesce.cton | 6 +- lib/cretonne/src/ir/dfg.rs | 5 + lib/cretonne/src/ir/valueloc.rs | 2 +- lib/cretonne/src/regalloc/coloring.rs | 229 ++++++++++++++++++------- lib/cretonne/src/regalloc/context.rs | 7 +- lib/cretonne/src/regalloc/diversion.rs | 2 +- 6 files changed, 183 insertions(+), 68 deletions(-) diff --git a/filetests/regalloc/coalesce.cton b/filetests/regalloc/coalesce.cton index 8b76c8db6b..60ef905d15 100644 --- a/filetests/regalloc/coalesce.cton +++ b/filetests/regalloc/coalesce.cton @@ -61,7 +61,8 @@ ebb0(v0: i32): ; v1 and v0 interfere here: trapnz v0 ; check: $(cp1=$V) = copy $v1 - ; nextln: jump $ebb1($cp1) + ; not: copy + ; check: jump $ebb1($cp1) jump ebb1(v1) ebb1(v10: i32): @@ -85,7 +86,8 @@ ebb1(v10: i32, v11: i32): v12 = iadd v10, v11 v13 = icmp ult v12, v0 ; check: $(nv11b=$V) = copy $v11 - ; nextln: brnz $v13, $ebb1($nv11b, $v12) + ; not: copy + ; check: brnz $v13, $ebb1($nv11b, $v12) brnz v13, ebb1(v11, v12) return v12 } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 40f209a49b..51ada16775 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -104,6 +104,11 @@ impl DataFlowGraph { pub fn ebb_is_valid(&self, ebb: Ebb) -> bool { self.ebbs.is_valid(ebb) } + + /// Get the total number of values. + pub fn num_values(&self) -> usize { + self.values.len() + } } /// Resolve value aliases. diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index 8c5ea50dfa..83dbcd0010 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -8,7 +8,7 @@ use ir::StackSlot; use std::fmt; /// Value location. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ValueLoc { /// This value has not been assigned to a location yet. Unassigned, diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 878656ae84..cfb918d832 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -23,6 +23,10 @@ //! operands are allowed to read spilled values, but each such instance must be counted as using //! a register. //! +//! 5. The code must be in conventional SSA form. Among other things, this means that values passed +//! as arguments when branching to an EBB must belong to the same virtual register as the +//! corresponding EBB argument value. +//! //! # Iteration order //! //! The SSA property guarantees that whenever the live range of two values overlap, one of the @@ -30,10 +34,16 @@ //! a topological order relative to the dominance relation, we can assign colors to the values //! defined by the instruction and only consider the colors of other values that are live at the //! instruction. +//! +//! The first time we see a branch to an EBB, the EBB's argument values are colored to match the +//! registers currently holding branch argument values passed to the predecessor branch. By +//! visiting EBBs in a CFG topological order, we guarantee that at least one predecessor branch has +//! been visited before the destination EBB. Therefore, the EBB's arguments are already colored. +//! +//! The exception is the entry block whose arguments are colored from the ABI requirements. use dominator_tree::DominatorTree; -use entity_map::EntityMap; -use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, ValueLocations}; +use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Layout, ValueLocations}; use ir::{InstBuilder, Signature, ArgumentType, ArgumentLoc}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind}; @@ -42,8 +52,8 @@ use regalloc::affinity::Affinity; use regalloc::allocatable_set::AllocatableSet; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; +use regalloc::liverange::LiveRange; use regalloc::solver::Solver; -use topo_order::TopoOrder; /// Data structures for the coloring pass. @@ -75,7 +85,6 @@ struct Context<'a> { // References to working set data structures. // If we need to borrow out of a data structure across a method call, it must be passed as a // function argument instead, see the `LiveValueTracker` arguments. - topo: &'a mut TopoOrder, divert: &'a mut RegDiversions, solver: &'a mut Solver, @@ -99,7 +108,6 @@ impl Coloring { func: &mut Function, domtree: &DominatorTree, liveness: &mut Liveness, - topo: &mut TopoOrder, tracker: &mut LiveValueTracker) { dbg!("Coloring for:\n{}", func.display(isa)); let mut ctx = Context { @@ -107,7 +115,6 @@ impl Coloring { encinfo: isa.encoding_info(), domtree, liveness, - topo, divert: &mut self.divert, solver: &mut self.solver, usable_regs: isa.allocatable_registers(func), @@ -119,10 +126,11 @@ impl Coloring { impl<'a> Context<'a> { /// Run the coloring algorithm. fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { - // Just visit blocks in layout order, letting `self.topo` enforce a topological ordering. - // TODO: Once we have a loop tree, we could visit hot blocks first. - self.topo.reset(func.layout.ebbs()); - while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { + func.locations.resize(func.dfg.num_values()); + + // Visit blocks in reverse post-order. We need to ensure that at least one predecessor has + // been visited before each EBB. That guarantees that the EBB arguments have been colored. + for &ebb in self.domtree.cfg_postorder().iter().rev() { self.visit_ebb(ebb, func, tracker); } } @@ -164,28 +172,29 @@ impl<'a> Context<'a> { tracker: &mut LiveValueTracker) -> AllocatableSet { // Reposition the live value tracker and deal with the EBB arguments. - let (liveins, args) = - tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); - // Arguments to the entry block have ABI constraints. if func.layout.entry_block() == Some(ebb) { - assert_eq!(liveins.len(), 0); - self.color_entry_args(&func.signature, args, &mut func.locations) + // Arguments to the entry block have ABI constraints. + self.color_entry_args(&func.signature, tracker.live(), &mut func.locations) } else { - // The live-ins have already been assigned a register. Reconstruct the allocatable set. - let regs = self.livein_regs(liveins, func); - self.color_args(args, regs, &mut func.locations) + // The live-ins and arguments to a non-entry EBB have already been assigned a register. + // Reconstruct the allocatable set. + self.livein_regs(tracker.live(), func) } } /// Initialize a set of allocatable registers from the values that are live-in to a block. /// These values must already be colored when the dominating blocks were processed. - fn livein_regs(&self, liveins: &[LiveValue], func: &Function) -> AllocatableSet { + /// + /// Also process the EBB arguments which were colored when the first predecessor branch was + /// encountered. + fn livein_regs(&self, live: &[LiveValue], func: &Function) -> AllocatableSet { // Start from the registers that are actually usable. We don't want to include any reserved // registers in the set. let mut regs = self.usable_regs.clone(); - for lv in liveins { + for lv in live.iter().filter(|lv| !lv.is_dead) { let value = lv.value; let affinity = self.liveness .get(value) @@ -234,7 +243,7 @@ impl<'a> Context<'a> { if !lv.is_dead { regs.take(rc, reg); } - *locations.ensure(lv.value) = ValueLoc::Reg(reg); + locations[lv.value] = ValueLoc::Reg(reg); } else { // This should have been fixed by the reload pass. panic!("Entry arg {} has {} affinity, but ABI {}", @@ -266,39 +275,6 @@ impl<'a> Context<'a> { regs } - /// Color the live arguments to the current block. - /// - /// It is assumed that any live-in register values have already been taken out of the register - /// set. - fn color_args(&self, - args: &[LiveValue], - mut regs: AllocatableSet, - locations: &mut ValueLocations) - -> AllocatableSet { - // Available registers *after* filtering out the dead arguments. - let mut live_regs = regs.clone(); - - for lv in args { - // Only look at the register arguments. - if let Affinity::Reg(rci) = lv.affinity { - let rc = self.reginfo.rc(rci); - // TODO: Fall back to a top-level super-class. Sub-classes are only hints. - let reg = regs.iter(rc) - .next() - .expect("Out of registers for arguments"); - regs.take(rc, reg); - if !lv.is_dead { - live_regs.take(rc, reg); - } - *locations.ensure(lv.value) = ValueLoc::Reg(reg); - } - } - - // All arguments are accounted for in `regs`. We don't care about the dead arguments now - // that we have made sure they don't interfere. - live_regs - } - /// Color the values defined by `inst` and insert any necessary shuffle code to satisfy /// instruction constraints. /// @@ -315,6 +291,10 @@ impl<'a> Context<'a> { func_signature: &Signature) { dbg!("Coloring {}", dfg.display_inst(inst)); + // EBB whose arguments should be colored to match the current branch instruction's + // arguments. + let mut color_dest_args = None; + // Program the solver with register constraints for the input side. self.solver.reset(regs); self.program_input_constraints(inst, constraints.ins, dfg, locations); @@ -323,7 +303,25 @@ impl<'a> Context<'a> { self.program_input_abi(inst, &dfg.signatures[sig].argument_types, dfg, locations); } else if dfg[inst].opcode().is_return() { self.program_input_abi(inst, &func_signature.return_types, dfg, locations); + } else if dfg[inst].opcode().is_branch() { + // This is a branch, so we need to make sure that globally live values are in their + // global registers. For EBBs that take arguments, we also need to place the argument + // values in the expected registers. + if let Some(dest) = dfg[inst].branch_destination() { + if self.program_ebb_arguments(inst, dest, dfg, pos.layout, locations) { + color_dest_args = Some(dest); + } + } else { + // This is a multi-way branch like `br_table`. We only support arguments on + // single-destination branches. + assert_eq!(dfg.inst_variable_args(inst).len(), + 0, + "Can't handle EBB arguments: {}", + dfg.display_inst(inst)); + self.undivert_regs(|lr| !lr.is_local()); + } } + if self.solver.has_fixed_input_conflicts() { self.divert_fixed_input_conflicts(tracker.live(), locations); } @@ -365,9 +363,15 @@ impl<'a> Context<'a> { // registers around. self.shuffle_inputs(pos, dfg, regs); + // If this is the first time we branch to `dest`, color its arguments to match the current + // register state. + if let Some(dest) = color_dest_args { + self.color_ebb_arguments(inst, dest, dfg, locations); + } + // Apply the solution to the defs. for v in self.solver.vars().iter().filter(|&v| v.is_define()) { - *locations.ensure(v.value) = ValueLoc::Reg(v.solution); + locations[v.value] = ValueLoc::Reg(v.solution); } // Update `regs` for the next instruction, remove the dead defs. @@ -391,7 +395,7 @@ impl<'a> Context<'a> { inst: Inst, constraints: &[OperandConstraint], dfg: &DataFlowGraph, - locations: &EntityMap) { + locations: &ValueLocations) { for (op, &value) in constraints .iter() .zip(dfg.inst_args(inst)) @@ -425,7 +429,7 @@ impl<'a> Context<'a> { inst: Inst, abi_types: &[ArgumentType], dfg: &DataFlowGraph, - locations: &EntityMap) { + locations: &ValueLocations) { for (abi, &value) in abi_types.iter().zip(dfg.inst_variable_args(inst)) { if let ArgumentLoc::Reg(reg) = abi.location { if let Affinity::Reg(rci) = @@ -443,6 +447,115 @@ impl<'a> Context<'a> { } } + /// Prepare for a branch to `dest`. + /// + /// 1. Any values that are live-in to `dest` must be un-diverted so they live in their globally + /// assigned register. + /// 2. If the `dest` EBB takes arguments, reassign the branch argument values to the matching + /// registers. + /// + /// Returns true if this is the first time a branch to `dest` is seen, so the `dest` argument + /// values should be colored after `shuffle_inputs`. + fn program_ebb_arguments(&mut self, + inst: Inst, + dest: Ebb, + dfg: &DataFlowGraph, + layout: &Layout, + locations: &ValueLocations) + -> bool { + // Find diverted registers that are live-in to `dest` and reassign them to their global + // home. + // + // Values with a global live range that are not live in to `dest` could appear as branch + // arguments, so they can't always be un-diverted. + self.undivert_regs(|lr| lr.livein_local_end(dest, layout).is_some()); + + // Now handle the EBB arguments. + let br_args = dfg.inst_variable_args(inst); + let dest_args = dfg.ebb_args(dest); + assert_eq!(br_args.len(), dest_args.len()); + for (&dest_arg, &br_arg) in dest_args.iter().zip(br_args) { + // The first time we encounter a branch to `dest`, we get to pick the location. The + // following times we see a branch to `dest`, we must follow suit. + match locations[dest_arg] { + ValueLoc::Unassigned => { + // This is the first branch to `dest`, so we should color `dest_arg` instead of + // `br_arg`. However, we don't know where `br_arg` will end up until + // after `shuffle_inputs`. See `color_ebb_arguments` below. + return true; + } + ValueLoc::Reg(dest_reg) => { + // We've branched to `dest` before. Make sure we use the correct argument + // registers by reassigning `br_arg`. + let br_lr = self.liveness + .get(br_arg) + .expect("Missing live range for branch argument"); + if let Affinity::Reg(rci) = br_lr.affinity { + let rc = self.reginfo.rc(rci); + let br_reg = self.divert.reg(br_arg, locations); + self.solver.reassign_in(br_arg, rc, br_reg, dest_reg); + } else { + panic!("Branch argument {} is not in a register", br_arg); + } + } + ValueLoc::Stack(ss) => { + // The spiller should already have given us identical stack slots. + debug_assert_eq!(ValueLoc::Stack(ss), locations[br_arg]); + } + } + } + + // No `dest` arguments need coloring. + false + } + + /// Knowing that we've never seen a branch to `dest` before, color its arguments to match our + /// register state. + /// + /// This function is only called when `program_ebb_arguments()` returned `true`. + fn color_ebb_arguments(&mut self, + inst: Inst, + dest: Ebb, + dfg: &DataFlowGraph, + locations: &mut ValueLocations) { + let br_args = dfg.inst_variable_args(inst); + let dest_args = dfg.ebb_args(dest); + assert_eq!(br_args.len(), dest_args.len()); + for (&dest_arg, &br_arg) in dest_args.iter().zip(br_args) { + match locations[dest_arg] { + ValueLoc::Unassigned => { + let br_reg = self.divert.reg(br_arg, locations); + locations[dest_arg] = ValueLoc::Reg(br_reg); + } + ValueLoc::Reg(_) => panic!("{} arg {} already colored", dest, dest_arg), + // Spilled value consistency is verified by `program_ebb_arguments()` above. + ValueLoc::Stack(_) => {} + } + } + } + + /// Find all diverted registers where `pred` returns `true` and undo their diversion so they + /// are reallocated to their global register assignments. + fn undivert_regs(&mut self, mut pred: Pred) + where Pred: FnMut(&LiveRange) -> bool + { + for rdiv in self.divert.all() { + let lr = self.liveness + .get(rdiv.value) + .expect("Missing live range for diverted register"); + if pred(lr) { + if let Affinity::Reg(rci) = lr.affinity { + let rc = self.reginfo.rc(rci); + self.solver.reassign_in(rdiv.value, rc, rdiv.to, rdiv.from); + } else { + panic!("Diverted register {} with {} affinity", + rdiv.value, + lr.affinity.display(&self.reginfo)); + } + } + } + } + // Find existing live values that conflict with the fixed input register constraints programmed // into the constraint solver. Convert them to solver variables so they can be diverted. fn divert_fixed_input_conflicts(&mut self, @@ -525,7 +638,7 @@ impl<'a> Context<'a> { let ok = self.solver.add_fixed_output(rc, reg); assert!(ok, "Couldn't clear fixed output interference for {}", value); } - *locations.ensure(value) = ValueLoc::Reg(reg); + locations[value] = ValueLoc::Reg(reg); } /// Program the output-side constraints for `inst` into the constraint solver. diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 0908b02a1c..1961add95c 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -119,12 +119,7 @@ impl Context { // Pass: Coloring. self.coloring - .run(isa, - func, - domtree, - &mut self.liveness, - &mut self.topo, - &mut self.tracker); + .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); if isa.flags().enable_verifier() { verify_context(func, cfg, domtree, Some(isa))?; diff --git a/lib/cretonne/src/regalloc/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs index e07b694ab9..4f17d3685d 100644 --- a/lib/cretonne/src/regalloc/diversion.rs +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -61,7 +61,7 @@ impl RegDiversions { self.current.iter().find(|d| d.value == value) } - /// Get all current diversion. + /// Get all current diversions. pub fn all(&self) -> &[Diversion] { self.current.as_slice() } From ed3157f508a90f94e3c62a511650121357f06bf3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 28 Jun 2017 14:05:11 -0700 Subject: [PATCH 824/968] Add an offset to StackSlotData. The offset is relative to the stack pointer in the calling function, so it excludes the return address pushed by the call instruction itself on Intel ISAs. Change the ArgumentLoc::Stack offset to an i32, so it matches the stack slot offsets. --- filetests/parser/tiny.cton | 4 ++-- lib/cretonne/src/ir/extfunc.rs | 4 ++-- lib/cretonne/src/ir/stackslot.rs | 30 ++++++++++++++++++++++++++---- lib/cretonne/src/ir/types.rs | 5 +++++ lib/cretonne/src/ir/valueloc.rs | 2 +- lib/cretonne/src/isa/riscv/abi.rs | 3 ++- lib/reader/src/parser.rs | 23 ++++++++++++++++------- 7 files changed, 54 insertions(+), 17 deletions(-) diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 704477e6c2..ff3d386947 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -98,7 +98,7 @@ ebb0(v90: i32, v91: f32): function %stack() { ss10 = spill_slot 8 ss2 = local 4 - ss3 = incoming_arg 4 + ss3 = incoming_arg 4, offset 8 ss4 = outgoing_arg 4 ebb0: @@ -110,7 +110,7 @@ ebb0: ; sameln: function %stack() { ; nextln: $ss10 = spill_slot 8 ; nextln: $ss2 = local 4 -; nextln: $ss3 = incoming_arg 4 +; nextln: $ss3 = incoming_arg 4, offset 8 ; nextln: $ss4 = outgoing_arg 4 ; check: ebb0: diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 57346df5f2..e46ef39a54 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -51,8 +51,8 @@ impl Signature { let bytes = self.argument_types .iter() .filter_map(|arg| match arg.location { - ArgumentLoc::Stack(offset) => { - Some(offset + arg.value_type.bits() as u32 / 8) + ArgumentLoc::Stack(offset) if offset > 0 => { + Some(offset as u32 + arg.value_type.bytes()) } _ => None, }) diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index a1a5f0cb94..baeba2dd23 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -69,18 +69,33 @@ pub struct StackSlotData { /// Size of stack slot in bytes. pub size: u32, + + /// Offset of stack slot relative to the stack pointer in the caller. + /// + /// On Intel ISAs, the base address is the stack pointer *before* the return address was + /// pushed. On RISC ISAs, the base address is the value of the stack pointer on entry to the + /// function. + pub offset: i32, } impl StackSlotData { /// Create a stack slot with the specified byte size. pub fn new(kind: StackSlotKind, size: u32) -> StackSlotData { - StackSlotData { kind, size } + StackSlotData { + kind, + size, + offset: 0, + } } } impl fmt::Display for StackSlotData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.kind, self.size) + write!(f, "{} {}", self.kind, self.size)?; + if self.offset != 0 { + write!(f, ", offset {}", self.offset)?; + } + Ok(()) } } @@ -136,8 +151,15 @@ impl StackSlots { impl StackSlots { /// Create a new spill slot for spilling values of type `ty`. pub fn make_spill_slot(&mut self, ty: Type) -> StackSlot { - let bytes = (ty.bits() as u32 + 7) / 8; - self.push(StackSlotData::new(StackSlotKind::SpillSlot, bytes)) + self.push(StackSlotData::new(StackSlotKind::SpillSlot, ty.bytes())) + } + + /// Create a stack slot representing an incoming function argument. + pub fn make_incoming_arg(&mut self, ty: Type, offset: u32) -> StackSlot { + let mut data = StackSlotData::new(StackSlotKind::IncomingArg, ty.bytes()); + assert!(offset <= i32::max_value() as u32 - data.size); + data.offset = offset as i32; + self.push(data) } } diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index 550f25804b..e6379be2e6 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -197,6 +197,11 @@ impl Type { self.lane_bits() as u16 * self.lane_count() } + /// Get the number of bytes used to store this type in memory. + pub fn bytes(self) -> u32 { + (self.bits() as u32 + 7) / 8 + } + /// Get a SIMD vector type with `n` times more lanes than this one. /// /// If this is a scalar type, this produces a SIMD type with this as a lane type and `n` lanes. diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index 83dbcd0010..a582f1d934 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -86,7 +86,7 @@ pub enum ArgumentLoc { /// Argument is passed in a register. Reg(RegUnit), /// Argument is passed on the stack, at the given byte offset into the argument array. - Stack(u32), + Stack(i32), } impl Default for ArgumentLoc { diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index ab9e485ed5..66d4c2e6b7 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -77,8 +77,9 @@ impl ArgAssigner for Args { ArgumentLoc::Reg(reg).into() } else { // Assign a stack location. - let loc = ArgumentLoc::Stack(self.offset); + let loc = ArgumentLoc::Stack(self.offset as i32); self.offset += self.pointer_bytes; + assert!(self.offset <= i32::max_value() as u32); loc.into() } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index ae88e09873..f698b1f54f 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -428,15 +428,15 @@ impl<'a> Parser<'a> { } } - // Match and consume a u32 immediate. + // Match and consume an i32 immediate. // This is used for stack argument byte offsets. - fn match_uimm32(&mut self, err_msg: &str) -> Result { + fn match_imm32(&mut self, err_msg: &str) -> Result { if let Some(Token::Integer(text)) = self.token() { self.consume(); // Lexer just gives us raw text that looks like an integer. - // Parse it as a u32 to check for overflow and other issues. + // Parse it as a i32 to check for overflow and other issues. text.parse() - .map_err(|_| self.error("expected u32 decimal immediate")) + .map_err(|_| self.error("expected i32 decimal immediate")) } else { err!(self.loc, err_msg) } @@ -837,7 +837,7 @@ impl<'a> Parser<'a> { } } Some(Token::Integer(_)) => { - let offset = self.match_uimm32("expected stack argument byte offset")?; + let offset = self.match_imm32("expected stack argument byte offset")?; Ok(ArgumentLoc::Stack(offset)) } Some(Token::Minus) => { @@ -870,8 +870,9 @@ impl<'a> Parser<'a> { match self.token() { Some(Token::StackSlot(..)) => { self.gather_comments(ctx.function.stack_slots.next_key()); + let loc = self.loc; self.parse_stack_slot_decl() - .and_then(|(num, dat)| ctx.add_ss(num, dat, &self.loc)) + .and_then(|(num, dat)| ctx.add_ss(num, dat, &loc)) } Some(Token::SigRef(..)) => { self.gather_comments(ctx.function.dfg.signatures.next_key()); @@ -915,7 +916,15 @@ impl<'a> Parser<'a> { if bytes > u32::MAX as i64 { return err!(self.loc, "stack slot too large"); } - let data = StackSlotData::new(kind, bytes as u32); + let mut data = StackSlotData::new(kind, bytes as u32); + + // Take additional options. + while self.optional(Token::Comma) { + match self.match_any_identifier("expected stack slot flags")? { + "offset" => data.offset = self.match_imm32("expected byte offset")?, + other => return err!(self.loc, "Unknown stack slot flag '{}'", other), + } + } // TBD: stack-slot-decl ::= StackSlot(ss) "=" stack-slot-kind Bytes * {"," stack-slot-flag} Ok((number, data)) From 2a600b3632d30536b3352406017831061e0af42c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 28 Jun 2017 14:59:59 -0700 Subject: [PATCH 825/968] Assign stack slots to incoming function arguments. Function arguments that don't fit in registers are passed on the stack. Create "incoming_arg" stack slots representing the stack arguments, and assign them to the value arguments during spilling. --- filetests/regalloc/spill.cton | 12 +++++++++++ lib/cretonne/src/ir/stackslot.rs | 6 +++--- lib/cretonne/src/ir/valueloc.rs | 8 ++++++++ lib/cretonne/src/regalloc/coloring.rs | 15 ++------------ lib/cretonne/src/regalloc/spilling.rs | 29 ++++++++++++++++++++++++++- 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index 23ddf7129b..d30903dc2d 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -109,3 +109,15 @@ ebb0(v0: i32): ; check: call_indirect $sig0, $v0($v0, $c) return } + +; Two arguments on the stack. +function %stackargs(i32, i32, i32, i32, i32, i32, i32, i32) -> i32 { +; check: ss0 = incoming_arg 4 +; check: ss1 = incoming_arg 4, offset 4 +; not: incoming_arg +ebb0(v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32): + ; unordered: fill $v6 + ; unordered: fill $v7 + v10 = iadd v6, v7 + return v10 +} diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index baeba2dd23..c6fd64ce5c 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -155,10 +155,10 @@ impl StackSlots { } /// Create a stack slot representing an incoming function argument. - pub fn make_incoming_arg(&mut self, ty: Type, offset: u32) -> StackSlot { + pub fn make_incoming_arg(&mut self, ty: Type, offset: i32) -> StackSlot { let mut data = StackSlotData::new(StackSlotKind::IncomingArg, ty.bytes()); - assert!(offset <= i32::max_value() as u32 - data.size); - data.offset = offset as i32; + assert!(offset <= i32::max_value() - data.size as i32); + data.offset = offset; self.push(data) } } diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index a582f1d934..cae3042170 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -112,6 +112,14 @@ impl ArgumentLoc { } } + /// Is this a stack location? + pub fn is_stack(&self) -> bool { + match *self { + ArgumentLoc::Stack(_) => true, + _ => false, + } + } + /// Return an object that can display this argument location, using the register info from the /// target ISA. pub fn display<'a, R: Into>>(self, regs: R) -> DisplayArgumentLoc<'a> { diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index cfb918d832..c2353de40e 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -253,19 +253,8 @@ impl<'a> Context<'a> { } } - Affinity::Stack => { - if let ArgumentLoc::Stack(_offset) = abi.location { - // TODO: Allocate a stack slot at incoming offset and assign it. - panic!("Unimplemented {}: {} stack allocation", - lv.value, - abi.display(&self.reginfo)); - } else { - // This should have been fixed by the reload pass. - panic!("Entry arg {} has stack affinity, but ABI {}", - lv.value, - abi.display(&self.reginfo)); - } - } + // The spiller will have assigned an incoming stack slot already. + Affinity::Stack => assert!(abi.location.is_stack()), // This is a ghost value, unused in the function. Don't assign it to a location // either. Affinity::None => {} diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 09559c9d26..c64fd565e3 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -17,7 +17,7 @@ use dominator_tree::DominatorTree; use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; -use ir::{Function, Ebb, Inst, Value, ValueLoc, SigRef}; +use ir::{Function, Ebb, Inst, Value, ValueLoc, ArgumentLoc, Signature, SigRef}; use ir::{InstEncodings, StackSlots, ValueLocations}; use isa::registers::{RegClass, RegClassMask}; use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; @@ -45,6 +45,7 @@ struct Context<'a> { encodings: &'a mut InstEncodings, stack_slots: &'a mut StackSlots, locations: &'a mut ValueLocations, + func_signature: &'a Signature, // References to contextual data structures we need. domtree: &'a DominatorTree, @@ -92,6 +93,7 @@ impl Spilling { encodings: &mut func.encodings, stack_slots: &mut func.stack_slots, locations: &mut func.locations, + func_signature: &func.signature, domtree, liveness, virtregs, @@ -109,12 +111,37 @@ impl<'a> Context<'a> { layout: &mut Layout, dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker) { + if let Some(entry) = layout.entry_block() { + self.spill_entry_arguments(entry, dfg); + } + self.topo.reset(layout.ebbs()); while let Some(ebb) = self.topo.next(layout, self.domtree) { self.visit_ebb(ebb, layout, dfg, tracker); } } + /// Assign stack slots to incoming function arguments on the stack. + fn spill_entry_arguments(&mut self, entry: Ebb, dfg: &DataFlowGraph) { + for (abi, &arg) in self.func_signature + .argument_types + .iter() + .zip(dfg.ebb_args(entry)) { + if let ArgumentLoc::Stack(offset) = abi.location { + // Function arguments passed on the stack can't be part of a virtual register. We + // would need to write other values to the stack slot, but it belongs to the + // caller. (Not that the caller would care, nobody depends on stack arguments being + // preserved across calls). + assert_eq!(self.virtregs.get(arg), + None, + "Stack argument {} can't be part of a virtual register", + arg); + let ss = self.stack_slots.make_incoming_arg(abi.value_type, offset); + *self.locations.ensure(arg) = ValueLoc::Stack(ss); + } + } + } + fn visit_ebb(&mut self, ebb: Ebb, layout: &mut Layout, From c4532b901ef4ae61094c0722ae991d84ea31ed70 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 28 Jun 2017 15:37:38 -0700 Subject: [PATCH 826/968] Don't coalesce incoming stack arguments. A function parameter in an incoming_arg stack slot should not be coalesced into any virtual registers. We don't want to force the whole virtual register to spill to the incoming_arg slot. --- filetests/regalloc/coalesce.cton | 18 ++++++++++++++++++ lib/cretonne/src/regalloc/coalescing.rs | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/filetests/regalloc/coalesce.cton b/filetests/regalloc/coalesce.cton index 60ef905d15..41fc6e9771 100644 --- a/filetests/regalloc/coalesce.cton +++ b/filetests/regalloc/coalesce.cton @@ -91,3 +91,21 @@ ebb1(v10: i32, v11: i32): brnz v13, ebb1(v11, v12) return v12 } + +; Function arguments passed on the stack aren't allowed to be part of a virtual +; register, at least for now. This is because the other values in the virtual +; register would need to be spilled to the incoming_arg stack slot which we treat +; as belonging to the caller. +function %stackarg(i32, i32, i32, i32, i32, i32, i32, i32, i32) -> i32 { +; check: ss0 = incoming_arg 4 +; not: incoming_arg +ebb0(v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32, v8: i32): + ; check: fill v8 + ; not: v8 + brnz v0, ebb1(v8) + jump ebb1(v7) + +ebb1(v10: i32): + v11 = iadd_imm v10, 1 + return v11 +} diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 70a20e3897..634f0c0a32 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -8,7 +8,7 @@ use dbg::DisplayList; use dominator_tree::DominatorTree; use flowgraph::{ControlFlowGraph, BasicBlock}; -use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; +use ir::{DataFlowGraph, Layout, Cursor, InstBuilder, ValueDef}; use ir::{Function, Ebb, Inst, Value, ExpandedProgramPoint}; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; @@ -400,6 +400,22 @@ impl<'a> Context<'a> { pred_val, pred_ebb, self.func.dfg.display_inst(pred_inst)); + + // Never coalesce incoming function arguments on the stack. These arguments are + // pre-spilled, and the rest of the virtual register would be forced to spill to the + // `incoming_arg` stack slot too. + if let ValueDef::Arg(def_ebb, def_num) = self.func.dfg.value_def(pred_val) { + if Some(def_ebb) == self.func.layout.entry_block() && + self.func.signature.argument_types[def_num] + .location + .is_stack() { + dbg!("Isolating incoming stack parameter {}", pred_val); + let new_val = self.split_pred(pred_inst, pred_ebb, argnum, pred_val); + assert!(self.add_class(new_val).is_ok()); + continue; + } + } + if let Err((a, b)) = self.add_class(pred_val) { dbg!("Found conflict between {} and {}", a, b); // We have a conflict between the already merged value `a` and one of the new From cd1503eced90a6505662016bc1fa646ef3d458fb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 28 Jun 2017 19:30:36 -0700 Subject: [PATCH 827/968] Make sure return values are assigned an affinity. When an EBB argument value is used only as a return value, it still needs to be given a register affinity. Otherwise it would appear as a ghost value with no affinity. Do the same to call arguments. --- filetests/regalloc/basic.cton | 23 +++++++++++++++++++++++ lib/cretonne/src/regalloc/liveness.rs | 17 ++++++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index 36c0e5c81c..dd6cab11cf 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -40,3 +40,26 @@ ebb0(v1: i32, v2: i32): ; nextln: return $v2, $v1 return v2, v1 } + +; Return an EBB argument. +function %retebb(i32, i32) -> i32 { +ebb0(v1: i32, v2: i32): + brnz v1, ebb1(v1) + jump ebb1(v2) + +ebb1(v10: i32): + return v10 +} + +; Pass an EBB argument as a function argument. +function %callebb(i32, i32) -> i32 { + fn0 = function %foo(i32) -> i32 + +ebb0(v1: i32, v2: i32): + brnz v1, ebb1(v1) + jump ebb1(v2) + +ebb1(v10: i32): + v11 = call fn0(v10) + return v11 +} diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index afccbffaa3..71fa4961e7 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -376,9 +376,9 @@ impl Liveness { } // Iterator of constraints, one per value operand. - // TODO: Should we fail here if the instruction doesn't have a valid encoding? + let encoding = func.encodings[inst]; let mut operand_constraints = enc_info - .operand_constraints(func.encodings[inst]) + .operand_constraints(encoding) .map(|c| c.ins) .unwrap_or(&[]) .iter(); @@ -392,11 +392,18 @@ impl Liveness { // Apply operand constraint, ignoring any variable arguments after the fixed // operands described by `operand_constraints`. Variable arguments are either - // EBB arguments or call/return ABI arguments. EBB arguments need to be - // resolved by the coloring algorithm, and ABI constraints require specific - // registers or stack slots which the affinities don't model anyway. + // EBB arguments or call/return ABI arguments. if let Some(constraint) = operand_constraints.next() { lr.affinity.merge(constraint, ®_info); + } else if lr.affinity.is_none() && encoding.is_legal() && + !func.dfg[inst].opcode().is_branch() { + // This is a real encoded instruction using a value that doesn't yet have a + // concrete affinity. Most likely a call argument or a return value. Give + // the value a register affinity matching the ABI type. + // + // EBB arguments on a branch are not required to have an affinity. + let rc = isa.regclass_for_abi_type(func.dfg.value_type(arg)); + lr.affinity = Affinity::Reg(rc.into()); } } } From 6d34476cd695fc9f041f90fdecd9f1090e0fae0e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 29 Jun 2017 10:30:26 -0700 Subject: [PATCH 828/968] Propagate affinities for EBB arguments. A priory, an EBB argument value only gets an affinity if it is used directly by a non-ghost instruction. A use by a branch passing arguments to an EBB doesn't count. When an EBB argument value does have an affinity, the values passed by all the predecessors must also have affinities. This can cause EBB argument values to get affinities recursively. - Add a second pass to the liveness computation for propagating EBB argument affinities, possibly recursively. - Verify EBB argument affinities correctly: A value passed to a branch must have an affinity only if the corresponding EBB argument value in the destination has an affinity. --- filetests/regalloc/basic.cton | 15 ++++++++++ lib/cretonne/src/regalloc/liveness.rs | 41 +++++++++++++++++++++++++++ lib/cretonne/src/verifier/liveness.rs | 33 +++++++++++++++++++-- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton index dd6cab11cf..89e160d75a 100644 --- a/filetests/regalloc/basic.cton +++ b/filetests/regalloc/basic.cton @@ -63,3 +63,18 @@ ebb1(v10: i32): v11 = call fn0(v10) return v11 } + +; Pass an EBB argument as a jump argument. +function %jumpebb(i32, i32) -> i32 { + fn0 = function %foo(i32) -> i32 + +ebb0(v1: i32, v2: i32): + brnz v1, ebb1(v1, v2) + jump ebb1(v2, v1) + +ebb1(v10: i32, v11: i32): + jump ebb2(v10, v11) + +ebb2(v20: i32, v21: i32): + return v21 +} diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 71fa4961e7..2093b15299 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -279,6 +279,9 @@ pub struct Liveness { /// This vector is always empty, except for inside that function. /// It lives here to avoid repeated allocation of scratch memory. worklist: Vec, + + /// Working space for the `propagate_ebb_arguments` algorithm. + ebb_args: Vec, } impl Liveness { @@ -290,6 +293,7 @@ impl Liveness { Liveness { ranges: LiveRangeSet::new(), worklist: Vec::new(), + ebb_args: Vec::new(), } } @@ -408,5 +412,42 @@ impl Liveness { } } } + + self.propagate_ebb_arguments(func, cfg); + } + + /// Propagate affinities for EBB arguments. + /// + /// If an EBB argument value has an affinity, all predecessors must pass a value with an + /// affinity. + pub fn propagate_ebb_arguments(&mut self, func: &Function, cfg: &ControlFlowGraph) { + assert!(self.ebb_args.is_empty()); + + for ebb in func.layout.ebbs() { + for &arg in func.dfg.ebb_args(ebb).iter() { + let affinity = self.ranges.get(arg).unwrap().affinity; + if affinity.is_none() { + continue; + } + self.ebb_args.push(arg); + + // Now apply the affinity to all predecessors recursively. + while let Some(succ_arg) = self.ebb_args.pop() { + let (succ_ebb, num) = match func.dfg.value_def(succ_arg) { + ValueDef::Arg(e, n) => (e, n), + _ => continue, + }; + + for &(_, pred_branch) in cfg.get_predecessors(succ_ebb) { + let pred_arg = func.dfg.inst_variable_args(pred_branch)[num]; + let pred_affinity = &mut self.ranges.get_mut(pred_arg).unwrap().affinity; + if pred_affinity.is_none() { + *pred_affinity = affinity; + self.ebb_args.push(pred_arg); + } + } + } + } + } } } diff --git a/lib/cretonne/src/verifier/liveness.rs b/lib/cretonne/src/verifier/liveness.rs index 51911b3f38..2dff4a6e3e 100644 --- a/lib/cretonne/src/verifier/liveness.rs +++ b/lib/cretonne/src/verifier/liveness.rs @@ -93,7 +93,7 @@ impl<'a> LivenessVerifier<'a> { } // Check the uses. - for &val in self.func.dfg.inst_args(inst) { + for (idx, &val) in self.func.dfg.inst_args(inst).iter().enumerate() { let lr = match self.liveness.get(val) { Some(lr) => lr, None => return err!(inst, "{} has no live range", val), @@ -104,7 +104,10 @@ impl<'a> LivenessVerifier<'a> { if encoding.is_legal() { // A legal instruction is not allowed to depend on ghost values. - if lr.affinity.is_none() { + // + // A branch argument can be a ghost value if the corresponding destination + // EBB argument is a ghost value. + if lr.affinity.is_none() && !self.is_ghost_branch_argument(inst, idx) { return err!(inst, "{} is a ghost value used by a real [{}] instruction", val, @@ -134,6 +137,32 @@ impl<'a> LivenessVerifier<'a> { } } + /// Is argument `argnum` on `inst` a branch argument that leads to a ghost EBB argument? + fn is_ghost_branch_argument(&self, inst: Inst, argnum: usize) -> bool { + let dest = match self.func.dfg[inst].branch_destination() { + Some(d) => d, + None => return false, + }; + + let fixed_args = self.func.dfg[inst] + .opcode() + .constraints() + .fixed_value_arguments(); + if argnum < fixed_args { + return false; + } + + // If the EBB argument value in the destination is a ghost value, we'll allow a ghost + // branch argument. + self.func + .dfg + .ebb_args(dest) + .get(argnum - fixed_args) + .and_then(|&v| self.liveness.get(v)) + .map(|lr| lr.affinity.is_none()) + .unwrap_or(false) + } + /// Check the integrity of the live range `lr`. fn check_lr(&self, def: ProgramPoint, val: Value, lr: &LiveRange) -> Result { let l = &self.func.layout; From 66dbf9517b4243a5789ec145415abba715eb6e64 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 29 Jun 2017 11:11:46 -0700 Subject: [PATCH 829/968] Split spill_from() into spill_candidate() and spill_reg(). We'll need to pick a spill candidate from a set and allow for the search to fail to find anything. This also allows slightly better panic messages when we run out of registers. --- lib/cretonne/src/regalloc/spilling.rs | 52 ++++++++++++++++----------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index c64fd565e3..1b438a7b50 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -260,7 +260,14 @@ impl<'a> Context<'a> { // Add register def to pressure, spill if needed. while let Err(mask) = self.pressure.take_transient(op.regclass) { dbg!("Need {} reg from {} throughs", op.regclass, throughs.len()); - self.spill_from(mask, throughs, dfg, pos.layout); + match self.spill_candidate(mask, throughs, dfg, pos.layout) { + Some(cand) => self.spill_reg(cand, dfg), + None => { + panic!("Ran out of {} registers for {}", + op.regclass, + dfg.display_inst(inst)) + } + } } } } @@ -341,22 +348,32 @@ impl<'a> Context<'a> { // Spill a live register that is *not* used by the current instruction. // Spilling a use wouldn't help. let args = dfg.inst_args(inst); - self.spill_from(mask, - tracker.live().iter().filter(|lv| !args.contains(&lv.value)), - dfg, - &pos.layout); + match self.spill_candidate(mask, + tracker.live().iter().filter(|lv| { + !args.contains(&lv.value) + }), + dfg, + &pos.layout) { + Some(cand) => self.spill_reg(cand, dfg), + None => { + panic!("Ran out of {} registers when inserting copy before {}", + rc, + dfg.display_inst(inst)) + } + } } } self.pressure.reset_transient(); self.reg_uses.clear() } - // Spill a candidate from `candidates` whose top-level register class is in `mask`. - fn spill_from<'ii, II>(&mut self, - mask: RegClassMask, - candidates: II, - dfg: &DataFlowGraph, - layout: &Layout) + // Find a spill candidate from `candidates` whose top-level register class is in `mask`. + fn spill_candidate<'ii, II>(&self, + mask: RegClassMask, + candidates: II, + dfg: &DataFlowGraph, + layout: &Layout) + -> Option where II: IntoIterator { // Find the best viable spill candidate. @@ -367,7 +384,7 @@ impl<'a> Context<'a> { // // We know that all candidate defs dominate the current instruction, so one of them will // dominate the others. That is the earliest def. - let best = candidates + candidates .into_iter() .filter_map(|lv| { // Viable candidates are registers in one of the `mask` classes, and not already in @@ -385,14 +402,7 @@ impl<'a> Context<'a> { // Find the minimum candidate according to the RPO of their defs. self.domtree .rpo_cmp(dfg.value_def(a), dfg.value_def(b), layout) - }); - - if let Some(value) = best { - // Found a spill candidate. - self.spill_reg(value, dfg); - } else { - panic!("Ran out of registers for mask={}", mask); - } + }) } /// Spill `value` immediately by @@ -424,7 +434,7 @@ impl<'a> Context<'a> { /// Process any pending spills in the `self.spills` vector. /// /// It is assumed that spills are removed from the pressure tracker immediately, see - /// `spill_from` above. + /// `spill_reg` above. /// /// We also need to update the live range affinity and remove spilled values from the live /// value tracker. From 8c84e2b2aa323430ab11d09d452d37b49f204ac1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 29 Jun 2017 13:29:43 -0700 Subject: [PATCH 830/968] Only color EBB arguments that have register affinity. It is possible to pass a register value as an argument to an EBB that expects a "None" affinity. In that case, the destination EBB value should not be colored. --- lib/cretonne/src/regalloc/coloring.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index c2353de40e..1a21f78f7e 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -200,13 +200,13 @@ impl<'a> Context<'a> { .get(value) .expect("No live range for live-in") .affinity; + dbg!("Live-in: {}:{} in {}", + value, + affinity.display(&self.reginfo), + func.locations[value].display(&self.reginfo)); if let Affinity::Reg(rci) = affinity { let rc = self.reginfo.rc(rci); let loc = func.locations[value]; - dbg!("Live-in: {}:{} in {}", - lv.value, - rc, - loc.display(&self.reginfo)); match loc { ValueLoc::Reg(reg) => regs.take(rc, reg), ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", value), @@ -471,7 +471,12 @@ impl<'a> Context<'a> { // This is the first branch to `dest`, so we should color `dest_arg` instead of // `br_arg`. However, we don't know where `br_arg` will end up until // after `shuffle_inputs`. See `color_ebb_arguments` below. - return true; + // + // It is possible for `dest_arg` to have no affinity, and then it should simply + // be ignored. + if self.liveness.get(dest_arg).unwrap().affinity.is_reg() { + return true; + } } ValueLoc::Reg(dest_reg) => { // We've branched to `dest` before. Make sure we use the correct argument @@ -513,8 +518,10 @@ impl<'a> Context<'a> { for (&dest_arg, &br_arg) in dest_args.iter().zip(br_args) { match locations[dest_arg] { ValueLoc::Unassigned => { - let br_reg = self.divert.reg(br_arg, locations); - locations[dest_arg] = ValueLoc::Reg(br_reg); + if self.liveness.get(dest_arg).unwrap().affinity.is_reg() { + let br_reg = self.divert.reg(br_arg, locations); + locations[dest_arg] = ValueLoc::Reg(br_reg); + } } ValueLoc::Reg(_) => panic!("{} arg {} already colored", dest, dest_arg), // Spilled value consistency is verified by `program_ebb_arguments()` above. From 3fbcdb4ea610a9658f4e4ec9b0beff5dc1dc97a1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 29 Jun 2017 14:07:19 -0700 Subject: [PATCH 831/968] Spill live-ins and EBB arguments if there are too many. --- filetests/regalloc/spill.cton | 23 ++++++++++++++++ lib/cretonne/src/regalloc/pressure.rs | 8 ++++++ lib/cretonne/src/regalloc/spilling.rs | 38 +++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index d30903dc2d..71c16ed50a 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -121,3 +121,26 @@ ebb0(v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32): v10 = iadd v6, v7 return v10 } + +; More EBB arguments than registers. +function %ebbargs(i32) -> i32 { +ebb0(v1: i32): + ; check: $v1 = spill + v2 = iconst.i32 1 + jump ebb1(v2, v2, v2, v2, v2, v2, v2, v2, v2, v2, v2, v2) + +ebb1(v10: i32, v11: i32, v12: i32, v13: i32, v14: i32, v15: i32, v16: i32, v17: i32, v18: i32, v19: i32, v20: i32, v21: i32): + v22 = iadd v10, v11 + v23 = iadd v22, v12 + v24 = iadd v23, v13 + v25 = iadd v24, v14 + v26 = iadd v25, v15 + v27 = iadd v26, v16 + v28 = iadd v27, v17 + v29 = iadd v28, v18 + v30 = iadd v29, v19 + v31 = iadd v30, v20 + v32 = iadd v31, v21 + v33 = iadd v32, v1 + return v33 +} diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index 4839c7451a..3bf1e68f54 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -224,6 +224,14 @@ impl Pressure { e.transient_count = 0; } } + + /// Preserve the transient counts by transferring them to the base counts. + pub fn preserve_transient(&mut self) { + for e in &mut self.toprc { + e.base_count += e.transient_count; + e.transient_count = 0; + } + } } impl fmt::Display for Pressure { diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 1b438a7b50..2a05fbb8c3 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -200,8 +200,42 @@ impl<'a> Context<'a> { self.pressure.reset(); self.take_live_regs(liveins); - // TODO: Process and count EBB arguments. Some may need spilling. - self.take_live_regs(args); + // An EBB can have an arbitrary (up to 2^16...) number of EBB arguments, so they are not + // guaranteed to fit in registers. + for lv in args { + if let Affinity::Reg(rci) = lv.affinity { + let rc = self.reginfo.rc(rci); + 'try_take: while let Err(mask) = self.pressure.take_transient(rc) { + dbg!("Need {} reg for EBB argument {} from {} live-ins", + rc, + lv.value, + liveins.len()); + match self.spill_candidate(mask, liveins, dfg, layout) { + Some(cand) => { + dbg!("Spilling live-in {} to make room for {} EBB argument {}", + cand, + rc, + lv.value); + self.spill_reg(cand, dfg); + } + None => { + // We can't spill any of the live-in registers, so we have to spill an + // EBB argument. Since the current spill metric would consider all the + // EBB arguments equal, just spill the present register. + dbg!("Spilling {} EBB argument {}", rc, lv.value); + + // Since `spill_reg` will free a register, add the current one here. + self.pressure.take(rc); + self.spill_reg(lv.value, dfg); + break 'try_take; + } + } + } + } + } + + // The transient pressure counts for the EBB arguments are accurate. Just preserve them. + self.pressure.preserve_transient(); } fn visit_inst(&mut self, From 51dcabd87c29bf9a57db81ce17e43ca6ca2bd09c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 29 Jun 2017 15:13:04 -0700 Subject: [PATCH 832/968] Add an Index implementation to Liveness. Use it to access live ranges that are supposed to be there. --- lib/cretonne/src/regalloc/coloring.rs | 9 +++------ lib/cretonne/src/regalloc/live_value_tracker.rs | 5 +---- lib/cretonne/src/regalloc/liveness.rs | 12 ++++++++++++ lib/cretonne/src/regalloc/reload.rs | 3 +-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 1a21f78f7e..733506ef0d 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -474,17 +474,14 @@ impl<'a> Context<'a> { // // It is possible for `dest_arg` to have no affinity, and then it should simply // be ignored. - if self.liveness.get(dest_arg).unwrap().affinity.is_reg() { + if self.liveness[dest_arg].affinity.is_reg() { return true; } } ValueLoc::Reg(dest_reg) => { // We've branched to `dest` before. Make sure we use the correct argument // registers by reassigning `br_arg`. - let br_lr = self.liveness - .get(br_arg) - .expect("Missing live range for branch argument"); - if let Affinity::Reg(rci) = br_lr.affinity { + if let Affinity::Reg(rci) = self.liveness[br_arg].affinity { let rc = self.reginfo.rc(rci); let br_reg = self.divert.reg(br_arg, locations); self.solver.reassign_in(br_arg, rc, br_reg, dest_reg); @@ -518,7 +515,7 @@ impl<'a> Context<'a> { for (&dest_arg, &br_arg) in dest_args.iter().zip(br_args) { match locations[dest_arg] { ValueLoc::Unassigned => { - if self.liveness.get(dest_arg).unwrap().affinity.is_reg() { + if self.liveness[dest_arg].affinity.is_reg() { let br_reg = self.divert.reg(br_arg, locations); locations[dest_arg] = ValueLoc::Reg(br_reg); } diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index b243b93923..bee25aa287 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -260,10 +260,7 @@ impl LiveValueTracker { // Add the values defined by `inst`. let first_def = self.live.values.len(); for &value in dfg.inst_results(inst) { - let lr = match liveness.get(value) { - Some(lr) => lr, - None => panic!("{} result {} has no live range", dfg[inst].opcode(), value), - }; + let lr = &liveness[value]; assert_eq!(lr.def(), inst.into()); match lr.def_local_end().into() { ExpandedProgramPoint::Inst(endpoint) => { diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 2093b15299..973e278f62 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -183,6 +183,7 @@ use regalloc::affinity::Affinity; use regalloc::liverange::LiveRange; use sparse_map::SparseMap; use std::mem; +use std::ops::Index; /// A set of live ranges, indexed by value number. type LiveRangeSet = SparseMap; @@ -451,3 +452,14 @@ impl Liveness { } } } + +impl Index for Liveness { + type Output = LiveRange; + + fn index(&self, index: Value) -> &LiveRange { + match self.ranges.get(index) { + Some(lr) => lr, + None => panic!("{} has no live range", index), + } + } +} diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index e8327b5a73..93860071d3 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -285,8 +285,7 @@ impl<'a> Context<'a> { for (op, &arg) in constraints.ins.iter().zip(args) { if op.kind != ConstraintKind::Stack { - let lv = self.liveness.get(arg).expect("Missing live range for arg"); - if lv.affinity.is_stack() { + if self.liveness[arg].affinity.is_stack() { self.candidates .push(ReloadCandidate { value: arg, From bf5281ca41a62c8e36917274881f26494fbb79aa Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 29 Jun 2017 16:51:05 -0700 Subject: [PATCH 833/968] Repair constraint violations during spilling. The following constraints may need to be resolved during spilling because the resolution increases register pressure: - A tied operand whose value is live through the instruction. - A fixed register constraint for a value used more than once. - A register use of a spilled value needs to account for the reload register. --- filetests/regalloc/spill.cton | 50 +++++++++ lib/cretonne/src/regalloc/spilling.rs | 152 ++++++++++++++++---------- 2 files changed, 147 insertions(+), 55 deletions(-) diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index 71c16ed50a..4c470a73e6 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -144,3 +144,53 @@ ebb1(v10: i32, v11: i32, v12: i32, v13: i32, v14: i32, v15: i32, v16: i32, v17: v33 = iadd v32, v1 return v33 } + +; In straight-line code, the first value defined is spilled. +; That is in order: +; 1. The argument v1. +; 2. The link register. +; 3. The first computed value, v2 +function %use_spilled_value(i32) -> i32 { +; check: ss0 = spill_slot 4 +; check: ss1 = spill_slot 4 +; check: ss2 = spill_slot 4 +ebb0(v1: i32): +; check: $ebb0($(rv1=$V): i32, $(rlink=$V): i32) + ; check: ,ss0]$WS $v1 = spill $rv1 + ; nextln: ,ss1]$WS $(link=$V) = spill $rlink + ; not: spill + v2 = iadd_imm v1, 12 + ; check: $(r1v2=$V) = iadd_imm + ; nextln: ,ss2]$WS $v2 = spill $r1v2 + v3 = iadd_imm v2, 12 + v4 = iadd_imm v3, 12 + v5 = iadd_imm v4, 12 + v6 = iadd_imm v5, 12 + v7 = iadd_imm v6, 12 + v8 = iadd_imm v7, 12 + v9 = iadd_imm v8, 12 + v10 = iadd_imm v9, 12 + v11 = iadd_imm v10, 12 + v12 = iadd_imm v11, 12 + v13 = iadd_imm v12, 12 + v14 = iadd_imm v13, 12 + + ; Here we have maximum register pressure, and v2 has been spilled. + ; What happens if we use it? + v33 = iadd v2, v14 + v32 = iadd v33, v12 + v31 = iadd v32, v11 + v30 = iadd v31, v10 + v29 = iadd v30, v9 + v28 = iadd v29, v8 + v27 = iadd v28, v7 + v26 = iadd v27, v6 + v25 = iadd v26, v5 + v24 = iadd v25, v4 + v23 = iadd v24, v3 + v22 = iadd v23, v2 + v21 = iadd v22, v1 + v20 = iadd v21, v13 + v19 = iadd v20, v2 + return v21 +} diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 2a05fbb8c3..58f8fdc7fa 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -19,7 +19,7 @@ use dominator_tree::DominatorTree; use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; use ir::{Function, Ebb, Inst, Value, ValueLoc, ArgumentLoc, Signature, SigRef}; use ir::{InstEncodings, StackSlots, ValueLocations}; -use isa::registers::{RegClass, RegClassMask}; +use isa::registers::{RegClassMask, RegClassIndex}; use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; @@ -245,18 +245,10 @@ impl<'a> Context<'a> { dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker) { dbg!("Inst {}, {}", dfg.display_inst(inst), self.pressure); - // TODO: Repair constraint violations by copying input values. - // - // - Tied use of value that is not killed. - // - Count pressure for register uses of spilled values too. + // We may need to resolve register constraints if there are any noteworthy uses. assert!(self.reg_uses.is_empty()); - - // If the instruction has any fixed register operands, we may need to resolve register - // constraints. - if constraints.fixed_ins { - self.collect_reg_uses(inst, constraints, dfg); - } + self.collect_reg_uses(inst, constraints, dfg); // Calls usually have fixed register uses. let call_sig = dfg.call_signature(inst); @@ -268,7 +260,6 @@ impl<'a> Context<'a> { self.process_reg_uses(inst, pos, dfg, tracker); } - // Update the live value tracker with this instruction. let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); @@ -312,7 +303,12 @@ impl<'a> Context<'a> { // This won't cause spilling. self.take_live_regs(defs); } - // Collect register uses from the fixed input constraints. + + // Collect register uses that are noteworthy in one of the following ways: + // + // 1. It's a fixed register constraint. + // 2. It's a use of a spilled value. + // 3. It's a tied register constraint and the value isn't killed. // // We are assuming here that if a value is used both by a fixed register operand and a register // class operand, they two are compatible. We are also assuming that two register class @@ -323,11 +319,23 @@ impl<'a> Context<'a> { dfg: &DataFlowGraph) { let args = dfg.inst_args(inst); for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() { + let mut reguse = RegUse::new(arg, idx, op.regclass.into()); match op.kind { - ConstraintKind::FixedReg(_) => { - self.reg_uses.push(RegUse::new(arg, idx)); + ConstraintKind::Stack => continue, + ConstraintKind::FixedReg(_) => reguse.fixed = true, + ConstraintKind::Tied(_) => { + // TODO: If `arg` isn't killed here, we need a copy } - _ => {} + ConstraintKind::Reg => {} + } + let lr = &self.liveness[arg]; + if lr.affinity.is_stack() { + reguse.spilled = true; + } + + // Only collect the interesting register uses. + if reguse.fixed || reguse.spilled { + self.reg_uses.push(reguse); } } } @@ -343,7 +351,17 @@ impl<'a> Context<'a> { .zip(args) .enumerate() { if abi.location.is_reg() { - self.reg_uses.push(RegUse::new(arg, fixed_args + idx)); + let (rci, spilled) = match self.liveness[arg].affinity { + Affinity::Reg(rci) => (rci, false), + Affinity::Stack => { + (self.isa.regclass_for_abi_type(abi.value_type).into(), true) + } + Affinity::None => panic!("Missing affinity for {}", arg), + }; + let mut reguse = RegUse::new(arg, fixed_args + idx, rci); + reguse.fixed = true; + reguse.spilled = spilled; + self.reg_uses.push(reguse); } } } @@ -364,35 +382,49 @@ impl<'a> Context<'a> { // outside nightly Rust. self.reg_uses.sort_by_key(|u| (u.value, u.opidx)); - // We are assuming that `reg_uses` has an entry per fixed register operand, and that any - // non-fixed register operands are compatible with one of the fixed uses of the value. - for i in 1..self.reg_uses.len() { + for i in 0..self.reg_uses.len() { let ru = self.reg_uses[i]; - if self.reg_uses[i - 1].value != ru.value { - continue; + + // Do we need to insert a copy for this use? + let need_copy = if ru.tied { + true + } else if ru.fixed { + // This is a fixed register use which doesn't necessarily require a copy. + // Make a copy only if this is not the first use of the value. + self.reg_uses + .get(i.wrapping_sub(1)) + .map(|ru2| ru2.value == ru.value) + .unwrap_or(false) + } else { + false + }; + + if need_copy { + let copy = self.insert_copy(ru.value, ru.rci, pos, dfg); + dfg.inst_args_mut(inst)[ru.opidx as usize] = copy; } - // We have two fixed uses of the same value. Make a copy. - let (copy, rc) = self.insert_copy(ru.value, pos, dfg); - dfg.inst_args_mut(inst)[ru.opidx as usize] = copy; - - // Make sure the new copy doesn't blow the register pressure. - while let Err(mask) = self.pressure.take_transient(rc) { - dbg!("Copy of {} reg causes spill", rc); - // Spill a live register that is *not* used by the current instruction. - // Spilling a use wouldn't help. - let args = dfg.inst_args(inst); - match self.spill_candidate(mask, - tracker.live().iter().filter(|lv| { - !args.contains(&lv.value) - }), - dfg, - &pos.layout) { - Some(cand) => self.spill_reg(cand, dfg), - None => { - panic!("Ran out of {} registers when inserting copy before {}", - rc, - dfg.display_inst(inst)) + // Even if we don't insert a copy, we may need to account for register pressure for the + // reload pass. + if need_copy || ru.spilled { + let rc = self.reginfo.rc(ru.rci); + while let Err(mask) = self.pressure.take_transient(rc) { + dbg!("Copy of {} reg causes spill", rc); + // Spill a live register that is *not* used by the current instruction. + // Spilling a use wouldn't help. + let args = dfg.inst_args(inst); + match self.spill_candidate(mask, + tracker.live().iter().filter(|lv| { + !args.contains(&lv.value) + }), + dfg, + &pos.layout) { + Some(cand) => self.spill_reg(cand, dfg), + None => { + panic!("Ran out of {} registers when inserting copy before {}", + rc, + dfg.display_inst(inst)) + } } } } @@ -481,12 +513,13 @@ impl<'a> Context<'a> { /// Insert a `copy value` before `pos` and give it a live range extending to `pos`. /// - /// Returns the new local value created and its register class. + /// Returns the new local value created. fn insert_copy(&mut self, value: Value, + rci: RegClassIndex, pos: &mut Cursor, dfg: &mut DataFlowGraph) - -> (Value, RegClass) { + -> Value { let copy = dfg.ins(pos).copy(value); let inst = dfg.value_def(copy).unwrap_inst(); let ty = dfg.value_type(copy); @@ -498,21 +531,14 @@ impl<'a> Context<'a> { *self.encodings.ensure(inst) = encoding; // Update live ranges. - let rc = self.encinfo - .operand_constraints(encoding) - .expect("Bad copy encoding") - .outs - [0] - .regclass; - self.liveness - .create_dead(copy, inst, Affinity::Reg(rc.into())); + self.liveness.create_dead(copy, inst, Affinity::Reg(rci)); self.liveness .extend_locally(copy, pos.layout.pp_ebb(inst), pos.current_inst().expect("must be at an instruction"), pos.layout); - (copy, rc) + copy } } @@ -522,13 +548,29 @@ impl<'a> Context<'a> { struct RegUse { value: Value, opidx: u16, + + // Register class required by the use. + rci: RegClassIndex, + + // A use with a fixed register constraint. + fixed: bool, + + // A register use of a spilled value. + spilled: bool, + + // A use with a tied register constraint *and* the used value is not killed. + tied: bool, } impl RegUse { - fn new(value: Value, idx: usize) -> RegUse { + fn new(value: Value, idx: usize, rci: RegClassIndex) -> RegUse { RegUse { value, opidx: idx as u16, + rci, + fixed: false, + spilled: false, + tied: false, } } } From 6c5f5e1147a2356532f5c2bd81a89272be6f05f4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 08:41:54 -0700 Subject: [PATCH 834/968] Hook up the handling of tied register constraints. Tests are forthcoming, we need to implement Intel ABI lowering first. --- lib/cretonne/src/regalloc/liverange.rs | 9 ++++++++- lib/cretonne/src/regalloc/spilling.rs | 14 +++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index 95dfb49f2e..9174e0cdba 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -399,7 +399,7 @@ impl LiveRange { } } - /// Check if this live range reaches a use at `inst` in `ebb`. + /// Check if this live range reaches a use at `user` in `ebb`. pub fn reaches_use(&self, user: Inst, ebb: Ebb, order: &PO) -> bool where PO: ProgramOrder { @@ -415,6 +415,13 @@ impl LiveRange { None => false, } } + + /// Check if this live range is killed at `user` in `ebb`. + pub fn killed_at(&self, user: Inst, ebb: Ebb, order: &PO) -> bool + where PO: ProgramOrder + { + self.def_local_end() == user.into() || self.livein_local_end(ebb, order) == Some(user) + } } /// Allow a `LiveRange` to be stored in a `SparseMap` indexed by values. diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 58f8fdc7fa..37ff870a26 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -155,7 +155,7 @@ impl<'a> Context<'a> { pos.goto_top(ebb); while let Some(inst) = pos.next_inst() { if let Some(constraints) = self.encinfo.operand_constraints(self.encodings[inst]) { - self.visit_inst(inst, constraints, &mut pos, dfg, tracker); + self.visit_inst(inst, ebb, constraints, &mut pos, dfg, tracker); } else { let (_throughs, kills) = tracker.process_ghost(inst); self.free_regs(kills); @@ -240,6 +240,7 @@ impl<'a> Context<'a> { fn visit_inst(&mut self, inst: Inst, + ebb: Ebb, constraints: &RecipeConstraints, pos: &mut Cursor, dfg: &mut DataFlowGraph, @@ -248,7 +249,7 @@ impl<'a> Context<'a> { // We may need to resolve register constraints if there are any noteworthy uses. assert!(self.reg_uses.is_empty()); - self.collect_reg_uses(inst, constraints, dfg); + self.collect_reg_uses(inst, ebb, constraints, dfg, pos.layout); // Calls usually have fixed register uses. let call_sig = dfg.call_signature(inst); @@ -315,20 +316,23 @@ impl<'a> Context<'a> { // operands are always compatible. fn collect_reg_uses(&mut self, inst: Inst, + ebb: Ebb, constraints: &RecipeConstraints, - dfg: &DataFlowGraph) { + dfg: &DataFlowGraph, + layout: &Layout) { let args = dfg.inst_args(inst); for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() { let mut reguse = RegUse::new(arg, idx, op.regclass.into()); + let lr = &self.liveness[arg]; match op.kind { ConstraintKind::Stack => continue, ConstraintKind::FixedReg(_) => reguse.fixed = true, ConstraintKind::Tied(_) => { - // TODO: If `arg` isn't killed here, we need a copy + // A tied operand must kill the used value. + reguse.tied = !lr.killed_at(inst, ebb, layout); } ConstraintKind::Reg => {} } - let lr = &self.liveness[arg]; if lr.affinity.is_stack() { reguse.spilled = true; } From 68ca285507854fba4011bb6899402334ad421134 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 09:32:42 -0700 Subject: [PATCH 835/968] Generate an enum with all the register units in a target. It is sometimes useful to create constant lists of register units by name. The generated RU enum can be used for that. --- lib/cretonne/meta/gen_registers.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/cretonne/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py index 463364709a..6d41a40330 100644 --- a/lib/cretonne/meta/gen_registers.py +++ b/lib/cretonne/meta/gen_registers.py @@ -30,6 +30,19 @@ def gen_regbank(regbank, fmt): fmt.format('num_toprcs: {},', len(regbank.toprcs)) +def gen_regbank_units(regbank, fmt): + # type: (RegBank, srcgen.Formatter) -> None + """ + Emit constants for all the register units in `regbank`. + """ + for unit in range(regbank.units): + v = unit + regbank.first_unit + if unit < len(regbank.names): + fmt.format("{} = {},", regbank.names[unit], v) + else: + fmt.format("{}{} = {},", regbank.prefix, unit, v) + + def gen_regclass(rc, fmt): # type: (RegClass, srcgen.Formatter) -> None """ @@ -77,6 +90,13 @@ def gen_isa(isa, fmt): 'pub const {}: RegClass = &CLASSES[{}];' .format(rc.name, rc.index)) + # Emit constants for all the register units. + fmt.line('#[allow(dead_code, non_camel_case_types)]') + fmt.line('#[derive(Clone, Copy)]') + with fmt.indented('pub enum RU {', '}'): + for regbank in isa.regbanks: + gen_regbank_units(regbank, fmt) + def generate(isas, out_dir): # type: (Sequence[TargetISA], str) -> None From 9766fc3fcdd9764e800d44b8a98ba0eb8494cf7c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 10:37:42 -0700 Subject: [PATCH 836/968] Implement the basics of the x86-64 ABI. This is just a rough sketch to get us started. There are bound to be some issues. This also legalizes signatures for x86-32, but probably not correctly. It's basically implementing the x86-64 ABI for 32-bit. --- filetests/isa/intel/abi64.cton | 20 ++++++ lib/cretonne/src/isa/intel/abi.rs | 114 ++++++++++++++++++++++++++++-- lib/cretonne/src/isa/intel/mod.rs | 2 +- 3 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 filetests/isa/intel/abi64.cton diff --git a/filetests/isa/intel/abi64.cton b/filetests/isa/intel/abi64.cton new file mode 100644 index 0000000000..ecd3d29fd6 --- /dev/null +++ b/filetests/isa/intel/abi64.cton @@ -0,0 +1,20 @@ +; Test the legalization of function signatures. +test legalizer +set is_64bit +isa intel + +; regex: V=v\d+ + +function %f() { + sig0 = signature(i32) -> i32 + ; check: sig0 = signature(i32 [%rdi]) -> i32 [%rax] + + sig1 = signature(i64) -> b1 + ; check: sig1 = signature(i64 [%rdi]) -> b1 [%rax] + + sig2 = signature(f32, i64) -> f64 + ; check: sig2 = signature(f32 [%xmm0], i64 [%rdi]) -> f64 [%xmm0] + +ebb0: + return +} diff --git a/lib/cretonne/src/isa/intel/abi.rs b/lib/cretonne/src/isa/intel/abi.rs index e6c5edd5bf..240e9d327d 100644 --- a/lib/cretonne/src/isa/intel/abi.rs +++ b/lib/cretonne/src/isa/intel/abi.rs @@ -1,16 +1,102 @@ //! Intel ABI implementation. use ir; -use isa::RegClass; +use isa::{RegClass, RegUnit}; use regalloc::AllocatableSet; use settings as shared_settings; -use super::registers::{GPR, FPR}; +use super::registers::{GPR, FPR, RU}; +use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; +use ir::{ArgumentType, ArgumentLoc, ArgumentExtension}; + +/// Argument registers for x86-64 +static ARG_GPRS: [RU; 6] = [RU::rdi, RU::rsi, RU::rdx, RU::rcx, RU::r8, RU::r9]; + +/// Return value registers. +static RET_GPRS: [RU; 3] = [RU::rax, RU::rdx, RU::rcx]; + +struct Args { + pointer_bytes: u32, + pointer_bits: u16, + pointer_type: ir::Type, + gpr: &'static [RU], + gpr_used: usize, + fpr_limit: usize, + fpr_used: usize, + offset: u32, +} + +impl Args { + fn new(bits: u16, gpr: &'static [RU], fpr_limit: usize) -> Args { + Args { + pointer_bytes: bits as u32 / 8, + pointer_bits: bits, + pointer_type: ir::Type::int(bits).unwrap(), + gpr, + gpr_used: 0, + fpr_limit, + fpr_used: 0, + offset: 0, + } + } +} + +impl ArgAssigner for Args { + fn assign(&mut self, arg: &ArgumentType) -> ArgAction { + let ty = arg.value_type; + + // Check for a legal type. + // We don't support SIMD yet, so break all vectors down. + if !ty.is_scalar() { + return ValueConversion::VectorSplit.into(); + } + + // Large integers and booleans are broken down to fit in a register. + if !ty.is_float() && ty.bits() > self.pointer_bits { + return ValueConversion::IntSplit.into(); + } + + // Small integers are extended to the size of a pointer register. + if ty.is_int() && ty.bits() < self.pointer_bits { + match arg.extension { + ArgumentExtension::None => {} + ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(), + ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(), + } + } + + // Try to use a GPR. + if !ty.is_float() && self.gpr_used < self.gpr.len() { + let reg = self.gpr[self.gpr_used] as RegUnit; + self.gpr_used += 1; + return ArgumentLoc::Reg(reg).into(); + } + + // Try to use an FPR. + if ty.is_float() && self.fpr_used < self.fpr_limit { + let reg = FPR.unit(self.fpr_used); + self.fpr_used += 1; + return ArgumentLoc::Reg(reg).into(); + } + + // Assign a stack location. + let loc = ArgumentLoc::Stack(self.offset as i32); + self.offset += self.pointer_bytes; + assert!(self.offset <= i32::max_value() as u32); + loc.into() + } +} /// Legalize `sig`. -pub fn legalize_signature(_sig: &mut ir::Signature, - _flags: &shared_settings::Flags, +pub fn legalize_signature(sig: &mut ir::Signature, + flags: &shared_settings::Flags, _current: bool) { - unimplemented!() + let bits = if flags.is_64bit() { 64 } else { 32 }; + + let mut args = Args::new(bits, &ARG_GPRS, 8); + legalize_args(&mut sig.argument_types, &mut args); + + let mut rets = Args::new(bits, &RET_GPRS, 2); + legalize_args(&mut sig.return_types, &mut rets); } /// Get register class for a type appearing in a legalized signature. @@ -19,6 +105,20 @@ pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass { } /// Get the set of allocatable registers for `func`. -pub fn allocatable_registers(_func: &ir::Function) -> AllocatableSet { - unimplemented!() +pub fn allocatable_registers(_func: &ir::Function, + flags: &shared_settings::Flags) + -> AllocatableSet { + let mut regs = AllocatableSet::new(); + regs.take(GPR, RU::rsp as RegUnit); + regs.take(GPR, RU::rbp as RegUnit); + + // 32-bit arch only has 8 registers. + if !flags.is_64bit() { + for i in 8..16 { + regs.take(GPR, GPR.unit(i)); + regs.take(FPR, FPR.unit(i)); + } + } + + regs } diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 6cfd0b4b85..88df62c8ac 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -88,7 +88,7 @@ impl TargetIsa for Isa { } fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { - abi::allocatable_registers(func) + abi::allocatable_registers(func, &self.shared_flags) } fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { From 3608be35a95ea81f3dbd26d13792771092b568bc Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 11:41:06 -0700 Subject: [PATCH 837/968] Add Intel iconst.i32 encoding. --- filetests/isa/intel/binary32.cton | 6 ++++-- lib/cretonne/meta/isa/intel/encodings.py | 3 +++ lib/cretonne/meta/isa/intel/recipes.py | 7 ++++++- lib/cretonne/src/isa/intel/binemit.rs | 13 +++++++++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 818335d349..6d13844497 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -9,8 +9,10 @@ isa intel function %I32() { ebb0: - [-,%rcx] v1 = iconst.i32 1 - [-,%rsi] v2 = iconst.i32 2 + ; asm: movl $1, %ecx + [-,%rcx] v1 = iconst.i32 1 ; bin: b9 00000001 + ; asm: movl $2, %esi + [-,%rsi] v2 = iconst.i32 2 ; bin: be 00000002 ; Integer Register-Register Operations. diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 575c36baca..4010ffb64a 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -22,6 +22,9 @@ for inst, rrr in [ I32.enc(inst, *r.rib(0x83, rrr=rrr)) I32.enc(inst, *r.rid(0x81, rrr=rrr)) +# Immediate constant. +I32.enc(base.iconst.i32, *r.uid(0xb8)) + # 32-bit shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit # and 16-bit shifts would need explicit masking. diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 71b8260f42..70377f8756 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -4,7 +4,7 @@ Intel Encoding recipes. from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual -from base.formats import Binary, BinaryImm, Store, Load +from base.formats import UnaryImm, Binary, BinaryImm, Store, Load from .registers import GPR, ABCD try: @@ -155,6 +155,11 @@ rid = TailRecipe( 'rid', BinaryImm, size=5, ins=GPR, outs=0, instp=IsSignedInt(BinaryImm.imm, 32)) +# XX+rd id unary with 32-bit immediate. +uid = TailRecipe( + 'uid', UnaryImm, size=4, ins=(), outs=GPR, + instp=IsSignedInt(UnaryImm.imm, 32)) + # # Store recipes. # diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 87587463ca..aded1e41e6 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -133,6 +133,19 @@ fn recipe_op1rid(func: &Function, inst: Inst, sink: &mut } } +fn recipe_op1uid(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::UnaryImm { imm, .. } = func.dfg[inst] { + let bits = func.encodings[inst].bits(); + let reg = func.locations[func.dfg.first_result(inst)].unwrap_reg(); + // The destination register is encoded in the low bits of the opcode. No ModR/M + put_op1(bits | (reg & 7), sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); + } else { + panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); + } +} + // Store recipes. fn recipe_op1st(func: &Function, inst: Inst, sink: &mut CS) { From 1a24489a0e185a28b2a265590b8caa1957f84d2a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 12:21:36 -0700 Subject: [PATCH 838/968] Add Intel call/return encodings. --- filetests/isa/intel/binary32.cton | 14 ++++++++- lib/cretonne/meta/isa/intel/encodings.py | 7 +++++ lib/cretonne/meta/isa/intel/recipes.py | 8 +++++ lib/cretonne/src/isa/intel/binemit.rs | 38 ++++++++++++++++++++++-- 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 6d13844497..2e42d01fcc 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -8,6 +8,9 @@ isa intel ; function %I32() { + fn0 = function %foo() + sig0 = signature() + ebb0: ; asm: movl $1, %ecx [-,%rcx] v1 = iconst.i32 1 ; bin: b9 00000001 @@ -199,5 +202,14 @@ ebb0: ; asm: movsbl -50000(%esi), %edx [-,%rdx] v129 = sload8.i32 v2-50000 ; bin: 0f be 96 ffff3cb0 - return + ; asm: call foo + call fn0() ; bin: e8 PCRel4(fn0) 00000000 + + ; asm: call *%ecx + call_indirect sig0, v1() ; bin: ff d1 + ; asm: call *%esi + call_indirect sig0, v2() ; bin: ff d6 + + ; asm: ret + return ; bin: c3 } diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 4010ffb64a..c05308177a 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -64,3 +64,10 @@ I32.enc(base.uload8.i32.i32, *r.ldDisp32(0x0f, 0xb6)) I32.enc(base.sload8.i32.i32, *r.ld(0x0f, 0xbe)) I32.enc(base.sload8.i32.i32, *r.ldDisp8(0x0f, 0xbe)) I32.enc(base.sload8.i32.i32, *r.ldDisp32(0x0f, 0xbe)) + +# +# Call/return +# +I32.enc(base.call, *r.call_id(0xe8)) +I32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2)) +I32.enc(base.x_return, *r.ret(0xc3)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 70377f8756..4e41c9b990 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -5,6 +5,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual from base.formats import UnaryImm, Binary, BinaryImm, Store, Load +from base.formats import MultiAry, Call, IndirectCall from .registers import GPR, ABCD try: @@ -206,3 +207,10 @@ ldDisp8 = TailRecipe( ldDisp32 = TailRecipe( 'ldDisp32', Load, size=5, ins=(GPR), outs=(GPR), instp=IsSignedInt(Load.offset, 32)) + +# +# Call/return +# +call_id = TailRecipe('call_id', Call, size=4, ins=(), outs=()) +call_r = TailRecipe('call_r', IndirectCall, size=1, ins=GPR, outs=()) +ret = TailRecipe('ret', MultiAry, size=0, ins=(), outs=()) diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index aded1e41e6..001a41e009 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -1,12 +1,24 @@ //! Emitting binary Intel machine code. -use binemit::{CodeSink, bad_encoding}; +use binemit::{CodeSink, Reloc, bad_encoding}; use ir::{Function, Inst, InstructionData}; use isa::RegUnit; include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); -pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; +/// Intel relocations. +pub enum RelocKind { + /// A 4-byte relative function reference. Based from relocation + 4 bytes. + PCRel4, +} + +pub static RELOC_NAMES: [&'static str; 1] = ["PCRel4"]; + +impl Into for RelocKind { + fn into(self) -> Reloc { + Reloc(self as u16) + } +} // Emit single-byte opcode. fn put_op1(bits: u16, sink: &mut CS) { @@ -310,3 +322,25 @@ fn recipe_op2lddisp32(func: &Function, inst: Inst, sink: panic!("Expected Load format: {:?}", func.dfg[inst]); } } + +fn recipe_op1call_id(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Call { func_ref, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + sink.reloc_func(RelocKind::PCRel4.into(), func_ref); + sink.put4(0); + } else { + panic!("Expected Call format: {:?}", func.dfg[inst]); + } +} + +fn recipe_op1call_r(func: &Function, inst: Inst, sink: &mut CS) { + let bits = func.encodings[inst].bits(); + put_op1(bits, sink); + modrm_r_bits(func.locations[func.dfg.inst_args(inst)[0]].unwrap_reg(), + bits, + sink); +} + +fn recipe_op1ret(func: &Function, inst: Inst, sink: &mut CS) { + put_op1(func.encodings[inst].bits(), sink); +} From 8c60555409a36780052f3f1eb706c1f349474dcb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 13:34:18 -0700 Subject: [PATCH 839/968] Add support for tied operands. Include a very basic test using an Intel 'sub' instruction. More to follow. --- filetests/regalloc/constraints.cton | 15 +++++++++++++++ lib/cretonne/src/regalloc/coloring.rs | 25 +++++++++++++++++++++---- lib/cretonne/src/regalloc/solver.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 filetests/regalloc/constraints.cton diff --git a/filetests/regalloc/constraints.cton b/filetests/regalloc/constraints.cton new file mode 100644 index 0000000000..e88bcc514c --- /dev/null +++ b/filetests/regalloc/constraints.cton @@ -0,0 +1,15 @@ +test regalloc +isa intel + +; regex: V=v\d+ + +; Tied operands, both are killed at instruction. +function %tied_easy() -> i32 { +ebb0: + v0 = iconst.i32 12 + v1 = iconst.i32 13 + ; not: copy + ; check: isub + v2 = isub v0, v1 + return v2 +} diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 733506ef0d..4ebf3340f9 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -363,6 +363,17 @@ impl<'a> Context<'a> { locations[v.value] = ValueLoc::Reg(v.solution); } + // Tied defs are not part of the solution above. + // Copy register assignments from tied inputs to tied outputs. + if constraints.tied_ops { + for (op, lv) in constraints.outs.iter().zip(defs) { + if let ConstraintKind::Tied(num) = op.kind { + let arg = dfg.inst_args(inst)[num as usize]; + locations[lv.value] = locations[arg]; + } + } + } + // Update `regs` for the next instruction, remove the dead defs. for lv in defs { if lv.endpoint == inst { @@ -638,11 +649,11 @@ impl<'a> Context<'a> { /// /// It is assumed that all fixed outputs have already been handled. fn program_output_constraints(&mut self, - _inst: Inst, + inst: Inst, constraints: &[OperandConstraint], defs: &[LiveValue], - _dfg: &mut DataFlowGraph, - _locations: &mut ValueLocations) { + dfg: &mut DataFlowGraph, + locations: &mut ValueLocations) { for (op, lv) in constraints.iter().zip(defs) { match op.kind { ConstraintKind::FixedReg(_) | @@ -650,7 +661,13 @@ impl<'a> Context<'a> { ConstraintKind::Reg => { self.solver.add_def(lv.value, op.regclass); } - ConstraintKind::Tied(_) => unimplemented!(), + ConstraintKind::Tied(num) => { + // Find the input operand we're tied to. + // The solver doesn't care about the output value. + let arg = dfg.inst_args(inst)[num as usize]; + self.solver + .add_tied_input(arg, op.regclass, self.divert.reg(arg, locations)); + } } } } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index b2e73bf91f..b135f051f3 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -488,6 +488,32 @@ impl Solver { self.regs_out.free(rc, reg); } + /// Record that an input register is tied to an output register. + /// + /// It is assumed that `add_kill` was called previously with the same arguments. + /// + /// The output value that must have the same register as the input value is not recorded in the + /// solver. + pub fn add_tied_input(&mut self, value: Value, rc: RegClass, reg: RegUnit) { + debug_assert!(self.inputs_done); + + // If a fixed assignment is tied, the `to` register is not available on the output side. + if let Some(a) = self.assignments.get(value) { + debug_assert_eq!(a.from, reg); + self.regs_out.take(a.rc, a.to); + return; + } + + // Check if a variable was created. + if let Some(v) = self.vars.iter_mut().find(|v| v.value == value) { + assert!(v.is_input); + v.is_output = true; + return; + } + + self.regs_out.take(rc, reg); + } + /// Add a fixed output assignment. /// /// This means that `to` will not be available for variables on the output side of the From f867ddbf0c1debef2db3b3a9cb3956953677ea0b Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Wed, 5 Jul 2017 08:44:51 -0700 Subject: [PATCH 840/968] Fixed bug in verifier (#109) * Fixed bug in verifier Does not check variable def for unreachable codex * Check reachability first + file test --- filetests/verifier/unreachable_code.cton | 23 +++++++++++++++++++++++ lib/cretonne/src/verifier/mod.rs | 6 ++++-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 filetests/verifier/unreachable_code.cton diff --git a/filetests/verifier/unreachable_code.cton b/filetests/verifier/unreachable_code.cton new file mode 100644 index 0000000000..474eaec6b4 --- /dev/null +++ b/filetests/verifier/unreachable_code.cton @@ -0,0 +1,23 @@ +test verifier + +function %test() -> i32 { ; Ok +ebb0: + v0 = iconst.i32 0 + v1 = iconst.i32 0 + jump ebb2 + +ebb2: + jump ebb4 + +ebb4: + jump ebb2 + +ebb3(v2: i32): + v4 = iadd.i32 v1, v2 + jump ebb9(v4) + +ebb9(v7: i32): + v9 = iadd.i32 v2, v7 + return v9 + +} diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 08aeb9249f..2f817d1a48 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -363,7 +363,8 @@ impl<'a> Verifier<'a> { def_inst); } // Defining instruction dominates the instruction that uses the value. - if !self.domtree + if self.domtree.is_reachable(self.func.layout.pp_ebb(loc_inst)) && + !self.domtree .dominates(def_inst, loc_inst, &self.func.layout) { return err!(loc_inst, "uses value from non-dominating {}", def_inst); } @@ -381,7 +382,8 @@ impl<'a> Verifier<'a> { ebb); } // The defining EBB dominates the instruction using this value. - if !self.domtree.dominates(ebb, loc_inst, &self.func.layout) { + if self.domtree.is_reachable(ebb) && + !self.domtree.dominates(ebb, loc_inst, &self.func.layout) { return err!(loc_inst, "uses value arg from non-dominating {}", ebb); } } From a5c96ef6bf05ee10468c577f147ae3a5369f211e Mon Sep 17 00:00:00 2001 From: d1m0 Date: Wed, 5 Jul 2017 09:16:44 -0700 Subject: [PATCH 841/968] Add better type inference and encapsulate it in its own file (#110) * Add more rigorous type inference and encapsulate the type inferece code in its own file (ti.py). Add constraints accumulation during type inference, to represent constraints that cannot be expressed using bijective derivation functions between typevars. Add testing for new type inference code. * Additional annotations to appease mypy --- lib/cretonne/meta/cdsl/ast.py | 147 +------ lib/cretonne/meta/cdsl/test_ti.py | 432 +++++++++++++++++++ lib/cretonne/meta/cdsl/test_typevar.py | 62 ++- lib/cretonne/meta/cdsl/ti.py | 556 +++++++++++++++++++++++++ lib/cretonne/meta/cdsl/typevar.py | 125 ++++-- lib/cretonne/meta/cdsl/xform.py | 82 +--- 6 files changed, 1123 insertions(+), 281 deletions(-) create mode 100644 lib/cretonne/meta/cdsl/test_ti.py create mode 100644 lib/cretonne/meta/cdsl/ti.py diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 2b671fc46e..6efc492cf5 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -100,8 +100,8 @@ class Var(Expr): # TypeVar representing the type of this variable. self.typevar = None # type: TypeVar # The original 'typeof(x)' type variable that was created for this Var. - # This one doesn't change. `self.typevar` above may be joined with - # other typevars. + # This one doesn't change. `self.typevar` above may be changed to + # another typevar by type inference. self.original_typevar = None # type: TypeVar def __str__(self): @@ -180,16 +180,9 @@ class Var(Expr): self.typevar = tv return self.typevar - def link_typevar(self, base, derived_func): - # type: (TypeVar, str) -> None - """ - Link the type variable on this Var to the type variable `base` using - `derived_func`. - """ - self.original_typevar = None - self.typevar.change_to_derived(base, derived_func) - # Possibly eliminate redundant SAMEAS links. - self.typevar = self.typevar.strip_sameas() + def set_typevar(self, tv): + # type: (TypeVar) -> None + self.typevar = tv def has_free_typevar(self): # type: () -> bool @@ -213,136 +206,6 @@ class Var(Expr): """ return self.typevar.rust_expr() - def constrain_typevar(self, sym_typevar, sym_ctrl, ctrl_var): - # type: (TypeVar, TypeVar, Var) -> None - """ - Constrain the set of allowed types for this variable. - - Merge type variables for the involved variables to minimize the set for - free type variables. - - Suppose we're looking at an instruction defined like this: - - c = Operand('c', TxN.as_bool()) - x = Operand('x', TxN) - y = Operand('y', TxN) - a = Operand('a', TxN) - vselect = Instruction('vselect', ins=(c, x, y), outs=a) - - And suppose the instruction is used in a pattern like this: - - v0 << vselect(v1, v2, v3) - - We want to reconcile the types of the variables v0-v3 with the - constraints from the definition of vselect. This means that v0, v2, and - v3 must all have the same type, and v1 must have the type - `typeof(v2).as_bool()`. - - The types are reconciled by calling this function once for each - input/output operand on the instruction in the pattern with these - arguments. - - :param sym_typevar: Symbolic type variable constraining this variable - in the definition of the instruction. - :param sym_ctrl: Controlling type variable of `sym_typevar` in the - definition of the instruction. - :param ctrl_var: Variable determining the type of `sym_ctrl`. - - When processing `v1` as used in the pattern above, we would get: - - - self: v1 - - sym_typevar: TxN.as_bool() - - sym_ctrl: TxN - - ctrl_var: v2 - - Here, 'v2' represents the controlling variable because of how the - `Ternary` instruction format is defined with `typevar_operand=1`. - """ - # First check if sym_typevar is tied to the controlling type variable - # in the instruction definition. We also allow free type variables on - # instruction inputs that can't be tied to anything else. - # - # This also covers non-polymorphic instructions and other cases where - # we don't have a Var representing the controlling type variable. - sym_free_var = sym_typevar.free_typevar() - if not sym_free_var or sym_free_var is not sym_ctrl or not ctrl_var: - # Just constrain our type to be compatible with the required - # typeset. - self.get_typevar().constrain_types(sym_typevar) - return - - # Now sym_typevar is known to be tied to (or identical to) the - # controlling type variable. - - if not self.typevar: - # If this variable is not yet constrained, just infer its type and - # link it to the controlling type variable. - if not sym_typevar.is_derived: - assert sym_typevar is sym_ctrl - # Identity mapping. - # Note that `self == ctrl_var` is both possible and common. - self.typevar = ctrl_var.get_typevar() - else: - assert self is not ctrl_var, ( - 'Impossible type constraints for {}: {}' - .format(self, sym_typevar)) - # Create a derived type variable identical to sym_typevar, but - # with a different base. - self.typevar = TypeVar.derived( - ctrl_var.get_typevar(), - sym_typevar.derived_func) - # Match the type set constraints of the instruction. - self.typevar.constrain_types(sym_typevar) - return - - # We already have a self.typevar describing our constraints. We need to - # reconcile with the additional constraints. - - # It's likely that ctrl_var and self already share a type - # variable. (Often because `ctrl_var == self`). - if ctrl_var.typevar == self.typevar: - return - - if not sym_typevar.is_derived: - assert sym_typevar is sym_ctrl - # sym_typevar is a direct use of sym_ctrl, so we need to reconcile - # self with ctrl_var. - assert not sym_typevar.is_derived - self.typevar.constrain_types(sym_typevar) - - # It's possible that ctrl_var has not yet been assigned a type - # variable. - if not ctrl_var.typevar: - ctrl_var.typevar = self.typevar - return - - # We can also bind variables with a free type variable to another - # variable. Prefer to do this to temps because they aren't allowed - # to be free, - if self.is_temp() and self.has_free_typevar(): - self.link_typevar(ctrl_var.typevar, TypeVar.SAMEAS) - return - if ctrl_var.is_temp() and ctrl_var.has_free_typevar(): - ctrl_var.link_typevar(self.typevar, TypeVar.SAMEAS) - return - if self.has_free_typevar(): - self.link_typevar(ctrl_var.typevar, TypeVar.SAMEAS) - return - if ctrl_var.has_free_typevar(): - ctrl_var.link_typevar(self.typevar, TypeVar.SAMEAS) - return - - # TODO: Other cases are harder to handle. - # - # - If either variable is an independent free type variable, it - # should be changed to be linked to the other. - # - If both variable are free, we should pick one to link to the - # other. In particular, if one is a temp, it should be linked. - else: - # sym_typevar is derived from sym_ctrl. - # TODO: Other cases are harder to handle. - pass - class Apply(Expr): """ diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py new file mode 100644 index 0000000000..d97902568a --- /dev/null +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -0,0 +1,432 @@ +from __future__ import absolute_import +from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint,\ + b1, icmp, iadd_cout, iadd_cin +from base.legalize import narrow, expand +from base.immediates import intcc +from .typevar import TypeVar +from .ast import Var, Def +from .xform import Rtl, XForm +from .ti import ti_rtl, subst, TypeEnv, get_type_env +from unittest import TestCase +from functools import reduce + +try: + from .ti import TypeMap, ConstraintList, VarMap, TypingOrError # noqa + from .ti import Constraint + from typing import List, Dict, Tuple, TYPE_CHECKING, cast # noqa +except ImportError: + TYPE_CHECKING = False + + +def sort_constr(c): + # type: (Constraint) -> Constraint + """ + Sort the 2 typevars in a constraint by name for comparison + """ + r = tuple(sorted(c, key=lambda y: y.name)) + if TYPE_CHECKING: + return cast(Constraint, r) + else: + return r + + +def agree(me, other): + # type: (TypeEnv, TypeEnv) -> bool + """ + Given TypeEnvs me and other, check if they agree. As part of that build + a map m from TVs in me to their corresponding TVs in other. + Specifically: + + 1. Check that all TVs that are keys in me.type_map are also defined + in other.type_map + + 2. For any tv in me.type_map check that: + me[tv].get_typeset() == other[tv].get_typeset() + + 3. Set m[me[tv]] = other[tv] in the substitution m + + 4. If we find another tv1 such that me[tv1] == me[tv], assert that + other[tv1] == m[me[tv1]] == m[me[tv]] = other[tv] + + 5. Check that me and other have the same constraints under the + substitution m + """ + m = {} # type: TypeMap + # Check that our type map and other's agree and built substitution m + for tv in me.type_map: + if (me[tv] not in m): + m[me[tv]] = other[tv] + if me[tv].get_typeset() != other[tv].get_typeset(): + return False + else: + if m[me[tv]] != other[tv]: + return False + + # Tranlsate our constraints using m, and sort + me_equiv_constr = [(subst(a, m), subst(b, m)) for (a, b) in me.constraints] + me_equiv_constr = sorted([sort_constr(x) for x in me_equiv_constr]) + + # Sort other's constraints + other_equiv_constr = sorted([sort_constr(x) for x in other.constraints], + key=lambda y: y[0].name) + + return me_equiv_constr == other_equiv_constr + + +def check_typing(got_or_err, expected, symtab=None): + # type: (TypingOrError, Tuple[VarMap, ConstraintList], Dict[str, Var]) -> None # noqa + """ + Check that a the typying we received (got_or_err) complies with the + expected typing (expected). If symtab is specified, substitute the Vars in + expected using symtab first (used when checking type inference on XForms) + """ + (m, c) = expected + got = get_type_env(got_or_err) + + if (symtab is not None): + # For xforms we first need to re-write our TVs in terms of the tvs + # stored internally in the XForm. Use the symtab passed + subst_m = {k.get_typevar(): symtab[str(k)].get_typevar() + for k in m.keys()} + # Convert m from a Var->TypeVar map to TypeVar->TypeVar map where + # the key TypeVar is re-written to its XForm internal version + tv_m = {subst(k.get_typevar(), subst_m): v for (k, v) in m.items()} + # Rewrite the TVs in the input constraints to their XForm internal + # versions + c = [(subst(a, subst_m), subst(b, subst_m)) for (a, b) in c] + else: + # If no symtab, just convert m from Var->TypeVar map to a + # TypeVar->TypeVar map + tv_m = {k.get_typevar(): v for (k, v) in m.items()} + + expected_typ = TypeEnv((tv_m, c)) + assert agree(expected_typ, got), \ + "typings disagree:\n {} \n {}".format(got.dot(), + expected_typ.dot()) + + +def check_concrete_typing_rtl(var_types, rtl): + # type: (VarMap, Rtl) -> None + """ + Check that a concrete type assignment var_types (Dict[Var, TypeVar]) is + valid for an Rtl rtl. Specifically check that: + + 1) For each Var v \in rtl, v is defined in var_types + + 2) For all v, var_types[v] is a singleton type + + 3) For each v, and each location u, where v is used with expected type + tv_u, var_types[v].get_typeset() is a subset of + subst(tv_u, m).get_typeset() where m is the substitution of + formals->actuals we are building so far. + + 4) If tv_u is non-derived and not in m, set m[tv_u]= var_types[v] + """ + for d in rtl.rtl: + assert isinstance(d, Def) + inst = d.expr.inst + # Accumulate all actual TVs for value defs/opnums in actual_tvs + actual_tvs = [var_types[d.defs[i]] for i in inst.value_results] + for v in [d.expr.args[i] for i in inst.value_opnums]: + assert isinstance(v, Var) + actual_tvs.append(var_types[v]) + + # Accumulate all formal TVs for value defs/opnums in actual_tvs + formal_tvs = [inst.outs[i].typevar for i in inst.value_results] +\ + [inst.ins[i].typevar for i in inst.value_opnums] + m = {} # type: TypeMap + + # For each actual/formal pair check that they agree + for (actual_tv, formal_tv) in zip(actual_tvs, formal_tvs): + # actual should be a singleton + assert actual_tv.singleton_type() is not None + formal_tv = subst(formal_tv, m) + # actual should agree with the concretized formal + assert actual_tv.get_typeset().issubset(formal_tv.get_typeset()) + + if formal_tv not in m and not formal_tv.is_derived: + m[formal_tv] = actual_tv + + +def check_concrete_typing_xform(var_types, xform): + # type: (VarMap, XForm) -> None + """ + Check a concrete type assignment var_types for an XForm xform + """ + check_concrete_typing_rtl(var_types, xform.src) + check_concrete_typing_rtl(var_types, xform.dst) + + +class TypeCheckingBaseTest(TestCase): + def setUp(self): + # type: () -> None + self.v0 = Var("v0") + self.v1 = Var("v1") + self.v2 = Var("v2") + self.v3 = Var("v3") + self.v4 = Var("v4") + self.v5 = Var("v5") + self.v6 = Var("v6") + self.v7 = Var("v7") + self.v8 = Var("v8") + self.v9 = Var("v9") + self.imm0 = Var("imm0") + self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True, + scalars=False, simd=True) + self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True, + scalars=False, simd=True) + self.b1 = TypeVar.singleton(b1) + + +class TestRTL(TypeCheckingBaseTest): + def test_bad_rtl1(self): + # type: () -> None + r = Rtl( + (self.v0, self.v1) << vsplit(self.v2), + self.v3 << vconcat(self.v0, self.v2), + ) + ti = TypeEnv() + self.assertEqual(ti_rtl(r, ti), + "On line 1: fail ti on `typeof_v2` <: `2`: " + + "Error: empty type created when unifying " + + "`typeof_v2` and `half_vector(typeof_v2)`") + + def test_vselect(self): + # type: () -> None + r = Rtl( + self.v0 << vselect(self.v1, self.v2, self.v3), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + txn = self.TxN.get_fresh_copy("TxN1") + check_typing(typing, ({ + self.v0: txn, + self.v1: txn.as_bool(), + self.v2: txn, + self.v3: txn + }, [])) + + def test_vselect_icmpimm(self): + # type: () -> None + r = Rtl( + self.v0 << iconst(self.imm0), + self.v1 << icmp(intcc.eq, self.v2, self.v0), + self.v5 << vselect(self.v1, self.v3, self.v4), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + ixn = self.IxN_nonscalar.get_fresh_copy("IxN1") + txn = self.TxN.get_fresh_copy("TxN1") + check_typing(typing, ({ + self.v0: ixn, + self.v1: ixn.as_bool(), + self.v2: ixn, + self.v3: txn, + self.v4: txn, + self.v5: txn, + }, [(ixn.as_bool(), txn.as_bool())])) + + def test_vselect_vsplits(self): + # type: () -> None + r = Rtl( + self.v3 << vselect(self.v0, self.v1, self.v2), + (self.v4, self.v5) << vsplit(self.v3), + (self.v6, self.v7) << vsplit(self.v4), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + t = TypeVar("t", "", ints=True, bools=True, floats=True, + simd=(4, 256)) + check_typing(typing, ({ + self.v0: t.as_bool(), + self.v1: t, + self.v2: t, + self.v3: t, + self.v4: t.half_vector(), + self.v5: t.half_vector(), + self.v6: t.half_vector().half_vector(), + self.v7: t.half_vector().half_vector(), + }, [])) + + def test_vselect_vconcats(self): + # type: () -> None + r = Rtl( + self.v3 << vselect(self.v0, self.v1, self.v2), + self.v8 << vconcat(self.v3, self.v3), + self.v9 << vconcat(self.v8, self.v8), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + t = TypeVar("t", "", ints=True, bools=True, floats=True, + simd=(2, 64)) + check_typing(typing, ({ + self.v0: t.as_bool(), + self.v1: t, + self.v2: t, + self.v3: t, + self.v8: t.double_vector(), + self.v9: t.double_vector().double_vector(), + }, [])) + + def test_vselect_vsplits_vconcats(self): + # type: () -> None + r = Rtl( + self.v3 << vselect(self.v0, self.v1, self.v2), + (self.v4, self.v5) << vsplit(self.v3), + (self.v6, self.v7) << vsplit(self.v4), + self.v8 << vconcat(self.v3, self.v3), + self.v9 << vconcat(self.v8, self.v8), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + t = TypeVar("t", "", ints=True, bools=True, floats=True, + simd=(4, 64)) + check_typing(typing, ({ + self.v0: t.as_bool(), + self.v1: t, + self.v2: t, + self.v3: t, + self.v4: t.half_vector(), + self.v5: t.half_vector(), + self.v6: t.half_vector().half_vector(), + self.v7: t.half_vector().half_vector(), + self.v8: t.double_vector(), + self.v9: t.double_vector().double_vector(), + }, [])) + + def test_bint(self): + # type: () -> None + r = Rtl( + self.v4 << iadd(self.v1, self.v2), + self.v5 << bint(self.v3), + self.v0 << iadd(self.v4, self.v5) + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + itype = TypeVar("t", "", ints=True, simd=(1, 256)) + btype = TypeVar("b", "", bools=True, simd=True) + + # Check that self.v5 gets the same integer type as + # the rest of them + # TODO: Add constraint nlanes(v3) == nlanes(v1) when we + # add that type constraint to bint + check_typing(typing, ({ + self.v1: itype, + self.v2: itype, + self.v4: itype, + self.v5: itype, + self.v3: btype, + self.v0: itype, + }, [])) + + +class TestXForm(TypeCheckingBaseTest): + def test_iadd_cout(self): + # type: () -> None + x = XForm(Rtl((self.v0, self.v1) << iadd_cout(self.v2, self.v3),), + Rtl( + self.v0 << iadd(self.v2, self.v3), + self.v1 << icmp(intcc.ult, self.v0, self.v2) + )) + itype = TypeVar("t", "", ints=True, simd=(1, 1)) + + check_typing(x.ti, ({ + self.v0: itype, + self.v2: itype, + self.v3: itype, + self.v1: itype.as_bool(), + }, []), x.symtab) + + def test_iadd_cin(self): + # type: () -> None + x = XForm(Rtl(self.v0 << iadd_cin(self.v1, self.v2, self.v3)), + Rtl( + self.v4 << iadd(self.v1, self.v2), + self.v5 << bint(self.v3), + self.v0 << iadd(self.v4, self.v5) + )) + itype = TypeVar("t", "", ints=True, simd=(1, 1)) + + check_typing(x.ti, ({ + self.v0: itype, + self.v1: itype, + self.v2: itype, + self.v3: self.b1, + self.v4: itype, + self.v5: itype, + }, []), x.symtab) + + def test_enumeration_with_constraints(self): + # type: () -> None + xform = XForm( + Rtl( + self.v0 << iconst(self.imm0), + self.v1 << icmp(intcc.eq, self.v2, self.v0), + self.v5 << vselect(self.v1, self.v3, self.v4) + ), + Rtl( + self.v0 << iconst(self.imm0), + self.v1 << icmp(intcc.eq, self.v2, self.v0), + self.v5 << vselect(self.v1, self.v3, self.v4) + )) + + # Check all var assigns are correct + assert len(xform.ti.constraints) > 0 + concrete_var_assigns = list(xform.ti.concrete_typings()) + + v0 = xform.symtab[str(self.v0)] + v1 = xform.symtab[str(self.v1)] + v2 = xform.symtab[str(self.v2)] + v3 = xform.symtab[str(self.v3)] + v4 = xform.symtab[str(self.v4)] + v5 = xform.symtab[str(self.v5)] + + for var_m in concrete_var_assigns: + assert var_m[v0] == var_m[v2] and \ + var_m[v3] == var_m[v4] and\ + var_m[v5] == var_m[v3] and\ + var_m[v1] == var_m[v2].as_bool() and\ + var_m[v1].get_typeset() == var_m[v3].as_bool().get_typeset() + check_concrete_typing_xform(var_m, xform) + + # The number of possible typings here is: + # 8 cases for v0 = i8xN times 2 options for v3 - i8, b8 = 16 + # 8 cases for v0 = i16xN times 2 options for v3 - i16, b16 = 16 + # 8 cases for v0 = i32xN times 3 options for v3 - i32, b32, f32 = 24 + # 8 cases for v0 = i64xN times 3 options for v3 - i64, b64, f64 = 24 + # + # (Note we have 8 cases for lanes since vselect prevents scalars) + # Total: 2*16 + 2*24 = 80 + assert len(concrete_var_assigns) == 80 + + def test_base_legalizations_enumeration(self): + # type: () -> None + for xform in narrow.xforms + expand.xforms: + # Any legalization patterns we defined should have at least 1 + # concrete typing + concrete_typings_list = list(xform.ti.concrete_typings()) + assert len(concrete_typings_list) > 0 + + # If there are no free_typevars, this is a non-polymorphic pattern. + # There should be only one possible concrete typing. + if (len(xform.free_typevars) == 0): + assert len(concrete_typings_list) == 1 + continue + + # For any patterns where the type env includes constraints, at + # least one of the "theoretically possible" concrete typings must + # be prevented by the constraints. (i.e. we are not emitting + # unneccessary constraints). + # We check that by asserting that the number of concrete typings is + # less than the number of all possible free typevar assignments + if (len(xform.ti.constraints) > 0): + theoretical_num_typings =\ + reduce(lambda x, y: x*y, + [tv.get_typeset().size() + for tv in xform.free_typevars], 1) + assert len(concrete_typings_list) < theoretical_num_typings + + # Check the validity of each individual concrete typing against the + # xform + for concrete_typing in concrete_typings_list: + check_concrete_typing_xform(concrete_typing, xform) diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index f655c8966d..d990d7804f 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -125,59 +125,56 @@ class TestTypeSet(TestCase): self.assertEqual(TypeSet(lanes=(4, 4), ints=(32, 32)).get_singleton(), i32.by(4)) - def test_map_inverse(self): + def test_preimage(self): t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32)) - self.assertEqual(t, t.map_inverse(TypeVar.SAMEAS)) + self.assertEqual(t, t.preimage(TypeVar.SAMEAS)) # LANEOF self.assertEqual(TypeSet(lanes=True, ints=(8, 8), floats=(32, 32)), - t.map_inverse(TypeVar.LANEOF)) + t.preimage(TypeVar.LANEOF)) # Inverse of empty set is still empty across LANEOF self.assertEqual(TypeSet(), - TypeSet().map_inverse(TypeVar.LANEOF)) + TypeSet().preimage(TypeVar.LANEOF)) # ASBOOL t = TypeSet(lanes=(1, 4), bools=(1, 64)) - self.assertEqual(t.map_inverse(TypeVar.ASBOOL), + self.assertEqual(t.preimage(TypeVar.ASBOOL), TypeSet(lanes=(1, 4), ints=True, bools=True, floats=True)) - # Inverse image across ASBOOL of TS not involving b1 cannot have - # lanes=1 - t = TypeSet(lanes=(1, 4), bools=(16, 32)) - self.assertEqual(t.map_inverse(TypeVar.ASBOOL), - TypeSet(lanes=(2, 4), ints=(16, 32), bools=(16, 32), - floats=(32, 32))) - # Half/Double Vector t = TypeSet(lanes=(1, 1), ints=(8, 8)) t1 = TypeSet(lanes=(256, 256), ints=(8, 8)) - self.assertEqual(t.map_inverse(TypeVar.DOUBLEVECTOR).size(), 0) - self.assertEqual(t1.map_inverse(TypeVar.HALFVECTOR).size(), 0) + self.assertEqual(t.preimage(TypeVar.DOUBLEVECTOR).size(), 0) + self.assertEqual(t1.preimage(TypeVar.HALFVECTOR).size(), 0) t = TypeSet(lanes=(1, 16), ints=(8, 16), floats=(32, 32)) t1 = TypeSet(lanes=(64, 256), bools=(1, 32)) - self.assertEqual(t.map_inverse(TypeVar.DOUBLEVECTOR), + self.assertEqual(t.preimage(TypeVar.DOUBLEVECTOR), TypeSet(lanes=(1, 8), ints=(8, 16), floats=(32, 32))) - self.assertEqual(t1.map_inverse(TypeVar.HALFVECTOR), + self.assertEqual(t1.preimage(TypeVar.HALFVECTOR), TypeSet(lanes=(128, 256), bools=(1, 32))) # Half/Double Width t = TypeSet(ints=(8, 8), floats=(32, 32), bools=(1, 8)) t1 = TypeSet(ints=(64, 64), floats=(64, 64), bools=(64, 64)) - self.assertEqual(t.map_inverse(TypeVar.DOUBLEWIDTH).size(), 0) - self.assertEqual(t1.map_inverse(TypeVar.HALFWIDTH).size(), 0) + self.assertEqual(t.preimage(TypeVar.DOUBLEWIDTH).size(), 0) + self.assertEqual(t1.preimage(TypeVar.HALFWIDTH).size(), 0) t = TypeSet(lanes=(1, 16), ints=(8, 16), floats=(32, 64)) t1 = TypeSet(lanes=(64, 256), bools=(1, 64)) - self.assertEqual(t.map_inverse(TypeVar.DOUBLEWIDTH), + self.assertEqual(t.preimage(TypeVar.DOUBLEWIDTH), TypeSet(lanes=(1, 16), ints=(8, 8), floats=(32, 32))) - self.assertEqual(t1.map_inverse(TypeVar.HALFWIDTH), + self.assertEqual(t1.preimage(TypeVar.HALFWIDTH), TypeSet(lanes=(64, 256), bools=(16, 64))) +def has_non_bijective_derived_f(iterable): + return any(not TypeVar.is_bijection(x) for x in iterable) + + class TestTypeVar(TestCase): def test_functions(self): x = TypeVar('x', 'all ints', ints=True) @@ -220,7 +217,7 @@ class TestTypeVar(TestCase): self.assertEqual(len(x.type_set.bools), 0) def test_stress_constrain_types(self): - # Get all 49 possible derived vars of lentgh 2. Since we have SAMEAS + # Get all 49 possible derived vars of length 2. Since we have SAMEAS # this includes singly derived and non-derived vars funcs = [TypeVar.SAMEAS, TypeVar.LANEOF, TypeVar.ASBOOL, TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR, @@ -231,18 +228,18 @@ class TestTypeVar(TestCase): for (i1, i2) in product(v, v): # Compute the derived sets for each starting with a full typeset full_ts = TypeSet(lanes=True, floats=True, ints=True, bools=True) - ts1 = reduce(lambda ts, func: ts.map(func), i1, full_ts) - ts2 = reduce(lambda ts, func: ts.map(func), i2, full_ts) + ts1 = reduce(lambda ts, func: ts.image(func), i1, full_ts) + ts2 = reduce(lambda ts, func: ts.image(func), i2, full_ts) # Compute intersection intersect = ts1.copy() intersect &= ts2 # Propagate instersections backward - ts1_src = reduce(lambda ts, func: ts.map_inverse(func), + ts1_src = reduce(lambda ts, func: ts.preimage(func), reversed(i1), intersect) - ts2_src = reduce(lambda ts, func: ts.map_inverse(func), + ts2_src = reduce(lambda ts, func: ts.preimage(func), reversed(i2), intersect) @@ -262,13 +259,10 @@ class TestTypeVar(TestCase): i2, TypeVar.from_typeset(ts2_src)) - # The typesets of the two derived variables should be subsets of - # the intersection we computed originally - assert tv1.get_typeset().issubset(intersect) - assert tv2.get_typeset().issubset(intersect) - - # In the absence of AS_BOOL map(map_inverse(f)) == f so the + # In the absence of AS_BOOL image(preimage(f)) == f so the # typesets of tv1 and tv2 should be exactly intersection - assert (tv1.get_typeset() == tv2.get_typeset() and - tv1.get_typeset() == intersect) or\ - TypeVar.ASBOOL in set(i1 + i2) + assert tv1.get_typeset() == intersect or\ + has_non_bijective_derived_f(i1) + + assert tv2.get_typeset() == intersect or\ + has_non_bijective_derived_f(i2) diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py new file mode 100644 index 0000000000..5b376f1e5d --- /dev/null +++ b/lib/cretonne/meta/cdsl/ti.py @@ -0,0 +1,556 @@ +""" +Type Inference +""" +from .typevar import TypeVar +from .ast import Def, Var +from copy import copy +from itertools import product + +try: + from typing import Dict, TYPE_CHECKING, Union, Tuple, Optional, Set # noqa + from typing import Iterable # noqa + from typing import cast, List + from .xform import Rtl, XForm # noqa + from .ast import Expr # noqa + if TYPE_CHECKING: + Constraint = Tuple[TypeVar, TypeVar] + ConstraintList = List[Constraint] + TypeMap = Dict[TypeVar, TypeVar] + VarMap = Dict[Var, TypeVar] +except ImportError: + TYPE_CHECKING = False + pass + + +class TypeEnv(object): + """ + Class encapsulating the neccessary book keeping for type inference. + :attribute type_map: dict holding the equivalence relations between tvs + :attribute constraints: a list of accumulated constraints - tuples + (tv1, tv2)) where tv1 and tv2 are equal + :attribute ranks: dictionary recording the (optional) ranks for tvs. + tvs corresponding to real variables have explicitly + specified ranks. + :attribute vars: a set containing all known Vars + :attribute idx: counter used to get fresh ids + """ + def __init__(self, arg=None): + # type: (Optional[Tuple[TypeMap, ConstraintList]]) -> None + self.ranks = {} # type: Dict[TypeVar, int] + self.vars = set() # type: Set[Var] + + if arg is None: + self.type_map = {} # type: TypeMap + self.constraints = [] # type: ConstraintList + else: + self.type_map, self.constraints = arg + + self.idx = 0 + + def __getitem__(self, arg): + # type: (Union[TypeVar, Var]) -> TypeVar + """ + Lookup the canonical representative for a Var/TypeVar. + """ + if (isinstance(arg, Var)): + tv = arg.get_typevar() + else: + assert (isinstance(arg, TypeVar)) + tv = arg + + while tv in self.type_map: + tv = self.type_map[tv] + + if tv.is_derived: + tv = TypeVar.derived(self[tv.base], tv.derived_func) + return tv + + def equivalent(self, tv1, tv2): + # type: (TypeVar, TypeVar) -> None + """ + Record a that the free tv1 is part of the same equivalence class as + tv2. The canonical representative of the merged class is tv2's + cannonical representative. + """ + assert not tv1.is_derived + assert self[tv1] == tv1 + + # Make sure we don't create cycles + if tv2.is_derived: + assert self[tv2.base] != tv1 + + self.type_map[tv1] = tv2 + + def add_constraint(self, tv1, tv2): + # type: (TypeVar, TypeVar) -> None + """ + Add a new equivalence constraint between tv1 and tv2 + """ + self.constraints.append((tv1, tv2)) + + def get_uid(self): + # type: () -> str + r = str(self.idx) + self.idx += 1 + return r + + def __repr__(self): + # type: () -> str + return self.dot() + + def rank(self, tv): + # type: (TypeVar) -> int + """ + Get the rank of tv in the partial order. TVs directly associated with a + Var get their rank from the Var (see register()). + Internally generated non-derived TVs implicitly get the lowest rank (0) + Internal derived variables get the highest rank. + """ + default_rank = 5 if tv.is_derived else 0 + return self.ranks.get(tv, default_rank) + + def register(self, v): + # type: (Var) -> None + """ + Register a new Var v. This computes a rank for the associated TypeVar + for v, which is used to impose a partial order on type variables. + """ + self.vars.add(v) + + if v.is_input(): + r = 4 + elif v.is_intermediate(): + r = 3 + elif v.is_output(): + r = 2 + else: + assert(v.is_temp()) + r = 1 + + self.ranks[v.get_typevar()] = r + + def free_typevars(self): + # type: () -> Set[TypeVar] + """ + Get the free typevars in the current type env. + """ + tvs = set([self[tv].free_typevar() for tv in self.type_map.keys()]) + # Filter out None here due to singleton type vars + return set(filter(lambda x: x is not None, tvs)) + + def normalize(self): + # type: () -> None + """ + Normalize by: + - collapsing any roots that don't correspond to a concrete TV AND + have a single TV derived from them or equivalent to them + + E.g. if we have a root of the tree that looks like: + + typeof_a typeof_b + \ / + typeof_x + | + half_width(1) + | + 1 + + we want to collapse the linear path between 1 and typeof_x. The + resulting graph is: + + typeof_a typeof_b + \ / + typeof_x + """ + source_tvs = set([v.get_typevar() for v in self.vars]) + children = {} # type: Dict[TypeVar, Set[TypeVar]] + for v in self.type_map.values(): + if not v.is_derived: + continue + + t = v.free_typevar() + s = children.get(t, set()) + s.add(v) + children[t] = s + + for (a, b) in self.type_map.items(): + s = children.get(b, set()) + s.add(a) + children[b] = s + + for r in list(self.free_typevars()): + while (r not in source_tvs and r in children and + len(children[r]) == 1): + child = list(children[r])[0] + if child in self.type_map: + assert self.type_map[child] == r + del self.type_map[child] + + r = child + + def extract(self): + # type: () -> TypeEnv + """ + Extract a clean type environment from self, that only mentions + TVs associated with real variables + """ + vars_tvs = set([v.get_typevar() for v in self.vars]) + new_type_map = {tv: self[tv] for tv in vars_tvs if tv != self[tv]} + new_constraints = [(self[tv1], self[tv2]) + for (tv1, tv2) in self.constraints] + + # Sanity: new constraints and the new type_map should only contain + # tvs associated with real vars + for (a, b) in new_constraints: + assert a.free_typevar() in vars_tvs and\ + b.free_typevar() in vars_tvs + + for (k, v) in new_type_map.items(): + assert k in vars_tvs + assert v.free_typevar() is None or v.free_typevar() in vars_tvs + + t = TypeEnv() + t.type_map = new_type_map + t.constraints = new_constraints + # ranks and vars contain only TVs associated with real vars + t.ranks = copy(self.ranks) + t.vars = copy(self.vars) + return t + + def concrete_typings(self): + # type: () -> Iterable[VarMap] + """ + Return an iterable over all possible concrete typings permitted by this + TypeEnv. + """ + free_tvs = self.free_typevars() + free_tv_iters = [tv.get_typeset().concrete_types() for tv in free_tvs] + for concrete_types in product(*free_tv_iters): + # Build type substitutions for all free vars + m = {tv: TypeVar.singleton(typ) + for (tv, typ) in zip(free_tvs, concrete_types)} + + concrete_var_map = {v: subst(self[v.get_typevar()], m) + for v in self.vars} + + # Check if constraints are satisfied for this typing + failed = None + for (tv1, tv2) in self.constraints: + tv1 = subst(tv1, m) + tv2 = subst(tv2, m) + assert tv1.get_typeset().size() == 1 and\ + tv2.get_typeset().size() == 1 + if (tv1.get_typeset() != tv2.get_typeset()): + failed = (tv1, tv2) + break + + if (failed is not None): + continue + + yield concrete_var_map + + def dot(self): + # type: () -> str + """ + Return a representation of self as a graph in dot format. + Nodes correspond to TypeVariables. + Dotted edges correspond to equivalences between TVS + Solid edges correspond to derivation relations between TVs. + Dashed edges correspond to equivalence constraints. + """ + def label(s): + # type: (TypeVar) -> str + return "\"" + str(s) + "\"" + + # Add all registered TVs (as some of them may be singleton nodes not + # appearing in the graph + nodes = set([v.get_typevar() for v in self.vars]) # type: Set[TypeVar] + edges = set() # type: Set[Tuple[TypeVar, TypeVar, str, Optional[str]]] + + for (k, v) in self.type_map.items(): + # Add all intermediate TVs appearing in edges + nodes.add(k) + nodes.add(v) + edges.add((k, v, "dotted", None)) + while (v.is_derived): + nodes.add(v.base) + edges.add((v, v.base, "solid", v.derived_func)) + v = v.base + + for (a, b) in self.constraints: + assert a in nodes and b in nodes + edges.add((a, b, "dashed", None)) + + root_nodes = set([x for x in nodes + if x not in self.type_map and not x.is_derived]) + + r = "digraph {\n" + for n in nodes: + r += label(n) + if n in root_nodes: + r += "[xlabel=\"{}\"]".format(self[n].get_typeset()) + r += ";\n" + + for (n1, n2, style, elabel) in edges: + e = label(n1) + if style == "dashed": + e += '--' + else: + e += '->' + e += label(n2) + e += "[style={}".format(style) + + if elabel is not None: + e += ",label={}".format(elabel) + e += "];\n" + + r += e + r += "}" + + return r + + +if TYPE_CHECKING: + TypingError = str + TypingOrError = Union[TypeEnv, TypingError] + + +def get_error(typing_or_err): + # type: (TypingOrError) -> Optional[TypingError] + """ + Helper function to appease mypy when checking the result of typing. + """ + if isinstance(typing_or_err, str): + if (TYPE_CHECKING): + return cast(TypingError, typing_or_err) + else: + return typing_or_err + else: + return None + + +def get_type_env(typing_or_err): + # type: (TypingOrError) -> TypeEnv + """ + Helper function to appease mypy when checking the result of typing. + """ + assert isinstance(typing_or_err, TypeEnv) + if (TYPE_CHECKING): + return cast(TypeEnv, typing_or_err) + else: + return typing_or_err + + +def subst(tv, tv_map): + # type: (TypeVar, TypeMap) -> TypeVar + """ + Perform substition on the input tv using the TypeMap tv_map. + """ + if tv in tv_map: + return tv_map[tv] + + if tv.is_derived: + return TypeVar.derived(subst(tv.base, tv_map), tv.derived_func) + + return tv + + +def normalize_tv(tv): + # type: (TypeVar) -> TypeVar + """ + Normalize a (potentially derived) TV using the following rules: + - collapse SAMEAS + SAMEAS(base) -> base + + - vector and width derived functions commute + {HALF,DOUBLE}VECTOR({HALF,DOUBLE}WIDTH(base)) -> + {HALF,DOUBLE}WIDTH({HALF,DOUBLE}VECTOR(base)) + + - half/double pairs collapse + {HALF,DOUBLE}WIDTH({DOUBLE,HALF}WIDTH(base)) -> base + {HALF,DOUBLE}VECTOR({DOUBLE,HALF}VECTOR(base)) -> base + """ + vector_derives = [TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR] + width_derives = [TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH] + + if not tv.is_derived: + return tv + + df = tv.derived_func + + # Collapse SAMEAS edges + if (df == TypeVar.SAMEAS): + return normalize_tv(tv.base) + + if (tv.base.is_derived): + base_df = tv.base.derived_func + + # Reordering: {HALFWIDTH, DOUBLEWIDTH} commute with {HALFVECTOR, + # DOUBLEVECTOR}. Arbitrarily pick WIDTH < VECTOR + if df in vector_derives and base_df in width_derives: + return normalize_tv( + TypeVar.derived( + TypeVar.derived(tv.base.base, df), base_df)) + + # Cancelling: HALFWIDTH, DOUBLEWIDTH and HALFVECTOR, DOUBLEVECTOR + # cancel each other. TODO: Does this cancellation hide type + # overflow/underflow? + + if (df, base_df) in \ + [(TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR), + (TypeVar.DOUBLEVECTOR, TypeVar.HALFVECTOR), + (TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH), + (TypeVar.DOUBLEWIDTH, TypeVar.HALFWIDTH)]: + return normalize_tv(tv.base.base) + + return TypeVar.derived(normalize_tv(tv.base), df) + + +def constrain_fixpoint(tv1, tv2): + # type: (TypeVar, TypeVar) -> None + """ + Given typevars tv1 and tv2 (which could be derived from one another) + constrain their typesets to be the same. When one is derived from the + other, repeat the constrain process until fixpoint. + """ + # Constrain tv2's typeset as long as tv1's typeset is changing. + while True: + old_tv1_ts = tv1.get_typeset().copy() + tv2.constrain_types(tv1) + if tv1.get_typeset() == old_tv1_ts: + break + + old_tv2_ts = tv2.get_typeset().copy() + tv1.constrain_types(tv2) + assert old_tv2_ts == tv2.get_typeset() + + +def unify(tv1, tv2, typ): + # type: (TypeVar, TypeVar, TypeEnv) -> TypingOrError + """ + Unify tv1 and tv2 in the current type environment typ, and return an + updated type environment or error. + """ + tv1 = normalize_tv(typ[tv1]) + tv2 = normalize_tv(typ[tv2]) + + # Already unified + if tv1 == tv2: + return typ + + if typ.rank(tv2) < typ.rank(tv1): + return unify(tv2, tv1, typ) + + constrain_fixpoint(tv1, tv2) + + if (tv1.get_typeset().size() == 0 or tv2.get_typeset().size() == 0): + return "Error: empty type created when unifying {} and {}"\ + .format(tv1, tv2) + + # Free -> Derived(Free) + if not tv1.is_derived: + typ.equivalent(tv1, tv2) + return typ + + assert tv2.is_derived, "Ordering gives us !tv1.is_derived==>tv2.is_derived" + + if (tv1.is_derived and TypeVar.is_bijection(tv1.derived_func)): + inv_f = TypeVar.inverse_func(tv1.derived_func) + return unify(tv1.base, normalize_tv(TypeVar.derived(tv2, inv_f)), typ) + + typ.add_constraint(tv1, tv2) + return typ + + +def ti_def(definition, typ): + # type: (Def, TypeEnv) -> TypingOrError + """ + Perform type inference on one Def in the current type environment typ and + return an updated type environment or error. + + At a high level this works by creating fresh copies of each formal type var + in the Def's instruction's signature, and unifying the formal tv with the + corresponding actual tv. + """ + expr = definition.expr + inst = expr.inst + + # Create a map m mapping each free typevar in the signature of definition + # to a fresh copy of itself + all_formal_tvs = \ + [inst.outs[i].typevar for i in inst.value_results] +\ + [inst.ins[i].typevar for i in inst.value_opnums] + free_formal_tvs = [tv for tv in all_formal_tvs if not tv.is_derived] + m = {tv: tv.get_fresh_copy(str(typ.get_uid())) for tv in free_formal_tvs} + + # Get fresh copies for each typevar in the signature (both free and + # derived) + fresh_formal_tvs = \ + [subst(inst.outs[i].typevar, m) for i in inst.value_results] +\ + [subst(inst.ins[i].typevar, m) for i in inst.value_opnums] + + # Get the list of actual Vars + actual_vars = [] # type: List[Expr] + actual_vars += [definition.defs[i] for i in inst.value_results] + actual_vars += [expr.args[i] for i in inst.value_opnums] + + # Get the list of the actual TypeVars + actual_tvs = [] + for v in actual_vars: + assert(isinstance(v, Var)) + # Register with TypeEnv that this typevar corresponds ot variable v, + # and thus has a given rank + typ.register(v) + actual_tvs.append(v.get_typevar()) + + # Unify each actual typevar with the correpsonding fresh formal tv + for (actual_tv, formal_tv) in zip(actual_tvs, fresh_formal_tvs): + typ_or_err = unify(actual_tv, formal_tv, typ) + err = get_error(typ_or_err) + if (err): + return "fail ti on {} <: {}: ".format(actual_tv, formal_tv) + err + + typ = get_type_env(typ_or_err) + + return typ + + +def ti_rtl(rtl, typ): + # type: (Rtl, TypeEnv) -> TypingOrError + """ + Perform type inference on an Rtl in a starting type env typ. Return an + updated type environment or error. + """ + for (i, d) in enumerate(rtl.rtl): + assert (isinstance(d, Def)) + typ_or_err = ti_def(d, typ) + err = get_error(typ_or_err) # type: Optional[TypingError] + if (err): + return "On line {}: ".format(i) + err + + typ = get_type_env(typ_or_err) + + return typ + + +def ti_xform(xform, typ): + # type: (XForm, TypeEnv) -> TypingOrError + """ + Perform type inference on an Rtl in a starting type env typ. Return an + updated type environment or error. + """ + typ_or_err = ti_rtl(xform.src, typ) + err = get_error(typ_or_err) # type: Optional[TypingError] + if (err): + return "In src pattern: " + err + + typ = get_type_env(typ_or_err) + + typ_or_err = ti_rtl(xform.dst, typ) + err = get_error(typ_or_err) + if (err): + return "In dst pattern: " + err + + typ = get_type_env(typ_or_err) + + return get_type_env(typ_or_err) diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 69b419bfa3..1ea9831b91 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -8,7 +8,6 @@ from __future__ import absolute_import import math from . import types, is_power_of_two from copy import deepcopy -from .types import IntType, FloatType, BoolType try: from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa @@ -210,6 +209,10 @@ class TypeSet(object): else: return False + def __ne__(self, other): + # type: (object) -> bool + return not self.__eq__(other) + def __repr__(self): # type: () -> str s = 'TypeSet(lanes={}'.format(pp_set(self.lanes)) @@ -289,7 +292,9 @@ class TypeSet(object): new = self.copy() new.ints = set() new.floats = set() - new.bools = self.ints.union(self.floats).union(self.bools) + + if len(self.lanes.difference(set([1]))) > 0: + new.bools = self.ints.union(self.floats).union(self.bools) if 1 in self.lanes: new.bools.add(1) @@ -340,7 +345,7 @@ class TypeSet(object): return new - def map(self, func): + def image(self, func): # type: (str) -> TypeSet """ Return the image of self across the derived function func @@ -362,7 +367,7 @@ class TypeSet(object): else: assert False, "Unknown derived function: " + func - def map_inverse(self, func): + def preimage(self, func): # type: (str) -> TypeSet """ Return the inverse image of self across the derived function func @@ -379,16 +384,14 @@ class TypeSet(object): return new elif (func == TypeVar.ASBOOL): new = self.copy() - new.ints = self.bools.difference(set([1])) - new.floats = self.bools.intersection(set([32, 64])) if 1 not in self.bools: - try: - # If the range doesn't have b1, then the domain can't - # include scalars, as as_bool(scalar)=b1 - new.lanes.remove(1) - except KeyError: - pass + new.ints = self.bools.difference(set([1])) + new.floats = self.bools.intersection(set([32, 64])) + else: + new.ints = set([2**x for x in range(3, 7)]) + new.floats = set([32, 64]) + return new elif (func == TypeVar.HALFWIDTH): return self.double_width() @@ -409,27 +412,32 @@ class TypeSet(object): return len(self.lanes) * (len(self.ints) + len(self.floats) + len(self.bools)) + def concrete_types(self): + # type: () -> Iterable[types.ValueType] + def by(scalar, lanes): + # type: (types.ScalarType, int) -> types.ValueType + if (lanes == 1): + return scalar + else: + return scalar.by(lanes) + + for nlanes in self.lanes: + for bits in self.ints: + yield by(types.IntType.with_bits(bits), nlanes) + for bits in self.floats: + yield by(types.FloatType.with_bits(bits), nlanes) + for bits in self.bools: + yield by(types.BoolType.with_bits(bits), nlanes) + def get_singleton(self): # type: () -> types.ValueType """ Return the singleton type represented by self. Can only call on typesets containing 1 type. """ - assert self.size() == 1 - scalar_type = None # type: types.ScalarType - if len(self.ints) > 0: - scalar_type = IntType.with_bits(tuple(self.ints)[0]) - elif len(self.floats) > 0: - scalar_type = FloatType.with_bits(tuple(self.floats)[0]) - else: - scalar_type = BoolType.with_bits(tuple(self.bools)[0]) - - nlanes = tuple(self.lanes)[0] - - if nlanes == 1: - return scalar_type - else: - return scalar_type.by(nlanes) + types = list(self.concrete_types()) + assert len(types) == 1 + return types[0] class TypeVar(object): @@ -519,6 +527,13 @@ class TypeVar(object): 'TypeVar({}, {})' .format(self.name, self.type_set)) + def __hash__(self): + # type: () -> int + if (not self.is_derived): + return object.__hash__(self) + + return hash((self.derived_func, self.base)) + def __eq__(self, other): # type: (object) -> bool if not isinstance(other, TypeVar): @@ -530,6 +545,10 @@ class TypeVar(object): else: return self is other + def __ne__(self, other): + # type: (object) -> bool + return not self.__eq__(other) + # Supported functions for derived type variables. # The names here must match the method names on `ir::types::Type`. # The camel_case of the names must match `enum OperandConstraint` in @@ -542,6 +561,27 @@ class TypeVar(object): HALFVECTOR = 'half_vector' DOUBLEVECTOR = 'double_vector' + @staticmethod + def is_bijection(func): + # type: (str) -> bool + return func in [ + TypeVar.SAMEAS, + TypeVar.HALFWIDTH, + TypeVar.DOUBLEWIDTH, + TypeVar.HALFVECTOR, + TypeVar.DOUBLEVECTOR] + + @staticmethod + def inverse_func(func): + # type: (str) -> str + return { + TypeVar.SAMEAS: TypeVar.SAMEAS, + TypeVar.HALFWIDTH: TypeVar.DOUBLEWIDTH, + TypeVar.DOUBLEWIDTH: TypeVar.HALFWIDTH, + TypeVar.HALFVECTOR: TypeVar.DOUBLEVECTOR, + TypeVar.DOUBLEVECTOR: TypeVar.HALFVECTOR + }[func] + @staticmethod def derived(base, derived_func): # type: (TypeVar, str) -> TypeVar @@ -668,7 +708,7 @@ class TypeVar(object): Get the free type variable controlling this one. """ if self.is_derived: - return self.base + return self.base.free_typevar() elif self.singleton_type() is not None: # A singleton type variable is not a proper free variable. return None @@ -697,7 +737,7 @@ class TypeVar(object): if not self.is_derived: self.type_set &= ts else: - self.base.constrain_types_by_ts(ts.map_inverse(self.derived_func)) + self.base.constrain_types_by_ts(ts.preimage(self.derived_func)) def constrain_types(self, other): # type: (TypeVar) -> None @@ -723,19 +763,14 @@ class TypeVar(object): if not self.is_derived: return self.type_set else: - if (self.derived_func == TypeVar.SAMEAS): - return self.base.get_typeset() - elif (self.derived_func == TypeVar.LANEOF): - return self.base.get_typeset().lane_of() - elif (self.derived_func == TypeVar.ASBOOL): - return self.base.get_typeset().as_bool() - elif (self.derived_func == TypeVar.HALFWIDTH): - return self.base.get_typeset().half_width() - elif (self.derived_func == TypeVar.DOUBLEWIDTH): - return self.base.get_typeset().double_width() - elif (self.derived_func == TypeVar.HALFVECTOR): - return self.base.get_typeset().half_vector() - elif (self.derived_func == TypeVar.DOUBLEVECTOR): - return self.base.get_typeset().double_vector() - else: - assert False, "Unknown derived function: " + self.derived_func + return self.base.get_typeset().image(self.derived_func) + + def get_fresh_copy(self, name): + # type: (str) -> TypeVar + """ + Get a fresh copy of self. Can only be called on free typevars. + """ + assert not self.is_derived + tv = TypeVar.from_typeset(self.type_set.copy()) + tv.name = name + return tv diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 9ce93c9ed9..a8fbb7f66e 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -3,6 +3,7 @@ Instruction transformations. """ from __future__ import absolute_import from .ast import Def, Var, Apply +from .ti import ti_xform, TypeEnv, get_type_env try: from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa @@ -83,6 +84,8 @@ class XForm(object): self._rewrite_rtl(src, symtab, Var.SRCCTX) num_src_inputs = len(self.inputs) self._rewrite_rtl(dst, symtab, Var.DSTCTX) + # Needed for testing type inference on XForms + self.symtab = symtab # Check for inconsistently used inputs. for i in self.inputs: @@ -96,9 +99,25 @@ class XForm(object): "extra inputs in dst RTL: {}".format( self.inputs[num_src_inputs:])) - self._infer_types(self.src) - self._infer_types(self.dst) - self._collect_typevars() + # Perform type inference and cleanup + raw_ti = get_type_env(ti_xform(self, TypeEnv())) + raw_ti.normalize() + self.ti = raw_ti.extract() + + # Sanity: The set of inferred free typevars should be a subset of the + # TVs corresponding to Vars appearing in src + self.free_typevars = self.ti.free_typevars() + src_vars = set(self.inputs).union( + [x for x in self.defs if not x.is_temp()]) + src_tvs = set([v.get_typevar() for v in src_vars]) + if (not self.free_typevars.issubset(src_tvs)): + raise AssertionError( + "Some free vars don't appear in src - {}" + .format(self.free_typevars.difference(src_tvs))) + + # Update the type vars for each Var to their inferred values + for v in self.inputs + self.defs: + v.set_typevar(self.ti[v.get_typevar()]) def __repr__(self): # type: () -> str @@ -202,63 +221,6 @@ class XForm(object): raise AssertionError( '{} not defined in dest pattern'.format(d)) - def _infer_types(self, rtl): - # type: (Rtl) -> None - """Assign type variables to all value variables used in `rtl`.""" - for d in rtl.rtl: - inst = d.expr.inst - - # Get the Var corresponding to the controlling type variable. - ctrl_var = None # type: Var - if inst.is_polymorphic: - if inst.use_typevar_operand: - # Should this be an assertion instead? - # Should all value operands be required to be Vars? - arg = d.expr.args[inst.format.typevar_operand] - if isinstance(arg, Var): - ctrl_var = arg - else: - ctrl_var = d.defs[inst.value_results[0]] - - # Reconcile arguments with the requirements of `inst`. - for opnum in inst.value_opnums: - inst_tv = inst.ins[opnum].typevar - v = d.expr.args[opnum] - if isinstance(v, Var): - v.constrain_typevar(inst_tv, inst.ctrl_typevar, ctrl_var) - - # Reconcile results with the requirements of `inst`. - for resnum in inst.value_results: - inst_tv = inst.outs[resnum].typevar - v = d.defs[resnum] - v.constrain_typevar(inst_tv, inst.ctrl_typevar, ctrl_var) - - def _collect_typevars(self): - # type: () -> None - """ - Collect a list of variables whose type can be used to infer the types - of all expressions. - - This should be called after `_infer_types()` above has computed type - variables for all the used vars. - """ - fvars = list(v for v in self.inputs if v.has_free_typevar()) - fvars += list(v for v in self.defs if v.has_free_typevar()) - self.free_typevars = fvars - - # When substituting a pattern, we know the types of all variables that - # appear on the source side: inut, output, and intermediate values. - # However, temporary values which appear only on the destination side - # must have their type computed somehow. - # - # Some variables have a fixed type which appears as a type variable - # with a singleton_type field set. That's allowed for temps too. - for v in fvars: - if v.is_temp() and not v.typevar.singleton_type(): - raise AssertionError( - "Cannot determine type of temp '{}' in xform:\n{}" - .format(v, self)) - class XFormGroup(object): """ From c75004339b7eedd2490e12a97e07033793a99f10 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 30 Jun 2017 19:09:10 -0700 Subject: [PATCH 842/968] Implement fmt::Display for AllocatableSet. Also add a display() method which accepts a RegInfo reference. --- lib/cretonne/src/regalloc/allocatable_set.rs | 48 +++++++++++++++++++- lib/cretonne/src/regalloc/coloring.rs | 4 +- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index aecd338e7f..063c8e63ac 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -5,7 +5,8 @@ //! "register unit" abstraction. Every register contains one or more register units. Registers that //! share a register unit can't be in use at the same time. -use isa::registers::{RegUnit, RegUnitMask, RegClass}; +use isa::registers::{RegInfo, RegUnit, RegUnitMask, RegClass}; +use std::fmt; use std::iter::ExactSizeIterator; use std::mem::size_of_val; @@ -98,6 +99,12 @@ impl AllocatableSet { *x &= y; } } + + /// Return an object that can display this register set, using the register info from the + /// target ISA. + pub fn display<'a, R: Into>>(&self, regs: R) -> DisplayAllocatableSet<'a> { + DisplayAllocatableSet(self.clone(), regs.into()) + } } /// Iterator over available registers in a register class. @@ -138,6 +145,45 @@ impl Iterator for RegSetIter { impl ExactSizeIterator for RegSetIter {} +/// Displaying an `AllocatableSet` correctly requires the associated `RegInfo` from the target ISA. +pub struct DisplayAllocatableSet<'a>(AllocatableSet, Option<&'a RegInfo>); + +impl<'a> fmt::Display for DisplayAllocatableSet<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[")?; + match self.1 { + None => { + for w in &self.0.avail { + write!(f, " #{:08x}", w)?; + } + } + Some(reginfo) => { + let toprcs = reginfo + .banks + .iter() + .map(|b| b.first_toprc + b.num_toprcs) + .max() + .expect("No register banks"); + for rc in ®info.classes[0..toprcs] { + if rc.width == 1 { + write!(f, " {}:", rc)?; + for u in self.0.iter(rc) { + write!(f, " {}", reginfo.display_regunit(u))?; + } + } + } + } + } + write!(f, " ]") + } +} + +impl fmt::Display for AllocatableSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.display(None).fmt(f) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 4ebf3340f9..e4e5a6599f 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -278,7 +278,9 @@ impl<'a> Context<'a> { regs: &mut AllocatableSet, locations: &mut ValueLocations, func_signature: &Signature) { - dbg!("Coloring {}", dfg.display_inst(inst)); + dbg!("Coloring {}\n {}", + dfg.display_inst(inst), + regs.display(&self.reginfo)); // EBB whose arguments should be colored to match the current branch instruction's // arguments. From 9a7ee4ca1226c650132f72c42a550a84375dd8b0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Jul 2017 11:14:08 -0700 Subject: [PATCH 843/968] Add a test with a fixed register constraint. Make sure we use the diverted register location for tied operands. --- filetests/regalloc/constraints.cton | 13 +++++++++++++ lib/cretonne/src/regalloc/coloring.rs | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/filetests/regalloc/constraints.cton b/filetests/regalloc/constraints.cton index e88bcc514c..c6108b0673 100644 --- a/filetests/regalloc/constraints.cton +++ b/filetests/regalloc/constraints.cton @@ -13,3 +13,16 @@ ebb0: v2 = isub v0, v1 return v2 } + +; Fixed register constraint. +function %fixed_op() -> i32 { +ebb0: + ; check: ,%rax] + ; sameln: $v0 = iconst.i32 12 + v0 = iconst.i32 12 + v1 = iconst.i32 13 + ; The dynamic shift amount must be in %rcx + ; check: regmove $v0, %rax -> %rcx + v2 = ishl v1, v0 + return v2 +} diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index e4e5a6599f..f61e88e9b7 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -371,7 +371,8 @@ impl<'a> Context<'a> { for (op, lv) in constraints.outs.iter().zip(defs) { if let ConstraintKind::Tied(num) = op.kind { let arg = dfg.inst_args(inst)[num as usize]; - locations[lv.value] = locations[arg]; + let reg = self.divert.reg(arg, locations); + locations[lv.value] = ValueLoc::Reg(reg); } } } From fe127ab3eba52314c15591cb7fbd2ad01e117ce3 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Jul 2017 12:19:55 -0700 Subject: [PATCH 844/968] Test two consecutive fixed operands. We need to move the previous value out of the way first. --- filetests/regalloc/constraints.cton | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/filetests/regalloc/constraints.cton b/filetests/regalloc/constraints.cton index c6108b0673..28758a3bdd 100644 --- a/filetests/regalloc/constraints.cton +++ b/filetests/regalloc/constraints.cton @@ -2,6 +2,7 @@ test regalloc isa intel ; regex: V=v\d+ +; regex: REG=%r([abcd]x|[sd]i) ; Tied operands, both are killed at instruction. function %tied_easy() -> i32 { @@ -26,3 +27,20 @@ ebb0: v2 = ishl v1, v0 return v2 } + +; Fixed register constraint twice. +function %fixed_op_twice() -> i32 { +ebb0: + ; check: ,%rax] + ; sameln: $v0 = iconst.i32 12 + v0 = iconst.i32 12 + v1 = iconst.i32 13 + ; The dynamic shift amount must be in %rcx + ; check: regmove $v0, %rax -> %rcx + v2 = ishl v1, v0 + ; check: regmove $v0, %rcx -> $REG + ; check: regmove $v2, $REG -> %rcx + v3 = ishl v0, v2 + + return v3 +} From 83e55525d6d28f5a28457f6a6206ac404bb3fde8 Mon Sep 17 00:00:00 2001 From: d1m0 Date: Wed, 5 Jul 2017 15:47:44 -0700 Subject: [PATCH 845/968] Cleanup typos; Remove SAMEAS; More descriptive rank comments; Introduce explicit sorting in free_typevars() (#111) As per the comment in TypeEnv.normalize_tv about cancellation, whenever we create a TypeVar we must assert that there is no under/overflow. To make sure this always happen move the safety checks to TypeVar.derived() from the other helper methods --- lib/cretonne/meta/cdsl/test_ti.py | 8 +-- lib/cretonne/meta/cdsl/test_typevar.py | 8 +-- lib/cretonne/meta/cdsl/ti.py | 44 +++++++------ lib/cretonne/meta/cdsl/typevar.py | 90 +++++++++----------------- lib/cretonne/meta/cdsl/xform.py | 6 +- 5 files changed, 66 insertions(+), 90 deletions(-) diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index d97902568a..396d5ef844 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -62,7 +62,7 @@ def agree(me, other): if m[me[tv]] != other[tv]: return False - # Tranlsate our constraints using m, and sort + # Translate our constraints using m, and sort me_equiv_constr = [(subst(a, m), subst(b, m)) for (a, b) in me.constraints] me_equiv_constr = sorted([sort_constr(x) for x in me_equiv_constr]) @@ -76,7 +76,7 @@ def agree(me, other): def check_typing(got_or_err, expected, symtab=None): # type: (TypingOrError, Tuple[VarMap, ConstraintList], Dict[str, Var]) -> None # noqa """ - Check that a the typying we received (got_or_err) complies with the + Check that a the typing we received (got_or_err) complies with the expected typing (expected). If symtab is specified, substitute the Vars in expected using symtab first (used when checking type inference on XForms) """ @@ -409,7 +409,7 @@ class TestXForm(TypeCheckingBaseTest): # If there are no free_typevars, this is a non-polymorphic pattern. # There should be only one possible concrete typing. - if (len(xform.free_typevars) == 0): + if (len(xform.ti.free_typevars()) == 0): assert len(concrete_typings_list) == 1 continue @@ -423,7 +423,7 @@ class TestXForm(TypeCheckingBaseTest): theoretical_num_typings =\ reduce(lambda x, y: x*y, [tv.get_typeset().size() - for tv in xform.free_typevars], 1) + for tv in xform.ti.free_typevars()], 1) assert len(concrete_typings_list) < theoretical_num_typings # Check the validity of each individual concrete typing against the diff --git a/lib/cretonne/meta/cdsl/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py index d990d7804f..dca5ba1b48 100644 --- a/lib/cretonne/meta/cdsl/test_typevar.py +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -127,7 +127,6 @@ class TestTypeSet(TestCase): def test_preimage(self): t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32)) - self.assertEqual(t, t.preimage(TypeVar.SAMEAS)) # LANEOF self.assertEqual(TypeSet(lanes=True, ints=(8, 8), floats=(32, 32)), @@ -217,12 +216,11 @@ class TestTypeVar(TestCase): self.assertEqual(len(x.type_set.bools), 0) def test_stress_constrain_types(self): - # Get all 49 possible derived vars of length 2. Since we have SAMEAS - # this includes singly derived and non-derived vars - funcs = [TypeVar.SAMEAS, TypeVar.LANEOF, + # Get all 43 possible derived vars of length up to 2 + funcs = [TypeVar.LANEOF, TypeVar.ASBOOL, TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR, TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH] - v = list(product(*[funcs, funcs])) + v = [()] + [(x,) for x in funcs] + list(product(*[funcs, funcs])) # For each pair of derived variables for (i1, i2) in product(v, v): diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index 5b376f1e5d..ce2f05c750 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -29,11 +29,19 @@ class TypeEnv(object): :attribute constraints: a list of accumulated constraints - tuples (tv1, tv2)) where tv1 and tv2 are equal :attribute ranks: dictionary recording the (optional) ranks for tvs. - tvs corresponding to real variables have explicitly - specified ranks. + 'rank' is a partial ordering on TVs based on their + origin. See comments in rank() and register(). :attribute vars: a set containing all known Vars :attribute idx: counter used to get fresh ids """ + + RANK_DERIVED = 5 + RANK_INPUT = 4 + RANK_INTERMEDIATE = 3 + RANK_OUTPUT = 2 + RANK_TEMP = 1 + RANK_INTERNAL = 0 + def __init__(self, arg=None): # type: (Optional[Tuple[TypeMap, ConstraintList]]) -> None self.ranks = {} # type: Dict[TypeVar, int] @@ -104,9 +112,10 @@ class TypeEnv(object): Get the rank of tv in the partial order. TVs directly associated with a Var get their rank from the Var (see register()). Internally generated non-derived TVs implicitly get the lowest rank (0) - Internal derived variables get the highest rank. + Derived variables get the highest rank. """ - default_rank = 5 if tv.is_derived else 0 + default_rank = TypeEnv.RANK_DERIVED if tv.is_derived else\ + TypeEnv.RANK_INTERNAL return self.ranks.get(tv, default_rank) def register(self, v): @@ -118,25 +127,26 @@ class TypeEnv(object): self.vars.add(v) if v.is_input(): - r = 4 + r = TypeEnv.RANK_INPUT elif v.is_intermediate(): - r = 3 + r = TypeEnv.RANK_INTERMEDIATE elif v.is_output(): - r = 2 + r = TypeEnv.RANK_OUTPUT else: assert(v.is_temp()) - r = 1 + r = TypeEnv.RANK_TEMP self.ranks[v.get_typevar()] = r def free_typevars(self): - # type: () -> Set[TypeVar] + # type: () -> List[TypeVar] """ Get the free typevars in the current type env. """ tvs = set([self[tv].free_typevar() for tv in self.type_map.keys()]) # Filter out None here due to singleton type vars - return set(filter(lambda x: x is not None, tvs)) + return sorted(filter(lambda x: x is not None, tvs), + key=lambda x: x.name) def normalize(self): # type: () -> None @@ -178,7 +188,7 @@ class TypeEnv(object): s.add(a) children[b] = s - for r in list(self.free_typevars()): + for r in self.free_typevars(): while (r not in source_tvs and r in children and len(children[r]) == 1): child = list(children[r])[0] @@ -359,9 +369,6 @@ def normalize_tv(tv): # type: (TypeVar) -> TypeVar """ Normalize a (potentially derived) TV using the following rules: - - collapse SAMEAS - SAMEAS(base) -> base - - vector and width derived functions commute {HALF,DOUBLE}VECTOR({HALF,DOUBLE}WIDTH(base)) -> {HALF,DOUBLE}WIDTH({HALF,DOUBLE}VECTOR(base)) @@ -378,10 +385,6 @@ def normalize_tv(tv): df = tv.derived_func - # Collapse SAMEAS edges - if (df == TypeVar.SAMEAS): - return normalize_tv(tv.base) - if (tv.base.is_derived): base_df = tv.base.derived_func @@ -393,8 +396,9 @@ def normalize_tv(tv): TypeVar.derived(tv.base.base, df), base_df)) # Cancelling: HALFWIDTH, DOUBLEWIDTH and HALFVECTOR, DOUBLEVECTOR - # cancel each other. TODO: Does this cancellation hide type - # overflow/underflow? + # cancel each other. Note: This doesn't hide any over/underflows, + # since we 1) assert the safety of each TV in the chain upon its + # creation, and 2) the base typeset is only allowed to shrink. if (df, base_df) in \ [(TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR), diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 1ea9831b91..b28502f61b 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -350,9 +350,7 @@ class TypeSet(object): """ Return the image of self across the derived function func """ - if (func == TypeVar.SAMEAS): - return self - elif (func == TypeVar.LANEOF): + if (func == TypeVar.LANEOF): return self.lane_of() elif (func == TypeVar.ASBOOL): return self.as_bool() @@ -376,9 +374,7 @@ class TypeSet(object): if (self.size() == 0): return self - if (func == TypeVar.SAMEAS): - return self - elif (func == TypeVar.LANEOF): + if (func == TypeVar.LANEOF): new = self.copy() new.lanes = set([2**i for i in range(0, int_log2(MAX_LANES)+1)]) return new @@ -388,6 +384,9 @@ class TypeSet(object): if 1 not in self.bools: new.ints = self.bools.difference(set([1])) new.floats = self.bools.intersection(set([32, 64])) + # If b1 is not in our typeset, than lanes=1 cannot be in the + # pre-image, as as_bool() of scalars is always b1. + new.lanes = self.lanes.difference(set([1])) else: new.ints = set([2**x for x in range(3, 7)]) new.floats = set([32, 64]) @@ -553,7 +552,6 @@ class TypeVar(object): # The names here must match the method names on `ir::types::Type`. # The camel_case of the names must match `enum OperandConstraint` in # `instructions.rs`. - SAMEAS = 'same_as' LANEOF = 'lane_of' ASBOOL = 'as_bool' HALFWIDTH = 'half_width' @@ -565,7 +563,6 @@ class TypeVar(object): def is_bijection(func): # type: (str) -> bool return func in [ - TypeVar.SAMEAS, TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH, TypeVar.HALFVECTOR, @@ -575,7 +572,6 @@ class TypeVar(object): def inverse_func(func): # type: (str) -> str return { - TypeVar.SAMEAS: TypeVar.SAMEAS, TypeVar.HALFWIDTH: TypeVar.DOUBLEWIDTH, TypeVar.DOUBLEWIDTH: TypeVar.HALFWIDTH, TypeVar.HALFVECTOR: TypeVar.DOUBLEVECTOR, @@ -586,6 +582,31 @@ class TypeVar(object): def derived(base, derived_func): # type: (TypeVar, str) -> TypeVar """Create a type variable that is a function of another.""" + + # Safety checks to avoid over/underflows. + ts = base.get_typeset() + + if derived_func == TypeVar.HALFWIDTH: + if len(ts.ints) > 0: + assert min(ts.ints) > 8, "Can't halve all integer types" + if len(ts.floats) > 0: + assert min(ts.floats) > 32, "Can't halve all float types" + if len(ts.bools) > 0: + assert min(ts.bools) > 8, "Can't halve all boolean types" + elif derived_func == TypeVar.DOUBLEWIDTH: + if len(ts.ints) > 0: + assert max(ts.ints) < MAX_BITS,\ + "Can't double all integer types." + if len(ts.floats) > 0: + assert max(ts.floats) < MAX_BITS,\ + "Can't double all float types." + if len(ts.bools) > 0: + assert max(ts.bools) < MAX_BITS, "Can't double all bool types." + elif derived_func == TypeVar.HALFVECTOR: + assert min(ts.lanes) > 1, "Can't halve a scalar type" + elif derived_func == TypeVar.DOUBLEVECTOR: + assert max(ts.lanes) < MAX_LANES, "Can't double 256 lanes." + return TypeVar(None, None, base=base, derived_func=derived_func) @staticmethod @@ -596,27 +617,6 @@ class TypeVar(object): tv.type_set = ts return tv - def change_to_derived(self, base, derived_func): - # type: (TypeVar, str) -> None - """Change this type variable into a derived one.""" - self.type_set = None - self.is_derived = True - self.base = base - self.derived_func = derived_func - - def strip_sameas(self): - # type: () -> TypeVar - """ - Strip any `SAMEAS` functions from this typevar. - - Also rewrite any `SAMEAS` functions nested under this typevar. - """ - if self.is_derived: - self.base = self.base.strip_sameas() - if self.derived_func == self.SAMEAS: - return self.base - return self - def lane_of(self): # type: () -> TypeVar """ @@ -642,14 +642,6 @@ class TypeVar(object): Return a derived type variable that has the same number of vector lanes as this one, but the lanes are half the width. """ - ts = self.get_typeset() - if len(ts.ints) > 0: - assert min(ts.ints) > 8, "Can't halve all integer types" - if len(ts.floats) > 0: - assert min(ts.floats) > 32, "Can't halve all float types" - if len(ts.bools) > 0: - assert min(ts.bools) > 8, "Can't halve all boolean types" - return TypeVar.derived(self, self.HALFWIDTH) def double_width(self): @@ -658,14 +650,6 @@ class TypeVar(object): Return a derived type variable that has the same number of vector lanes as this one, but the lanes are double the width. """ - ts = self.get_typeset() - if len(ts.ints) > 0: - assert max(ts.ints) < MAX_BITS, "Can't double all integer types." - if len(ts.floats) > 0: - assert max(ts.floats) < MAX_BITS, "Can't double all float types." - if len(ts.bools) > 0: - assert max(ts.bools) < MAX_BITS, "Can't double all bool types." - return TypeVar.derived(self, self.DOUBLEWIDTH) def half_vector(self): @@ -674,9 +658,6 @@ class TypeVar(object): Return a derived type variable that has half the number of vector lanes as this one, with the same lane type. """ - ts = self.get_typeset() - assert min(ts.lanes) > 1, "Can't halve a scalar type" - return TypeVar.derived(self, self.HALFVECTOR) def double_vector(self): @@ -685,9 +666,6 @@ class TypeVar(object): Return a derived type variable that has twice the number of vector lanes as this one, with the same lane type. """ - ts = self.get_typeset() - assert max(ts.lanes) < MAX_LANES, "Can't double 256 lanes." - return TypeVar.derived(self, self.DOUBLEVECTOR) def singleton_type(self): @@ -744,15 +722,11 @@ class TypeVar(object): """ Constrain the range of types this variable can assume to a subset of those `other` can assume. - - If this is a SAMEAS-derived type variable, constrain the base instead. """ - a = self.strip_sameas() - b = other.strip_sameas() - if a is b: + if self is other: return - a.constrain_types_by_ts(b.get_typeset()) + self.constrain_types_by_ts(other.get_typeset()) def get_typeset(self): # type: () -> TypeSet diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index a8fbb7f66e..fd3c1dff1f 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -106,14 +106,14 @@ class XForm(object): # Sanity: The set of inferred free typevars should be a subset of the # TVs corresponding to Vars appearing in src - self.free_typevars = self.ti.free_typevars() + free_typevars = set(self.ti.free_typevars()) src_vars = set(self.inputs).union( [x for x in self.defs if not x.is_temp()]) src_tvs = set([v.get_typevar() for v in src_vars]) - if (not self.free_typevars.issubset(src_tvs)): + if (not free_typevars.issubset(src_tvs)): raise AssertionError( "Some free vars don't appear in src - {}" - .format(self.free_typevars.difference(src_tvs))) + .format(free_typevars.difference(src_tvs))) # Update the type vars for each Var to their inferred values for v in self.inputs + self.defs: From 0f285cb13759596690818cf22b209a1c238c2614 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Jul 2017 15:12:35 -0700 Subject: [PATCH 846/968] Intel 32-bit encodings for copy.i32. --- filetests/isa/intel/binary32.cton | 5 +++++ lib/cretonne/meta/isa/intel/encodings.py | 2 ++ lib/cretonne/meta/isa/intel/recipes.py | 8 ++++++-- lib/cretonne/src/isa/intel/binemit.rs | 10 ++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 2e42d01fcc..14f050c2e7 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -56,6 +56,11 @@ ebb0: ; asm: sarl %cl, %ecx [-,%rcx] v25 = sshr v1, v1 ; bin: d3 f9 + ; asm: movl %esi, %ecx + [-,%rcx] v26 = copy v2 ; bin: 89 f1 + ; asm: movl %ecx, %esi + [-,%rsi] v27 = copy v1 ; bin: 89 ce + ; Integer Register - Immediate 8-bit operations. ; The 8-bit immediate is sign-extended. diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index c05308177a..0b59b32da3 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -13,6 +13,8 @@ I32.enc(base.band.i32, *r.rr(0x21)) I32.enc(base.bor.i32, *r.rr(0x09)) I32.enc(base.bxor.i32, *r.rr(0x31)) +I32.enc(base.copy.i32, *r.ur(0x89)) + # Immediate instructions with sign-extended 8-bit and 32-bit immediate. for inst, rrr in [ (base.iadd_imm.i32, 0), diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 4e41c9b990..3700ff60b5 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -4,8 +4,8 @@ Intel Encoding recipes. from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual -from base.formats import UnaryImm, Binary, BinaryImm, Store, Load -from base.formats import MultiAry, Call, IndirectCall +from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry +from base.formats import Call, IndirectCall, Store, Load from .registers import GPR, ABCD try: @@ -143,6 +143,10 @@ class TailRecipe: # XX /r rr = TailRecipe('rr', Binary, size=1, ins=(GPR, GPR), outs=0) +# XX /r, but for a unary operator with separate input/output register, like +# copies. +ur = TailRecipe('ur', Unary, size=1, ins=GPR, outs=GPR) + # XX /n with one arg in %rcx, for shifts. rc = TailRecipe('rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0) diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 001a41e009..10bd6288a7 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -111,6 +111,16 @@ fn recipe_op1rr(func: &Function, inst: Inst, sink: &mut C } } +fn recipe_op1ur(func: &Function, inst: Inst, sink: &mut CS) { + if let InstructionData::Unary { arg, .. } = func.dfg[inst] { + put_op1(func.encodings[inst].bits(), sink); + let res = func.locations[func.dfg.first_result(inst)].unwrap_reg(); + modrm_rr(res, func.locations[arg].unwrap_reg(), sink); + } else { + panic!("Expected Unary format: {:?}", func.dfg[inst]); + } +} + fn recipe_op1rc(func: &Function, inst: Inst, sink: &mut CS) { if let InstructionData::Binary { args, .. } = func.dfg[inst] { let bits = func.encodings[inst].bits(); From 6f8262438b320e485ca32fa96ed6a3f001483d1c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Jul 2017 14:22:34 -0700 Subject: [PATCH 847/968] Only print pressure for toprcs containing registers. Many ISAs don't need 4 top-level register classes, so don't print them. --- lib/cretonne/src/regalloc/pressure.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index 3bf1e68f54..fd9cf7c363 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -238,7 +238,9 @@ impl fmt::Display for Pressure { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Pressure[")?; for rc in &self.toprc { - write!(f, " {}+{}/{}", rc.base_count, rc.transient_count, rc.limit)?; + if rc.limit > 0 { + write!(f, " {}+{}/{}", rc.base_count, rc.transient_count, rc.limit)?; + } } write!(f, " ]") } From d8d07a6dfccc2cd6c1f446ce6951d1d23344fd52 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Jul 2017 12:50:20 -0700 Subject: [PATCH 848/968] Test a tied operand following a fixed register operand. The redefined tied value lives in the diverted register. --- filetests/regalloc/constraints.cton | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/filetests/regalloc/constraints.cton b/filetests/regalloc/constraints.cton index 28758a3bdd..f405e67db9 100644 --- a/filetests/regalloc/constraints.cton +++ b/filetests/regalloc/constraints.cton @@ -44,3 +44,26 @@ ebb0: return v3 } + +; Tied use of a diverted register. +function %fixed_op_twice() -> i32 { +ebb0: + ; check: ,%rax] + ; sameln: $v0 = iconst.i32 12 + v0 = iconst.i32 12 + v1 = iconst.i32 13 + ; The dynamic shift amount must be in %rcx + ; check: regmove $v0, %rax -> %rcx + ; check: $v2 = ishl $v1, $v0 + v2 = ishl v1, v0 + + ; Now v0 is globally allocated to %rax, but diverted to %rcx. + ; Check that the tied def gets the diverted register. + v3 = isub v0, v2 + ; not: regmove + ; check: ,%rcx] + ; sameln: isub + ; Move it into place for the return value. + ; check: regmove $v3, %rcx -> %rax + return v3 +} From 22541086fdf075813826c947f1ff683ab17712f8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 5 Jul 2017 14:23:58 -0700 Subject: [PATCH 849/968] Handle tied operands that are not killed by their use. Any tied register uses are interesting enough to be added to the reguses list if their value is not killed. A copy needs to be inserted in that case. --- filetests/regalloc/constraints.cton | 13 +++++++++++++ lib/cretonne/src/regalloc/spilling.rs | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/filetests/regalloc/constraints.cton b/filetests/regalloc/constraints.cton index f405e67db9..720a886263 100644 --- a/filetests/regalloc/constraints.cton +++ b/filetests/regalloc/constraints.cton @@ -15,6 +15,19 @@ ebb0: return v2 } +; Tied operand is live after instruction. +function %tied_alive() -> i32 { +ebb0: + v0 = iconst.i32 12 + v1 = iconst.i32 13 + ; check: $(v0c=$V) = copy $v0 + ; check: $v2 = isub $v0c, $v1 + v2 = isub v0, v1 + ; check: $v3 = iadd $v2, $v0 + v3 = iadd v2, v0 + return v3 +} + ; Fixed register constraint. function %fixed_op() -> i32 { ebb0: diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 37ff870a26..465efa7856 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -26,6 +26,7 @@ use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; use regalloc::pressure::Pressure; use regalloc::virtregs::VirtRegs; +use std::fmt; use topo_order::TopoOrder; /// Persistent data structures for the spilling pass. @@ -338,7 +339,8 @@ impl<'a> Context<'a> { } // Only collect the interesting register uses. - if reguse.fixed || reguse.spilled { + if reguse.fixed || reguse.tied || reguse.spilled { + dbg!(" reguse: {}", reguse); self.reg_uses.push(reguse); } } @@ -578,3 +580,19 @@ impl RegUse { } } } + +impl fmt::Display for RegUse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}@op{}", self.value, self.opidx)?; + if self.fixed { + write!(f, "/fixed")?; + } + if self.spilled { + write!(f, "/spilled")?; + } + if self.tied { + write!(f, "/tied")?; + } + Ok(()) + } +} From 814d0769362e522cb314ed547211cc27c356fc82 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 7 Jul 2017 14:32:13 -0700 Subject: [PATCH 850/968] Add a fmt.multi_line() method to srcgen.Formatter. Write out multiple code lines from a single string after stripping a common indentation. Also use this for doc_comment(). --- lib/cretonne/meta/srcgen.py | 43 ++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py index d91ecc4ab2..f0f7931ea1 100644 --- a/lib/cretonne/meta/srcgen.py +++ b/lib/cretonne/meta/srcgen.py @@ -8,7 +8,6 @@ source code. from __future__ import absolute_import import sys import os -import re try: from typing import Any, List # noqa @@ -130,6 +129,12 @@ class Formatter(object): # type: (str, *Any) -> None self.line(fmt.format(*args)) + def multi_line(self, s): + # type: (str) -> None + """Add one or more lines after stripping common indentation.""" + for l in parse_multiline(s): + self.line(l) + def comment(self, s): # type: (str) -> None """Add a comment line.""" @@ -138,5 +143,37 @@ class Formatter(object): def doc_comment(self, s): # type: (str) -> None """Add a (multi-line) documentation comment.""" - s = re.sub('^', self.indent + '/// ', s, flags=re.M) + '\n' - self.lines.append(s) + for l in parse_multiline(s): + self.line('/// ' + l if l else '///') + + +def _indent(s): + # type: (str) -> int + """ + Compute the indentation of s, or None of an empty line. + + Example: + >>> _indent("foo") + 0 + >>> _indent(" bar") + 4 + >>> _indent(" ") + >>> _indent("") + """ + t = s.lstrip() + return len(s) - len(t) if t else None + + +def parse_multiline(s): + # type: (str) -> List[str] + """ + Given a multi-line string, split it into a sequence of lines after + stripping a common indentation. This is useful for strings defined with doc + strings: + >>> parse_multiline('\\n hello\\n world\\n') + [None, 'hello', 'world'] + """ + lines = s.splitlines() + indents = list(i for i in (_indent(l) for l in lines) if i) + indent = min(indents) if indents else 0 + return list(l[indent:] if len(l) > indent else None for l in lines) From 464f2625d4717686798e66dd744c4371fe598b3e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 7 Jul 2017 11:33:38 -0700 Subject: [PATCH 851/968] Generate instruction unwrapping code for binemit recipes. Generate code to: - Unwrap the instruction and generate an error if the instruction format doesn't match the recipe. - Look up the value locations of register and stack arguments. The recipe_* functions in the ISA binemit modules now take these unwrapped items as arguments. Also add an optional `emit` argument to the EncRecipe constructor which makes it possible to provide inline Rust code snippets for code emission. This requires a lot less boilerplate than recipe_* functions. --- lib/cretonne/meta/cdsl/isa.py | 5 +- lib/cretonne/meta/gen_binemit.py | 105 +++++- lib/cretonne/meta/isa/riscv/recipes.py | 91 ++++- lib/cretonne/src/ir/valueloc.rs | 8 + lib/cretonne/src/isa/intel/binemit.rs | 465 +++++++++++++------------ lib/cretonne/src/isa/riscv/binemit.rs | 180 ---------- 6 files changed, 435 insertions(+), 419 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index bc85f7fb8f..0562df10dc 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -202,6 +202,7 @@ class EncRecipe(object): :param: branch_range `(origin, bits)` range for branches. :param: instp Instruction predicate. :param: isap ISA predicate. + :param: emit Rust code for binary emission. """ def __init__( @@ -213,7 +214,8 @@ class EncRecipe(object): outs, # type: ConstraintSeq branch_range=None, # type: BranchRange instp=None, # type: PredNode - isap=None # type: PredNode + isap=None, # type: PredNode + emit=None # type: str ): # type: (...) -> None self.name = name @@ -223,6 +225,7 @@ class EncRecipe(object): self.branch_range = branch_range self.instp = instp self.isap = isap + self.emit = emit if instp: assert instp.predicate_context() == format self.number = None # type: int diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py index 8d1954ce55..858fc36b39 100644 --- a/lib/cretonne/meta/gen_binemit.py +++ b/lib/cretonne/meta/gen_binemit.py @@ -3,15 +3,108 @@ Generate binary emission code for each ISA. """ from __future__ import absolute_import +from cdsl.registers import RegClass, Stack import srcgen try: from typing import Sequence, List # noqa - from cdsl.isa import TargetISA # noqa + from cdsl.isa import TargetISA, EncRecipe # noqa except ImportError: pass +def gen_recipe(recipe, fmt): + # type: (EncRecipe, srcgen.Formatter) -> None + """ + Generate code to handle a single recipe. + + - Unpack the instruction data, knowing the format. + - Determine register locations for operands with register constraints. + - Determine stack slot locations for operands with stack constraints. + - Call hand-written code for the actual emission. + """ + iform = recipe.format + nvops = iform.num_value_operands + want_args = any(isinstance(i, RegClass) or isinstance(i, Stack) + for i in recipe.ins) + assert not want_args or nvops > 0 + want_outs = any(isinstance(o, RegClass) or isinstance(o, Stack) + for o in recipe.outs) + + # First unpack the instruction. + with fmt.indented( + 'if let InstructionData::{} {{'.format(iform.name), + '}'): + for f in iform.imm_fields: + fmt.line('{},'.format(f.member)) + if want_args: + if iform.has_value_list or nvops > 1: + fmt.line('ref args,') + else: + fmt.line('arg,') + fmt.line('..') + fmt.outdented_line('} = func.dfg[inst] {') + + # Normalize to an `args` array. + if want_args: + if iform.has_value_list: + fmt.line('let args = args.as_slice(&func.dfg.value_lists);') + elif nvops == 1: + fmt.line('let args = [arg];') + + # Unwrap interesting input arguments. + # Don't bother with fixed registers. + args = '' + for i, arg in enumerate(recipe.ins): + if isinstance(arg, RegClass): + v = 'in_reg{}'.format(i) + args += ', ' + v + fmt.line( + 'let {} = func.locations[args[{}]].unwrap_reg();' + .format(v, i)) + elif isinstance(arg, Stack): + v = 'in_ss{}'.format(i) + args += ', ' + v + fmt.line( + 'let {} = func.locations[args[{}]].unwrap_stack();' + .format(v, i)) + + # Pass arguments in this order: inputs, imm_fields, outputs. + for f in iform.imm_fields: + args += ', ' + f.member + + # Unwrap interesting output arguments. + if want_outs: + if len(recipe.outs) == 1: + fmt.line('let results = [func.dfg.first_result(inst)];') + else: + fmt.line('let results = func.dfg.inst_results(inst);') + for i, res in enumerate(recipe.outs): + if isinstance(res, RegClass): + v = 'out_reg{}'.format(i) + args += ', ' + v + fmt.line( + 'let {} = func.locations[results[{}]].unwrap_reg();' + .format(v, i)) + elif isinstance(res, Stack): + v = 'out_ss{}'.format(i) + args += ', ' + v + fmt.line( + 'let {} = func.locations[results[{}]].unwrap_stack();' + .format(v, i)) + + # Call hand-written code. If the recipe contains a code snippet, use + # that. Otherwise cal a recipe function in the target ISA's binemit + # module. + if recipe.emit is None: + fmt.format( + 'return recipe_{}(func, inst, sink, bits{});', + recipe.name.lower(), args) + else: + fmt.multi_line(recipe.emit) + fmt.line('return;') + + def gen_isa(isa, fmt): # type: (TargetISA, srcgen.Formatter) -> None """ @@ -28,14 +121,18 @@ def gen_isa(isa, fmt): '(func: &Function, inst: Inst, _sink: &mut CS) {', '}'): fmt.line('bad_encoding(func, inst)') else: + fmt.line('#[allow(unused_variables, unreachable_code)]') with fmt.indented( 'pub fn emit_inst' '(func: &Function, inst: Inst, sink: &mut CS) {', '}'): + fmt.line('let bits = func.encodings[inst].bits();') with fmt.indented('match func.encodings[inst].recipe() {', '}'): for i, recipe in enumerate(isa.all_recipes): - fmt.line('{} => recipe_{}(func, inst, sink),'.format( - i, recipe.name.lower())) - fmt.line('_ => bad_encoding(func, inst),') + fmt.comment(recipe.name) + with fmt.indented('{} => {{'.format(i), '}'): + gen_recipe(recipe, fmt) + fmt.line('_ => {}') + fmt.line('bad_encoding(func, inst);') def generate(isas, out_dir): diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 2ea7f35597..510e13c860 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -92,68 +92,123 @@ def LUI(): # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) -R = EncRecipe('R', Binary, size=4, ins=(GPR, GPR), outs=GPR) +R = EncRecipe( + 'R', Binary, size=4, ins=(GPR, GPR), outs=GPR, + emit='put_r(bits, in_reg0, in_reg1, out_reg0, sink);') # R-type with an immediate shift amount instead of rs2. -Rshamt = EncRecipe('Rshamt', BinaryImm, size=4, ins=GPR, outs=GPR) +Rshamt = EncRecipe( + 'Rshamt', BinaryImm, size=4, ins=GPR, outs=GPR, + emit='put_rshamt(bits, in_reg0, imm.into(), out_reg0, sink);') # R-type encoding of an integer comparison. -Ricmp = EncRecipe('Ricmp', IntCompare, size=4, ins=(GPR, GPR), outs=GPR) +Ricmp = EncRecipe( + 'Ricmp', IntCompare, size=4, ins=(GPR, GPR), outs=GPR, + emit='put_r(bits, in_reg0, in_reg1, out_reg0, sink);') I = EncRecipe( 'I', BinaryImm, size=4, ins=GPR, outs=GPR, - instp=IsSignedInt(BinaryImm.imm, 12)) + instp=IsSignedInt(BinaryImm.imm, 12), + emit='put_i(bits, in_reg0, imm.into(), out_reg0, sink);') # I-type instruction with a hardcoded %x0 rs1. Iz = EncRecipe( 'Iz', UnaryImm, size=4, ins=(), outs=GPR, - instp=IsSignedInt(UnaryImm.imm, 12)) + instp=IsSignedInt(UnaryImm.imm, 12), + emit='put_i(bits, 0, imm.into(), out_reg0, sink);') # I-type encoding of an integer comparison. Iicmp = EncRecipe( 'Iicmp', IntCompareImm, size=4, ins=GPR, outs=GPR, - instp=IsSignedInt(IntCompareImm.imm, 12)) + instp=IsSignedInt(IntCompareImm.imm, 12), + emit='put_i(bits, in_reg0, imm.into(), out_reg0, sink);') # I-type encoding for `jalr` as a return instruction. We won't use the # immediate offset. # The variable return values are not encoded. -Iret = EncRecipe('Iret', MultiAry, size=4, ins=(), outs=()) +Iret = EncRecipe( + 'Iret', MultiAry, size=4, ins=(), outs=(), + emit=''' + // Return instructions are always a jalr to %x1. + // The return address is provided as a special-purpose link argument. + put_i(bits, + 1, // rs1 = %x1 + 0, // no offset. + 0, // rd = %x0: no address written. + sink); + ''') # I-type encoding for `jalr` as an indirect call. -Icall = EncRecipe('Icall', IndirectCall, size=4, ins=GPR, outs=()) +Icall = EncRecipe( + 'Icall', IndirectCall, size=4, ins=GPR, outs=(), + emit=''' + // Indirect instructions are jalr with rd=%x1. + put_i(bits, + in_reg0, + 0, // no offset. + 1, // rd = %x1: link register. + sink); + ''') + # Copy of a GPR is implemented as addi x, 0. -Icopy = EncRecipe('Icopy', Unary, size=4, ins=GPR, outs=GPR) +Icopy = EncRecipe( + 'Icopy', Unary, size=4, ins=GPR, outs=GPR, + emit='put_i(bits, in_reg0, 0, out_reg0, sink);') # U-type instructions have a 20-bit immediate that targets bits 12-31. U = EncRecipe( 'U', UnaryImm, size=4, ins=(), outs=GPR, - instp=IsSignedInt(UnaryImm.imm, 32, 12)) + instp=IsSignedInt(UnaryImm.imm, 32, 12), + emit='put_u(bits, imm.into(), out_reg0, sink);') # UJ-type unconditional branch instructions. -UJ = EncRecipe('UJ', Jump, size=4, ins=(), outs=(), branch_range=(0, 21)) -UJcall = EncRecipe('UJcall', Call, size=4, ins=(), outs=()) +UJ = EncRecipe( + 'UJ', Jump, size=4, ins=(), outs=(), branch_range=(0, 21), + emit=''' + let dest = func.offsets[destination] as i64; + let disp = dest - sink.offset() as i64; + put_uj(bits, disp, 0, sink); + ''') + +UJcall = EncRecipe( + 'UJcall', Call, size=4, ins=(), outs=(), + emit=''' + sink.reloc_func(RelocKind::Call.into(), func_ref); + // rd=%x1 is the standard link register. + put_uj(bits, 0, 1, sink); + ''') # SB-type branch instructions. -# TODO: These instructions have a +/- 4 KB branch range. How to encode that -# constraint? SB = EncRecipe( 'SB', BranchIcmp, size=4, ins=(GPR, GPR), outs=(), - branch_range=(0, 13)) + branch_range=(0, 13), + emit=''' + let dest = func.offsets[destination] as i64; + let disp = dest - sink.offset() as i64; + put_sb(bits, disp, in_reg0, in_reg1, sink); + ''') # SB-type branch instruction with rs2 fixed to zero. SBzero = EncRecipe( 'SBzero', Branch, size=4, ins=(GPR), outs=(), - branch_range=(0, 13)) + branch_range=(0, 13), + emit=''' + let dest = func.offsets[destination] as i64; + let disp = dest - sink.offset() as i64; + put_sb(bits, disp, in_reg0, 0, sink); + ''') # Spill of a GPR. GPsp = EncRecipe( 'GPsp', Unary, size=4, - ins=GPR, outs=Stack(GPR)) + ins=GPR, outs=Stack(GPR), + emit='unimplemented!();') # Fill of a GPR. GPfi = EncRecipe( 'GPfi', Unary, size=4, - ins=Stack(GPR), outs=GPR) + ins=Stack(GPR), outs=GPR, + emit='unimplemented!();') diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index cae3042170..a6a358e14b 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -33,6 +33,14 @@ impl ValueLoc { } } + /// Get the stack slot of this location, or panic. + pub fn unwrap_stack(self) -> StackSlot { + match self { + ValueLoc::Stack(ss) => ss, + _ => panic!("Expected stack slot: {:?}", self), + } + } + /// Return an object that can display this value location, using the register info from the /// target ISA. pub fn display<'a, R: Into>>(self, regs: R) -> DisplayValueLoc<'a> { diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 10bd6288a7..831ef4c756 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -1,7 +1,8 @@ //! Emitting binary Intel machine code. use binemit::{CodeSink, Reloc, bad_encoding}; -use ir::{Function, Inst, InstructionData}; +use ir::{self, Function, Inst, InstructionData, MemFlags}; +use ir::immediates::{Imm64, Offset32}; use isa::RegUnit; include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); @@ -100,257 +101,289 @@ fn modrm_disp32(rm: RegUnit, reg: RegUnit, sink: &mut CS) sink.put1(b); } -fn recipe_op1rr(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Binary { args, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_rr(func.locations[args[0]].unwrap_reg(), - func.locations[args[1]].unwrap_reg(), - sink); - } else { - panic!("Expected Binary format: {:?}", func.dfg[inst]); - } +fn recipe_op1rr(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit) { + put_op1(bits, sink); + modrm_rr(in_reg0, in_reg1, sink); } -fn recipe_op1ur(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Unary { arg, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - let res = func.locations[func.dfg.first_result(inst)].unwrap_reg(); - modrm_rr(res, func.locations[arg].unwrap_reg(), sink); - } else { - panic!("Expected Unary format: {:?}", func.dfg[inst]); - } +fn recipe_op1ur(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + out_reg0: RegUnit) { + put_op1(bits, sink); + modrm_rr(out_reg0, in_reg0, sink); } -fn recipe_op1rc(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Binary { args, .. } = func.dfg[inst] { - let bits = func.encodings[inst].bits(); - put_op1(bits, sink); - modrm_r_bits(func.locations[args[0]].unwrap_reg(), bits, sink); - } else { - panic!("Expected Binary format: {:?}", func.dfg[inst]); - } +fn recipe_op1rc(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit) { + put_op1(bits, sink); + modrm_r_bits(in_reg0, bits, sink); } -fn recipe_op1rib(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { - let bits = func.encodings[inst].bits(); - put_op1(bits, sink); - modrm_r_bits(func.locations[arg].unwrap_reg(), bits, sink); - let imm: i64 = imm.into(); - sink.put1(imm as u8); - } else { - panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); - } +fn recipe_op1rib(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + imm: Imm64) { + put_op1(bits, sink); + modrm_r_bits(in_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put1(imm as u8); } -fn recipe_op1rid(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { - let bits = func.encodings[inst].bits(); - put_op1(bits, sink); - modrm_r_bits(func.locations[arg].unwrap_reg(), bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - } else { - panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); - } +fn recipe_op1rid(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + imm: Imm64) { + put_op1(bits, sink); + modrm_r_bits(in_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); } -fn recipe_op1uid(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::UnaryImm { imm, .. } = func.dfg[inst] { - let bits = func.encodings[inst].bits(); - let reg = func.locations[func.dfg.first_result(inst)].unwrap_reg(); - // The destination register is encoded in the low bits of the opcode. No ModR/M - put_op1(bits | (reg & 7), sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - } else { - panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); - } +fn recipe_op1uid(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + imm: Imm64, + out_reg0: RegUnit) { + // The destination register is encoded in the low bits of the opcode. No ModR/M + put_op1(bits | (out_reg0 & 7), sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); } // Store recipes. -fn recipe_op1st(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Store { args, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_rm(func.locations[args[1]].unwrap_reg(), - func.locations[args[0]].unwrap_reg(), - sink); - } else { - panic!("Expected Store format: {:?}", func.dfg[inst]); - } +fn recipe_op1st(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + _flags: MemFlags, + _offset: Offset32) { + put_op1(bits, sink); + modrm_rm(in_reg1, in_reg0, sink); } // This is just a tighter register class constraint. -fn recipe_op1st_abcd(func: &Function, inst: Inst, sink: &mut CS) { - recipe_op1st(func, inst, sink) +fn recipe_op1st_abcd(func: &Function, + inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + flags: MemFlags, + offset: Offset32) { + recipe_op1st(func, inst, sink, bits, in_reg0, in_reg1, flags, offset) } -fn recipe_mp1st(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Store { args, .. } = func.dfg[inst] { - put_mp1(func.encodings[inst].bits(), sink); - modrm_rm(func.locations[args[1]].unwrap_reg(), - func.locations[args[0]].unwrap_reg(), - sink); - } else { - panic!("Expected Store format: {:?}", func.dfg[inst]); - } +fn recipe_mp1st(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + _flags: MemFlags, + _offset: Offset32) { + put_mp1(bits, sink); + modrm_rm(in_reg1, in_reg0, sink); } -fn recipe_op1stdisp8(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_disp8(func.locations[args[1]].unwrap_reg(), - func.locations[args[0]].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - } else { - panic!("Expected Store format: {:?}", func.dfg[inst]); - } +fn recipe_op1stdisp8(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + _flags: MemFlags, + offset: Offset32) { + put_op1(bits, sink); + modrm_disp8(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); } -fn recipe_op1stdisp8_abcd(func: &Function, inst: Inst, sink: &mut CS) { - recipe_op1stdisp8(func, inst, sink) +fn recipe_op1stdisp8_abcd(func: &Function, + inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + flags: MemFlags, + offset: Offset32) { + recipe_op1stdisp8(func, inst, sink, bits, in_reg0, in_reg1, flags, offset) } -fn recipe_mp1stdisp8(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { - put_mp1(func.encodings[inst].bits(), sink); - modrm_disp8(func.locations[args[1]].unwrap_reg(), - func.locations[args[0]].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - } else { - panic!("Expected Store format: {:?}", func.dfg[inst]); - } +fn recipe_mp1stdisp8(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + _flags: MemFlags, + offset: Offset32) { + put_mp1(bits, sink); + modrm_disp8(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); } -fn recipe_op1stdisp32(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_disp32(func.locations[args[1]].unwrap_reg(), - func.locations[args[0]].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - } else { - panic!("Expected Store format: {:?}", func.dfg[inst]); - } +fn recipe_op1stdisp32(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + _flags: MemFlags, + offset: Offset32) { + put_op1(bits, sink); + modrm_disp32(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); } -fn recipe_op1stdisp32_abcd(func: &Function, inst: Inst, sink: &mut CS) { - recipe_op1stdisp32(func, inst, sink) +fn recipe_op1stdisp32_abcd(func: &Function, + inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + flags: MemFlags, + offset: Offset32) { + recipe_op1stdisp32(func, inst, sink, bits, in_reg0, in_reg1, flags, offset) } -fn recipe_mp1stdisp32(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Store { args, offset, .. } = func.dfg[inst] { - put_mp1(func.encodings[inst].bits(), sink); - modrm_disp32(func.locations[args[1]].unwrap_reg(), - func.locations[args[0]].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - } else { - panic!("Expected Store format: {:?}", func.dfg[inst]); - } +fn recipe_mp1stdisp32(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + in_reg1: RegUnit, + _flags: MemFlags, + offset: Offset32) { + put_mp1(bits, sink); + modrm_disp32(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); } // Load recipes -fn recipe_op1ld(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Load { arg, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_rm(func.locations[arg].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected Load format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op1lddisp8(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_disp8(func.locations[arg].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - } else { - panic!("Expected Load format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op1lddisp32(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - modrm_disp32(func.locations[arg].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - } else { - panic!("Expected Load format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op2ld(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Load { arg, .. } = func.dfg[inst] { - put_op2(func.encodings[inst].bits(), sink); - modrm_rm(func.locations[arg].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected Load format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op2lddisp8(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { - put_op2(func.encodings[inst].bits(), sink); - modrm_disp8(func.locations[arg].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - } else { - panic!("Expected Load format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op2lddisp32(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Load { arg, offset, .. } = func.dfg[inst] { - put_op2(func.encodings[inst].bits(), sink); - modrm_disp32(func.locations[arg].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - } else { - panic!("Expected Load format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op1call_id(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Call { func_ref, .. } = func.dfg[inst] { - put_op1(func.encodings[inst].bits(), sink); - sink.reloc_func(RelocKind::PCRel4.into(), func_ref); - sink.put4(0); - } else { - panic!("Expected Call format: {:?}", func.dfg[inst]); - } -} - -fn recipe_op1call_r(func: &Function, inst: Inst, sink: &mut CS) { - let bits = func.encodings[inst].bits(); +fn recipe_op1ld(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _flags: MemFlags, + _offset: Offset32, + out_reg0: RegUnit) { put_op1(bits, sink); - modrm_r_bits(func.locations[func.dfg.inst_args(inst)[0]].unwrap_reg(), - bits, - sink); + modrm_rm(in_reg0, out_reg0, sink); } -fn recipe_op1ret(func: &Function, inst: Inst, sink: &mut CS) { - put_op1(func.encodings[inst].bits(), sink); +fn recipe_op1lddisp8(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _flags: MemFlags, + offset: Offset32, + out_reg0: RegUnit) { + put_op1(bits, sink); + modrm_disp8(in_reg0, out_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); +} + +fn recipe_op1lddisp32(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _flags: MemFlags, + offset: Offset32, + out_reg0: RegUnit) { + put_op1(bits, sink); + modrm_disp32(in_reg0, out_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); +} + +fn recipe_op2ld(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _flags: MemFlags, + _offset: Offset32, + out_reg0: RegUnit) { + put_op2(bits, sink); + modrm_rm(in_reg0, out_reg0, sink); +} + +fn recipe_op2lddisp8(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _flags: MemFlags, + offset: Offset32, + out_reg0: RegUnit) { + put_op2(bits, sink); + modrm_disp8(in_reg0, out_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); +} + +fn recipe_op2lddisp32(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _flags: MemFlags, + offset: Offset32, + out_reg0: RegUnit) { + put_op2(bits, sink); + modrm_disp32(in_reg0, out_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); +} + +fn recipe_op1call_id(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + func_ref: ir::FuncRef) { + put_op1(bits, sink); + sink.reloc_func(RelocKind::PCRel4.into(), func_ref); + sink.put4(0); +} + +fn recipe_op1call_r(_func: &Function, + _inst: Inst, + sink: &mut CS, + bits: u16, + in_reg0: RegUnit, + _sig_ref: ir::SigRef) { + put_op1(bits, sink); + modrm_r_bits(in_reg0, bits, sink); +} + +fn recipe_op1ret(_func: &Function, _inst: Inst, sink: &mut CS, bits: u16) { + put_op1(bits, sink); } diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 21638980b2..d193981887 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -87,42 +87,6 @@ fn put_rshamt(bits: u16, sink.put4(i); } -fn recipe_r(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Binary { args, .. } = func.dfg[inst] { - put_r(func.encodings[inst].bits(), - func.locations[args[0]].unwrap_reg(), - func.locations[args[1]].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected Binary format: {:?}", func.dfg[inst]); - } -} - -fn recipe_ricmp(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::IntCompare { args, .. } = func.dfg[inst] { - put_r(func.encodings[inst].bits(), - func.locations[args[0]].unwrap_reg(), - func.locations[args[1]].unwrap_reg(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected IntCompare format: {:?}", func.dfg[inst]); - } -} - -fn recipe_rshamt(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { - put_rshamt(func.encodings[inst].bits(), - func.locations[arg].unwrap_reg(), - imm.into(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); - } -} - /// I-type instructions. /// /// 31 19 14 11 6 @@ -148,73 +112,6 @@ fn put_i(bits: u16, rs1: RegUnit, imm: i64, rd: RegUnit, sink.put4(i); } -fn recipe_i(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::BinaryImm { arg, imm, .. } = func.dfg[inst] { - put_i(func.encodings[inst].bits(), - func.locations[arg].unwrap_reg(), - imm.into(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected BinaryImm format: {:?}", func.dfg[inst]); - } -} - -fn recipe_iz(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::UnaryImm { imm, .. } = func.dfg[inst] { - put_i(func.encodings[inst].bits(), - 0, - imm.into(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); - } -} - -fn recipe_iicmp(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::IntCompareImm { arg, imm, .. } = func.dfg[inst] { - put_i(func.encodings[inst].bits(), - func.locations[arg].unwrap_reg(), - imm.into(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected IntCompareImm format: {:?}", func.dfg[inst]); - } -} - -fn recipe_iret(func: &Function, inst: Inst, sink: &mut CS) { - // Return instructions are always a jalr to %x1. - // The return address is provided as a special-purpose link argument. - put_i(func.encodings[inst].bits(), - 1, // rs1 = %x1 - 0, // no offset. - 0, // rd = %x0: no address written. - sink); -} - -fn recipe_icall(func: &Function, inst: Inst, sink: &mut CS) { - // Indirect instructions are jalr with rd=%x1. - put_i(func.encodings[inst].bits(), - func.locations[func.dfg.inst_args(inst)[0]].unwrap_reg(), - 0, // no offset. - 1, // rd = %x1: link register. - sink); -} - -fn recipe_icopy(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Unary { arg, .. } = func.dfg[inst] { - put_i(func.encodings[inst].bits(), - func.locations[arg].unwrap_reg(), - 0, - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected Unary format: {:?}", func.dfg[inst]); - } -} - /// U-type instructions. /// /// 31 11 6 @@ -236,17 +133,6 @@ fn put_u(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) sink.put4(i); } -fn recipe_u(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::UnaryImm { imm, .. } = func.dfg[inst] { - put_u(func.encodings[inst].bits(), - imm.into(), - func.locations[func.dfg.first_result(inst)].unwrap_reg(), - sink); - } else { - panic!("Expected UnaryImm format: {:?}", func.dfg[inst]); - } -} - /// SB-type branch instructions. /// /// 31 24 19 14 11 6 @@ -280,44 +166,6 @@ fn put_sb(bits: u16, imm: i64, rs1: RegUnit, rs2: RegUnit sink.put4(i); } -fn recipe_sb(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::BranchIcmp { - destination, - ref args, - .. - } = func.dfg[inst] { - let dest = func.offsets[destination] as i64; - let disp = dest - sink.offset() as i64; - let args = &args.as_slice(&func.dfg.value_lists)[0..2]; - put_sb(func.encodings[inst].bits(), - disp, - func.locations[args[0]].unwrap_reg(), - func.locations[args[1]].unwrap_reg(), - sink); - } else { - panic!("Expected BranchIcmp format: {:?}", func.dfg[inst]); - } -} - -fn recipe_sbzero(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Branch { - destination, - ref args, - .. - } = func.dfg[inst] { - let dest = func.offsets[destination] as i64; - let disp = dest - sink.offset() as i64; - let args = &args.as_slice(&func.dfg.value_lists)[0..1]; - put_sb(func.encodings[inst].bits(), - disp, - func.locations[args[0]].unwrap_reg(), - 0, - sink); - } else { - panic!("Expected Branch format: {:?}", func.dfg[inst]); - } -} - /// UJ-type jump instructions. /// /// 31 11 6 @@ -346,31 +194,3 @@ fn put_uj(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS sink.put4(i); } - -fn recipe_uj(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Jump { destination, .. } = func.dfg[inst] { - let dest = func.offsets[destination] as i64; - let disp = dest - sink.offset() as i64; - put_uj(func.encodings[inst].bits(), disp, 0, sink); - } else { - panic!("Expected Jump format: {:?}", func.dfg[inst]); - } -} - -fn recipe_ujcall(func: &Function, inst: Inst, sink: &mut CS) { - if let InstructionData::Call { func_ref, .. } = func.dfg[inst] { - sink.reloc_func(RelocKind::Call.into(), func_ref); - // rd=%x1 is the standard link register. - put_uj(func.encodings[inst].bits(), 0, 1, sink); - } else { - panic!("Expected Call format: {:?}", func.dfg[inst]); - } -} - -fn recipe_gpsp(_func: &Function, _inst: Inst, _sink: &mut CS) { - unimplemented!(); -} - -fn recipe_gpfi(_func: &Function, _inst: Inst, _sink: &mut CS) { - unimplemented!(); -} From 98f822f3476cb600f78b7ff14036a82316f3e9e0 Mon Sep 17 00:00:00 2001 From: d1m0 Date: Mon, 10 Jul 2017 15:28:32 -0700 Subject: [PATCH 852/968] Emit runtime type checks in legalizer.rs (#112) * Emit runtime type checks in legalizer.rs --- lib/cretonne/meta/cdsl/ast.py | 39 +++++- lib/cretonne/meta/cdsl/test_ti.py | 26 +--- lib/cretonne/meta/cdsl/ti.py | 175 ++++++++++++++++++++---- lib/cretonne/meta/cdsl/xform.py | 8 ++ lib/cretonne/meta/gen_instr.py | 31 +++-- lib/cretonne/meta/gen_legalizer.py | 125 +++++++++++++++-- lib/cretonne/meta/test_gen_legalizer.py | 145 ++++++++++++++++++++ lib/cretonne/src/ir/instructions.rs | 12 +- lib/cretonne/src/legalizer/mod.rs | 2 + 9 files changed, 494 insertions(+), 69 deletions(-) create mode 100644 lib/cretonne/meta/test_gen_legalizer.py diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 6efc492cf5..0d87bf8914 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -10,7 +10,7 @@ from .typevar import TypeVar from .predicates import IsEqual, And try: - from typing import Union, Tuple, Sequence, TYPE_CHECKING # noqa + from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa if TYPE_CHECKING: from .operands import ImmediateKind # noqa from .predicates import PredNode # noqa @@ -18,6 +18,19 @@ except ImportError: pass +def replace_var(arg, m): + # type: (Expr, Dict[Var, Var]) -> Expr + """ + Given a var v return either m[v] or a new variable v' (and remember + m[v]=v'). Otherwise return the argument unchanged + """ + if isinstance(arg, Var): + new_arg = m.get(arg, Var(arg.name)) # type: Var + m[arg] = new_arg + return new_arg + return arg + + class Def(object): """ An AST definition associates a set of variables with the values produced by @@ -60,6 +73,21 @@ class Def(object): return "({}) << {!s}".format( ', '.join(map(str, self.defs)), self.expr) + def copy(self, m): + # type: (Dict[Var, Var]) -> Def + """ + Return a copy of this Def with vars replaced with fresh variables, + in accordance with the map m. Update m as neccessary. + """ + new_expr = self.expr.copy(m) + new_defs = [] # type: List[Var] + for v in self.defs: + new_v = replace_var(v, m) + assert(isinstance(new_v, Var)) + new_defs.append(new_v) + + return Def(tuple(new_defs), new_expr) + class Expr(object): """ @@ -303,6 +331,15 @@ class Apply(Expr): return pred + def copy(self, m): + # type: (Dict[Var, Var]) -> Apply + """ + Return a copy of this Expr with vars replaced with fresh variables, + in accordance with the map m. Update m as neccessary. + """ + return Apply(self.inst, tuple(map(lambda e: replace_var(e, m), + self.args))) + class Enumerator(Expr): """ diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index 396d5ef844..71f439864e 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -6,30 +6,17 @@ from base.immediates import intcc from .typevar import TypeVar from .ast import Var, Def from .xform import Rtl, XForm -from .ti import ti_rtl, subst, TypeEnv, get_type_env +from .ti import ti_rtl, subst, TypeEnv, get_type_env, ConstrainTVsEqual from unittest import TestCase from functools import reduce try: from .ti import TypeMap, ConstraintList, VarMap, TypingOrError # noqa - from .ti import Constraint from typing import List, Dict, Tuple, TYPE_CHECKING, cast # noqa except ImportError: TYPE_CHECKING = False -def sort_constr(c): - # type: (Constraint) -> Constraint - """ - Sort the 2 typevars in a constraint by name for comparison - """ - r = tuple(sorted(c, key=lambda y: y.name)) - if TYPE_CHECKING: - return cast(Constraint, r) - else: - return r - - def agree(me, other): # type: (TypeEnv, TypeEnv) -> bool """ @@ -63,13 +50,10 @@ def agree(me, other): return False # Translate our constraints using m, and sort - me_equiv_constr = [(subst(a, m), subst(b, m)) for (a, b) in me.constraints] - me_equiv_constr = sorted([sort_constr(x) for x in me_equiv_constr]) - + me_equiv_constr = sorted([constr.translate(m) + for constr in me.constraints]) # Sort other's constraints - other_equiv_constr = sorted([sort_constr(x) for x in other.constraints], - key=lambda y: y[0].name) - + other_equiv_constr = sorted(other.constraints) return me_equiv_constr == other_equiv_constr @@ -224,7 +208,7 @@ class TestRTL(TypeCheckingBaseTest): self.v3: txn, self.v4: txn, self.v5: txn, - }, [(ixn.as_bool(), txn.as_bool())])) + }, [ConstrainTVsEqual(ixn.as_bool(), txn.as_bool())])) def test_vselect_vsplits(self): # type: () -> None diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index ce2f05c750..fe602e1342 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -8,13 +8,12 @@ from itertools import product try: from typing import Dict, TYPE_CHECKING, Union, Tuple, Optional, Set # noqa - from typing import Iterable # noqa - from typing import cast, List + from typing import Iterable, List # noqa + from typing import cast from .xform import Rtl, XForm # noqa from .ast import Expr # noqa + from .typevar import TypeSet # noqa if TYPE_CHECKING: - Constraint = Tuple[TypeVar, TypeVar] - ConstraintList = List[Constraint] TypeMap = Dict[TypeVar, TypeVar] VarMap = Dict[Var, TypeVar] except ImportError: @@ -22,6 +21,122 @@ except ImportError: pass +class TypeConstraint(object): + """ + Base class for all runtime-emittable type constraints. + """ + + +class ConstrainTVsEqual(TypeConstraint): + """ + Constraint specifying that two derived type vars must have the same runtime + type. + """ + def __init__(self, tv1, tv2): + # type: (TypeVar, TypeVar) -> None + assert tv1.is_derived and tv2.is_derived + (self.tv1, self.tv2) = sorted([tv1, tv2], key=repr) + + def is_trivial(self): + # type: () -> bool + """ + Return true if this constrain is statically decidable. + """ + return self.tv1 == self.tv2 or \ + (self.tv1.singleton_type() is not None and + self.tv2.singleton_type() is not None) + + def translate(self, m): + # type: (Union[TypeEnv, TypeMap]) -> ConstrainTVsEqual + """ + Translate any TypeVars in the constraint according to the map m + """ + if isinstance(m, TypeEnv): + return ConstrainTVsEqual(m[self.tv1], m[self.tv2]) + else: + return ConstrainTVsEqual(subst(self.tv1, m), subst(self.tv2, m)) + + def __eq__(self, other): + # type: (object) -> bool + if (not isinstance(other, ConstrainTVsEqual)): + return False + + return (self.tv1, self.tv2) == (other.tv1, other.tv2) + + def __hash__(self): + # type: () -> int + return hash((self.tv1, self.tv2)) + + def eval(self): + # type: () -> bool + """ + Evaluate this constraint. Should only be called when the constraint has + been translated to concrete types. + """ + assert self.tv1.singleton_type() is not None and \ + self.tv2.singleton_type() is not None + return self.tv1.singleton_type() == self.tv2.singleton_type() + + +class ConstrainTVInTypeset(TypeConstraint): + """ + Constraint specifying that a type var must belong to some typeset. + """ + def __init__(self, tv, ts): + # type: (TypeVar, TypeSet) -> None + assert not tv.is_derived and tv.name.startswith("typeof_") + self.tv = tv + self.ts = ts + + def is_trivial(self): + # type: () -> bool + """ + Return true if this constrain is statically decidable. + """ + tv_ts = self.tv.get_typeset().copy() + + # Trivially True + if (tv_ts.issubset(self.ts)): + return True + + # Trivially false + tv_ts &= self.ts + if (tv_ts.size() == 0): + return True + + return False + + def translate(self, m): + # type: (Union[TypeEnv, TypeMap]) -> ConstrainTVInTypeset + """ + Translate any TypeVars in the constraint according to the map m + """ + if isinstance(m, TypeEnv): + return ConstrainTVInTypeset(m[self.tv], self.ts) + else: + return ConstrainTVInTypeset(subst(self.tv, m), self.ts) + + def __eq__(self, other): + # type: (object) -> bool + if (not isinstance(other, ConstrainTVInTypeset)): + return False + + return (self.tv, self.ts) == (other.tv, other.ts) + + def __hash__(self): + # type: () -> int + return hash((self.tv, self.ts)) + + def eval(self): + # type: () -> bool + """ + Evaluate this constraint. Should only be called when the constraint has + been translated to concrete types. + """ + assert self.tv.singleton_type() is not None + return self.tv.get_typeset().issubset(self.ts) + + class TypeEnv(object): """ Class encapsulating the neccessary book keeping for type inference. @@ -43,13 +158,13 @@ class TypeEnv(object): RANK_INTERNAL = 0 def __init__(self, arg=None): - # type: (Optional[Tuple[TypeMap, ConstraintList]]) -> None + # type: (Optional[Tuple[TypeMap, List[TypeConstraint]]]) -> None self.ranks = {} # type: Dict[TypeVar, int] self.vars = set() # type: Set[Var] if arg is None: self.type_map = {} # type: TypeMap - self.constraints = [] # type: ConstraintList + self.constraints = [] # type: List[TypeConstraint] else: self.type_map, self.constraints = arg @@ -94,7 +209,9 @@ class TypeEnv(object): """ Add a new equivalence constraint between tv1 and tv2 """ - self.constraints.append((tv1, tv2)) + constr = ConstrainTVsEqual(tv1, tv2) + if (constr not in self.constraints): + self.constraints.append(constr) def get_uid(self): # type: () -> str @@ -206,15 +323,24 @@ class TypeEnv(object): """ vars_tvs = set([v.get_typevar() for v in self.vars]) new_type_map = {tv: self[tv] for tv in vars_tvs if tv != self[tv]} - new_constraints = [(self[tv1], self[tv2]) - for (tv1, tv2) in self.constraints] - # Sanity: new constraints and the new type_map should only contain - # tvs associated with real vars - for (a, b) in new_constraints: - assert a.free_typevar() in vars_tvs and\ - b.free_typevar() in vars_tvs + new_constraints = [] # type: List[TypeConstraint] + for constr in self.constraints: + # Currently typeinference only generates ConstrainTVsEqual + # constraints + assert isinstance(constr, ConstrainTVsEqual) + constr = constr.translate(self) + if constr.is_trivial() or constr in new_constraints: + continue + + # Sanity: translated constraints should refer to only real vars + assert constr.tv1.free_typevar() in vars_tvs and\ + constr.tv2.free_typevar() in vars_tvs + + new_constraints.append(constr) + + # Sanity: translated typemap should refer to only real vars for (k, v) in new_type_map.items(): assert k in vars_tvs assert v.free_typevar() is None or v.free_typevar() in vars_tvs @@ -245,13 +371,13 @@ class TypeEnv(object): # Check if constraints are satisfied for this typing failed = None - for (tv1, tv2) in self.constraints: - tv1 = subst(tv1, m) - tv2 = subst(tv2, m) - assert tv1.get_typeset().size() == 1 and\ - tv2.get_typeset().size() == 1 - if (tv1.get_typeset() != tv2.get_typeset()): - failed = (tv1, tv2) + for constr in self.constraints: + # Currently typeinference only generates ConstrainTVsEqual + # constraints + assert isinstance(constr, ConstrainTVsEqual) + concrete_constr = constr.translate(m) + if not concrete_constr.eval(): + failed = concrete_constr break if (failed is not None): @@ -287,9 +413,10 @@ class TypeEnv(object): edges.add((v, v.base, "solid", v.derived_func)) v = v.base - for (a, b) in self.constraints: - assert a in nodes and b in nodes - edges.add((a, b, "dashed", None)) + for constr in self.constraints: + assert isinstance(constr, ConstrainTVsEqual) + assert constr.tv1 in nodes and constr.tv2 in nodes + edges.add((constr.tv1, constr.tv2, "dashed", None)) root_nodes = set([x for x in nodes if x not in self.type_map and not x.is_derived]) diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index fd3c1dff1f..f809a91ce6 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -41,6 +41,14 @@ class Rtl(object): # type: (*DefApply) -> None self.rtl = tuple(map(canonicalize_defapply, args)) + def copy(self, m): + # type: (Dict[Var, Var]) -> Rtl + """ + Return a copy of this rtl with all Vars substituted with copies or + according to m. Update m as neccessary. + """ + return Rtl(*[d.copy(m) for d in self.rtl]) + class XForm(object): """ diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 2d67311ae2..242e5152d3 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -336,6 +336,25 @@ def get_constraint(op, ctrl_typevar, type_sets): return 'Same' +# TypeSet indexes are encoded in 8 bits, with `0xff` reserved. +typeset_limit = 0xff + + +def gen_typesets_table(fmt, type_sets): + # type: (srcgen.Formatter, UniqueTable) -> None + """ + Generate the table of ValueTypeSets described by type_sets. + """ + fmt.comment('Table of value type sets.') + assert len(type_sets.table) <= typeset_limit, "Too many type sets" + with fmt.indented( + 'const TYPE_SETS : [ValueTypeSet; {}] = [' + .format(len(type_sets.table)), '];'): + for ts in type_sets.table: + with fmt.indented('ValueTypeSet {', '},'): + ts.emit_fields(fmt) + + def gen_type_constraints(fmt, instrs): # type: (srcgen.Formatter, Sequence[Instruction]) -> None """ @@ -360,9 +379,6 @@ def gen_type_constraints(fmt, instrs): # Preload table with constraints for typical binops. operand_seqs.add(['Same'] * 3) - # TypeSet indexes are encoded in 8 bits, with `0xff` reserved. - typeset_limit = 0xff - fmt.comment('Table of opcode constraints.') with fmt.indented( 'const OPCODE_CONSTRAINTS : [OpcodeConstraints; {}] = [' @@ -418,14 +434,7 @@ def gen_type_constraints(fmt, instrs): fmt.line('typeset_offset: {},'.format(ctrl_typeset)) fmt.line('constraint_offset: {},'.format(offset)) - fmt.comment('Table of value type sets.') - assert len(type_sets.table) <= typeset_limit, "Too many type sets" - with fmt.indented( - 'const TYPE_SETS : [ValueTypeSet; {}] = [' - .format(len(type_sets.table)), '];'): - for ts in type_sets.table: - with fmt.indented('ValueTypeSet {', '},'): - ts.emit_fields(fmt) + gen_typesets_table(fmt, type_sets) fmt.comment('Table of operand constraint sequences.') with fmt.indented( diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 1376e3bc21..ce2f559ef2 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -11,16 +11,116 @@ from __future__ import absolute_import from srcgen import Formatter from base import legalize, instructions from cdsl.ast import Var +from cdsl.ti import ti_rtl, TypeEnv, get_type_env, ConstrainTVsEqual,\ + ConstrainTVInTypeset +from unique_table import UniqueTable +from gen_instr import gen_typesets_table +from cdsl.typevar import TypeVar try: - from typing import Sequence # noqa + from typing import Sequence, List, Dict # noqa from cdsl.isa import TargetISA # noqa from cdsl.ast import Def # noqa from cdsl.xform import XForm, XFormGroup # noqa + from cdsl.typevar import TypeSet # noqa + from cdsl.ti import TypeConstraint # noqa except ImportError: pass +def get_runtime_typechecks(xform): + # type: (XForm) -> List[TypeConstraint] + """ + Given a XForm build a list of runtime type checks neccessary to determine + if it applies. We have 2 types of runtime checks: + 1) typevar tv belongs to typeset T - needed for free tvs whose + typeset is constrainted by their use in the dst pattern + + 2) tv1 == tv2 where tv1 and tv2 are derived TVs - caused by unification + of non-bijective functions + """ + check_l = [] # type: List[TypeConstraint] + + # 1) Perform ti only on the source RTL. Accumulate any free tvs that have a + # different inferred type in src, compared to the type inferred for both + # src and dst. + symtab = {} # type: Dict[Var, Var] + src_copy = xform.src.copy(symtab) + src_typenv = get_type_env(ti_rtl(src_copy, TypeEnv())) + + for v in xform.ti.vars: + if not v.has_free_typevar(): + continue + + # In rust the local variable containing a free TV associated with var v + # has name typeof_v. We rely on the python TVs having the same name. + assert "typeof_{}".format(v) == xform.ti[v].name + + if v not in symtab: + # We can have singleton vars defined only on dst. Ignore them + assert v.get_typevar().singleton_type() is not None + continue + + src_ts = src_typenv[symtab[v]].get_typeset() + xform_ts = xform.ti[v].get_typeset() + + assert xform_ts.issubset(src_ts) + if src_ts != xform_ts: + check_l.append(ConstrainTVInTypeset(xform.ti[v], xform_ts)) + + # 2,3) Add any constraints that appear in xform.ti + check_l.extend(xform.ti.constraints) + + return check_l + + +def emit_runtime_typecheck(check, fmt, type_sets): + # type: (TypeConstraint, Formatter, UniqueTable) -> None + """ + Emit rust code for the given check. + """ + def build_derived_expr(tv): + # type: (TypeVar) -> str + if not tv.is_derived: + assert tv.name.startswith('typeof_') + return "Some({})".format(tv.name) + + base_exp = build_derived_expr(tv.base) + if (tv.derived_func == TypeVar.LANEOF): + return "{}.map(|t: Type| -> t.lane_type())".format(base_exp) + elif (tv.derived_func == TypeVar.ASBOOL): + return "{}.map(|t: Type| -> t.as_bool())".format(base_exp) + elif (tv.derived_func == TypeVar.HALFWIDTH): + return "{}.and_then(|t: Type| -> t.half_width())".format(base_exp) + elif (tv.derived_func == TypeVar.DOUBLEWIDTH): + return "{}.and_then(|t: Type| -> t.double_width())"\ + .format(base_exp) + elif (tv.derived_func == TypeVar.HALFVECTOR): + return "{}.and_then(|t: Type| -> t.half_vector())".format(base_exp) + elif (tv.derived_func == TypeVar.DOUBLEVECTOR): + return "{}.and_then(|t: Type| -> t.by(2))".format(base_exp) + else: + assert False, "Unknown derived function {}".format(tv.derived_func) + + if (isinstance(check, ConstrainTVInTypeset)): + tv = check.tv.name + if check.ts not in type_sets.index: + type_sets.add(check.ts) + ts = type_sets.index[check.ts] + + fmt.comment("{} must belong to {}".format(tv, check.ts)) + with fmt.indented('if !TYPE_SETS[{}].contains({}) {{'.format(ts, tv), + '};'): + fmt.line('return false;') + elif (isinstance(check, ConstrainTVsEqual)): + tv1 = build_derived_expr(check.tv1) + tv2 = build_derived_expr(check.tv2) + with fmt.indented('if {} != {} {{'.format(tv1, tv2), '};'): + fmt.line('return false;') + else: + assert False, "Unknown check {}".format(check) + + def unwrap_inst(iref, node, fmt): # type: (str, Def, Formatter) -> bool """ @@ -183,8 +283,8 @@ def emit_dst_inst(node, fmt): fmt.line('pos.next_inst();') -def gen_xform(xform, fmt): - # type: (XForm, Formatter) -> None +def gen_xform(xform, fmt, type_sets): + # type: (XForm, Formatter, UniqueTable) -> None """ Emit code for `xform`, assuming the the opcode of xform's root instruction has already been matched. @@ -203,6 +303,10 @@ def gen_xform(xform, fmt): instp = xform.src.rtl[0].expr.inst_predicate() assert instp is None, "Instruction predicates not supported in legalizer" + # Emit any runtime checks. + for check in get_runtime_typechecks(xform): + emit_runtime_typecheck(check, fmt, type_sets) + # Emit the destination pattern. for dst in xform.dst.rtl: emit_dst_inst(dst, fmt) @@ -213,8 +317,8 @@ def gen_xform(xform, fmt): fmt.line('assert_eq!(pos.remove_inst(), inst);') -def gen_xform_group(xgrp, fmt): - # type: (XFormGroup, Formatter) -> None +def gen_xform_group(xgrp, fmt, type_sets): + # type: (XFormGroup, Formatter, UniqueTable) -> None fmt.doc_comment("Legalize the instruction pointed to by `pos`.") fmt.line('#[allow(unused_variables,unused_assignments)]') with fmt.indented( @@ -231,7 +335,7 @@ def gen_xform_group(xgrp, fmt): inst = xform.src.rtl[0].expr.inst with fmt.indented( 'Opcode::{} => {{'.format(inst.camel_name), '}'): - gen_xform(xform, fmt) + gen_xform(xform, fmt, type_sets) # We'll assume there are uncovered opcodes. fmt.line('_ => return false,') fmt.line('true') @@ -240,6 +344,11 @@ def gen_xform_group(xgrp, fmt): def generate(isas, out_dir): # type: (Sequence[TargetISA], str) -> None fmt = Formatter() - gen_xform_group(legalize.narrow, fmt) - gen_xform_group(legalize.expand, fmt) + # Table of TypeSet instances + type_sets = UniqueTable() + + gen_xform_group(legalize.narrow, fmt, type_sets) + gen_xform_group(legalize.expand, fmt, type_sets) + + gen_typesets_table(fmt, type_sets) fmt.update_file('legalizer.rs', out_dir) diff --git a/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py new file mode 100644 index 0000000000..38f26959f4 --- /dev/null +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -0,0 +1,145 @@ +import doctest +import gen_legalizer +from unittest import TestCase +from srcgen import Formatter +from gen_legalizer import get_runtime_typechecks, emit_runtime_typecheck +from base.instructions import vselect, vsplit, isplit, iconcat, vconcat, \ + iconst, b1, icmp, copy # noqa +from base.legalize import narrow, expand # noqa +from base.immediates import intcc # noqa +from cdsl.typevar import TypeVar, TypeSet +from cdsl.ast import Var, Def # noqa +from cdsl.xform import Rtl, XForm # noqa +from cdsl.ti import ti_rtl, subst, TypeEnv, get_type_env # noqa +from unique_table import UniqueTable +from functools import reduce + +try: + from typing import Callable, TYPE_CHECKING, Iterable, Any # noqa + if TYPE_CHECKING: + CheckProducer = Callable[[UniqueTable], str] +except ImportError: + TYPE_CHECKING = False + + +def load_tests(loader, tests, ignore): + # type: (Any, Any, Any) -> Any + tests.addTests(doctest.DocTestSuite(gen_legalizer)) + return tests + + +def format_check(typesets, s, *args): + # type: (...) -> str + def transform(x): + # type: (Any) -> str + if isinstance(x, TypeSet): + return str(typesets.index[x]) + elif isinstance(x, TypeVar): + assert not x.is_derived + return x.name + else: + return str(x) + + dummy_s = s # type: str + args = tuple(map(lambda x: transform(x), args)) + return dummy_s.format(*args) + + +def typeset_check(v, ts): + # type: (Var, TypeSet) -> CheckProducer + return lambda typesets: format_check( + typesets, + 'if !TYPE_SETS[{}].contains(typeof_{}) ' + + '{{\n return false;\n}};\n', ts, v) + + +def equiv_check(tv1, tv2): + # type: (TypeVar, TypeVar) -> CheckProducer + return lambda typesets: format_check( + typesets, + 'if Some({}).map(|t: Type| -> t.as_bool()) != ' + + 'Some({}).map(|t: Type| -> t.as_bool()) ' + + '{{\n return false;\n}};\n', tv1, tv2) + + +def sequence(*args): + # type: (...) -> CheckProducer + dummy = args # type: Iterable[CheckProducer] + + def sequenceF(typesets): + # type: (UniqueTable) -> str + def strconcat(acc, el): + # type: (str, CheckProducer) -> str + return acc + el(typesets) + + return reduce(strconcat, dummy, "") + return sequenceF + + +class TestRuntimeChecks(TestCase): + + def setUp(self): + # type: () -> None + self.v0 = Var("v0") + self.v1 = Var("v1") + self.v2 = Var("v2") + self.v3 = Var("v3") + self.v4 = Var("v4") + self.v5 = Var("v5") + self.v6 = Var("v6") + self.v7 = Var("v7") + self.v8 = Var("v8") + self.v9 = Var("v9") + self.imm0 = Var("imm0") + self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True, + scalars=False, simd=True) + self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True, + scalars=False, simd=True) + self.b1 = TypeVar.singleton(b1) + + def check_yo_check(self, xform, expected_f): + # type: (XForm, CheckProducer) -> None + fmt = Formatter() + type_sets = UniqueTable() + for check in get_runtime_typechecks(xform): + emit_runtime_typecheck(check, fmt, type_sets) + + # Remove comments + got = "".join([l for l in fmt.lines if not l.strip().startswith("//")]) + expected = expected_f(type_sets) + self.assertEqual(got, expected) + + def test_width_check(self): + # type: () -> None + x = XForm(Rtl(self.v0 << copy(self.v1)), + Rtl((self.v2, self.v3) << isplit(self.v1), + self.v0 << iconcat(self.v2, self.v3))) + + WideInt = TypeSet(lanes=(1, 256), ints=(16, 64)) + self.check_yo_check(x, typeset_check(self.v1, WideInt)) + + def test_lanes_check(self): + # type: () -> None + x = XForm(Rtl(self.v0 << copy(self.v1)), + Rtl((self.v2, self.v3) << vsplit(self.v1), + self.v0 << vconcat(self.v2, self.v3))) + + WideVec = TypeSet(lanes=(2, 256), ints=(8, 64), floats=(32, 64), + bools=(1, 64)) + self.check_yo_check(x, typeset_check(self.v1, WideVec)) + + def test_vselect_imm(self): + # type: () -> None + ts = TypeSet(lanes=(2, 256), ints=(8, 64), + floats=(32, 64), bools=(8, 64)) + r = Rtl( + self.v0 << iconst(self.imm0), + self.v1 << icmp(intcc.eq, self.v2, self.v0), + self.v5 << vselect(self.v1, self.v3, self.v4), + ) + x = XForm(r, r) + + self.check_yo_check( + x, sequence(typeset_check(self.v3, ts), + equiv_check(self.v2.get_typevar(), + self.v3.get_typevar()))) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 35a200de08..cf914b853a 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -506,10 +506,14 @@ type BitSet16 = BitSet; /// A value type set describes the permitted set of types for a type variable. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ValueTypeSet { - lanes: BitSet16, - ints: BitSet8, - floats: BitSet8, - bools: BitSet8, + /// Allowed lane sizes + pub lanes: BitSet16, + /// Allowed int widths + pub ints: BitSet8, + /// Allowed float widths + pub floats: BitSet8, + /// Allowed bool widths + pub bools: BitSet8, } impl ValueTypeSet { diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 803f5808c2..f21f7e1d91 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -18,6 +18,8 @@ use flowgraph::ControlFlowGraph; use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; use ir::condcodes::IntCC; use isa::{TargetIsa, Legalize}; +use bitset::BitSet; +use ir::instructions::ValueTypeSet; mod boundary; mod split; From c5cddc3eacb17ae50c2184cf7e0b13a0fd171204 Mon Sep 17 00:00:00 2001 From: d1m0 Date: Tue, 11 Jul 2017 08:39:22 -0700 Subject: [PATCH 853/968] Handle bound instructions in pattern type inference (#113) --- lib/cretonne/meta/cdsl/test_ti.py | 106 +++++++++++++++++++++++++++++- lib/cretonne/meta/cdsl/ti.py | 17 +++-- 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index 71f439864e..e89cedc16c 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -1,8 +1,9 @@ from __future__ import absolute_import from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint,\ - b1, icmp, iadd_cout, iadd_cin + b1, icmp, iadd_cout, iadd_cin, uextend, ireduce from base.legalize import narrow, expand from base.immediates import intcc +from base.types import i32, i8 from .typevar import TypeVar from .ast import Var, Def from .xform import Rtl, XForm @@ -171,7 +172,7 @@ class TestRTL(TypeCheckingBaseTest): ) ti = TypeEnv() self.assertEqual(ti_rtl(r, ti), - "On line 1: fail ti on `typeof_v2` <: `2`: " + + "On line 1: fail ti on `typeof_v2` <: `1`: " + "Error: empty type created when unifying " + "`typeof_v2` and `half_vector(typeof_v2)`") @@ -303,6 +304,21 @@ class TestRTL(TypeCheckingBaseTest): self.v0: itype, }, [])) + def test_fully_bound_inst_inference_bad(self): + # Incompatible bound instructions fail accordingly + r = Rtl( + self.v3 << uextend.i32(self.v1), + self.v4 << uextend.i16(self.v2), + self.v5 << iadd(self.v3, self.v4), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + + self.assertEqual(typing, + "On line 2: fail ti on `typeof_v4` <: `4`: " + + "Error: empty type created when unifying " + + "`typeof_v4` and `typeof_v5`") + class TestXForm(TypeCheckingBaseTest): def test_iadd_cout(self): @@ -414,3 +430,89 @@ class TestXForm(TypeCheckingBaseTest): # xform for concrete_typing in concrete_typings_list: check_concrete_typing_xform(concrete_typing, xform) + + def test_bound_inst_inference(self): + # First example from issue #26 + x = XForm( + Rtl( + self.v0 << iadd(self.v1, self.v2), + ), + Rtl( + self.v3 << uextend.i32(self.v1), + self.v4 << uextend.i32(self.v2), + self.v5 << iadd(self.v3, self.v4), + self.v0 << ireduce(self.v5) + )) + itype = TypeVar("t", "", ints=True, simd=True) + i32t = TypeVar.singleton(i32) + + check_typing(x.ti, ({ + self.v0: itype, + self.v1: itype, + self.v2: itype, + self.v3: i32t, + self.v4: i32t, + self.v5: i32t, + }, []), x.symtab) + + def test_bound_inst_inference1(self): + # Second example taken from issue #26 + x = XForm( + Rtl( + self.v0 << iadd(self.v1, self.v2), + ), + Rtl( + self.v3 << uextend(self.v1), + self.v4 << uextend(self.v2), + self.v5 << iadd.i32(self.v3, self.v4), + self.v0 << ireduce(self.v5) + )) + itype = TypeVar("t", "", ints=True, simd=True) + i32t = TypeVar.singleton(i32) + + check_typing(x.ti, ({ + self.v0: itype, + self.v1: itype, + self.v2: itype, + self.v3: i32t, + self.v4: i32t, + self.v5: i32t, + }, []), x.symtab) + + def test_fully_bound_inst_inference(self): + # Second example taken from issue #26 with complete bounds + x = XForm( + Rtl( + self.v0 << iadd(self.v1, self.v2), + ), + Rtl( + self.v3 << uextend.i32.i8(self.v1), + self.v4 << uextend.i32.i8(self.v2), + self.v5 << iadd(self.v3, self.v4), + self.v0 << ireduce(self.v5) + )) + i8t = TypeVar.singleton(i8) + i32t = TypeVar.singleton(i32) + + check_typing(x.ti, ({ + self.v0: i8t, + self.v1: i8t, + self.v2: i8t, + self.v3: i32t, + self.v4: i32t, + self.v5: i32t, + }, []), x.symtab) + + def test_fully_bound_inst_inference_bad(self): + # Can't force a mistyped XForm using bound instructions + with self.assertRaises(AssertionError): + XForm( + Rtl( + self.v0 << iadd(self.v1, self.v2), + ), + Rtl( + self.v3 << uextend.i32.i8(self.v1), + self.v4 << uextend.i32.i16(self.v2), + self.v5 << iadd(self.v3, self.v4), + self.v0 << ireduce(self.v5) + )) diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index fe602e1342..85bd92507f 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -606,14 +606,19 @@ def ti_def(definition, typ): expr = definition.expr inst = expr.inst - # Create a map m mapping each free typevar in the signature of definition - # to a fresh copy of itself - all_formal_tvs = \ - [inst.outs[i].typevar for i in inst.value_results] +\ - [inst.ins[i].typevar for i in inst.value_opnums] - free_formal_tvs = [tv for tv in all_formal_tvs if not tv.is_derived] + # Create a dict m mapping each free typevar in the signature of definition + # to a fresh copy of itself. + if inst.is_polymorphic: + free_formal_tvs = [inst.ctrl_typevar] + inst.other_typevars + else: + free_formal_tvs = [] + m = {tv: tv.get_fresh_copy(str(typ.get_uid())) for tv in free_formal_tvs} + # Update m with any explicitly bound type vars + for (idx, bound_typ) in enumerate(expr.typevars): + m[free_formal_tvs[idx]] = TypeVar.singleton(bound_typ) + # Get fresh copies for each typevar in the signature (both free and # derived) fresh_formal_tvs = \ From 263779ac5649e78a07e559960f0483a1151693ed Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Jul 2017 14:06:08 -0700 Subject: [PATCH 854/968] Move Intel recipe_* bodies into intel/recipes.py. Use a PUT_OP macro in the TailRecipe Python class to replace the code snippet that emits the prefixes + opcode part of the instruction encoding. Prepare for the addition of REX prefixes by giving the PUT_OP functions a third argument representing the REX prefix. For the non-REX encodings, verify that no REX bits wold be needed. --- lib/cretonne/meta/isa/intel/recipes.py | 163 ++++++++++-- lib/cretonne/src/isa/intel/binemit.rs | 332 +++---------------------- 2 files changed, 176 insertions(+), 319 deletions(-) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 3700ff60b5..ebeacc5968 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -43,7 +43,9 @@ OPCODE_PREFIX = { (0xf2, 0x0f, 0x3a): ('Mp3', 0b1111) } -# VEX/XOP and EVEX prefixes are not yet supported. +# The table above does not include the REX prefix which goes after the +# mandatory prefix. VEX/XOP and EVEX prefixes are not yet supported. Encodings +# using any of these prefixes are represented by separate recipes. # # The encoding bits are: # @@ -79,6 +81,18 @@ def decode_ops(ops, rrr=0, w=0): return (name, op | (mmpp << 8) | (rrr << 12) | (w << 15)) +def replace_put_op(emit, prefix): + # type: (str, str) -> str + """ + Given a snippet of Rust code (or None), replace the `PUT_OP` macro with the + corresponding `put_*` function from the `binemit.rs` module. + """ + if emit is None: + return None + else: + return emit.replace('PUT_OP', 'put_' + prefix.lower()) + + class TailRecipe: """ Generate encoding recipes on demand. @@ -92,6 +106,10 @@ class TailRecipe: The arguments are the same as for an `EncRecipe`, except for `size` which does not include the size of the opcode. + + The `emit` parameter contains Rust code to actually emit an encoding, like + `EncRecipe` does it. Additionally, the text `PUT_OP` is substituted with + the proper `put_*` function from the `intel/binemit.rs` module. """ def __init__( @@ -103,7 +121,8 @@ class TailRecipe: outs, # type: ConstraintSeq branch_range=None, # type: BranchRange instp=None, # type: PredNode - isap=None # type: PredNode + isap=None, # type: PredNode + emit=None # type: str ): # type: (...) -> None self.name = name @@ -114,6 +133,7 @@ class TailRecipe: self.branch_range = branch_range self.instp = instp self.isap = isap + self.emit = emit # Cached recipes, keyed by name prefix. self.recipes = dict() # type: Dict[str, EncRecipe] @@ -136,34 +156,69 @@ class TailRecipe: outs=self.outs, branch_range=self.branch_range, instp=self.instp, - isap=self.isap) + isap=self.isap, + emit=replace_put_op(self.emit, name)) return (self.recipes[name], bits) # XX /r -rr = TailRecipe('rr', Binary, size=1, ins=(GPR, GPR), outs=0) +rr = TailRecipe( + 'rr', Binary, size=1, ins=(GPR, GPR), outs=0, + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_rr(in_reg0, in_reg1, sink); + ''') # XX /r, but for a unary operator with separate input/output register, like # copies. -ur = TailRecipe('ur', Unary, size=1, ins=GPR, outs=GPR) +ur = TailRecipe( + 'ur', Unary, size=1, ins=GPR, outs=GPR, + emit=''' + PUT_OP(bits, rex2(out_reg0, in_reg0), sink); + modrm_rr(out_reg0, in_reg0, sink); + ''') # XX /n with one arg in %rcx, for shifts. -rc = TailRecipe('rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0) +rc = TailRecipe( + 'rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0, + emit=''' + PUT_OP(bits, rex1(in_reg0), sink); + modrm_r_bits(in_reg0, bits, sink); + ''') # XX /n ib with 8-bit immediate sign-extended. rib = TailRecipe( 'rib', BinaryImm, size=2, ins=GPR, outs=0, - instp=IsSignedInt(BinaryImm.imm, 8)) + instp=IsSignedInt(BinaryImm.imm, 8), + emit=''' + PUT_OP(bits, rex1(in_reg0), sink); + modrm_r_bits(in_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put1(imm as u8); + ''') # XX /n id with 32-bit immediate sign-extended. rid = TailRecipe( 'rid', BinaryImm, size=5, ins=GPR, outs=0, - instp=IsSignedInt(BinaryImm.imm, 32)) + instp=IsSignedInt(BinaryImm.imm, 32), + emit=''' + PUT_OP(bits, rex1(in_reg0), sink); + modrm_r_bits(in_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); + ''') # XX+rd id unary with 32-bit immediate. uid = TailRecipe( 'uid', UnaryImm, size=4, ins=(), outs=GPR, - instp=IsSignedInt(UnaryImm.imm, 32)) + instp=IsSignedInt(UnaryImm.imm, 32), + emit=''' + // The destination register is encoded in the low bits of the opcode. + // No ModR/M. + PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); + ''') # # Store recipes. @@ -172,26 +227,59 @@ uid = TailRecipe( # XX /r register-indirect store with no offset. st = TailRecipe( 'st', Store, size=1, ins=(GPR, GPR), outs=(), - instp=IsEqual(Store.offset, 0)) + instp=IsEqual(Store.offset, 0), + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_rm(in_reg1, in_reg0, sink); + ''') # XX /r register-indirect store with no offset. # Only ABCD allowed for stored value. This is for byte stores. st_abcd = TailRecipe( 'st_abcd', Store, size=1, ins=(ABCD, GPR), outs=(), - instp=IsEqual(Store.offset, 0)) + instp=IsEqual(Store.offset, 0), + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_rm(in_reg1, in_reg0, sink); + ''') # XX /r register-indirect store with 8-bit offset. stDisp8 = TailRecipe( 'stDisp8', Store, size=2, ins=(GPR, GPR), outs=(), - instp=IsSignedInt(Store.offset, 8)) + instp=IsSignedInt(Store.offset, 8), + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_disp8(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + ''') stDisp8_abcd = TailRecipe( 'stDisp8_abcd', Store, size=2, ins=(ABCD, GPR), outs=(), - instp=IsSignedInt(Store.offset, 8)) + instp=IsSignedInt(Store.offset, 8), + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_disp8(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + ''') # XX /r register-indirect store with 32-bit offset. -stDisp32 = TailRecipe('stDisp32', Store, size=5, ins=(GPR, GPR), outs=()) +stDisp32 = TailRecipe( + 'stDisp32', Store, size=5, ins=(GPR, GPR), outs=(), + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_disp32(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + ''') stDisp32_abcd = TailRecipe( - 'stDisp32_abcd', Store, size=5, ins=(ABCD, GPR), outs=()) + 'stDisp32_abcd', Store, size=5, ins=(ABCD, GPR), outs=(), + emit=''' + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_disp32(in_reg1, in_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + ''') # # Load recipes @@ -200,21 +288,54 @@ stDisp32_abcd = TailRecipe( # XX /r load with no offset. ld = TailRecipe( 'ld', Load, size=1, ins=(GPR), outs=(GPR), - instp=IsEqual(Load.offset, 0)) + instp=IsEqual(Load.offset, 0), + emit=''' + PUT_OP(bits, rex2(out_reg0, in_reg0), sink); + modrm_rm(in_reg0, out_reg0, sink); + ''') # XX /r load with 8-bit offset. ldDisp8 = TailRecipe( 'ldDisp8', Load, size=2, ins=(GPR), outs=(GPR), - instp=IsSignedInt(Load.offset, 8)) + instp=IsSignedInt(Load.offset, 8), + emit=''' + PUT_OP(bits, rex2(out_reg0, in_reg0), sink); + modrm_disp8(in_reg0, out_reg0, sink); + let offset: i32 = offset.into(); + sink.put1(offset as u8); + ''') # XX /r load with 32-bit offset. ldDisp32 = TailRecipe( 'ldDisp32', Load, size=5, ins=(GPR), outs=(GPR), - instp=IsSignedInt(Load.offset, 32)) + instp=IsSignedInt(Load.offset, 32), + emit=''' + PUT_OP(bits, rex2(out_reg0, in_reg0), sink); + modrm_disp32(in_reg0, out_reg0, sink); + let offset: i32 = offset.into(); + sink.put4(offset as u32); + ''') # # Call/return # -call_id = TailRecipe('call_id', Call, size=4, ins=(), outs=()) -call_r = TailRecipe('call_r', IndirectCall, size=1, ins=GPR, outs=()) -ret = TailRecipe('ret', MultiAry, size=0, ins=(), outs=()) +call_id = TailRecipe( + 'call_id', Call, size=4, ins=(), outs=(), + emit=''' + PUT_OP(bits, BASE_REX, sink); + sink.reloc_func(RelocKind::PCRel4.into(), func_ref); + sink.put4(0); + ''') + +call_r = TailRecipe( + 'call_r', IndirectCall, size=1, ins=GPR, outs=(), + emit=''' + PUT_OP(bits, rex1(in_reg0), sink); + modrm_r_bits(in_reg0, bits, sink); + ''') + +ret = TailRecipe( + 'ret', MultiAry, size=0, ins=(), outs=(), + emit=''' + PUT_OP(bits, BASE_REX, sink); + ''') diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 831ef4c756..e2cb5396bc 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -1,8 +1,7 @@ //! Emitting binary Intel machine code. use binemit::{CodeSink, Reloc, bad_encoding}; -use ir::{self, Function, Inst, InstructionData, MemFlags}; -use ir::immediates::{Imm64, Offset32}; +use ir::{Function, Inst, InstructionData}; use isa::RegUnit; include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); @@ -21,27 +20,51 @@ impl Into for RelocKind { } } -// Emit single-byte opcode. -fn put_op1(bits: u16, sink: &mut CS) { - debug_assert_eq!(bits & 0x0f00, 0, "Invalid encoding bits for Op1*"); +// Mandatory prefix bytes for Mp* opcodes. +const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2]; + +// A REX prefix with no bits set: 0b0100WRXB. +const BASE_REX: u8 = 0b0100_0000; + +// Create a single-register REX prefix, setting the B bit to bit 3 of the register. +// This is used for instructions that encode a register in the low 3 bits of the opcode and for +// instructions that use the ModR/M `reg` field for something else. +fn rex1(reg_b: RegUnit) -> u8 { + let b = ((reg_b >> 3) & 1) as u8; + BASE_REX | b +} + +// Create a dual-register REX prefix, setting: +// +// REX.B = bit 3 of r/m register. +// REX.R = bit 3 of reg register. +fn rex2(rm: RegUnit, reg: RegUnit) -> u8 { + let b = ((rm >> 3) & 1) as u8; + let r = ((reg >> 3) & 1) as u8; + BASE_REX | b | (r << 2) +} + +// Emit a single-byte opcode with no REX prefix. +fn put_op1(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x8f00, 0, "Invalid encoding bits for Op1*"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding"); sink.put1(bits as u8); } // Emit two-byte opcode: 0F XX -fn put_op2(bits: u16, sink: &mut CS) { - debug_assert_eq!(bits & 0x0f00, 0x0400, "Invalid encoding bits for Op2*"); +fn put_op2(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x8f00, 0x0400, "Invalid encoding bits for Op2*"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding"); sink.put1(0x0f); sink.put1(bits as u8); } -// Mandatory prefix bytes for Mp* opcodes. -const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2]; - // Emit single-byte opcode with mandatory prefix. -fn put_mp1(bits: u16, sink: &mut CS) { +fn put_mp1(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x0c00, 0, "Invalid encoding bits for Mp1*"); let pp = (bits >> 8) & 3; sink.put1(PREFIX[(pp - 1) as usize]); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding"); sink.put1(bits as u8); } @@ -100,290 +123,3 @@ fn modrm_disp32(rm: RegUnit, reg: RegUnit, sink: &mut CS) b |= rm; sink.put1(b); } - -fn recipe_op1rr(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit) { - put_op1(bits, sink); - modrm_rr(in_reg0, in_reg1, sink); -} - -fn recipe_op1ur(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - out_reg0: RegUnit) { - put_op1(bits, sink); - modrm_rr(out_reg0, in_reg0, sink); -} - -fn recipe_op1rc(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit) { - put_op1(bits, sink); - modrm_r_bits(in_reg0, bits, sink); -} - -fn recipe_op1rib(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - imm: Imm64) { - put_op1(bits, sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put1(imm as u8); -} - -fn recipe_op1rid(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - imm: Imm64) { - put_op1(bits, sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); -} - -fn recipe_op1uid(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - imm: Imm64, - out_reg0: RegUnit) { - // The destination register is encoded in the low bits of the opcode. No ModR/M - put_op1(bits | (out_reg0 & 7), sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); -} - -// Store recipes. - -fn recipe_op1st(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - _flags: MemFlags, - _offset: Offset32) { - put_op1(bits, sink); - modrm_rm(in_reg1, in_reg0, sink); -} - -// This is just a tighter register class constraint. -fn recipe_op1st_abcd(func: &Function, - inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - flags: MemFlags, - offset: Offset32) { - recipe_op1st(func, inst, sink, bits, in_reg0, in_reg1, flags, offset) -} - -fn recipe_mp1st(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - _flags: MemFlags, - _offset: Offset32) { - put_mp1(bits, sink); - modrm_rm(in_reg1, in_reg0, sink); -} - -fn recipe_op1stdisp8(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - _flags: MemFlags, - offset: Offset32) { - put_op1(bits, sink); - modrm_disp8(in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); -} - -fn recipe_op1stdisp8_abcd(func: &Function, - inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - flags: MemFlags, - offset: Offset32) { - recipe_op1stdisp8(func, inst, sink, bits, in_reg0, in_reg1, flags, offset) -} - -fn recipe_mp1stdisp8(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - _flags: MemFlags, - offset: Offset32) { - put_mp1(bits, sink); - modrm_disp8(in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); -} - -fn recipe_op1stdisp32(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - _flags: MemFlags, - offset: Offset32) { - put_op1(bits, sink); - modrm_disp32(in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); -} - -fn recipe_op1stdisp32_abcd(func: &Function, - inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - flags: MemFlags, - offset: Offset32) { - recipe_op1stdisp32(func, inst, sink, bits, in_reg0, in_reg1, flags, offset) -} - -fn recipe_mp1stdisp32(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - in_reg1: RegUnit, - _flags: MemFlags, - offset: Offset32) { - put_mp1(bits, sink); - modrm_disp32(in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); -} - -// Load recipes - -fn recipe_op1ld(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _flags: MemFlags, - _offset: Offset32, - out_reg0: RegUnit) { - put_op1(bits, sink); - modrm_rm(in_reg0, out_reg0, sink); -} - -fn recipe_op1lddisp8(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _flags: MemFlags, - offset: Offset32, - out_reg0: RegUnit) { - put_op1(bits, sink); - modrm_disp8(in_reg0, out_reg0, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); -} - -fn recipe_op1lddisp32(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _flags: MemFlags, - offset: Offset32, - out_reg0: RegUnit) { - put_op1(bits, sink); - modrm_disp32(in_reg0, out_reg0, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); -} - -fn recipe_op2ld(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _flags: MemFlags, - _offset: Offset32, - out_reg0: RegUnit) { - put_op2(bits, sink); - modrm_rm(in_reg0, out_reg0, sink); -} - -fn recipe_op2lddisp8(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _flags: MemFlags, - offset: Offset32, - out_reg0: RegUnit) { - put_op2(bits, sink); - modrm_disp8(in_reg0, out_reg0, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); -} - -fn recipe_op2lddisp32(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _flags: MemFlags, - offset: Offset32, - out_reg0: RegUnit) { - put_op2(bits, sink); - modrm_disp32(in_reg0, out_reg0, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); -} - -fn recipe_op1call_id(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - func_ref: ir::FuncRef) { - put_op1(bits, sink); - sink.reloc_func(RelocKind::PCRel4.into(), func_ref); - sink.put4(0); -} - -fn recipe_op1call_r(_func: &Function, - _inst: Inst, - sink: &mut CS, - bits: u16, - in_reg0: RegUnit, - _sig_ref: ir::SigRef) { - put_op1(bits, sink); - modrm_r_bits(in_reg0, bits, sink); -} - -fn recipe_op1ret(_func: &Function, _inst: Inst, sink: &mut CS, bits: u16) { - put_op1(bits, sink); -} From 6ae4eb82f8815380e768a2da2fae8f149590a05c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Jul 2017 16:16:22 -0700 Subject: [PATCH 855/968] Start adding Intel 64-bit encodings. Add a TailRecipe.rex() method which creates an encoding recipe with a REX prefix. Define I64 encodings with REX.W for i64 operations and with/without REX for i32 ops. Only test the with-REX encodings for now. We don't yet have an instruction shrinking pass that can select the non-REX encodings. --- filetests/isa/intel/binary64.cton | 248 +++++++++++++++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 68 +++++-- lib/cretonne/meta/isa/intel/recipes.py | 50 ++++- lib/cretonne/src/isa/intel/binemit.rs | 17 ++ 4 files changed, 365 insertions(+), 18 deletions(-) create mode 100644 filetests/isa/intel/binary64.cton diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton new file mode 100644 index 0000000000..48e328b455 --- /dev/null +++ b/filetests/isa/intel/binary64.cton @@ -0,0 +1,248 @@ +; binary emission of 64-bit code. +test binemit +set is_64bit +isa intel + +; The binary encodings can be verified with the command: +; +; sed -ne 's/^ *; asm: *//p' filetests/isa/intel/binary64.cton | llvm-mc -show-encoding -triple=x86_64 +; + +; Tests for i64 instructions. +function %I64() { + fn0 = function %foo() + sig0 = signature() + +ebb0: + + ; Integer Constants. + + ; asm: movq $0x01020304f1f2f3f4, %rcx + [-,%rcx] v1 = iconst.i64 0x0102_0304_f1f2_f3f4 ; bin: 48 b9 01020304f1f2f3f4 + ; asm: movq $0x11020304f1f2f3f4, %rsi + [-,%rsi] v2 = iconst.i64 0x1102_0304_f1f2_f3f4 ; bin: 48 be 11020304f1f2f3f4 + ; asm: movq $0x21020304f1f2f3f4, %r10 + [-,%r10] v3 = iconst.i64 0x2102_0304_f1f2_f3f4 ; bin: 49 ba 21020304f1f2f3f4 + ; asm: movl $0xff001122, %r8d # 32-bit zero-extended constant. + [-,%r8] v4 = iconst.i64 0xff00_1122 ; bin: 41 b8 ff001122 + ; asm: movq $0xffffffff88001122, %r14 # 32-bit sign-extended constant. + [-,%r14] v5 = iconst.i64 0xffff_ffff_8800_1122 ; bin: 49 c7 c6 88001122 + + ; Integer Register-Register Operations. + + ; asm: addq %rsi, %rcx + [-,%rcx] v10 = iadd v1, v2 ; bin: 48 01 f1 + ; asm: addq %r10, %rsi + [-,%rsi] v11 = iadd v2, v3 ; bin: 4c 01 d6 + ; asm: addq %rcx, %r10 + [-,%r10] v12 = iadd v3, v1 ; bin: 49 01 ca + + ; asm: subq %rsi, %rcx + [-,%rcx] v20 = isub v1, v2 ; bin: 48 29 f1 + ; asm: subq %r10, %rsi + [-,%rsi] v21 = isub v2, v3 ; bin: 4c 29 d6 + ; asm: subq %rcx, %r10 + [-,%r10] v22 = isub v3, v1 ; bin: 49 29 ca + + ; asm: andq %rsi, %rcx + [-,%rcx] v30 = band v1, v2 ; bin: 48 21 f1 + ; asm: andq %r10, %rsi + [-,%rsi] v31 = band v2, v3 ; bin: 4c 21 d6 + ; asm: andq %rcx, %r10 + [-,%r10] v32 = band v3, v1 ; bin: 49 21 ca + + ; asm: orq %rsi, %rcx + [-,%rcx] v40 = bor v1, v2 ; bin: 48 09 f1 + ; asm: orq %r10, %rsi + [-,%rsi] v41 = bor v2, v3 ; bin: 4c 09 d6 + ; asm: orq %rcx, %r10 + [-,%r10] v42 = bor v3, v1 ; bin: 49 09 ca + + ; asm: xorq %rsi, %rcx + [-,%rcx] v50 = bxor v1, v2 ; bin: 48 31 f1 + ; asm: xorq %r10, %rsi + [-,%rsi] v51 = bxor v2, v3 ; bin: 4c 31 d6 + ; asm: xorq %rcx, %r10 + [-,%r10] v52 = bxor v3, v1 ; bin: 49 31 ca + + ; asm: movq %rsi, %rcx + [-,%rcx] v60 = copy v2 ; bin: 48 89 f1 + ; asm: movq %r10, %rsi + [-,%rsi] v61 = copy v3 ; bin: 4c 89 d6 + ; asm: movq %rcx, %r10 + [-,%r10] v62 = copy v1 ; bin: 49 89 ca + + + ; Integer Register-Immediate Operations. + ; These 64-bit ops all use a 32-bit immediate that is sign-extended to 64 bits. + ; Some take 8-bit immediates that are sign-extended to 64 bits. + + ; asm: addq $-100000, %rcx + [-,%rcx] v70 = iadd_imm v1, -100000 ; bin: 48 81 c1 fffe7960 + ; asm: addq $100000, %rsi + [-,%rsi] v71 = iadd_imm v2, 100000 ; bin: 48 81 c6 000186a0 + ; asm: addq $0x7fffffff, %r10 + [-,%r10] v72 = iadd_imm v3, 0x7fff_ffff ; bin: 49 81 c2 7fffffff + ; asm: addq $100, %r8 + [-,%r8] v73 = iadd_imm v4, 100 ; bin: 49 83 c0 64 + ; asm: addq $-100, %r14 + [-,%r14] v74 = iadd_imm v5, -100 ; bin: 49 83 c6 9c + + ; asm: andq $-100000, %rcx + [-,%rcx] v80 = band_imm v1, -100000 ; bin: 48 81 e1 fffe7960 + ; asm: andq $100000, %rsi + [-,%rsi] v81 = band_imm v2, 100000 ; bin: 48 81 e6 000186a0 + ; asm: andq $0x7fffffff, %r10 + [-,%r10] v82 = band_imm v3, 0x7fff_ffff ; bin: 49 81 e2 7fffffff + ; asm: andq $100, %r8 + [-,%r8] v83 = band_imm v4, 100 ; bin: 49 83 e0 64 + ; asm: andq $-100, %r14 + [-,%r14] v84 = band_imm v5, -100 ; bin: 49 83 e6 9c + + ; asm: orq $-100000, %rcx + [-,%rcx] v90 = bor_imm v1, -100000 ; bin: 48 81 c9 fffe7960 + ; asm: orq $100000, %rsi + [-,%rsi] v91 = bor_imm v2, 100000 ; bin: 48 81 ce 000186a0 + ; asm: orq $0x7fffffff, %r10 + [-,%r10] v92 = bor_imm v3, 0x7fff_ffff ; bin: 49 81 ca 7fffffff + ; asm: orq $100, %r8 + [-,%r8] v93 = bor_imm v4, 100 ; bin: 49 83 c8 64 + ; asm: orq $-100, %r14 + [-,%r14] v94 = bor_imm v5, -100 ; bin: 49 83 ce 9c + ; asm: ret + + ; asm: xorq $-100000, %rcx + [-,%rcx] v100 = bxor_imm v1, -100000 ; bin: 48 81 f1 fffe7960 + ; asm: xorq $100000, %rsi + [-,%rsi] v101 = bxor_imm v2, 100000 ; bin: 48 81 f6 000186a0 + ; asm: xorq $0x7fffffff, %r10 + [-,%r10] v102 = bxor_imm v3, 0x7fff_ffff ; bin: 49 81 f2 7fffffff + ; asm: xorq $100, %r8 + [-,%r8] v103 = bxor_imm v4, 100 ; bin: 49 83 f0 64 + ; asm: xorq $-100, %r14 + [-,%r14] v104 = bxor_imm v5, -100 ; bin: 49 83 f6 9c + + return ; bin: c3 +} + +; Tests for i32 instructions in 64-bit mode. +; +; Note that many i32 instructions can be encoded both with and without a REX +; prefix if they only use the low 8 registers. Here, we are testing the REX +; encodings which are chosen by default. Switching to non-REX encodings should +; be done by an instruction shrinking pass. +function %I32() { + fn0 = function %foo() + sig0 = signature() + +ebb0: + + ; Integer Constants. + + ; asm: movl $0x01020304, %ecx + [-,%rcx] v1 = iconst.i32 0x0102_0304 ; bin: 40 b9 01020304 + ; asm: movl $0x11020304, %esi + [-,%rsi] v2 = iconst.i32 0x1102_0304 ; bin: 40 be 11020304 + ; asm: movl $0x21020304, %r10d + [-,%r10] v3 = iconst.i32 0x2102_0304 ; bin: 41 ba 21020304 + ; asm: movl $0xff001122, %r8d + [-,%r8] v4 = iconst.i32 0xff00_1122 ; bin: 41 b8 ff001122 + ; asm: movl $0x88001122, %r14d + [-,%r14] v5 = iconst.i32 0xffff_ffff_8800_1122 ; bin: 41 be 88001122 + + ; Integer Register-Register Operations. + + ; asm: addl %esi, %ecx + [-,%rcx] v10 = iadd v1, v2 ; bin: 40 01 f1 + ; asm: addl %r10d, %esi + [-,%rsi] v11 = iadd v2, v3 ; bin: 44 01 d6 + ; asm: addl %ecx, %r10d + [-,%r10] v12 = iadd v3, v1 ; bin: 41 01 ca + + ; asm: subl %esi, %ecx + [-,%rcx] v20 = isub v1, v2 ; bin: 40 29 f1 + ; asm: subl %r10d, %esi + [-,%rsi] v21 = isub v2, v3 ; bin: 44 29 d6 + ; asm: subl %ecx, %r10d + [-,%r10] v22 = isub v3, v1 ; bin: 41 29 ca + + ; asm: andl %esi, %ecx + [-,%rcx] v30 = band v1, v2 ; bin: 40 21 f1 + ; asm: andl %r10d, %esi + [-,%rsi] v31 = band v2, v3 ; bin: 44 21 d6 + ; asm: andl %ecx, %r10d + [-,%r10] v32 = band v3, v1 ; bin: 41 21 ca + + ; asm: orl %esi, %ecx + [-,%rcx] v40 = bor v1, v2 ; bin: 40 09 f1 + ; asm: orl %r10d, %esi + [-,%rsi] v41 = bor v2, v3 ; bin: 44 09 d6 + ; asm: orl %ecx, %r10d + [-,%r10] v42 = bor v3, v1 ; bin: 41 09 ca + + ; asm: xorl %esi, %ecx + [-,%rcx] v50 = bxor v1, v2 ; bin: 40 31 f1 + ; asm: xorl %r10d, %esi + [-,%rsi] v51 = bxor v2, v3 ; bin: 44 31 d6 + ; asm: xorl %ecx, %r10d + [-,%r10] v52 = bxor v3, v1 ; bin: 41 31 ca + + ; asm: movl %esi, %ecx + [-,%rcx] v60 = copy v2 ; bin: 40 89 f1 + ; asm: movl %r10d, %esi + [-,%rsi] v61 = copy v3 ; bin: 44 89 d6 + ; asm: movl %ecx, %r10d + [-,%r10] v62 = copy v1 ; bin: 41 89 ca + + + ; Integer Register-Immediate Operations. + ; These 64-bit ops all use a 32-bit immediate that is sign-extended to 64 bits. + ; Some take 8-bit immediates that are sign-extended to 64 bits. + + ; asm: addl $-100000, %ecx + [-,%rcx] v70 = iadd_imm v1, -100000 ; bin: 40 81 c1 fffe7960 + ; asm: addl $100000, %esi + [-,%rsi] v71 = iadd_imm v2, 100000 ; bin: 40 81 c6 000186a0 + ; asm: addl $0x7fffffff, %r10d + [-,%r10] v72 = iadd_imm v3, 0x7fff_ffff ; bin: 41 81 c2 7fffffff + ; asm: addl $100, %r8d + [-,%r8] v73 = iadd_imm v4, 100 ; bin: 41 83 c0 64 + ; asm: addl $-100, %r14d + [-,%r14] v74 = iadd_imm v5, -100 ; bin: 41 83 c6 9c + + ; asm: andl $-100000, %ecx + [-,%rcx] v80 = band_imm v1, -100000 ; bin: 40 81 e1 fffe7960 + ; asm: andl $100000, %esi + [-,%rsi] v81 = band_imm v2, 100000 ; bin: 40 81 e6 000186a0 + ; asm: andl $0x7fffffff, %r10d + [-,%r10] v82 = band_imm v3, 0x7fff_ffff ; bin: 41 81 e2 7fffffff + ; asm: andl $100, %r8d + [-,%r8] v83 = band_imm v4, 100 ; bin: 41 83 e0 64 + ; asm: andl $-100, %r14d + [-,%r14] v84 = band_imm v5, -100 ; bin: 41 83 e6 9c + + ; asm: orl $-100000, %ecx + [-,%rcx] v90 = bor_imm v1, -100000 ; bin: 40 81 c9 fffe7960 + ; asm: orl $100000, %esi + [-,%rsi] v91 = bor_imm v2, 100000 ; bin: 40 81 ce 000186a0 + ; asm: orl $0x7fffffff, %r10d + [-,%r10] v92 = bor_imm v3, 0x7fff_ffff ; bin: 41 81 ca 7fffffff + ; asm: orl $100, %r8d + [-,%r8] v93 = bor_imm v4, 100 ; bin: 41 83 c8 64 + ; asm: orl $-100, %r14d + [-,%r14] v94 = bor_imm v5, -100 ; bin: 41 83 ce 9c + ; asm: ret + + ; asm: xorl $-100000, %ecx + [-,%rcx] v100 = bxor_imm v1, -100000 ; bin: 40 81 f1 fffe7960 + ; asm: xorl $100000, %esi + [-,%rsi] v101 = bxor_imm v2, 100000 ; bin: 40 81 f6 000186a0 + ; asm: xorl $0x7fffffff, %r10d + [-,%r10] v102 = bxor_imm v3, 0x7fff_ffff ; bin: 41 81 f2 7fffffff + ; asm: xorl $100, %r8d + [-,%r8] v103 = bxor_imm v4, 100 ; bin: 41 83 f0 64 + ; asm: xorl $-100, %r14d + [-,%r14] v104 = bxor_imm v5, -100 ; bin: 41 83 f6 9c + + return ; bin: c3 +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 0b59b32da3..74dd5b5999 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -2,30 +2,65 @@ Intel Encodings. """ from __future__ import absolute_import +from cdsl.predicates import IsUnsignedInt from base import instructions as base -from .defs import I32 +from base.formats import UnaryImm +from .defs import I32, I64 from . import recipes as r -I32.enc(base.iadd.i32, *r.rr(0x01)) -I32.enc(base.isub.i32, *r.rr(0x29)) +for inst, opc in [ + (base.iadd, 0x01), + (base.isub, 0x29), + (base.band, 0x21), + (base.bor, 0x09), + (base.bxor, 0x31)]: + I32.enc(inst.i32, *r.rr(opc)) -I32.enc(base.band.i32, *r.rr(0x21)) -I32.enc(base.bor.i32, *r.rr(0x09)) -I32.enc(base.bxor.i32, *r.rr(0x31)) + I64.enc(inst.i64, *r.rr.rex(opc, w=1)) + I64.enc(inst.i32, *r.rr.rex(opc)) + # REX-less encoding must come after REX encoding so we don't use it by + # default. Otherwise reg-alloc would never use r8 and up. + I64.enc(inst.i32, *r.rr(opc)) I32.enc(base.copy.i32, *r.ur(0x89)) -# Immediate instructions with sign-extended 8-bit and 32-bit immediate. -for inst, rrr in [ - (base.iadd_imm.i32, 0), - (base.band_imm.i32, 4), - (base.bor_imm.i32, 1), - (base.bxor_imm.i32, 6)]: - I32.enc(inst, *r.rib(0x83, rrr=rrr)) - I32.enc(inst, *r.rid(0x81, rrr=rrr)) +I64.enc(base.copy.i64, *r.ur.rex(0x89, w=1)) +I64.enc(base.copy.i32, *r.ur.rex(0x89)) +I64.enc(base.copy.i32, *r.ur(0x89)) -# Immediate constant. -I32.enc(base.iconst.i32, *r.uid(0xb8)) +# Immediate instructions with sign-extended 8-bit and 32-bit immediate. +for inst, rrr in [ + (base.iadd_imm, 0), + (base.band_imm, 4), + (base.bor_imm, 1), + (base.bxor_imm, 6)]: + I32.enc(inst.i32, *r.rib(0x83, rrr=rrr)) + I32.enc(inst.i32, *r.rid(0x81, rrr=rrr)) + + I64.enc(inst.i64, *r.rib.rex(0x83, rrr=rrr, w=1)) + I64.enc(inst.i64, *r.rid.rex(0x81, rrr=rrr, w=1)) + I64.enc(inst.i32, *r.rib.rex(0x83, rrr=rrr)) + I64.enc(inst.i32, *r.rid.rex(0x81, rrr=rrr)) + I64.enc(inst.i32, *r.rib(0x83, rrr=rrr)) + I64.enc(inst.i32, *r.rid(0x81, rrr=rrr)) + +# TODO: band_imm.i64 with an unsigned 32-bit immediate can be encoded as +# band_imm.i32. Can even use the single-byte immediate for 0xffff_ffXX masks. + +# Immediate constants. +I32.enc(base.iconst.i32, *r.puid(0xb8)) + +I64.enc(base.iconst.i32, *r.puid.rex(0xb8)) +I64.enc(base.iconst.i32, *r.puid(0xb8)) +# The 32-bit immediate movl also zero-extends to 64 bits. +I64.enc(base.iconst.i64, *r.puid.rex(0xb8), + instp=IsUnsignedInt(UnaryImm.imm, 32)) +I64.enc(base.iconst.i64, *r.puid(0xb8), + instp=IsUnsignedInt(UnaryImm.imm, 32)) +# Sign-extended 32-bit immediate. +I64.enc(base.iconst.i64, *r.uid.rex(0xc7, rrr=0, w=1)) +# Finally, the 0xb8 opcode takes an 8-byte immediate with a REX.W prefix. +I64.enc(base.iconst.i64, *r.puiq.rex(0xb8, w=1)) # 32-bit shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit @@ -73,3 +108,4 @@ I32.enc(base.sload8.i32.i32, *r.ldDisp32(0x0f, 0xbe)) I32.enc(base.call, *r.call_id(0xe8)) I32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2)) I32.enc(base.x_return, *r.ret(0xc3)) +I64.enc(base.x_return, *r.ret(0xc3)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index ebeacc5968..2d1fc62d80 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -160,6 +160,33 @@ class TailRecipe: emit=replace_put_op(self.emit, name)) return (self.recipes[name], bits) + def rex(self, *ops, **kwargs): + # type: (*int, **int) -> Tuple[EncRecipe, int] + """ + Create a REX encoding recipe and encoding bits for the opcode bytes in + `ops`. + + The recipe will always generate a REX prefix, whether it is required or + not. For instructions that don't require a REX prefix, two encodings + should be added: One with REX and one without. + """ + rrr = kwargs.get('rrr', 0) + w = kwargs.get('w', 0) + name, bits = decode_ops(ops, rrr, w) + name = 'Rex' + name + if name not in self.recipes: + self.recipes[name] = EncRecipe( + name + self.name, + self.format, + 1 + len(ops) + self.size, + ins=self.ins, + outs=self.outs, + branch_range=self.branch_range, + instp=self.instp, + isap=self.isap, + emit=replace_put_op(self.emit, name)) + return (self.recipes[name], bits) + # XX /r rr = TailRecipe( @@ -208,11 +235,21 @@ rid = TailRecipe( sink.put4(imm as u32); ''') -# XX+rd id unary with 32-bit immediate. +# XX /n id with 32-bit immediate sign-extended. UnaryImm version. uid = TailRecipe( - 'uid', UnaryImm, size=4, ins=(), outs=GPR, + 'uid', UnaryImm, size=5, ins=(), outs=GPR, instp=IsSignedInt(UnaryImm.imm, 32), emit=''' + PUT_OP(bits, rex1(out_reg0), sink); + modrm_r_bits(out_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); + ''') + +# XX+rd id unary with 32-bit immediate. Note no recipe predicate. +puid = TailRecipe( + 'uid', UnaryImm, size=4, ins=(), outs=GPR, + emit=''' // The destination register is encoded in the low bits of the opcode. // No ModR/M. PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); @@ -220,6 +257,15 @@ uid = TailRecipe( sink.put4(imm as u32); ''') +# XX+rd iq unary with 64-bit immediate. +puiq = TailRecipe( + 'uiq', UnaryImm, size=8, ins=(), outs=GPR, + emit=''' + PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); + let imm: i64 = imm.into(); + sink.put8(imm as u64); + ''') + # # Store recipes. # diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index e2cb5396bc..9db0ee1234 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -44,6 +44,16 @@ fn rex2(rm: RegUnit, reg: RegUnit) -> u8 { BASE_REX | b | (r << 2) } +// Emit a REX prefix. +// +// The R, X, and B bits are computed from registers using the functions above. The W bit is +// extracted from `bits`. +fn rex_prefix(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(rex & 0xf8, BASE_REX); + let w = ((bits >> 15) & 1) as u8; + sink.put1(rex | (w << 3)); +} + // Emit a single-byte opcode with no REX prefix. fn put_op1(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8f00, 0, "Invalid encoding bits for Op1*"); @@ -51,6 +61,13 @@ fn put_op1(bits: u16, rex: u8, sink: &mut CS) { sink.put1(bits as u8); } +// Emit a single-byte opcode with REX prefix. +fn put_rexop1(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x0f00, 0, "Invalid encoding bits for Op1*"); + rex_prefix(bits, rex, sink); + sink.put1(bits as u8); +} + // Emit two-byte opcode: 0F XX fn put_op2(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8f00, 0x0400, "Invalid encoding bits for Op2*"); From de5501bc47e5488371d8520ab87f32a152c73346 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Tue, 11 Jul 2017 15:08:57 -0700 Subject: [PATCH 856/968] Cretonne IL frontend: ILBuilder (#97) * API and data structures proposal for the SSA construction module * Polished API and implemented trivial functions * API more explicit, Variable now struct parameter * Sample test written to see how the API could be used * Implemented local value numbering for SSABuilder * Implemented SSA within a single Ebb * Unfinished unoptimized implementation for recursive use and seal * Working global value numbering The SSABuilder now create ebb args and modifies jump instructions accordingly * Updated doc and improved branch argument modifying. Removed instructions::branch_arguments and instructions::branch_argument_mut * SSA building: bugfix, asserts and new test case Missing a key optimization to remove cycles of Phi * SSA Building: small changes after code review Created helper function for seal_block (which now contains sanity checks) * Optimization: removed useless phis (ebb arguments) Using pessimistic assumption that when using a non-def variable in an unsealed block we create an ebb argument which is removed when sealing if we detect it as useless Using aliases to avoid rewriting variables * Changed the semantics of remove_ebb_arg and turned it into a proper API method * Adapted ssa branch to changes in the DFG API * Abandonned SparseMaps for EntityMaps, added named structure for headr block data. * Created skeletton for a Cretonne IL builder frontend * Frontend IL builder: first draft of implementation with example of instruction methods * Working basic implementation of the frontend Missing handling of function arguments and return values * Interaction with function signature, sample test, more checks * Test with function verifier, seal and fill sanity check * Implemented python script to generate ILBuilder methods * Added support for jump tables and stack slot * Major API overhaul * No longer generating rust through Python but implements InstBuilder * No longer parametrized by user's blocks but use regular `Ebb` * Reuse of allocated memory via distinction between ILBuilder and FunctionBuilder * Integrate changes from StackSlot * Improved error message * Added support for jump arguments supplied by the user * Added an ebb_args proxy method needed * Adapted to Entity_ref splitted into a new module * Better error messages and fixed tests * Added method to change jump destination * We whould be able to add unreachable code * Added inst_result proxy to frontend * Import support * Added optimization for SSA construction: If multiple predecessors but agree on value don't create EBB argument * Move unsafe and not write-only funcs apart, improved doc * Added proxy function for append_ebb_arg * Support for unreachable code and better layout of the Ebbs * Fixed a bug yielding an infinite loop in SSA construction * SSA predecessors lookup code refactoring * Fixed bug in unreachable definition * New sanity check and display debug function * Fixed bug in verifier and added is_pristine ;ethod for frontend * Extended set of characters printable in function names To be able to print names of functions in test suite * Fixes and improvements of SSA construction after code review * Bugfixes for frontend code simplification * On-the-fly critical edge splitting in case of br_table with jump arguments * No more dangling undefined values, now attached as EBB args * Bugfix: only split corresponding edges on demand, not all br_table edges * Added signature retrieval method * Bugfix for critical edge splitting not sealing the ebbs it created * Proper handling of SSA side effects by the frontend * Code refactoring: moving frontend and SSA to new crate * Frontend: small changes and bugfixes after code review --- Cargo.toml | 1 + lib/cretonne/src/ir/builder.rs | 2 + lib/cretonne/src/ir/dfg.rs | 31 + lib/cretonne/src/ir/mod.rs | 1 + lib/cretonne/src/lib.rs | 2 +- lib/frontend/Cargo.toml | 15 + lib/frontend/src/frontend.rs | 682 +++++++++++++++++ lib/frontend/src/lib.rs | 152 ++++ lib/frontend/src/ssa.rs | 1276 ++++++++++++++++++++++++++++++++ test-all.sh | 2 +- 10 files changed, 2162 insertions(+), 2 deletions(-) create mode 100644 lib/frontend/Cargo.toml create mode 100644 lib/frontend/src/frontend.rs create mode 100644 lib/frontend/src/lib.rs create mode 100644 lib/frontend/src/ssa.rs diff --git a/Cargo.toml b/Cargo.toml index 06eed706e9..6df15bd86c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ path = "src/cton-util.rs" [dependencies] cretonne = { path = "lib/cretonne" } cretonne-reader = { path = "lib/reader" } +cretonne-frontend = { path ="lib/frontend" } filecheck = { path = "lib/filecheck" } docopt = "0.8.0" serde = "1.0.8" diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 256845bcfb..332ce43b03 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -23,6 +23,8 @@ pub trait InstBuilderBase<'f>: Sized { /// Get an immutable reference to the data flow graph that will hold the constructed /// instructions. fn data_flow_graph(&self) -> &DataFlowGraph; + /// Get a mutable reference to the data flow graph that will hold the constructed + /// instructions. fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph; /// Insert an instruction and return a reference to it, consuming the builder. diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 51ada16775..85e971a9fc 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -728,6 +728,37 @@ impl DataFlowGraph { num as usize } + /// Removes `val` from `ebb`'s arguments by a standard linear time list removal which preserves + /// ordering. Also updates the values' data. + pub fn remove_ebb_arg(&mut self, val: Value) { + let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = self.values[val] { + (ebb, num) + } else { + panic!("{} must be an EBB argument", val); + }; + self.ebbs[ebb] + .args + .remove(num as usize, &mut self.value_lists); + for index in num..(self.ebb_args(ebb).len() as u16) { + match self.values[self.ebbs[ebb] + .args + .get(index as usize, &self.value_lists) + .unwrap()] { + ValueData::Arg { ref mut num, .. } => { + *num -= 1; + } + _ => { + panic!("{} must be an EBB argument", + self.ebbs[ebb] + .args + .get(index as usize, &self.value_lists) + .unwrap()) + } + } + } + } + + /// Append an existing argument value to `ebb`. /// /// The appended value can't already be attached to something else. diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index c79edfa4c6..ebe39a3fcb 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -31,6 +31,7 @@ pub use ir::function::Function; pub use ir::builder::InstBuilder; pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; pub use ir::memflags::MemFlags; +pub use ir::builder::InstBuilderBase; use binemit; use entity_map::EntityMap; diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 08c1c4354b..0b09a8f10d 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -24,6 +24,7 @@ pub mod flowgraph; pub mod ir; pub mod isa; pub mod loop_analysis; +pub mod packed_option; pub mod regalloc; pub mod result; pub mod settings; @@ -36,7 +37,6 @@ mod context; mod iterators; mod legalizer; mod licm; -mod packed_option; mod partition_slice; mod predicates; mod ref_slice; diff --git a/lib/frontend/Cargo.toml b/lib/frontend/Cargo.toml new file mode 100644 index 0000000000..7ff44a1f6f --- /dev/null +++ b/lib/frontend/Cargo.toml @@ -0,0 +1,15 @@ +[package] +authors = ["The Cretonne Project Developers"] +name = "cretonne-frontend" +version = "0.0.0" +description = "Cretonne IL builder helper" +license = "Apache-2.0" +documentation = "https://cretonne.readthedocs.io/" +repository = "https://github.com/stoklund/cretonne" +publish = false + +[lib] +name = "cton_frontend" + +[dependencies] +cretonne = { path = "../cretonne" } diff --git a/lib/frontend/src/frontend.rs b/lib/frontend/src/frontend.rs new file mode 100644 index 0000000000..7f9fb9edb6 --- /dev/null +++ b/lib/frontend/src/frontend.rs @@ -0,0 +1,682 @@ +//! A frontend for building Cretonne IL from other languages. +use cretonne::ir::{Ebb, Type, Value, Function, Inst, JumpTable, StackSlot, JumpTableData, + StackSlotData, DataFlowGraph, InstructionData, ExtFuncData, FuncRef, SigRef, + Signature, InstBuilderBase}; +use cretonne::ir::instructions::BranchInfo; +use cretonne::ir::function::DisplayFunction; +use cretonne::isa::TargetIsa; +use ssa::{SSABuilder, SideEffects, Block}; +use cretonne::entity_map::{EntityMap, PrimaryEntityData}; +use cretonne::entity_ref::EntityRef; +use std::hash::Hash; + +/// Permanent structure used for translating into Cretonne IL. +pub struct ILBuilder + where Variable: EntityRef + Hash + Default +{ + ssa: SSABuilder, + ebbs: EntityMap, + types: EntityMap, + function_args_values: Vec, +} + + +/// Temporary object used to build a Cretonne IL `Function`. +pub struct FunctionBuilder<'a, Variable: 'a> + where Variable: EntityRef + Hash + Default +{ + func: &'a mut Function, + builder: &'a mut ILBuilder, + position: Position, + pristine: bool, +} + +#[derive(Clone, Default)] +struct EbbData { + filled: bool, + pristine: bool, + user_arg_count: usize, +} + +impl PrimaryEntityData for EbbData {} + +struct Position { + ebb: Ebb, + basic_block: Block, +} + +impl ILBuilder + where Variable: EntityRef + Hash + Default +{ + /// Creates a ILBuilder structure. The structure is automatically cleared each time it is + /// passed to a [`FunctionBuilder`](struct.FunctionBuilder.html) for creation. + pub fn new() -> ILBuilder { + ILBuilder { + ssa: SSABuilder::new(), + ebbs: EntityMap::new(), + types: EntityMap::new(), + function_args_values: Vec::new(), + } + } + + fn clear(&mut self) { + self.ssa.clear(); + self.ebbs.clear(); + self.types.clear(); + self.function_args_values.clear(); + } +} + +/// Implementation of the [`InstBuilder`](../cretonne/ir/builder/trait.InstBuilder.html) that has +/// one convenience method per Cretonne IL instruction. +pub struct FuncInstBuilder<'short, 'long: 'short, Variable: 'long> + where Variable: EntityRef + Hash + Default +{ + builder: &'short mut FunctionBuilder<'long, Variable>, + ebb: Ebb, +} + +impl<'short, 'long, Variable> FuncInstBuilder<'short, 'long, Variable> + where Variable: EntityRef + Hash + Default +{ + fn new<'s, 'l>(builder: &'s mut FunctionBuilder<'l, Variable>, + ebb: Ebb) + -> FuncInstBuilder<'s, 'l, Variable> { + FuncInstBuilder { builder, ebb } + } +} + +impl<'short, 'long, Variable> InstBuilderBase<'short> for FuncInstBuilder<'short, 'long, Variable> + where Variable: EntityRef + Hash + Default +{ + fn data_flow_graph(&self) -> &DataFlowGraph { + &self.builder.func.dfg + } + + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + &mut self.builder.func.dfg + } + + // This implementation is richer than `InsertBuilder` because we use the data of the + // instruction being inserted to add related info to the DFG and the SSA building system, + // and perform debug sanity checks. + fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'short mut DataFlowGraph) { + if data.opcode().is_return() { + self.builder + .check_return_args(data.arguments(&self.builder.func.dfg.value_lists)) + } + // We only insert the Ebb in the layout when an instruction is added to it + if self.builder.builder.ebbs[self.builder.position.ebb].pristine { + if !self.builder + .func + .layout + .is_ebb_inserted(self.builder.position.ebb) { + self.builder + .func + .layout + .append_ebb(self.builder.position.ebb); + } + self.builder.builder.ebbs[self.builder.position.ebb].pristine = false; + } else { + debug_assert!(!self.builder.builder.ebbs[self.builder.position.ebb].filled, + "you cannot add an instruction to a block already filled"); + } + let inst = self.builder.func.dfg.make_inst(data.clone()); + self.builder.func.dfg.make_inst_results(inst, ctrl_typevar); + self.builder.func.layout.append_inst(inst, self.ebb); + if data.opcode().is_branch() { + match data.branch_destination() { + Some(dest_ebb) => { + // If the user has supplied jump arguments we must adapt the arguments of + // the destination ebb + // TODO: find a way not to allocate a vector + let args_types: Vec = + match data.analyze_branch(&self.builder.func.dfg.value_lists) { + BranchInfo::SingleDest(_, args) => { + args.iter() + .map(|arg| self.builder.func.dfg.value_type(arg.clone())) + .collect() + } + _ => panic!("should not happen"), + }; + self.builder + .ebb_args_adjustement(dest_ebb, args_types.as_slice()); + self.builder.declare_successor(dest_ebb, inst); + } + None => { + // branch_destination() doesn't detect jump_tables + match data { + // If jump table we declare all entries successor + // TODO: not collect with vector? + InstructionData::BranchTable { table, .. } => { + for dest_ebb in self.builder + .func + .jump_tables + .get(table) + .expect("you are referencing an undeclared jump table") + .entries() + .map(|(_, ebb)| ebb) + .collect::>() { + self.builder.declare_successor(dest_ebb, inst) + } + } + // If not we do nothing + _ => {} + } + } + } + } + if data.opcode().is_terminator() { + self.builder.fill_current_block() + } else if data.opcode().is_branch() { + self.builder.move_to_next_basic_block() + } + (inst, &mut self.builder.func.dfg) + } +} + +/// This module allows you to create a function in Cretonne IL in a straightforward way, hiding +/// all the complexity of its internal representation. +/// +/// The module is parametrized by one type which is the representation of variables in your +/// origin language. It offers a way to conveniently append instruction to your program flow. +/// You are responsible to split your instruction flow into extended blocks (declared with +/// `create_ebb`) whose properties are: +/// +/// - branch and jump instructions can only point at the top of extended blocks; +/// - the last instruction of each block is a terminator instruction which has no natural sucessor, +/// and those instructions can only appear at the end of extended blocks. +/// +/// The parameters of Cretonne IL instructions are Cretonne IL values, which can only be created +/// as results of other Cretonne IL instructions. To be able to create variables redefined multiple +/// times in your program, use the `def_var` and `use_var` command, that will maintain the +/// correspondance between your variables and Cretonne IL SSA values. +/// +/// The first block for which you call `switch_to_block` will be assumed to be the beginning of +/// the function. +/// +/// At creation, a `FunctionBuilder` instance borrows an already allocated `Function` which it +/// modifies with the information stored in the mutable borrowed +/// [`ILBuilder`](struct.ILBuilder.html). The function passed in argument should be newly created +/// with [`Function::with_name_signature()`](../function/struct.Function.html), whereas the +/// `ILBuilder` can be kept as is between two function translations. +/// +/// # Errors +/// +/// The functions below will panic in debug mode whenever you try to modify the Cretonne IL +/// function in a way that violate the coherence of the code. For instance: switching to a new +/// `Ebb` when you haven't filled the current one with a terminator instruction, inserting a +/// return instruction with arguments that don't match the function's signature. +impl<'a, Variable> FunctionBuilder<'a, Variable> + where Variable: EntityRef + Hash + Default +{ + /// Creates a new FunctionBuilder structure that will operate on a `Function` using a + /// `IlBuilder`. + pub fn new(func: &'a mut Function, + builder: &'a mut ILBuilder) + -> FunctionBuilder<'a, Variable> { + builder.clear(); + FunctionBuilder { + func: func, + builder: builder, + position: Position { + ebb: Ebb::new(0), + basic_block: Block::new(0), + }, + pristine: true, + } + } + + /// Creates a new `Ebb` for the function and returns its reference. + pub fn create_ebb(&mut self) -> Ebb { + let ebb = self.func.dfg.make_ebb(); + self.builder.ssa.declare_ebb_header_block(ebb); + *self.builder.ebbs.ensure(ebb) = EbbData { + filled: false, + pristine: true, + user_arg_count: 0, + }; + ebb + } + + /// After the call to this function, new instructions will be inserted into the designated + /// block, in the order they are declared. You must declare the types of the Ebb arguments + /// you will use here. + /// + /// When inserting the terminator instruction (which doesn't have a falltrough to its immediate + /// successor), the block will be declared filled and it will not be possible to append + /// instructions to it. + pub fn switch_to_block(&mut self, ebb: Ebb, jump_args: &[Type]) -> &[Value] { + if self.pristine { + self.fill_function_args_values(ebb); + } + if !self.builder.ebbs[self.position.ebb].pristine { + // First we check that the previous block has been filled. + debug_assert!(self.is_unreachable() || self.builder.ebbs[self.position.ebb].filled, + "you have to fill your block before switching"); + } + // We cannot switch to a filled block + debug_assert!(!self.builder.ebbs[ebb].filled, + "you cannot switch to a block which is already filled"); + + let basic_block = self.builder.ssa.header_block(ebb); + // Then we change the cursor position. + self.position = Position { + ebb: ebb, + basic_block: basic_block, + }; + self.ebb_args_adjustement(ebb, jump_args); + self.func.dfg.ebb_args(ebb) + } + + /// Declares that all the predecessors of this block are known. + /// + /// Function to call with `ebb` as soon as the last branch instruction to `ebb` has been + /// created. Forgetting to call this method on every block will cause inconsistences in the + /// produced functions. + pub fn seal_block(&mut self, ebb: Ebb) { + let side_effects = self.builder + .ssa + .seal_ebb_header_block(ebb, + &mut self.func.dfg, + &mut self.func.layout, + &mut self.func.jump_tables); + self.handle_ssa_side_effects(side_effects); + } + + /// In order to use a variable in a `use_var`, you need to declare its type with this method. + pub fn declare_var(&mut self, var: Variable, ty: Type) { + *self.builder.types.ensure(var) = ty; + } + + /// Returns the Cretonne IL value corresponding to the utilization at the current program + /// position of a previously defined user variable. + pub fn use_var(&mut self, var: Variable) -> Value { + let ty = *self.builder + .types + .get(var) + .expect("this variable is used but its type has not been declared"); + let (val, side_effects) = self.builder + .ssa + .use_var(&mut self.func.dfg, + &mut self.func.layout, + &mut self.func.jump_tables, + var, + ty, + self.position.basic_block); + self.handle_ssa_side_effects(side_effects); + val + } + + /// Register a new definition of a user variable. Panics if the type of the value is not the + /// same as the type registered for the variable. + pub fn def_var(&mut self, var: Variable, val: Value) { + debug_assert!(self.func.dfg.value_type(val) == self.builder.types[var], + "the type of the value is not the type registered for the variable"); + self.builder + .ssa + .def_var(var, val, self.position.basic_block); + } + + /// Returns the value corresponding to the `i`-th argument of the function as defined by + /// the function signature. Panics if `i` is out of bounds or if called before the first call + /// to `switch_to_block`. + pub fn arg_value(&self, i: usize) -> Value { + debug_assert!(!self.pristine, "you have to call switch_to_block first."); + self.builder.function_args_values[i] + } + + /// Creates a jump table in the function, to be used by `br_table` instructions. + pub fn create_jump_table(&mut self) -> JumpTable { + self.func.jump_tables.push(JumpTableData::new()) + } + + /// Inserts an entry in a previously declared jump table. + pub fn insert_jump_table_entry(&mut self, jt: JumpTable, index: usize, ebb: Ebb) { + self.func.jump_tables[jt].set_entry(index, ebb); + } + + /// Creates a stack slot in the function, to be used by `stack_load`, `stack_store` and + /// `stack_addr` instructions. + pub fn create_stack_slot(&mut self, data: StackSlotData) -> StackSlot { + self.func.stack_slots.push(data) + } + + /// Adds a signature which can later be used to declare an external function import. + pub fn import_signature(&mut self, signature: Signature) -> SigRef { + self.func.dfg.signatures.push(signature) + } + + /// Declare an external function import. + pub fn import_function(&mut self, data: ExtFuncData) -> FuncRef { + self.func.dfg.ext_funcs.push(data) + } + + /// Returns an object with the [`InstBuilder`](../cretonne/ir/builder/trait.InstBuilder.html) + /// trait that allows to conveniently append an instruction to the current `Ebb` being built. + pub fn ins<'short>(&'short mut self) -> FuncInstBuilder<'short, 'a, Variable> { + let ebb = self.position.ebb; + FuncInstBuilder::new(self, ebb) + } +} + +/// All the functions documented in the previous block are write-only and help you build a valid +/// Cretonne IL functions via multiple debug asserts. However, you might need to improve the +/// performance of your translation perform more complex transformations to your Cretonne IL +/// function. The functions below help you inspect the function you're creating and modify it +/// in ways that can be unsafe if used incorrectly. +impl<'a, Variable> FunctionBuilder<'a, Variable> + where Variable: EntityRef + Hash + Default +{ + /// Retrieves all the arguments for an `Ebb` currently infered from the jump instructions + /// inserted that target it and the SSA construction. + pub fn ebb_args(&self, ebb: Ebb) -> &[Value] { + self.func.dfg.ebb_args(ebb) + } + + /// Retrieves the signature with reference `sigref` previously added with `import_signature`. + pub fn signature(&self, sigref: SigRef) -> Option<&Signature> { + self.func.dfg.signatures.get(sigref) + } + + /// Creates a argument for a specific `Ebb` by appending it to the list of already existing + /// arguments. + /// + /// **Note:** this function has to be called at the creation of the `Ebb` before adding + /// instructions to it, otherwise this could interfere with SSA construction. + pub fn append_ebb_arg(&mut self, ebb: Ebb, ty: Type) -> Value { + debug_assert!(self.builder.ebbs[ebb].pristine); + self.func.dfg.append_ebb_arg(ebb, ty) + } + + /// Returns the result values of an instruction. + pub fn inst_results(&self, inst: Inst) -> &[Value] { + self.func.dfg.inst_results(inst) + } + + /// Changes the destination of a jump instruction after creation. + /// + /// **Note:** You are responsible for maintaining the coherence with the arguments of + /// other jump instructions. + pub fn change_jump_destination(&mut self, inst: Inst, new_dest: Ebb) { + let old_dest = + self.func.dfg[inst] + .branch_destination_mut() + .expect("you want to change the jump destination of a non-jump instruction"); + let pred = self.builder.ssa.remove_ebb_predecessor(*old_dest, inst); + *old_dest = new_dest; + self.builder + .ssa + .declare_ebb_predecessor(new_dest, pred, inst); + } + + /// Returns `true` if and only if the current `Ebb` is sealed and has no predecessors declared. + /// + /// The entry block of a function is never unreachable. + pub fn is_unreachable(&self) -> bool { + let is_entry = match self.func.layout.entry_block() { + None => false, + Some(entry) => self.position.ebb == entry, + }; + (!is_entry && self.builder.ssa.is_sealed(self.position.ebb) && + self.builder.ssa.predecessors(self.position.ebb).is_empty()) + } + + /// Returns `true` if and only if no instructions have been added since the last call to + /// `switch_to_block`. + pub fn is_pristine(&self) -> bool { + self.builder.ebbs[self.position.ebb].pristine + } + + /// Returns `true` if and only if a terminator instruction has been inserted since the + /// last call to `switch_to_block`. + pub fn is_filled(&self) -> bool { + self.builder.ebbs[self.position.ebb].filled + } + + /// Returns a displayable object for the function as it is. + /// + /// Useful for debug purposes. Use it with `None` for standard printing. + pub fn display<'b, I: Into>>(&'b self, isa: I) -> DisplayFunction { + self.func.display(isa) + } +} + +impl<'a, Variable> Drop for FunctionBuilder<'a, Variable> + where Variable: EntityRef + Hash + Default +{ + /// When a `FunctionBuilder` goes out of scope, it means that the function is fully built. + /// We then proceed to check if all the `Ebb`s are filled and sealed + fn drop(&mut self) { + debug_assert!(self.builder + .ebbs + .keys() + .all(|ebb| { + self.builder.ebbs[ebb].pristine || + (self.builder.ssa.is_sealed(ebb) && + self.builder.ebbs[ebb].filled) + }), + "all blocks should be filled and sealed before dropping a FunctionBuilder") + } +} + +// Helper functions +impl<'a, Variable> FunctionBuilder<'a, Variable> + where Variable: EntityRef + Hash + Default +{ + fn move_to_next_basic_block(&mut self) { + self.position.basic_block = self.builder + .ssa + .declare_ebb_body_block(self.position.basic_block); + } + + fn fill_current_block(&mut self) { + self.builder.ebbs[self.position.ebb].filled = true; + } + + fn declare_successor(&mut self, dest_ebb: Ebb, jump_inst: Inst) { + self.builder + .ssa + .declare_ebb_predecessor(dest_ebb, self.position.basic_block, jump_inst); + } + + fn check_return_args(&self, args: &[Value]) { + debug_assert_eq!(args.len(), + self.func.signature.return_types.len(), + "the number of returned values doesn't match the function signature "); + for (i, arg) in args.iter().enumerate() { + let valty = self.func.dfg.value_type(*arg); + debug_assert_eq!(valty, + self.func.signature.return_types[i].value_type, + "the types of the values returned don't match the \ + function signature"); + } + } + + fn fill_function_args_values(&mut self, ebb: Ebb) { + debug_assert!(self.pristine); + for argtyp in self.func.signature.argument_types.iter() { + self.builder + .function_args_values + .push(self.func.dfg.append_ebb_arg(ebb, argtyp.value_type)); + } + self.pristine = false; + } + + + fn ebb_args_adjustement(&mut self, dest_ebb: Ebb, jump_args: &[Type]) { + let ty_to_append: Option> = + if self.builder.ssa.predecessors(dest_ebb).len() == 0 || + self.builder.ebbs[dest_ebb].pristine { + // This is the first jump instruction targeting this Ebb + // so the jump arguments supplied here are this Ebb' arguments + // However some of the arguments might already be there + // in the Ebb so we have to check they're consistent + let dest_ebb_args = self.func.dfg.ebb_args(dest_ebb); + debug_assert!(dest_ebb_args + .iter() + .zip(jump_args.iter().take(dest_ebb_args.len())) + .all(|(dest_arg, jump_arg)| { + *jump_arg == self.func.dfg.value_type(*dest_arg) + }), + "the jump argument supplied has not the \ + same type as the corresponding dest ebb argument"); + self.builder.ebbs[dest_ebb].user_arg_count = jump_args.len(); + Some(jump_args + .iter() + .skip(dest_ebb_args.len()) + .cloned() + .collect()) + } else { + let dest_ebb_args = self.func.dfg.ebb_args(dest_ebb); + // The Ebb already has predecessors + // We check that the arguments supplied match those supplied + // previously. + debug_assert!(jump_args.len() == self.builder.ebbs[dest_ebb].user_arg_count, + "the jump instruction doesn't have the same \ + number of arguments as its destination Ebb \ + ({} vs {}).", + jump_args.len(), + dest_ebb_args.len()); + debug_assert!(jump_args + .iter() + .zip(dest_ebb_args + .iter() + .take(self.builder.ebbs[dest_ebb].user_arg_count) + ) + .all(|(jump_arg, dest_arg)| { + *jump_arg == self.func.dfg.value_type(*dest_arg) + }), + "the jump argument supplied has not the \ + same type as the corresponding dest ebb argument"); + None + }; + if let Some(ty_args) = ty_to_append { + for ty in ty_args { + self.func.dfg.append_ebb_arg(dest_ebb, ty); + } + } + } + + fn handle_ssa_side_effects(&mut self, side_effects: SideEffects) { + for split_ebb in side_effects.split_ebbs_created { + self.builder.ebbs.ensure(split_ebb).filled = true + } + for modified_ebb in side_effects.instructions_added_to_ebbs { + self.builder.ebbs[modified_ebb].pristine = false + } + } +} + +#[cfg(test)] +mod tests { + + use cretonne::entity_ref::EntityRef; + use cretonne::ir::{FunctionName, Function, Signature, ArgumentType, InstBuilder}; + use cretonne::ir::types::*; + use frontend::{ILBuilder, FunctionBuilder}; + use cretonne::verifier::verify_function; + + use std::u32; + + // An opaque reference to variable. + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub struct Variable(u32); + impl EntityRef for Variable { + fn new(index: usize) -> Self { + assert!(index < (u32::MAX as usize)); + Variable(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } + } + impl Default for Variable { + fn default() -> Variable { + Variable(u32::MAX) + } + } + + #[test] + fn sample_function() { + let mut sig = Signature::new(); + sig.return_types.push(ArgumentType::new(I32)); + sig.argument_types.push(ArgumentType::new(I32)); + + let mut il_builder = ILBuilder::::new(); + let mut func = Function::with_name_signature(FunctionName::new("sample_function"), sig); + { + let mut builder = FunctionBuilder::::new(&mut func, &mut il_builder); + + let block0 = builder.create_ebb(); + let block1 = builder.create_ebb(); + let block2 = builder.create_ebb(); + let x = Variable(0); + let y = Variable(1); + let z = Variable(2); + builder.declare_var(x, I32); + builder.declare_var(y, I32); + builder.declare_var(z, I32); + + builder.switch_to_block(block0, &[]); + builder.seal_block(block0); + { + let tmp = builder.arg_value(0); + builder.def_var(x, tmp); + } + { + let tmp = builder.ins().iconst(I32, 2); + builder.def_var(y, tmp); + } + { + let arg1 = builder.use_var(x); + let arg2 = builder.use_var(y); + let tmp = builder.ins().iadd(arg1, arg2); + builder.def_var(z, tmp); + } + builder.ins().jump(block1, &[]); + + builder.switch_to_block(block1, &[]); + { + let arg1 = builder.use_var(y); + let arg2 = builder.use_var(z); + let tmp = builder.ins().iadd(arg1, arg2); + builder.def_var(z, tmp); + } + { + let arg = builder.use_var(y); + builder.ins().brnz(arg, block2, &[]); + } + { + let arg1 = builder.use_var(z); + let arg2 = builder.use_var(x); + let tmp = builder.ins().isub(arg1, arg2); + builder.def_var(z, tmp); + } + { + let arg = builder.use_var(y); + builder.ins().return_(&[arg]); + } + + builder.switch_to_block(block2, &[]); + builder.seal_block(block2); + + { + let arg1 = builder.use_var(y); + let arg2 = builder.use_var(x); + let tmp = builder.ins().isub(arg1, arg2); + builder.def_var(y, tmp); + } + builder.ins().jump(block1, &[]); + builder.seal_block(block1); + } + + let res = verify_function(&func, None); + // println!("{}", func.display(None)); + match res { + Ok(_) => {} + Err(err) => panic!("{}{}", func.display(None), err), + } + } +} diff --git a/lib/frontend/src/lib.rs b/lib/frontend/src/lib.rs new file mode 100644 index 0000000000..2cbbc30459 --- /dev/null +++ b/lib/frontend/src/lib.rs @@ -0,0 +1,152 @@ +//! Cretonne IL builder library. +//! +//! Provides a straightforward way to create a Cretonne IL function and fill it with instructions +//! translated from another language. Contains a SSA construction module that lets you translate +//! your non-SSA variables into SSA Cretonne IL values via `use_var` and `def_var` calls. +//! +//! To get started, create an [`IlBuilder`](struct.ILBuilder.html) and pass it as an argument +//! to a [`FunctionBuilder`](struct.FunctionBuilder.html). +//! +//! # Example +//! +//! Here is a pseudo-program we want to transform into Cretonne IL: +//! +//! ```cton +//! function(x) { +//! x, y, z : i32 +//! block0: +//! y = 2; +//! z = x + y; +//! jump block1 +//! block1: +//! z = z + y; +//! brnz y, block2; +//! z = z - x; +//! return y +//! block2: +//! y = y - x +//! jump block1 +//! } +//! ``` +//! +//! Here is how you build the corresponding Cretonne IL function using `ILBuilder`: +//! +//! ```rust +//! extern crate cretonne; +//! extern crate cton_frontend; +//! +//! use cretonne::entity_ref::EntityRef; +//! use cretonne::ir::{FunctionName, Function, Signature, ArgumentType, InstBuilder}; +//! use cretonne::ir::types::*; +//! use cton_frontend::{ILBuilder, FunctionBuilder}; +//! use cretonne::verifier::verify_function; +//! use std::u32; +//! +//! // An opaque reference to variable. +//! #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +//! pub struct Variable(u32); +//! impl EntityRef for Variable { +//! fn new(index: usize) -> Self { +//! assert!(index < (u32::MAX as usize)); +//! Variable(index as u32) +//! } +//! +//! fn index(self) -> usize { +//! self.0 as usize +//! } +//! } +//! impl Default for Variable { +//! fn default() -> Variable { +//! Variable(u32::MAX) +//! } +//! } +//! +//! fn main() { +//! let mut sig = Signature::new(); +//! sig.return_types.push(ArgumentType::new(I32)); +//! sig.argument_types.push(ArgumentType::new(I32)); +//! let mut il_builder = ILBuilder::::new(); +//! let mut func = Function::with_name_signature(FunctionName::new("sample_function"), sig); +//! { +//! let mut builder = FunctionBuilder::::new(&mut func, &mut il_builder); +//! +//! let block0 = builder.create_ebb(); +//! let block1 = builder.create_ebb(); +//! let block2 = builder.create_ebb(); +//! let x = Variable(0); +//! let y = Variable(1); +//! let z = Variable(2); +//! builder.declare_var(x, I32); +//! builder.declare_var(y, I32); +//! builder.declare_var(z, I32); +//! +//! builder.switch_to_block(block0, &[]); +//! builder.seal_block(block0); +//! { +//! let tmp = builder.arg_value(0); +//! builder.def_var(x, tmp); +//! } +//! { +//! let tmp = builder.ins().iconst(I32, 2); +//! builder.def_var(y, tmp); +//! } +//! { +//! let arg1 = builder.use_var(x); +//! let arg2 = builder.use_var(y); +//! let tmp = builder.ins().iadd(arg1, arg2); +//! builder.def_var(z, tmp); +//! } +//! builder.ins().jump(block1, &[]); +//! +//! builder.switch_to_block(block1, &[]); +//! { +//! let arg1 = builder.use_var(y); +//! let arg2 = builder.use_var(z); +//! let tmp = builder.ins().iadd(arg1, arg2); +//! builder.def_var(z, tmp); +//! } +//! { +//! let arg = builder.use_var(y); +//! builder.ins().brnz(arg, block2, &[]); +//! } +//! { +//! let arg1 = builder.use_var(z); +//! let arg2 = builder.use_var(x); +//! let tmp = builder.ins().isub(arg1, arg2); +//! builder.def_var(z, tmp); +//! } +//! { +//! let arg = builder.use_var(y); +//! builder.ins().return_(&[arg]); +//! } +//! +//! builder.switch_to_block(block2, &[]); +//! builder.seal_block(block2); +//! +//! { +//! let arg1 = builder.use_var(y); +//! let arg2 = builder.use_var(x); +//! let tmp = builder.ins().isub(arg1, arg2); +//! builder.def_var(y, tmp); +//! } +//! builder.ins().jump(block1, &[]); +//! builder.seal_block(block1); +//! } +//! +//! let res = verify_function(&func, None); +//! println!("{}", func.display(None)); +//! match res { +//! Ok(_) => {} +//! Err(err) => panic!("{}", err), +//! } +//! } +//! ``` + +#![deny(missing_docs)] + +extern crate cretonne; + +pub use frontend::{ILBuilder, FunctionBuilder}; + +mod frontend; +mod ssa; diff --git a/lib/frontend/src/ssa.rs b/lib/frontend/src/ssa.rs new file mode 100644 index 0000000000..46b44d5251 --- /dev/null +++ b/lib/frontend/src/ssa.rs @@ -0,0 +1,1276 @@ +//! A SSA-building API that handles incomplete CFGs. +//! +//! The algorithm is based upon Braun M., Buchwald S., Hack S., Leißa R., Mallon C., +//! Zwinkau A. (2013) Simple and Efficient Construction of Static Single Assignment Form. +//! In: Jhala R., De Bosschere K. (eds) Compiler Construction. CC 2013. +//! Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg + +use cretonne::ir::{Ebb, Value, Inst, Type, DataFlowGraph, JumpTables, Layout, Cursor, InstBuilder}; +use cretonne::ir::instructions::BranchInfo; +use std::hash::Hash; +use cretonne::entity_map::{EntityMap, PrimaryEntityData}; +use cretonne::entity_ref::EntityRef; +use cretonne::packed_option::PackedOption; +use cretonne::packed_option::ReservedValue; +use std::u32; +use cretonne::ir::types::{F32, F64}; +use cretonne::ir::immediates::{Ieee32, Ieee64}; +use std::collections::HashMap; + +/// Structure containing the data relevant the construction of SSA for a given function. +/// +/// The parameter struct `Variable` corresponds to the way variables are represented in the +/// non-SSA language you're translating from. +/// +/// The SSA building relies on information about the variables used and defined, as well as +/// their position relative to basic blocks which are stricter than extended basic blocks since +/// they don't allow branching in the middle of them. +/// +/// This SSA building module allows you to def and use variables on the fly while you are +/// constructing the CFG, no need for a separate SSA pass after the CFG is completed. +/// +/// A basic block is said _filled_ if all the instruction that it contains have been translated, +/// and it is said _sealed_ if all of its predecessors have been declared. Only filled predecessors +/// can be declared. +pub struct SSABuilder + where Variable: EntityRef + Default +{ + // Records for every variable and for every revelant block, the last definition of + // the variable in the block. + variables: EntityMap>, + // Records the position of the basic blocks and the list of values used but not defined in the + // block. + blocks: EntityMap>, + // Records the basic blocks at the beginning of the `Ebb`s. + ebb_headers: EntityMap>, +} + +/// Side effects of a `use_var` or a `seal_ebb_header_block` method call. +pub struct SideEffects { + /// When we want to append jump arguments to a `br_table` instruction, the critical edge is + /// splitted and the newly created `Ebb`s are signaled here. + pub split_ebbs_created: Vec, + /// When a variable is used but has never been defined before (this happens in the case of + /// unreachable code), a placeholder `iconst` or `fconst` value is added to the right `Ebb`. + /// This field signals if it is the case and return the `Ebb` to which the initialization has + /// been added. + pub instructions_added_to_ebbs: Vec, +} + +// Describes the current position of a basic block in the control flow graph. +enum BlockData { + // A block at the top of an `Ebb`. + EbbHeader(EbbHeaderBlockData), + // A block inside an `Ebb` with an unique other block as its predecessor. + // The block is implicitely sealed at creation. + EbbBody { predecessor: Block }, +} +impl PrimaryEntityData for BlockData {} + +impl BlockData { + fn add_predecessor(&mut self, pred: Block, inst: Inst) { + match self { + &mut BlockData::EbbBody { .. } => panic!("you can't add a predecessor to a body block"), + &mut BlockData::EbbHeader(ref mut data) => { + data.predecessors.insert(pred, inst); + () + } + } + } + fn remove_predecessor(&mut self, inst: Inst) -> Block { + match self { + &mut BlockData::EbbBody { .. } => panic!("should not happen"), + &mut BlockData::EbbHeader(ref mut data) => { + // This a linear complexity operation but the number of predecessors is low + // in all non-pathological cases + let pred: Block = match data.predecessors + .iter() + .find(|&(_, &jump_inst)| jump_inst == inst) { + None => panic!("the predecessor you are trying to remove is not declared"), + Some((&b, _)) => b.clone(), + }; + data.predecessors.remove(&pred); + pred + } + } + } +} + +struct EbbHeaderBlockData { + // The predecessors of the Ebb header block, with the block and branch instruction. + predecessors: HashMap, + // A ebb header block is sealed if all of its predecessors have been declared. + sealed: bool, + // The ebb which this block is part of. + ebb: Ebb, + // List of current Ebb arguments for which a earlier def has not been found yet. + undef_variables: Vec<(Variable, Value)>, +} + +/// A opaque reference to a basic block. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct Block(u32); +impl EntityRef for Block { + fn new(index: usize) -> Self { + assert!(index < (u32::MAX as usize)); + Block(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } +} + +impl ReservedValue for Block { + fn reserved_value() -> Block { + Block(u32::MAX) + } +} + +impl SSABuilder + where Variable: EntityRef + Default +{ + /// Allocate a new blank SSA builder struct. Use the API function to interact with the struct. + pub fn new() -> SSABuilder { + SSABuilder { + variables: EntityMap::new(), + blocks: EntityMap::new(), + ebb_headers: EntityMap::new(), + } + } + + /// Clears a `SSABuilder` from all its data, letting it in a pristine state without + /// deallocating memory. + pub fn clear(&mut self) { + self.variables.clear(); + self.blocks.clear(); + self.ebb_headers.clear(); + } +} + +// Small enum used for clarity in some functions. +#[derive(Debug)] +enum ZeroOneOrMore { + Zero(), + One(T), + More(), +} + +/// TODO: use entity list instead of vec +#[derive(Debug)] +enum UseVarCases { + Unsealed(Value), + SealedOnePredecessor(Block), + SealedMultiplePredecessors(Vec<(Block, Inst)>, Value, Ebb), +} + +/// The following methods are the API of the SSA builder. Here is how it should be used when +/// translating to Cretonne IL: +/// +/// - for each sequence of contiguous instructions (with no branches), create a corresponding +/// basic block with `declare_ebb_body_block` or `declare_ebb_header_block` depending on the +/// position of the basic block; +/// +/// - while traversing a basic block and translating instruction, use `def_var` and `use_var` +/// to record definitions and uses of variables, these methods will give you the corresponding +/// SSA values; +/// +/// - when all the instructions in a basic block have translated, the block is said _filled_ and +/// only then you can add it as a predecessor to other blocks with `declare_ebb_predecessor`; +/// +/// - when you have constructed all the predecessor to a basic block at the beginning of an `Ebb`, +/// call `seal_ebb_header_block` on it with the `Function` that you are building. +/// +/// This API will give you the correct SSA values to use as arguments of your instructions, +/// as well as modify the jump instruction and `Ebb` headers arguments to account for the SSA +/// Phi functions. +/// +impl SSABuilder + where Variable: EntityRef + Hash + Default +{ + /// Declares a new definition of a variable in a given basic block. + /// The SSA value is passed as an argument because it should be created with + /// `ir::DataFlowGraph::append_result`. + pub fn def_var(&mut self, var: Variable, val: Value, block: Block) { + self.variables.ensure(var).insert(block, val); + } + + /// Declares a use of a variable in a given basic block. Returns the SSA value corresponding + /// to the current SSA definition of this variable and a list of newly created Ebbs that + /// are the results of critical edge splitting for `br_table` with arguments. + /// + /// If the variable has never been defined in this blocks or recursively in its predecessors, + /// this method will silently create an initializer with `iconst` or `fconst`. You are + /// responsible for making sure that you initialize your variables. + pub fn use_var(&mut self, + dfg: &mut DataFlowGraph, + layout: &mut Layout, + jts: &mut JumpTables, + var: Variable, + ty: Type, + block: Block) + -> (Value, SideEffects) { + // First we lookup for the current definition of the variable in this block + if let Some(var_defs) = self.variables.get(var) { + if let Some(val) = var_defs.get(&block) { + return (*val, + SideEffects { + split_ebbs_created: Vec::new(), + instructions_added_to_ebbs: Vec::new(), + }); + } + }; + // At this point if we haven't returned it means that we have to search in the + // predecessors. + let case = match self.blocks[block] { + BlockData::EbbHeader(ref mut data) => { + // The block has multiple predecessors so we append an Ebb argument that + // will serve as a value. + if data.sealed { + if data.predecessors.len() == 1 { + // Only one predecessor, straightforward case + UseVarCases::SealedOnePredecessor(*data.predecessors.keys().next().unwrap()) + } else { + let val = dfg.append_ebb_arg(data.ebb, ty); + let preds = data.predecessors + .iter() + .map(|(&pred, &inst)| (pred, inst)) + .collect(); + UseVarCases::SealedMultiplePredecessors(preds, val, data.ebb) + } + } else { + let val = dfg.append_ebb_arg(data.ebb, ty); + data.undef_variables.push((var, val)); + UseVarCases::Unsealed(val) + } + } + BlockData::EbbBody { predecessor: pred, .. } => UseVarCases::SealedOnePredecessor(pred), + }; + // TODO: avoid recursion for the calls to use_var and predecessors_lookup. + match case { + // The block has a single predecessor or multiple predecessor with + // the same value, we look into it. + UseVarCases::SealedOnePredecessor(pred) => { + let (val, mids) = self.use_var(dfg, layout, jts, var, ty, pred); + self.def_var(var, val, block); + return (val, mids); + } + // The block has multiple predecessors, we register the ebb argument as the current + // definition for the variable. + UseVarCases::Unsealed(val) => { + self.def_var(var, val, block); + return (val, + SideEffects { + split_ebbs_created: Vec::new(), + instructions_added_to_ebbs: Vec::new(), + }); + } + UseVarCases::SealedMultiplePredecessors(preds, val, ebb) => { + // If multiple predecessor we look up a use_var in each of them: + // if they all yield the same value no need for an Ebb argument + self.def_var(var, val, block); + return self.predecessors_lookup(dfg, layout, jts, val, var, ebb, &preds); + } + }; + + } + + /// Declares a new basic block belonging to the body of a certain `Ebb` and having `pred` + /// as a predecessor. `pred` is the only predecessor of the block and the block is sealed + /// at creation. + /// + /// To declare a `Ebb` header block, see `declare_ebb_header_block`. + pub fn declare_ebb_body_block(&mut self, pred: Block) -> Block { + self.blocks.push(BlockData::EbbBody { predecessor: pred }) + } + + /// Declares a new basic block at the beginning of an `Ebb`. No predecessors are declared + /// here and the block is not sealed. + /// Predecessors have to be added with `declare_ebb_predecessor`. + pub fn declare_ebb_header_block(&mut self, ebb: Ebb) -> Block { + let block = self.blocks + .push(BlockData::EbbHeader(EbbHeaderBlockData { + predecessors: HashMap::new(), + sealed: false, + ebb: ebb, + undef_variables: Vec::new(), + })); + *self.ebb_headers.ensure(ebb) = block.into(); + block + } + /// Gets the header block corresponding to an Ebb, panics if the Ebb or the header block + /// isn't declared. + pub fn header_block(&self, ebb: Ebb) -> Block { + match self.ebb_headers.get(ebb) { + Some(&header) => { + match header.expand() { + Some(header) => header, + None => panic!("the header block has not been defined"), + } + } + None => panic!("the ebb has not been declared"), + } + } + + /// Declares a new predecessor for an `Ebb` header block and record the branch instruction + /// of the predecessor that leads to it. + /// + /// Note that the predecessor is a `Block` and not an `Ebb`. This `Block` must be filled + /// before added as predecessor. Note that you must provide no jump arguments to the branch + /// instruction when you create it since `SSABuilder` will fill them for you. + pub fn declare_ebb_predecessor(&mut self, ebb: Ebb, pred: Block, inst: Inst) { + let header_block = match self.blocks[self.header_block(ebb)] { + BlockData::EbbBody { .. } => panic!("you can't add predecessors to an Ebb body block"), + BlockData::EbbHeader(ref data) => { + assert!(!data.sealed); + self.header_block(ebb) + } + }; + self.blocks[header_block].add_predecessor(pred, inst) + } + + /// Remove a previously declared Ebb predecessor by giving a reference to the jump + /// instruction. Returns the basic block containing the instruction. + /// + /// Note: use only when you know what you are doing, this might break the SSA bbuilding problem + pub fn remove_ebb_predecessor(&mut self, ebb: Ebb, inst: Inst) -> Block { + let header_block = match self.blocks[self.header_block(ebb)] { + BlockData::EbbBody { .. } => panic!("you can't add predecessors to an Ebb body block"), + BlockData::EbbHeader(ref data) => { + assert!(!data.sealed); + self.header_block(ebb) + } + }; + self.blocks[header_block].remove_predecessor(inst) + } + + /// Completes the global value numbering for an `Ebb`, all of its predecessors having been + /// already sealed. + /// + /// This method modifies the function's `Layout` by adding arguments to the `Ebb`s to + /// take into account the Phi function placed by the SSA algorithm. + pub fn seal_ebb_header_block(&mut self, + ebb: Ebb, + dfg: &mut DataFlowGraph, + layout: &mut Layout, + jts: &mut JumpTables) + -> SideEffects { + let block = self.header_block(ebb); + + // Sanity check + match self.blocks[block] { + BlockData::EbbBody { .. } => panic!("you can't seal an Ebb body block"), + BlockData::EbbHeader(ref data) => { + assert!(!data.sealed); + } + } + + // Recurse over the predecessors to find good definitions. + let side_effects = self.resolve_undef_vars(block, dfg, layout, jts); + + // Then we mark the block as sealed. + match self.blocks[block] { + BlockData::EbbBody { .. } => panic!("this should not happen"), + BlockData::EbbHeader(ref mut data) => data.sealed = true, + }; + side_effects + } + + // For each undef_var in an Ebb header block, lookup in the predecessors to append the right + // jump argument to the branch instruction. + // Panics if called with a non-header block. + // Returns the list of newly created ebbs for critical edge splitting. + fn resolve_undef_vars(&mut self, + block: Block, + dfg: &mut DataFlowGraph, + layout: &mut Layout, + jts: &mut JumpTables) + -> SideEffects { + // TODO: find a way to not allocate vectors + let (predecessors, undef_vars, ebb): (Vec<(Block, Inst)>, + Vec<(Variable, Value)>, + Ebb) = match self.blocks[block] { + BlockData::EbbBody { .. } => panic!("this should not happen"), + BlockData::EbbHeader(ref mut data) => { + (data.predecessors.iter().map(|(&x, &y)| (x, y)).collect(), + data.undef_variables.clone(), + data.ebb) + } + }; + + let mut side_effects = SideEffects { + split_ebbs_created: Vec::new(), + instructions_added_to_ebbs: Vec::new(), + }; + // For each undef var we look up values in the predecessors and create an Ebb argument + // only if necessary. + for &(var, val) in undef_vars.iter() { + let (_, mut local_side_effects) = + self.predecessors_lookup(dfg, layout, jts, val, var, ebb, &predecessors); + side_effects + .split_ebbs_created + .append(&mut local_side_effects.split_ebbs_created); + side_effects + .instructions_added_to_ebbs + .append(&mut local_side_effects.instructions_added_to_ebbs); + } + + // Then we clear the undef_vars and mark the block as sealed. + match self.blocks[block] { + BlockData::EbbBody { .. } => panic!("this should not happen"), + BlockData::EbbHeader(ref mut data) => { + data.undef_variables.clear(); + } + }; + side_effects + } + + /// Look up in the predecessors of an Ebb the def for a value an decides wether or not + /// to keep the eeb arg, and act accordingly. Returns the chosen value and optionnaly a + /// list of Ebb that are the middle of newly created critical edges splits. + fn predecessors_lookup(&mut self, + dfg: &mut DataFlowGraph, + layout: &mut Layout, + jts: &mut JumpTables, + temp_arg_val: Value, + temp_arg_var: Variable, + dest_ebb: Ebb, + preds: &Vec<(Block, Inst)>) + -> (Value, SideEffects) { + let mut pred_values: ZeroOneOrMore = ZeroOneOrMore::Zero(); + // TODO: find a way not not allocate a vector + let mut jump_args_to_append: Vec<(Block, Inst, Value)> = Vec::new(); + let ty = dfg.value_type(temp_arg_val); + let mut side_effects = SideEffects { + split_ebbs_created: Vec::new(), + instructions_added_to_ebbs: Vec::new(), + }; + for &(pred, last_inst) in preds.iter() { + // For undef value and each predecessor we query what is the local SSA value + // corresponding to var and we put it as an argument of the branch instruction. + let (pred_val, mut local_side_effects) = + self.use_var(dfg, layout, jts, temp_arg_var, ty, pred); + pred_values = match pred_values { + ZeroOneOrMore::Zero() => { + if pred_val == temp_arg_val { + ZeroOneOrMore::Zero() + } else { + ZeroOneOrMore::One(pred_val) + } + } + ZeroOneOrMore::One(old_val) => { + if pred_val == temp_arg_val || pred_val == old_val { + ZeroOneOrMore::One(old_val) + } else { + ZeroOneOrMore::More() + } + } + ZeroOneOrMore::More() => ZeroOneOrMore::More(), + }; + jump_args_to_append.push((pred, last_inst, pred_val)); + side_effects + .split_ebbs_created + .append(&mut local_side_effects.split_ebbs_created); + side_effects + .instructions_added_to_ebbs + .append(&mut local_side_effects.instructions_added_to_ebbs); + } + match pred_values { + ZeroOneOrMore::Zero() => { + // The variable is used but never defined before. This is an irregularity in the + // code, but rather than throwing an error we silently initialize the variable to + // 0. This will have no effect since this situation happens in unreachable code. + if !layout.is_ebb_inserted(dest_ebb) { + layout.append_ebb(dest_ebb) + }; + let mut cur = Cursor::new(layout); + cur.goto_top(dest_ebb); + cur.next_inst(); + let ty = dfg.value_type(temp_arg_val); + let val = if ty.is_int() { + dfg.ins(&mut cur).iconst(ty, 0) + } else if ty == F32 { + dfg.ins(&mut cur).f32const(Ieee32::new(0.0)) + } else if ty == F64 { + dfg.ins(&mut cur).f64const(Ieee64::new(0.0)) + } else { + panic!("value used but never declared and initialization not supported") + }; + side_effects.instructions_added_to_ebbs.push(dest_ebb); + (val, side_effects) + } + ZeroOneOrMore::One(pred_val) => { + // Here all the predecessors use a single value to represent our variable + // so we don't need to have it as an ebb argument. + // We need to replace all the occurences of val with pred_val but since + // we can't afford a re-writing pass right now we just declare an alias. + dfg.remove_ebb_arg(temp_arg_val); + dfg.change_to_alias(temp_arg_val, pred_val); + (pred_val, side_effects) + } + ZeroOneOrMore::More() => { + // There is disagreement in the predecessors on which value to use so we have + // to keep the ebb argument. + for (pred_block, last_inst, pred_val) in jump_args_to_append { + match self.append_jump_argument(dfg, + layout, + last_inst, + pred_block, + dest_ebb, + pred_val, + temp_arg_var, + jts) { + None => (), + Some(middle_ebb) => side_effects.split_ebbs_created.push(middle_ebb), + }; + } + (temp_arg_val, side_effects) + } + } + } + + /// Appends a jump argument to a jump instruction, returns ebb created in case of + /// critical edge splitting. + fn append_jump_argument(&mut self, + dfg: &mut DataFlowGraph, + layout: &mut Layout, + jump_inst: Inst, + jump_inst_block: Block, + dest_ebb: Ebb, + val: Value, + var: Variable, + jts: &mut JumpTables) + -> Option { + match dfg[jump_inst].analyze_branch(&dfg.value_lists) { + BranchInfo::NotABranch => { + panic!("you have declared a non-branch instruction as a predecessor to an ebb"); + } + // For a single destination appending a jump argument to the instruction + // is sufficient. + BranchInfo::SingleDest(_, _) => { + dfg.append_inst_arg(jump_inst, val); + None + } + BranchInfo::Table(jt) => { + // In the case of a jump table, the situation is tricky because br_table doesn't + // support arguments. + // We have to split the critical edge + let indexes: Vec = jts[jt] + .entries() + .fold(Vec::new(), |mut acc, (index, dest)| if dest == dest_ebb { + acc.push(index); + acc + } else { + acc + }); + let middle_ebb = dfg.make_ebb(); + layout.append_ebb(middle_ebb); + let block = self.declare_ebb_header_block(middle_ebb); + self.blocks[block].add_predecessor(jump_inst_block, jump_inst); + self.seal_ebb_header_block(middle_ebb, dfg, layout, jts); + for index in indexes { + jts[jt].set_entry(index, middle_ebb) + } + let mut cur = Cursor::new(layout); + cur.goto_bottom(middle_ebb); + let middle_jump_inst = dfg.ins(&mut cur).jump(dest_ebb, &[val]); + let dest_header_block = self.header_block(dest_ebb); + self.blocks[dest_header_block].add_predecessor(block, middle_jump_inst); + self.blocks[dest_header_block].remove_predecessor(jump_inst); + self.def_var(var, val, block); + Some(middle_ebb) + } + } + } + + /// Returns the list of `Ebb`s that have been declared as predecessors of the argument. + pub fn predecessors(&self, ebb: Ebb) -> &HashMap { + let block = match self.ebb_headers[ebb].expand() { + Some(block) => block, + None => panic!("the ebb has not been declared yet"), + }; + match self.blocks[block] { + BlockData::EbbBody { .. } => panic!("should not happen"), + BlockData::EbbHeader(ref data) => &data.predecessors, + } + } + + /// Returns `true` if and only if `seal_ebb_header_block` has been called on the argument. + pub fn is_sealed(&self, ebb: Ebb) -> bool { + match self.blocks[self.header_block(ebb)] { + BlockData::EbbBody { .. } => panic!("should not happen"), + BlockData::EbbHeader(ref data) => data.sealed, + } + } +} + +#[cfg(test)] +mod tests { + use cretonne::entity_ref::EntityRef; + use cretonne::ir::{Function, InstBuilder, Cursor, Inst, JumpTableData}; + use cretonne::ir::types::*; + use cretonne::verify_function; + use cretonne::ir::instructions::BranchInfo; + use ssa::SSABuilder; + use std::u32; + + /// An opaque reference to variable. + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub struct Variable(u32); + impl EntityRef for Variable { + fn new(index: usize) -> Self { + assert!(index < (u32::MAX as usize)); + Variable(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } + } + impl Default for Variable { + fn default() -> Variable { + Variable(u32::MAX) + } + } + + #[test] + fn simple_block() { + let mut func = Function::new(); + let mut ssa: SSABuilder = SSABuilder::new(); + let ebb0 = func.dfg.make_ebb(); + // Here is the pseudo-program we want to translate: + // x = 1; + // y = 2; + // z = x + y; + // z = x + z; + + let block = ssa.declare_ebb_header_block(ebb0); + let x_var = Variable(0); + let x_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.insert_ebb(ebb0); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 1) + }; + ssa.def_var(x_var, x_ssa, block); + let y_var = Variable(1); + let y_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 2) + }; + ssa.def_var(y_var, y_ssa, block); + + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block) + .0, + x_ssa); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block) + .0, + y_ssa); + let z_var = Variable(2); + let x_use1 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block) + .0; + let y_use1 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block) + .0; + let z1_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iadd(x_use1, y_use1) + }; + ssa.def_var(z_var, z1_ssa, block); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block) + .0, + z1_ssa); + let x_use2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block) + .0; + let z_use1 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block) + .0; + let z2_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iadd(x_use2, z_use1) + }; + ssa.def_var(z_var, z2_ssa, block); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block) + .0, + z2_ssa); + } + + #[test] + fn sequence_of_blocks() { + let mut func = Function::new(); + let mut ssa: SSABuilder = SSABuilder::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + // Here is the pseudo-program we want to translate: + // ebb0: + // x = 1; + // y = 2; + // z = x + y; + // brnz y, ebb1; + // z = x + z; + // ebb1: + // y = x + y; + + let block0 = ssa.declare_ebb_header_block(ebb0); + let x_var = Variable(0); + let x_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.insert_ebb(ebb0); + cur.insert_ebb(ebb1); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 1) + }; + ssa.def_var(x_var, x_ssa, block0); + let y_var = Variable(1); + let y_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 2) + }; + ssa.def_var(y_var, y_ssa, block0); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block0) + .0, + x_ssa); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block0) + .0, + y_ssa); + let z_var = Variable(2); + let x_use1 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block0) + .0; + let y_use1 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block0) + .0; + let z1_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iadd(x_use1, y_use1) + }; + ssa.def_var(z_var, z1_ssa, block0); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block0) + .0, + z1_ssa); + let y_use2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block0) + .0; + let jump_inst: Inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).brnz(y_use2, ebb1, &[]) + }; + let block1 = ssa.declare_ebb_body_block(block0); + let x_use2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block1) + .0; + assert_eq!(x_use2, x_ssa); + let z_use1 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block1) + .0; + assert_eq!(z_use1, z1_ssa); + let z2_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iadd(x_use2, z_use1) + }; + ssa.def_var(z_var, z2_ssa, block1); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block1) + .0, + z2_ssa); + ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let block2 = ssa.declare_ebb_header_block(ebb1); + ssa.declare_ebb_predecessor(ebb1, block0, jump_inst); + ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let x_use3 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block2) + .0; + assert_eq!(x_ssa, x_use3); + let y_use3 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block2) + .0; + assert_eq!(y_ssa, y_use3); + let y2_ssa = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).iadd(x_use3, y_use3) + }; + ssa.def_var(y_var, y2_ssa, block2); + match func.dfg[jump_inst].analyze_branch(&func.dfg.value_lists) { + BranchInfo::SingleDest(dest, jump_args) => { + assert_eq!(dest, ebb1); + assert_eq!(jump_args.len(), 0); + } + _ => assert!(false), + }; + } + + #[test] + fn program_with_loop() { + let mut func = Function::new(); + let mut ssa: SSABuilder = SSABuilder::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + let ebb2 = func.dfg.make_ebb(); + // Here is the pseudo-program we want to translate: + // ebb0: + // x = 1; + // y = 2; + // z = x + y; + // jump ebb1 + // ebb1: + // z = z + y; + // brnz y, ebb1; + // z = z - x; + // return y + // ebb2: + // y = y - x + // jump ebb1 + + let block0 = ssa.declare_ebb_header_block(ebb0); + ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let x_var = Variable(0); + let x1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.insert_ebb(ebb0); + cur.insert_ebb(ebb1); + cur.insert_ebb(ebb2); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 1) + }; + ssa.def_var(x_var, x1, block0); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block0) + .0, + x1); + let y_var = Variable(1); + let y1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 2) + }; + ssa.def_var(y_var, y1, block0); + assert_eq!(ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block0) + .0, + y1); + let z_var = Variable(2); + let x2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block0) + .0; + assert_eq!(x2, x1); + let y2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block0) + .0; + assert_eq!(y2, y1); + let z1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iadd(x2, y2) + }; + ssa.def_var(z_var, z1, block0); + let jump_ebb0_ebb1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).jump(ebb1, &[]) + }; + let block1 = ssa.declare_ebb_header_block(ebb1); + ssa.declare_ebb_predecessor(ebb1, block0, jump_ebb0_ebb1); + let z2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block1) + .0; + let y3 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block1) + .0; + let z3 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).iadd(z2, y3) + }; + ssa.def_var(z_var, z3, block1); + let y4 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block1) + .0; + assert_eq!(y4, y3); + let jump_ebb1_ebb2 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).brnz(y4, ebb2, &[]) + }; + let block2 = ssa.declare_ebb_body_block(block1); + let z4 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block2) + .0; + assert_eq!(z4, z3); + let x3 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block2) + .0; + let z5 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).isub(z4, x3) + }; + ssa.def_var(z_var, z5, block2); + let y5 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block2) + .0; + assert_eq!(y5, y3); + { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).return_(&[y5]) + }; + + let block3 = ssa.declare_ebb_header_block(ebb2); + ssa.declare_ebb_predecessor(ebb2, block1, jump_ebb1_ebb2); + ssa.seal_ebb_header_block(ebb2, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let y6 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block3) + .0; + assert_eq!(y6, y3); + let x4 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block3) + .0; + assert_eq!(x4, x3); + let y7 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb2); + func.dfg.ins(cur).isub(y6, x4) + }; + ssa.def_var(y_var, y7, block3); + let jump_ebb2_ebb1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb2); + func.dfg.ins(cur).jump(ebb1, &[]) + }; + + ssa.declare_ebb_predecessor(ebb1, block3, jump_ebb2_ebb1); + ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + assert_eq!(func.dfg.ebb_args(ebb1)[0], z2); + assert_eq!(func.dfg.ebb_args(ebb1)[1], y3); + assert_eq!(func.dfg.resolve_aliases(x3), x1); + + } + + #[test] + fn br_table_with_args() { + // This tests the on-demand splitting of critical edges for br_table with jump arguments + let mut func = Function::new(); + let mut ssa: SSABuilder = SSABuilder::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + // Here is the pseudo-program we want to translate: + // ebb0: + // x = 0; + // br_table x ebb1 + // x = 1 + // jump ebb1 + // ebb1: + // x = x + 1 + // return + // + let block0 = ssa.declare_ebb_header_block(ebb0); + ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let x_var = Variable(0); + let x1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.insert_ebb(ebb0); + cur.insert_ebb(ebb1); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 1) + }; + ssa.def_var(x_var, x1, block0); + let mut jt_data = JumpTableData::new(); + jt_data.set_entry(0, ebb1); + let jt = func.jump_tables.push(jt_data); + ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block0) + .0; + let br_table = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).br_table(x1, jt) + }; + let block1 = ssa.declare_ebb_body_block(block0); + let x3 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 2) + }; + ssa.def_var(x_var, x3, block1); + let jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).jump(ebb1, &[]) + }; + let block2 = ssa.declare_ebb_header_block(ebb1); + ssa.declare_ebb_predecessor(ebb1, block1, jump_inst); + ssa.declare_ebb_predecessor(ebb1, block0, br_table); + ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let x4 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block2) + .0; + { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).iadd_imm(x4, 1) + }; + { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).return_(&[]) + }; + match verify_function(&func, None) { + Ok(()) => {} + Err(err) => panic!(err.message), + } + } + + #[test] + fn undef_values_reordering() { + let mut func = Function::new(); + let mut ssa: SSABuilder = SSABuilder::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb1 = func.dfg.make_ebb(); + // Here is the pseudo-program we want to translate: + // ebb0: + // x = 0 + // y = 1 + // z = 2 + // jump ebb1 + // ebb1: + // x = z + x + // y = y - x + // jump ebb1 + // + let block0 = ssa.declare_ebb_header_block(ebb0); + let x_var = Variable(0); + let y_var = Variable(1); + let z_var = Variable(2); + ssa.seal_ebb_header_block(ebb0, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + let x1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.insert_ebb(ebb0); + cur.insert_ebb(ebb1); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 0) + }; + ssa.def_var(x_var, x1, block0); + let y1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 1) + }; + ssa.def_var(y_var, y1, block0); + let z1 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).iconst(I32, 2) + }; + ssa.def_var(z_var, z1, block0); + let jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).jump(ebb1, &[]) + }; + let block1 = ssa.declare_ebb_header_block(ebb1); + ssa.declare_ebb_predecessor(ebb1, block0, jump_inst); + let z2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + z_var, + I32, + block1) + .0; + assert_eq!(func.dfg.ebb_args(ebb1)[0], z2); + let x2 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block1) + .0; + assert_eq!(func.dfg.ebb_args(ebb1)[1], x2); + let x3 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).iadd(x2, z2) + }; + ssa.def_var(x_var, x3, block1); + let x4 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + x_var, + I32, + block1) + .0; + let y3 = ssa.use_var(&mut func.dfg, + &mut func.layout, + &mut func.jump_tables, + y_var, + I32, + block1) + .0; + assert_eq!(func.dfg.ebb_args(ebb1)[2], y3); + let y4 = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).isub(y3, x4) + }; + ssa.def_var(y_var, y4, block1); + let jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).jump(ebb1, &[]) + }; + ssa.declare_ebb_predecessor(ebb1, block1, jump_inst); + ssa.seal_ebb_header_block(ebb1, &mut func.dfg, &mut func.layout, &mut func.jump_tables); + // At sealing the "z" argument disappear but the remaining "x" and "y" args have to be + // in the right order. + assert_eq!(func.dfg.ebb_args(ebb1)[1], y3); + assert_eq!(func.dfg.ebb_args(ebb1)[0], x2); + } +} diff --git a/test-all.sh b/test-all.sh index 07d6fa2bdd..72b07f8856 100755 --- a/test-all.sh +++ b/test-all.sh @@ -40,7 +40,7 @@ if [ -n "$needcheck" ]; then touch $tsfile || echo no target directory fi -PKGS="cretonne cretonne-reader cretonne-tools filecheck" +PKGS="cretonne cretonne-reader cretonne-tools cretonne-frontend filecheck" cd "$topdir" for PKG in $PKGS do From a9147ebd30808b133ba56279bc8924cad3c93e08 Mon Sep 17 00:00:00 2001 From: d1m0 Date: Wed, 12 Jul 2017 08:51:55 -0700 Subject: [PATCH 857/968] Add fix for #114 (#115) * Reduce code duplication in TypeConstraint subclasses; Add ConstrainWiderOrEqual to ti and to ireduce,{s,u}extend and f{promote,demote}; Fix bug in emitting constraint edges in TypeEnv.dot(); Modify runtime constraint checks to reject match when they encounter overflow * Rename Constrain types to something shorter; Move lane_bits/lane_counts in subclasses of ValueType; Add wider_or_eq function in rust and python; --- lib/cretonne/meta/base/instructions.py | 27 +-- lib/cretonne/meta/cdsl/instructions.py | 25 ++- lib/cretonne/meta/cdsl/test_ti.py | 103 ++++++++- lib/cretonne/meta/cdsl/ti.py | 271 ++++++++++++++++-------- lib/cretonne/meta/cdsl/types.py | 50 +++++ lib/cretonne/meta/gen_legalizer.py | 44 +++- lib/cretonne/meta/test_gen_legalizer.py | 76 ++++++- lib/cretonne/src/ir/types.rs | 7 + 8 files changed, 471 insertions(+), 132 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index dc9accf1cc..109bc73f9e 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -12,6 +12,7 @@ from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 from base.immediates import intcc, floatcc, memflags, regunit from base import entities +from cdsl.ti import WiderOrEq import base.formats # noqa GROUP = InstructionGroup("base", "Shared base instruction set") @@ -1405,7 +1406,7 @@ ireduce = Instruction( and each lane must not have more bits that the input lanes. If the input and output types are the same, this is a no-op. """, - ins=x, outs=a) + ins=x, outs=a, constraints=WiderOrEq(Int, IntTo)) IntTo = TypeVar( @@ -1427,7 +1428,7 @@ uextend = Instruction( and each lane must not have fewer bits that the input lanes. If the input and output types are the same, this is a no-op. """, - ins=x, outs=a) + ins=x, outs=a, constraints=WiderOrEq(IntTo, Int)) sextend = Instruction( 'sextend', r""" @@ -1441,7 +1442,7 @@ sextend = Instruction( and each lane must not have fewer bits that the input lanes. If the input and output types are the same, this is a no-op. """, - ins=x, outs=a) + ins=x, outs=a, constraints=WiderOrEq(IntTo, Int)) FloatTo = TypeVar( 'FloatTo', 'A scalar or vector floating point number', @@ -1457,14 +1458,14 @@ fpromote = Instruction( Each lane in `x` is converted to the destination floating point format. This is an exact operation. - Since Cretonne currently only supports two floating point formats, this - instruction always converts :type:`f32` to :type:`f64`. This may change - in the future. + Cretonne currently only supports two floating point formats + - :type:`f32` and :type:`f64`. This may change in the future. The result type must have the same number of vector lanes as the input, - and the result lanes must be larger than the input lanes. + and the result lanes must not have fewer bits than the input lanes. If + the input and output types are the same, this is a no-op. """, - ins=x, outs=a) + ins=x, outs=a, constraints=WiderOrEq(FloatTo, Float)) fdemote = Instruction( 'fdemote', r""" @@ -1473,14 +1474,14 @@ fdemote = Instruction( Each lane in `x` is converted to the destination floating point format by rounding to nearest, ties to even. - Since Cretonne currently only supports two floating point formats, this - instruction always converts :type:`f64` to :type:`f32`. This may change - in the future. + Cretonne currently only supports two floating point formats + - :type:`f32` and :type:`f64`. This may change in the future. The result type must have the same number of vector lanes as the input, - and the result lanes must be smaller than the input lanes. + and the result lanes must not have more bits than the input lanes. If + the input and output types are the same, this is a no-op. """, - ins=x, outs=a) + ins=x, outs=a, constraints=WiderOrEq(Float, FloatTo)) x = Operand('x', Float) a = Operand('a', IntTo) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 22c989bd65..511459fdbe 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -10,8 +10,10 @@ try: if TYPE_CHECKING: from .ast import Expr, Apply # noqa from .typevar import TypeVar # noqa + from .ti import TypeConstraint # noqa # List of operands for ins/outs: OpList = Union[Sequence[Operand], Operand] + ConstrList = Union[Sequence[TypeConstraint], TypeConstraint] MaybeBoundInst = Union['Instruction', 'BoundInstruction'] except ImportError: pass @@ -80,6 +82,7 @@ class Instruction(object): operands and other operand kinds. :param outs: Tuple of output operands. The output operands must be SSA values or `variable_args`. + :param constraints: Tuple of instruction-specific TypeConstraints. :param is_terminator: This is a terminator instruction. :param is_branch: This is a branch instruction. :param is_call: This is a call instruction. @@ -102,13 +105,14 @@ class Instruction(object): 'can_trap': 'Can this instruction cause a trap?', } - def __init__(self, name, doc, ins=(), outs=(), **kwargs): - # type: (str, str, OpList, OpList, **Any) -> None # noqa + def __init__(self, name, doc, ins=(), outs=(), constraints=(), **kwargs): + # type: (str, str, OpList, OpList, ConstrList, **Any) -> None self.name = name self.camel_name = camel_case(name) self.__doc__ = doc self.ins = self._to_operand_tuple(ins) self.outs = self._to_operand_tuple(outs) + self.constraints = self._to_constraint_tuple(constraints) self.format = InstructionFormat.lookup(self.ins, self.outs) # Opcode number, assigned by gen_instr.py. @@ -268,6 +272,23 @@ class Instruction(object): assert isinstance(op, Operand) return x + @staticmethod + def _to_constraint_tuple(x): + # type: (ConstrList) -> Tuple[TypeConstraint, ...] + """ + Allow a single TypeConstraint instance instead of the awkward singleton + tuple syntax. + """ + # import placed here to avoid circular dependency + from .ti import TypeConstraint # noqa + if isinstance(x, TypeConstraint): + x = (x,) + else: + x = tuple(x) + for op in x: + assert isinstance(op, TypeConstraint) + return x + def bind(self, *args): # type: (*ValueType) -> BoundInstruction """ diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index e89cedc16c..f60a9222f5 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -1,13 +1,14 @@ from __future__ import absolute_import from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint,\ - b1, icmp, iadd_cout, iadd_cin, uextend, ireduce + b1, icmp, iadd_cout, iadd_cin, uextend, sextend, ireduce, fpromote, \ + fdemote from base.legalize import narrow, expand from base.immediates import intcc from base.types import i32, i8 from .typevar import TypeVar from .ast import Var, Def from .xform import Rtl, XForm -from .ti import ti_rtl, subst, TypeEnv, get_type_env, ConstrainTVsEqual +from .ti import ti_rtl, subst, TypeEnv, get_type_env, TypesEqual, WiderOrEq from unittest import TestCase from functools import reduce @@ -52,9 +53,10 @@ def agree(me, other): # Translate our constraints using m, and sort me_equiv_constr = sorted([constr.translate(m) - for constr in me.constraints]) + for constr in me.constraints], key=repr) # Sort other's constraints - other_equiv_constr = sorted(other.constraints) + other_equiv_constr = sorted([constr.translate(other) + for constr in other.constraints], key=repr) return me_equiv_constr == other_equiv_constr @@ -78,7 +80,7 @@ def check_typing(got_or_err, expected, symtab=None): tv_m = {subst(k.get_typevar(), subst_m): v for (k, v) in m.items()} # Rewrite the TVs in the input constraints to their XForm internal # versions - c = [(subst(a, subst_m), subst(b, subst_m)) for (a, b) in c] + c = [constr.translate(subst_m) for constr in c] else: # If no symtab, just convert m from Var->TypeVar map to a # TypeVar->TypeVar map @@ -209,7 +211,7 @@ class TestRTL(TypeCheckingBaseTest): self.v3: txn, self.v4: txn, self.v5: txn, - }, [ConstrainTVsEqual(ixn.as_bool(), txn.as_bool())])) + }, [TypesEqual(ixn.as_bool(), txn.as_bool())])) def test_vselect_vsplits(self): # type: () -> None @@ -319,6 +321,90 @@ class TestRTL(TypeCheckingBaseTest): "Error: empty type created when unifying " + "`typeof_v4` and `typeof_v5`") + def test_extend_reduce(self): + # type: () -> None + r = Rtl( + self.v1 << uextend(self.v0), + self.v2 << ireduce(self.v1), + self.v3 << sextend(self.v2), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + typing = typing.extract() + + itype0 = TypeVar("t", "", ints=True, simd=(1, 256)) + itype1 = TypeVar("t1", "", ints=True, simd=(1, 256)) + itype2 = TypeVar("t2", "", ints=True, simd=(1, 256)) + itype3 = TypeVar("t3", "", ints=True, simd=(1, 256)) + + check_typing(typing, ({ + self.v0: itype0, + self.v1: itype1, + self.v2: itype2, + self.v3: itype3, + }, [WiderOrEq(itype1, itype0), + WiderOrEq(itype1, itype2), + WiderOrEq(itype3, itype2)])) + + def test_extend_reduce_enumeration(self): + # type: () -> None + for op in (uextend, sextend, ireduce): + r = Rtl( + self.v1 << op(self.v0), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti).extract() + + # The number of possible typings is 9 * (3+ 2*2 + 3) = 90 + l = [(t[self.v0], t[self.v1]) for t in typing.concrete_typings()] + assert (len(l) == len(set(l)) and len(l) == 90) + for (tv0, tv1) in l: + typ0, typ1 = (tv0.singleton_type(), tv1.singleton_type()) + if (op == ireduce): + assert typ0.wider_or_equal(typ1) + else: + assert typ1.wider_or_equal(typ0) + + def test_fpromote_fdemote(self): + # type: () -> None + r = Rtl( + self.v1 << fpromote(self.v0), + self.v2 << fdemote(self.v1), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti) + typing = typing.extract() + + ftype0 = TypeVar("t", "", floats=True, simd=(1, 256)) + ftype1 = TypeVar("t1", "", floats=True, simd=(1, 256)) + ftype2 = TypeVar("t2", "", floats=True, simd=(1, 256)) + + check_typing(typing, ({ + self.v0: ftype0, + self.v1: ftype1, + self.v2: ftype2, + }, [WiderOrEq(ftype1, ftype0), + WiderOrEq(ftype1, ftype2)])) + + def test_fpromote_fdemote_enumeration(self): + # type: () -> None + for op in (fpromote, fdemote): + r = Rtl( + self.v1 << op(self.v0), + ) + ti = TypeEnv() + typing = ti_rtl(r, ti).extract() + + # The number of possible typings is 9*(2 + 1) = 27 + l = [(t[self.v0], t[self.v1]) for t in typing.concrete_typings()] + assert (len(l) == len(set(l)) and len(l) == 27) + for (tv0, tv1) in l: + (typ0, typ1) = (tv0.singleton_type(), tv1.singleton_type()) + if (op == fdemote): + assert typ0.wider_or_equal(typ1) + else: + assert typ1.wider_or_equal(typ0) + class TestXForm(TypeCheckingBaseTest): def test_iadd_cout(self): @@ -453,7 +539,7 @@ class TestXForm(TypeCheckingBaseTest): self.v3: i32t, self.v4: i32t, self.v5: i32t, - }, []), x.symtab) + }, [WiderOrEq(i32t, itype)]), x.symtab) def test_bound_inst_inference1(self): # Second example taken from issue #26 @@ -477,7 +563,7 @@ class TestXForm(TypeCheckingBaseTest): self.v3: i32t, self.v4: i32t, self.v5: i32t, - }, []), x.symtab) + }, [WiderOrEq(i32t, itype)]), x.symtab) def test_fully_bound_inst_inference(self): # Second example taken from issue #26 with complete bounds @@ -494,6 +580,7 @@ class TestXForm(TypeCheckingBaseTest): i8t = TypeVar.singleton(i8) i32t = TypeVar.singleton(i32) + # Note no constraints here since they are all trivial check_typing(x.ti, ({ self.v0: i8t, self.v1: i8t, diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index 85bd92507f..79023b8b34 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -8,7 +8,7 @@ from itertools import product try: from typing import Dict, TYPE_CHECKING, Union, Tuple, Optional, Set # noqa - from typing import Iterable, List # noqa + from typing import Iterable, List, Any # noqa from typing import cast from .xform import Rtl, XForm # noqa from .ast import Expr # noqa @@ -25,9 +25,72 @@ class TypeConstraint(object): """ Base class for all runtime-emittable type constraints. """ + def translate(self, m): + # type: (Union[TypeEnv, TypeMap]) -> TypeConstraint + """ + Translate any TypeVars in the constraint according to the map or + TypeEnv m + """ + def translate_one(a): + # type: (Any) -> Any + if (isinstance(a, TypeVar)): + return m[a] if isinstance(m, TypeEnv) else subst(a, m) + return a + + res = None # type: TypeConstraint + res = self.__class__(*tuple(map(translate_one, self._args()))) + return res + + def __eq__(self, other): + # type: (object) -> bool + if (not isinstance(other, self.__class__)): + return False + + assert isinstance(other, TypeConstraint) # help MyPy figure out other + return self._args() == other._args() + + def is_concrete(self): + # type: () -> bool + """ + Return true iff all typevars in the constraint are singletons. + """ + tvs = filter(lambda x: isinstance(x, TypeVar), self._args()) + return [] == list(filter(lambda x: x.singleton_type() is None, tvs)) + + def __hash__(self): + # type: () -> int + return hash(self._args()) + + def _args(self): + # type: () -> Tuple[Any,...] + """ + Return a tuple with the exact arguments passed to __init__ to create + this object. + """ + assert False, "Abstract" + + def is_trivial(self): + # type: () -> bool + """ + Return true if this constrain is statically decidable. + """ + assert False, "Abstract" + + def eval(self): + # type: () -> bool + """ + Evaluate this constraint. Should only be called when the constraint has + been translated to concrete types. + """ + assert False, "Abstract" + + def __repr__(self): + # type: () -> str + return (self.__class__.__name__ + '(' + + ', '.join(map(str, self._args())) + ')') -class ConstrainTVsEqual(TypeConstraint): +class TypesEqual(TypeConstraint): """ Constraint specifying that two derived type vars must have the same runtime type. @@ -37,48 +100,24 @@ class ConstrainTVsEqual(TypeConstraint): assert tv1.is_derived and tv2.is_derived (self.tv1, self.tv2) = sorted([tv1, tv2], key=repr) + def _args(self): + # type: () -> Tuple[Any,...] + """ See TypeConstraint._args() """ + return (self.tv1, self.tv2) + def is_trivial(self): # type: () -> bool - """ - Return true if this constrain is statically decidable. - """ - return self.tv1 == self.tv2 or \ - (self.tv1.singleton_type() is not None and - self.tv2.singleton_type() is not None) - - def translate(self, m): - # type: (Union[TypeEnv, TypeMap]) -> ConstrainTVsEqual - """ - Translate any TypeVars in the constraint according to the map m - """ - if isinstance(m, TypeEnv): - return ConstrainTVsEqual(m[self.tv1], m[self.tv2]) - else: - return ConstrainTVsEqual(subst(self.tv1, m), subst(self.tv2, m)) - - def __eq__(self, other): - # type: (object) -> bool - if (not isinstance(other, ConstrainTVsEqual)): - return False - - return (self.tv1, self.tv2) == (other.tv1, other.tv2) - - def __hash__(self): - # type: () -> int - return hash((self.tv1, self.tv2)) + """ See TypeConstraint.is_trivial() """ + return self.tv1 == self.tv2 or self.is_concrete() def eval(self): # type: () -> bool - """ - Evaluate this constraint. Should only be called when the constraint has - been translated to concrete types. - """ - assert self.tv1.singleton_type() is not None and \ - self.tv2.singleton_type() is not None + """ See TypeConstraint.eval() """ + assert self.is_concrete() return self.tv1.singleton_type() == self.tv2.singleton_type() -class ConstrainTVInTypeset(TypeConstraint): +class InTypeset(TypeConstraint): """ Constraint specifying that a type var must belong to some typeset. """ @@ -88,11 +127,14 @@ class ConstrainTVInTypeset(TypeConstraint): self.tv = tv self.ts = ts + def _args(self): + # type: () -> Tuple[Any,...] + """ See TypeConstraint._args() """ + return (self.tv, self.ts) + def is_trivial(self): # type: () -> bool - """ - Return true if this constrain is statically decidable. - """ + """ See TypeConstraint.is_trivial() """ tv_ts = self.tv.get_typeset().copy() # Trivially True @@ -104,39 +146,78 @@ class ConstrainTVInTypeset(TypeConstraint): if (tv_ts.size() == 0): return True - return False - - def translate(self, m): - # type: (Union[TypeEnv, TypeMap]) -> ConstrainTVInTypeset - """ - Translate any TypeVars in the constraint according to the map m - """ - if isinstance(m, TypeEnv): - return ConstrainTVInTypeset(m[self.tv], self.ts) - else: - return ConstrainTVInTypeset(subst(self.tv, m), self.ts) - - def __eq__(self, other): - # type: (object) -> bool - if (not isinstance(other, ConstrainTVInTypeset)): - return False - - return (self.tv, self.ts) == (other.tv, other.ts) - - def __hash__(self): - # type: () -> int - return hash((self.tv, self.ts)) + return self.is_concrete() def eval(self): # type: () -> bool - """ - Evaluate this constraint. Should only be called when the constraint has - been translated to concrete types. - """ - assert self.tv.singleton_type() is not None + """ See TypeConstraint.eval() """ + assert self.is_concrete() return self.tv.get_typeset().issubset(self.ts) +class WiderOrEq(TypeConstraint): + """ + Constraint specifying that a type var tv1 must be wider than or equal to + type var tv2 at runtime. This requires that: + 1) They have the same number of lanes + 2) In a lane tv1 has at least as many bits as tv2. + """ + def __init__(self, tv1, tv2): + # type: (TypeVar, TypeVar) -> None + self.tv1 = tv1 + self.tv2 = tv2 + + def _args(self): + # type: () -> Tuple[Any,...] + """ See TypeConstraint._args() """ + return (self.tv1, self.tv2) + + def is_trivial(self): + # type: () -> bool + """ See TypeConstraint.is_trivial() """ + # Trivially true + if (self.tv1 == self.tv2): + return True + + ts1 = self.tv1.get_typeset() + ts2 = self.tv2.get_typeset() + + def set_wider_or_equal(s1, s2): + # type: (Set[int], Set[int]) -> bool + return len(s1) > 0 and len(s2) > 0 and min(s1) >= max(s2) + + # Trivially True + if set_wider_or_equal(ts1.ints, ts2.ints) and\ + set_wider_or_equal(ts1.floats, ts2.floats) and\ + set_wider_or_equal(ts1.bools, ts2.bools): + return True + + def set_narrower(s1, s2): + # type: (Set[int], Set[int]) -> bool + return len(s1) > 0 and len(s2) > 0 and min(s1) < max(s2) + + # Trivially False + if set_narrower(ts1.ints, ts2.ints) and\ + set_narrower(ts1.floats, ts2.floats) and\ + set_narrower(ts1.bools, ts2.bools): + return True + + # Trivially False + if len(ts1.lanes.intersection(ts2.lanes)) == 0: + return True + + return self.is_concrete() + + def eval(self): + # type: () -> bool + """ See TypeConstraint.eval() """ + assert self.is_concrete() + typ1 = self.tv1.singleton_type() + typ2 = self.tv2.singleton_type() + + return typ1.wider_or_equal(typ2) + + class TypeEnv(object): """ Class encapsulating the neccessary book keeping for type inference. @@ -204,12 +285,11 @@ class TypeEnv(object): self.type_map[tv1] = tv2 - def add_constraint(self, tv1, tv2): - # type: (TypeVar, TypeVar) -> None + def add_constraint(self, constr): + # type: (TypeConstraint) -> None """ - Add a new equivalence constraint between tv1 and tv2 + Add a new constraint """ - constr = ConstrainTVsEqual(tv1, tv2) if (constr not in self.constraints): self.constraints.append(constr) @@ -261,6 +341,7 @@ class TypeEnv(object): Get the free typevars in the current type env. """ tvs = set([self[tv].free_typevar() for tv in self.type_map.keys()]) + tvs = tvs.union(set([self[v].free_typevar() for v in self.vars])) # Filter out None here due to singleton type vars return sorted(filter(lambda x: x is not None, tvs), key=lambda x: x.name) @@ -326,17 +407,18 @@ class TypeEnv(object): new_constraints = [] # type: List[TypeConstraint] for constr in self.constraints: - # Currently typeinference only generates ConstrainTVsEqual - # constraints - assert isinstance(constr, ConstrainTVsEqual) constr = constr.translate(self) if constr.is_trivial() or constr in new_constraints: continue # Sanity: translated constraints should refer to only real vars - assert constr.tv1.free_typevar() in vars_tvs and\ - constr.tv2.free_typevar() in vars_tvs + for arg in constr._args(): + if (not isinstance(arg, TypeVar)): + continue + + arg_free_tv = arg.free_typevar() + assert arg_free_tv is None or arg_free_tv in vars_tvs new_constraints.append(constr) @@ -372,9 +454,6 @@ class TypeEnv(object): # Check if constraints are satisfied for this typing failed = None for constr in self.constraints: - # Currently typeinference only generates ConstrainTVsEqual - # constraints - assert isinstance(constr, ConstrainTVsEqual) concrete_constr = constr.translate(m) if not concrete_constr.eval(): failed = concrete_constr @@ -401,22 +480,27 @@ class TypeEnv(object): # Add all registered TVs (as some of them may be singleton nodes not # appearing in the graph nodes = set([v.get_typevar() for v in self.vars]) # type: Set[TypeVar] - edges = set() # type: Set[Tuple[TypeVar, TypeVar, str, Optional[str]]] + edges = set() # type: Set[Tuple[TypeVar, TypeVar, str, str, Optional[str]]] # noqa for (k, v) in self.type_map.items(): # Add all intermediate TVs appearing in edges nodes.add(k) nodes.add(v) - edges.add((k, v, "dotted", None)) + edges.add((k, v, "dotted", "forward", None)) while (v.is_derived): nodes.add(v.base) - edges.add((v, v.base, "solid", v.derived_func)) + edges.add((v, v.base, "solid", "forward", v.derived_func)) v = v.base for constr in self.constraints: - assert isinstance(constr, ConstrainTVsEqual) - assert constr.tv1 in nodes and constr.tv2 in nodes - edges.add((constr.tv1, constr.tv2, "dashed", None)) + if isinstance(constr, TypesEqual): + assert constr.tv1 in nodes and constr.tv2 in nodes + edges.add((constr.tv1, constr.tv2, "dashed", "none", "equal")) + elif isinstance(constr, WiderOrEq): + assert constr.tv1 in nodes and constr.tv2 in nodes + edges.add((constr.tv1, constr.tv2, "dashed", "forward", ">=")) + else: + assert False, "Can't display constraint {}".format(constr) root_nodes = set([x for x in nodes if x not in self.type_map and not x.is_derived]) @@ -428,17 +512,12 @@ class TypeEnv(object): r += "[xlabel=\"{}\"]".format(self[n].get_typeset()) r += ";\n" - for (n1, n2, style, elabel) in edges: - e = label(n1) - if style == "dashed": - e += '--' - else: - e += '->' - e += label(n2) - e += "[style={}".format(style) + for (n1, n2, style, direction, elabel) in edges: + e = label(n1) + "->" + label(n2) + e += "[style={},dir={}".format(style, direction) if elabel is not None: - e += ",label={}".format(elabel) + e += ",label=\"{}\"".format(elabel) e += "];\n" r += e @@ -589,7 +668,7 @@ def unify(tv1, tv2, typ): inv_f = TypeVar.inverse_func(tv1.derived_func) return unify(tv1.base, normalize_tv(TypeVar.derived(tv2, inv_f)), typ) - typ.add_constraint(tv1, tv2) + typ.add_constraint(TypesEqual(tv1, tv2)) return typ @@ -648,6 +727,10 @@ def ti_def(definition, typ): typ = get_type_env(typ_or_err) + # Add any instruction specific constraints + for constr in inst.constraints: + typ.add_constraint(constr.translate(m)) + return typ diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index eeb5325ae8..cebe472fbf 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -49,6 +49,26 @@ class ValueType(object): else: raise AttributeError("No type named '{}'".format(name)) + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + assert False, "Abstract" + + def lane_count(self): + # type: () -> int + """Return the number of lanes.""" + assert False, "Abstract" + + def wider_or_equal(self, other): + # type: (ValueType) -> bool + """ + Return true iff: + 1. self and other have equal number of lanes + 2. each lane in self has at least as many bits as a lane in other + """ + return (self.lane_count() == other.lane_count() and + self.lane_bits() >= other.lane_bits()) + class ScalarType(ValueType): """ @@ -85,6 +105,11 @@ class ScalarType(ValueType): self._vectors[lanes] = v return v + def lane_count(self): + # type: () -> int + """Return the number of lanes.""" + return 1 + class VectorType(ValueType): """ @@ -112,6 +137,16 @@ class VectorType(ValueType): return ('VectorType(base={}, lanes={})' .format(self.base.name, self.lanes)) + def lane_count(self): + # type: () -> int + """Return the number of lanes.""" + return self.lanes + + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + return self.base.lane_bits() + class IntType(ScalarType): """A concrete scalar integer type.""" @@ -138,6 +173,11 @@ class IntType(ScalarType): else: return typ + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + return self.bits + class FloatType(ScalarType): """A concrete scalar floating point type.""" @@ -164,6 +204,11 @@ class FloatType(ScalarType): else: return typ + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + return self.bits + class BoolType(ScalarType): """A concrete scalar boolean type.""" @@ -189,3 +234,8 @@ class BoolType(ScalarType): return cast(BoolType, typ) else: return typ + + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + return self.bits diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index ce2f559ef2..eeb567722e 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -11,8 +11,8 @@ from __future__ import absolute_import from srcgen import Formatter from base import legalize, instructions from cdsl.ast import Var -from cdsl.ti import ti_rtl, TypeEnv, get_type_env, ConstrainTVsEqual,\ - ConstrainTVInTypeset +from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\ + InTypeset, WiderOrEq from unique_table import UniqueTable from gen_instr import gen_typesets_table from cdsl.typevar import TypeVar @@ -66,7 +66,7 @@ def get_runtime_typechecks(xform): assert xform_ts.issubset(src_ts) if src_ts != xform_ts: - check_l.append(ConstrainTVInTypeset(xform.ti[v], xform_ts)) + check_l.append(InTypeset(xform.ti[v], xform_ts)) # 2,3) Add any constraints that appear in xform.ti check_l.extend(xform.ti.constraints) @@ -81,6 +81,14 @@ def emit_runtime_typecheck(check, fmt, type_sets): """ def build_derived_expr(tv): # type: (TypeVar) -> str + """ + Build an expression of type Option corresponding to a concrete + type transformed by the sequence of derivation functions in tv. + + We are using Option, as some constraints may cause an + over/underflow on patterns that do not match them. We want to capture + this without panicking at runtime. + """ if not tv.is_derived: assert tv.name.startswith('typeof_') return "Some({})".format(tv.name) @@ -102,7 +110,8 @@ def emit_runtime_typecheck(check, fmt, type_sets): else: assert False, "Unknown derived function {}".format(tv.derived_func) - if (isinstance(check, ConstrainTVInTypeset)): + if (isinstance(check, InTypeset)): + assert not check.tv.is_derived tv = check.tv.name if check.ts not in type_sets.index: type_sets.add(check.ts) @@ -112,11 +121,28 @@ def emit_runtime_typecheck(check, fmt, type_sets): with fmt.indented('if !TYPE_SETS[{}].contains({}) {{'.format(ts, tv), '};'): fmt.line('return false;') - elif (isinstance(check, ConstrainTVsEqual)): - tv1 = build_derived_expr(check.tv1) - tv2 = build_derived_expr(check.tv2) - with fmt.indented('if {} != {} {{'.format(tv1, tv2), '};'): - fmt.line('return false;') + elif (isinstance(check, TypesEqual)): + with fmt.indented('{', '};'): + fmt.line('let a = {};'.format(build_derived_expr(check.tv1))) + fmt.line('let b = {};'.format(build_derived_expr(check.tv2))) + + fmt.comment('On overflow constraint doesn\'t appply') + with fmt.indented('if a.is_none() || b.is_none() {', '};'): + fmt.line('return false;') + + with fmt.indented('if a != b {', '};'): + fmt.line('return false;') + elif (isinstance(check, WiderOrEq)): + with fmt.indented('{', '};'): + fmt.line('let a = {};'.format(build_derived_expr(check.tv1))) + fmt.line('let b = {};'.format(build_derived_expr(check.tv2))) + + fmt.comment('On overflow constraint doesn\'t appply') + with fmt.indented('if a.is_none() || b.is_none() {', '};'): + fmt.line('return false;') + + with fmt.indented('if !a.wider_or_equal(b) {', '};'): + fmt.line('return false;') else: assert False, "Unknown check {}".format(check) diff --git a/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py index 38f26959f4..538ff69b63 100644 --- a/lib/cretonne/meta/test_gen_legalizer.py +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -4,7 +4,7 @@ from unittest import TestCase from srcgen import Formatter from gen_legalizer import get_runtime_typechecks, emit_runtime_typecheck from base.instructions import vselect, vsplit, isplit, iconcat, vconcat, \ - iconst, b1, icmp, copy # noqa + iconst, b1, icmp, copy, sextend, uextend, ireduce, fdemote, fpromote # noqa from base.legalize import narrow, expand # noqa from base.immediates import intcc # noqa from cdsl.typevar import TypeVar, TypeSet @@ -57,9 +57,32 @@ def equiv_check(tv1, tv2): # type: (TypeVar, TypeVar) -> CheckProducer return lambda typesets: format_check( typesets, - 'if Some({}).map(|t: Type| -> t.as_bool()) != ' + - 'Some({}).map(|t: Type| -> t.as_bool()) ' + - '{{\n return false;\n}};\n', tv1, tv2) + '{{\n' + + ' let a = {};\n' + + ' let b = {};\n' + + ' if a.is_none() || b.is_none() {{\n' + + ' return false;\n' + + ' }};\n' + + ' if a != b {{\n' + + ' return false;\n' + + ' }};\n' + + '}};\n', tv1, tv2) + + +def wider_check(tv1, tv2): + # type: (TypeVar, TypeVar) -> CheckProducer + return lambda typesets: format_check( + typesets, + '{{\n' + + ' let a = {};\n' + + ' let b = {};\n' + + ' if a.is_none() || b.is_none() {{\n' + + ' return false;\n' + + ' }};\n' + + ' if !a.wider_or_equal(b) {{\n' + + ' return false;\n' + + ' }};\n' + + '}};\n', tv1, tv2) def sequence(*args): @@ -138,8 +161,49 @@ class TestRuntimeChecks(TestCase): self.v5 << vselect(self.v1, self.v3, self.v4), ) x = XForm(r, r) + tv2_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\ + .format(self.v2.get_typevar().name) + tv3_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\ + .format(self.v3.get_typevar().name) self.check_yo_check( x, sequence(typeset_check(self.v3, ts), - equiv_check(self.v2.get_typevar(), - self.v3.get_typevar()))) + equiv_check(tv2_exp, tv3_exp))) + + def test_reduce_extend(self): + # type: () -> None + r = Rtl( + self.v1 << uextend(self.v0), + self.v2 << ireduce(self.v1), + self.v3 << sextend(self.v2), + ) + x = XForm(r, r) + + tv0_exp = 'Some({})'.format(self.v0.get_typevar().name) + tv1_exp = 'Some({})'.format(self.v1.get_typevar().name) + tv2_exp = 'Some({})'.format(self.v2.get_typevar().name) + tv3_exp = 'Some({})'.format(self.v3.get_typevar().name) + + self.check_yo_check( + x, sequence(wider_check(tv1_exp, tv0_exp), + wider_check(tv1_exp, tv2_exp), + wider_check(tv3_exp, tv2_exp))) + + def test_demote_promote(self): + # type: () -> None + r = Rtl( + self.v1 << fpromote(self.v0), + self.v2 << fdemote(self.v1), + self.v3 << fpromote(self.v2), + ) + x = XForm(r, r) + + tv0_exp = 'Some({})'.format(self.v0.get_typevar().name) + tv1_exp = 'Some({})'.format(self.v1.get_typevar().name) + tv2_exp = 'Some({})'.format(self.v2.get_typevar().name) + tv3_exp = 'Some({})'.format(self.v3.get_typevar().name) + + self.check_yo_check( + x, sequence(wider_check(tv1_exp, tv0_exp), + wider_check(tv1_exp, tv2_exp), + wider_check(tv3_exp, tv2_exp))) diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index e6379be2e6..0007154025 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -236,6 +236,13 @@ impl Type { pub fn index(self) -> usize { self.0 as usize } + + /// True iff: + /// 1) self.lane_count() == other.lane_count() and + /// 2) self.lane_bits() >= other.lane_bits() + pub fn wider_or_equal(self, other: Type) -> bool { + self.lane_count() == other.lane_count() && self.lane_bits() >= other.lane_bits() + } } impl Display for Type { From 6ee432329d6f7034fbd1dc3fa879188dbf900d0f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 09:15:40 -0700 Subject: [PATCH 858/968] Add an other_side_effects instruction flag. This is used to indicate instructions that have some side effect that is not modelled by the more specific instruction flags. --- lib/cretonne/meta/cdsl/instructions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 511459fdbe..7b8cc55b31 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -90,6 +90,7 @@ class Instruction(object): :param can_trap: This instruction can trap. :param can_load: This instruction can load from memory. :param can_store: This instruction can store to memory. + :param other_side_effects: Instruction has other side effects. """ # Boolean instruction attributes that can be passed as keyword arguments to @@ -103,6 +104,8 @@ class Instruction(object): 'can_load': 'Can this instruction read from memory?', 'can_store': 'Can this instruction write to memory?', 'can_trap': 'Can this instruction cause a trap?', + 'other_side_effects': + 'Does this instruction have other side effects besides can_*', } def __init__(self, name, doc, ins=(), outs=(), constraints=(), **kwargs): From 24b53efc9d22fde5c18f981ce795704b3f9f5ef6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 09:29:48 -0700 Subject: [PATCH 859/968] Enforce encodings for instructions with side effects. We allow ghost instructions to exist if they have no side effects. Instructions that affect control flow or that have other side effects must be encoded. Teach the IL verifier to enforce this. Once any instruction has an encoding, all instructions with side effects must have an encoding. --- filetests/regalloc/coalesce.cton | 2 +- lib/cretonne/src/verifier/mod.rs | 80 ++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/filetests/regalloc/coalesce.cton b/filetests/regalloc/coalesce.cton index 41fc6e9771..80cd38e62e 100644 --- a/filetests/regalloc/coalesce.cton +++ b/filetests/regalloc/coalesce.cton @@ -59,7 +59,7 @@ ebb0(v0: i32): brnz v0, ebb1(v0) v1 = iadd_imm v0, 7 ; v1 and v0 interfere here: - trapnz v0 + v2 = iadd_imm v0, 8 ; check: $(cp1=$V) = copy $v1 ; not: copy ; check: jump $ebb1($cp1) diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 2f817d1a48..12b190e3da 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -666,30 +666,70 @@ impl<'a> Verifier<'a> { /// If the verifier has been set up with an ISA, make sure that the recorded encoding for the /// instruction (if any) matches how the ISA would encode it. fn verify_encoding(&self, inst: Inst) -> Result { - if let Some(isa) = self.isa { - let encoding = self.func.encodings.get_or_default(inst); - if encoding.is_legal() { - let verify_encoding = - isa.encode(&self.func.dfg, - &self.func.dfg[inst], - self.func.dfg.ctrl_typevar(inst)); - match verify_encoding { - Ok(verify_encoding) => { - if verify_encoding != encoding { - return err!(inst, - "Instruction re-encoding {} doesn't match {}", - isa.encoding_info().display(verify_encoding), - isa.encoding_info().display(encoding)); - } - } - Err(e) => { + // When the encodings table is empty, we don't require any instructions to be encoded. + // + // Once some instructions are encoded, we require all side-effecting instructions to have a + // legal encoding. + if self.func.encodings.is_empty() { + return Ok(()); + } + + let isa = match self.isa { + Some(isa) => isa, + None => return Ok(()), + }; + + let encoding = self.func.encodings.get_or_default(inst); + if encoding.is_legal() { + let verify_encoding = + isa.encode(&self.func.dfg, + &self.func.dfg[inst], + self.func.dfg.ctrl_typevar(inst)); + match verify_encoding { + Ok(verify_encoding) => { + if verify_encoding != encoding { return err!(inst, - "Instruction failed to re-encode {}: {:?}", - isa.encoding_info().display(encoding), - e) + "Instruction re-encoding {} doesn't match {}", + isa.encoding_info().display(verify_encoding), + isa.encoding_info().display(encoding)); } } + Err(e) => { + return err!(inst, + "Instruction failed to re-encode {}: {:?}", + isa.encoding_info().display(encoding), + e) + } } + return Ok(()); + } + + // Instruction is not encoded, so it is a ghost instruction. + // Instructions with side effects are not allowed to be ghost instructions. + let opcode = self.func.dfg[inst].opcode(); + + if opcode.is_branch() { + return err!(inst, "Branch must have an encoding"); + } + + if opcode.is_call() { + return err!(inst, "Call must have an encoding"); + } + + if opcode.is_return() { + return err!(inst, "Return must have an encoding"); + } + + if opcode.can_store() { + return err!(inst, "Store must have an encoding"); + } + + if opcode.can_trap() { + return err!(inst, "Trapping instruction must have an encoding"); + } + + if opcode.other_side_effects() { + return err!(inst, "Instruction with side effects must have an encoding"); } Ok(()) From 71af555e6f6fa094be42e51676492dd04d0c3053 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 09:59:00 -0700 Subject: [PATCH 860/968] Include ISA-specific information in verifier errors. When the test driver reports a verifier error, make sure to include the TargetIsa when printing the failing function. --- src/filetest/legalizer.rs | 2 +- src/filetest/licm.rs | 2 +- src/filetest/regalloc.rs | 4 ++-- src/filetest/runone.rs | 2 +- src/filetest/simple_gvn.rs | 2 +- src/utils.rs | 12 ++++++++---- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/filetest/legalizer.rs b/src/filetest/legalizer.rs index 806730b4ed..0c5ea04b77 100644 --- a/src/filetest/legalizer.rs +++ b/src/filetest/legalizer.rs @@ -43,7 +43,7 @@ impl SubTest for TestLegalizer { comp_ctx.flowgraph(); comp_ctx .legalize(isa) - .map_err(|e| pretty_error(&comp_ctx.func, e))?; + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; let mut text = String::new(); write!(&mut text, "{}", &comp_ctx.func.display(Some(isa))) diff --git a/src/filetest/licm.rs b/src/filetest/licm.rs index dcde7dd7be..548818e758 100644 --- a/src/filetest/licm.rs +++ b/src/filetest/licm.rs @@ -41,7 +41,7 @@ impl SubTest for TestLICM { comp_ctx.flowgraph(); comp_ctx .licm() - .map_err(|e| pretty_error(&comp_ctx.func, e))?; + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; let mut text = String::new(); write!(&mut text, "{}", &comp_ctx.func) diff --git a/src/filetest/regalloc.rs b/src/filetest/regalloc.rs index 1699d384a8..e90012973b 100644 --- a/src/filetest/regalloc.rs +++ b/src/filetest/regalloc.rs @@ -48,10 +48,10 @@ impl SubTest for TestRegalloc { // TODO: Should we have an option to skip legalization? comp_ctx .legalize(isa) - .map_err(|e| pretty_error(&comp_ctx.func, e))?; + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; comp_ctx .regalloc(isa) - .map_err(|e| pretty_error(&comp_ctx.func, e))?; + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; let mut text = String::new(); write!(&mut text, "{}", &comp_ctx.func.display(Some(isa))) diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs index b43d926f0e..3b23463fef 100644 --- a/src/filetest/runone.rs +++ b/src/filetest/runone.rs @@ -118,7 +118,7 @@ fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>), // Should we run the verifier before this test? if !context.verified && test.needs_verifier() { verify_function(&func, isa) - .map_err(|e| pretty_verifier_error(&func, e))?; + .map_err(|e| pretty_verifier_error(&func, isa, e))?; context.verified = true; } diff --git a/src/filetest/simple_gvn.rs b/src/filetest/simple_gvn.rs index f220cbdde7..a522c17bad 100644 --- a/src/filetest/simple_gvn.rs +++ b/src/filetest/simple_gvn.rs @@ -41,7 +41,7 @@ impl SubTest for TestSimpleGVN { comp_ctx.flowgraph(); comp_ctx .simple_gvn() - .map_err(|e| pretty_error(&comp_ctx.func, e))?; + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; let mut text = String::new(); write!(&mut text, "{}", &comp_ctx.func) diff --git a/src/utils.rs b/src/utils.rs index b637f7ca49..f620f5f316 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,7 @@ use cretonne::ir::entities::AnyEntity; use cretonne::{ir, verifier}; use cretonne::result::CtonError; +use cretonne::isa::TargetIsa; use std::fmt::Write; use std::fs::File; use std::io::{Result, Read}; @@ -34,7 +35,10 @@ pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> } /// Pretty-print a verifier error. -pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> String { +pub fn pretty_verifier_error(func: &ir::Function, + isa: Option<&TargetIsa>, + err: verifier::Error) + -> String { let mut msg = err.to_string(); match err.location { AnyEntity::Inst(inst) => { @@ -42,14 +46,14 @@ pub fn pretty_verifier_error(func: &ir::Function, err: verifier::Error) -> Strin } _ => msg.push('\n'), } - write!(msg, "{}", func).unwrap(); + write!(msg, "{}", func.display(isa)).unwrap(); msg } /// Pretty-print a Cretonne error. -pub fn pretty_error(func: &ir::Function, err: CtonError) -> String { +pub fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String { if let CtonError::Verifier(e) = err { - pretty_verifier_error(func, e) + pretty_verifier_error(func, isa, e) } else { err.to_string() } From b6d4b884ad058f77f40f1783b8e9f0428aac2749 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 10:12:20 -0700 Subject: [PATCH 861/968] Add an ISA argument to dfg.display_inst(). Include ISA-specific annotations in tracing and error messages. --- lib/cretonne/src/binemit/mod.rs | 2 +- lib/cretonne/src/binemit/relaxation.rs | 2 +- lib/cretonne/src/ir/dfg.rs | 31 ++++++++++++++----------- lib/cretonne/src/legalizer/boundary.rs | 6 ++--- lib/cretonne/src/legalizer/split.rs | 2 +- lib/cretonne/src/regalloc/coalescing.rs | 8 +++---- lib/cretonne/src/regalloc/coloring.rs | 6 +++-- lib/cretonne/src/regalloc/spilling.rs | 8 ++++--- src/filetest/binemit.rs | 8 +++---- src/utils.rs | 2 +- 10 files changed, 42 insertions(+), 33 deletions(-) diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index 33cce8d2b0..eaa721d510 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -53,5 +53,5 @@ pub trait CodeSink { pub fn bad_encoding(func: &Function, inst: Inst) -> ! { panic!("Bad encoding {} for {}", func.encodings[inst], - func.dfg.display_inst(inst)); + func.dfg.display_inst(inst, None)); } diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index 53b7fd0112..d80d17d7bb 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -135,7 +135,7 @@ fn relax_branch(dfg: &mut DataFlowGraph, let inst = pos.current_inst().unwrap(); dbg!("Relaxing [{}] {} for {:#x}-{:#x} range", encinfo.display(encodings[inst]), - dfg.display_inst(inst), + dfg.display_inst(inst, None), offset, dest_offset); unimplemented!(); diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 85e971a9fc..ad67f7722b 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,6 +1,7 @@ //! Data flow graph tracking Instructions, Values, and EBBs. use entity_map::{EntityMap, PrimaryEntityData}; +use isa::TargetIsa; use ir::builder::{InsertBuilder, ReplaceBuilder}; use ir::extfunc::ExtFuncData; use ir::instructions::{Opcode, InstructionData, CallInfo}; @@ -162,7 +163,7 @@ impl DataFlowGraph { self.results[inst].get(num as usize, &self.value_lists), "Dangling result value {}: {}", v, - self.display_inst(inst)); + self.display_inst(inst, None)); ValueDef::Res(inst, num as usize) } ValueData::Arg { ebb, num, .. } => { @@ -376,8 +377,11 @@ impl DataFlowGraph { } /// Returns an object that displays `inst`. - pub fn display_inst(&self, inst: Inst) -> DisplayInst { - DisplayInst(self, inst) + pub fn display_inst<'a, I: Into>>(&'a self, + inst: Inst, + isa: I) + -> DisplayInst<'a> { + DisplayInst(self, isa.into(), inst) } /// Get all value arguments on `inst` as a slice. @@ -552,7 +556,7 @@ impl DataFlowGraph { old_value, "{} wasn't detached from {}", old_value, - self.display_inst(inst)); + self.display_inst(inst, None)); new_value } @@ -830,14 +834,15 @@ impl EbbData { } /// Object that can display an instruction. -pub struct DisplayInst<'a>(&'a DataFlowGraph, Inst); +pub struct DisplayInst<'a>(&'a DataFlowGraph, Option<&'a TargetIsa>, Inst); impl<'a> fmt::Display for DisplayInst<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let dfg = self.0; - let inst = &dfg[self.1]; + let isa = self.1; + let inst = self.2; - if let Some((first, rest)) = dfg.inst_results(self.1).split_first() { + if let Some((first, rest)) = dfg.inst_results(inst).split_first() { write!(f, "{}", first)?; for v in rest { write!(f, ", {}", v)?; @@ -846,13 +851,13 @@ impl<'a> fmt::Display for DisplayInst<'a> { } - let typevar = dfg.ctrl_typevar(self.1); + let typevar = dfg.ctrl_typevar(inst); if typevar.is_void() { - write!(f, "{}", inst.opcode())?; + write!(f, "{}", dfg[inst].opcode())?; } else { - write!(f, "{}.{}", inst.opcode(), typevar)?; + write!(f, "{}.{}", dfg[inst].opcode(), typevar)?; } - write_operands(f, dfg, None, self.1) + write_operands(f, dfg, isa, inst) } } @@ -870,7 +875,7 @@ mod tests { let inst = dfg.make_inst(idata); dfg.make_inst_results(inst, types::I32); assert_eq!(inst.to_string(), "inst0"); - assert_eq!(dfg.display_inst(inst).to_string(), "v0 = iconst.i32"); + assert_eq!(dfg.display_inst(inst, None).to_string(), "v0 = iconst.i32"); // Immutable reference resolution. { @@ -902,7 +907,7 @@ mod tests { let idata = InstructionData::Nullary { opcode: Opcode::Trap }; let inst = dfg.make_inst(idata); - assert_eq!(dfg.display_inst(inst).to_string(), "trap"); + assert_eq!(dfg.display_inst(inst, None).to_string(), "trap"); // Result slice should be empty. assert_eq!(dfg.inst_results(inst), &[]); diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index b65eea0984..3f22694eb8 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -474,7 +474,7 @@ pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mu debug_assert!(check_call_signature(dfg, inst).is_ok(), "Signature still wrong: {}, {}{}", - dfg.display_inst(inst), + dfg.display_inst(inst, None), sig_ref, dfg.signatures[sig_ref]); @@ -523,7 +523,7 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph, if special_args > 0 { dbg!("Adding {} special-purpose arguments to {}", special_args, - dfg.display_inst(inst)); + dfg.display_inst(inst, None)); let mut vlist = dfg[inst].take_value_list().unwrap(); for arg in &sig.return_types[abi_args..] { match arg.purpose { @@ -550,7 +550,7 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph, debug_assert!(check_return_signature(dfg, inst, sig), "Signature still wrong: {} / signature {}", - dfg.display_inst(inst), + dfg.display_inst(inst, None), sig); // Yes, we changed stuff. diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index cbca25c9c2..3e3a36f619 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -123,7 +123,7 @@ fn split_any(dfg: &mut DataFlowGraph, let branch_opc = dfg[inst].opcode(); assert!(branch_opc.is_branch(), "Predecessor not a branch: {}", - dfg.display_inst(inst)); + dfg.display_inst(inst, None)); let fixed_args = branch_opc.constraints().fixed_value_arguments(); let mut args = dfg[inst] .take_value_list() diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 634f0c0a32..08ecf12589 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -399,7 +399,7 @@ impl<'a> Context<'a> { dbg!("Checking {}: {}: {}", pred_val, pred_ebb, - self.func.dfg.display_inst(pred_inst)); + self.func.dfg.display_inst(pred_inst, self.isa)); // Never coalesce incoming function arguments on the stack. These arguments are // pre-spilled, and the rest of the virtual register would be forced to spill to the @@ -474,9 +474,9 @@ impl<'a> Context<'a> { let ty = self.func.dfg.value_type(copy); dbg!("Inserted {}, before {}: {}", - self.func.dfg.display_inst(inst), + self.func.dfg.display_inst(inst, self.isa), pred_ebb, - self.func.dfg.display_inst(pred_inst)); + self.func.dfg.display_inst(pred_inst, self.isa)); // Give it an encoding. let encoding = self.isa @@ -519,7 +519,7 @@ impl<'a> Context<'a> { self.liveness.move_def_locally(succ_val, inst); dbg!("Inserted {}, following {}({}: {})", - self.func.dfg.display_inst(inst), + self.func.dfg.display_inst(inst, self.isa), ebb, new_val, ty); diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index f61e88e9b7..85911efd0b 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -73,6 +73,7 @@ pub struct Coloring { /// Immutable context information and mutable references that don't need to be borrowed across /// method calls should go in this struct. struct Context<'a> { + isa: &'a TargetIsa, // Cached ISA information. // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. reginfo: RegInfo, @@ -111,6 +112,7 @@ impl Coloring { tracker: &mut LiveValueTracker) { dbg!("Coloring for:\n{}", func.display(isa)); let mut ctx = Context { + isa, reginfo: isa.register_info(), encinfo: isa.encoding_info(), domtree, @@ -279,7 +281,7 @@ impl<'a> Context<'a> { locations: &mut ValueLocations, func_signature: &Signature) { dbg!("Coloring {}\n {}", - dfg.display_inst(inst), + dfg.display_inst(inst, self.isa), regs.display(&self.reginfo)); // EBB whose arguments should be colored to match the current branch instruction's @@ -308,7 +310,7 @@ impl<'a> Context<'a> { assert_eq!(dfg.inst_variable_args(inst).len(), 0, "Can't handle EBB arguments: {}", - dfg.display_inst(inst)); + dfg.display_inst(inst, self.isa)); self.undivert_regs(|lr| !lr.is_local()); } } diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 465efa7856..5733f6e80f 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -246,7 +246,9 @@ impl<'a> Context<'a> { pos: &mut Cursor, dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker) { - dbg!("Inst {}, {}", dfg.display_inst(inst), self.pressure); + dbg!("Inst {}, {}", + dfg.display_inst(inst, self.isa), + self.pressure); // We may need to resolve register constraints if there are any noteworthy uses. assert!(self.reg_uses.is_empty()); @@ -292,7 +294,7 @@ impl<'a> Context<'a> { None => { panic!("Ran out of {} registers for {}", op.regclass, - dfg.display_inst(inst)) + dfg.display_inst(inst, self.isa)) } } } @@ -429,7 +431,7 @@ impl<'a> Context<'a> { None => { panic!("Ran out of {} registers when inserting copy before {}", rc, - dfg.display_inst(inst)) + dfg.display_inst(inst, self.isa)) } } } diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 2f695bff1e..15f5b0a982 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -127,7 +127,7 @@ impl SubTest for TestBinEmit { AnyEntity::Inst(inst) => { if let Some(prev) = bins.insert(inst, want) { return Err(format!("multiple 'bin:' directives on {}: '{}' and '{}'", - func.dfg.display_inst(inst), + func.dfg.display_inst(inst, isa), prev, want)); } @@ -166,7 +166,7 @@ impl SubTest for TestBinEmit { encinfo.bytes(enc), "Inconsistent size for [{}] {}", encinfo.display(enc), - func.dfg.display_inst(inst)); + func.dfg.display_inst(inst, isa)); } // Check against bin: directives. @@ -174,13 +174,13 @@ impl SubTest for TestBinEmit { if !enc.is_legal() { return Err(format!("{} can't be encoded: {}", inst, - func.dfg.display_inst(inst))); + func.dfg.display_inst(inst, isa))); } let have = sink.text.trim(); if have != want { return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}", inst, - func.dfg.display_inst(inst), + func.dfg.display_inst(inst, isa), want, have)); } diff --git a/src/utils.rs b/src/utils.rs index f620f5f316..3bc8e7b1df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -42,7 +42,7 @@ pub fn pretty_verifier_error(func: &ir::Function, let mut msg = err.to_string(); match err.location { AnyEntity::Inst(inst) => { - write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst)).unwrap() + write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap() } _ => msg.push('\n'), } From edffd848bf4f7c2c53bcd6b6da7c9c572e098225 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 10:22:43 -0700 Subject: [PATCH 862/968] Add Intel regmove encodings. Same as a register copy, but different arguments. --- lib/cretonne/meta/isa/intel/encodings.py | 6 +++++- lib/cretonne/meta/isa/intel/recipes.py | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 74dd5b5999..ab32e77303 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -23,11 +23,15 @@ for inst, opc in [ I64.enc(inst.i32, *r.rr(opc)) I32.enc(base.copy.i32, *r.ur(0x89)) - I64.enc(base.copy.i64, *r.ur.rex(0x89, w=1)) I64.enc(base.copy.i32, *r.ur.rex(0x89)) I64.enc(base.copy.i32, *r.ur(0x89)) +I32.enc(base.regmove.i32, *r.rmov(0x89)) +I64.enc(base.regmove.i64, *r.rmov.rex(0x89, w=1)) +I64.enc(base.regmove.i32, *r.rmov.rex(0x89)) +I64.enc(base.regmove.i32, *r.rmov(0x89)) + # Immediate instructions with sign-extended 8-bit and 32-bit immediate. for inst, rrr in [ (base.iadd_imm, 0), diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 2d1fc62d80..c6c7b3f2af 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -6,6 +6,7 @@ from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry from base.formats import Call, IndirectCall, Store, Load +from base.formats import RegMove from .registers import GPR, ABCD try: @@ -205,6 +206,14 @@ ur = TailRecipe( modrm_rr(out_reg0, in_reg0, sink); ''') +# XX /r, for regmove instructions. +rmov = TailRecipe( + 'ur', RegMove, size=1, ins=GPR, outs=(), + emit=''' + PUT_OP(bits, rex2(dst, src), sink); + modrm_rr(dst, src, sink); + ''') + # XX /n with one arg in %rcx, for shifts. rc = TailRecipe( 'rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0, From 2ee37784ff19ea78c30ebe6904fa843c3da67604 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 10:43:13 -0700 Subject: [PATCH 863/968] Add RISC-V regmove encodings. --- lib/cretonne/meta/isa/riscv/encodings.py | 6 +++++- lib/cretonne/meta/isa/riscv/recipes.py | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index d8afbab2cf..9ec6b34fc0 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -8,7 +8,7 @@ from .defs import RV32, RV64 from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL from .recipes import LOAD, STORE from .recipes import R, Rshamt, Ricmp, I, Iz, Iicmp, Iret, Icall, Icopy -from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi +from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi, Irmov from .settings import use_m from cdsl.ast import Var @@ -135,3 +135,7 @@ RV64.enc(base.fill.i64, GPfi, LOAD(0b011)) RV32.enc(base.copy.i32, Icopy, OPIMM(0b000)) RV64.enc(base.copy.i64, Icopy, OPIMM(0b000)) RV64.enc(base.copy.i32, Icopy, OPIMM32(0b000)) + +RV32.enc(base.regmove.i32, Irmov, OPIMM(0b000)) +RV64.enc(base.regmove.i64, Irmov, OPIMM(0b000)) +RV64.enc(base.regmove.i32, Irmov, OPIMM32(0b000)) diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index 510e13c860..afffb2c0aa 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -14,7 +14,7 @@ from cdsl.predicates import IsSignedInt from cdsl.registers import Stack from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm from base.formats import Unary, UnaryImm, BranchIcmp, Branch, Jump -from base.formats import Call, IndirectCall +from base.formats import Call, IndirectCall, RegMove from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit @@ -156,6 +156,11 @@ Icopy = EncRecipe( 'Icopy', Unary, size=4, ins=GPR, outs=GPR, emit='put_i(bits, in_reg0, 0, out_reg0, sink);') +# Same for a GPR regmove. +Irmov = EncRecipe( + 'Irmov', RegMove, size=4, ins=GPR, outs=(), + emit='put_i(bits, src, 0, dst, sink);') + # U-type instructions have a 20-bit immediate that targets bits 12-31. U = EncRecipe( 'U', UnaryImm, size=4, ins=(), outs=GPR, From abc174348687cd05fae2a15269a8362fe850f973 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 10:37:50 -0700 Subject: [PATCH 864/968] Attach encodings to regmove instructions generated during coloring. All emitted regmove instructions must be materialized as real move instructions. --- lib/cretonne/src/regalloc/coloring.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 85911efd0b..21a4453bb2 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -43,7 +43,8 @@ //! The exception is the entry block whose arguments are colored from the ABI requirements. use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Layout, ValueLocations}; +use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Layout}; +use ir::{InstEncodings, ValueLocations}; use ir::{InstBuilder, Signature, ArgumentType, ArgumentLoc}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind}; @@ -156,6 +157,7 @@ impl<'a> Context<'a> { tracker, &mut regs, &mut func.locations, + &mut func.encodings, &func.signature); } else { let (_throughs, kills) = tracker.process_ghost(inst); @@ -279,6 +281,7 @@ impl<'a> Context<'a> { tracker: &mut LiveValueTracker, regs: &mut AllocatableSet, locations: &mut ValueLocations, + encodings: &mut InstEncodings, func_signature: &Signature) { dbg!("Coloring {}\n {}", dfg.display_inst(inst, self.isa), @@ -354,7 +357,7 @@ impl<'a> Context<'a> { // The solution and/or fixed input constraints may require us to shuffle the set of live // registers around. - self.shuffle_inputs(pos, dfg, regs); + self.shuffle_inputs(pos, dfg, regs, encodings); // If this is the first time we branch to `dest`, color its arguments to match the current // register state. @@ -695,12 +698,18 @@ impl<'a> Context<'a> { fn shuffle_inputs(&mut self, pos: &mut Cursor, dfg: &mut DataFlowGraph, - regs: &mut AllocatableSet) { + regs: &mut AllocatableSet, + encodings: &mut InstEncodings) { self.solver.schedule_moves(regs); for m in self.solver.moves() { + let ty = dfg.value_type(m.value); self.divert.regmove(m.value, m.from, m.to); - dfg.ins(pos).regmove(m.value, m.from, m.to); + let inst = dfg.ins(pos).regmove(m.value, m.from, m.to); + match self.isa.encode(dfg, &dfg[inst], ty) { + Ok(encoding) => *encodings.ensure(inst) = encoding, + _ => panic!("Can't encode {} {}", m.rc, dfg.display_inst(inst, self.isa)), + } } } From 6e0834eea93be03a758627c965b82a90af8f1559 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 09:49:02 -0700 Subject: [PATCH 865/968] Tag the regmove instruction with other_side_effects. This instruction moves a value between registers. This counts as a side effect that is not tracked by the SSA data flow graph. --- lib/cretonne/meta/base/instructions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 109bc73f9e..e1b38086e4 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -484,7 +484,8 @@ regmove = Instruction( before the value leaves the EBB. At the entry to a new EBB, all live values must be in their originally assigned registers. """, - ins=(x, src, dst)) + ins=(x, src, dst), + other_side_effects=True) # # Vector operations From 07a96e609ef5c9b428134c3a2c396d0f9b0c22f9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 11:17:59 -0700 Subject: [PATCH 866/968] Fix Vim syntax highlighting of numbers. Cretonne allows '_' in number constants. --- misc/vim/syntax/cton.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/vim/syntax/cton.vim b/misc/vim/syntax/cton.vim index 003029e1bb..d345742154 100644 --- a/misc/vim/syntax/cton.vim +++ b/misc/vim/syntax/cton.vim @@ -22,8 +22,8 @@ syn match ctonEntity /\<\(v\|ss\|jt\|fn\|sig\)\d\+\>/ syn match ctonLabel /\/ syn match ctonName /%\w\+\>/ -syn match ctonNumber /-\?\<\d\+\>/ -syn match ctonNumber /-\?\<0x\x\+\(\.\x*\)\(p[+-]\?\d\+\)\?\>/ +syn match ctonNumber /-\?\<[0-9_]\+\>/ +syn match ctonNumber /-\?\<0x[0-9a-fA-F_]\+\(\.[0-9a-fA-F_]*\)\?\(p[+-]\?\d\+\)\?\>/ syn match ctonHexSeq /#\x\+\>/ syn region ctonCommentLine start=";" end="$" contains=ctonFilecheck From 6cc729a69b06a887d8c37c6ea6f60f87baab4b5a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Jul 2017 16:26:16 -0700 Subject: [PATCH 867/968] Add a Context::compile() function which runs all compiler passes. This is the main entry point to the code generator. It returns the computed size of the functions code. Also add a 'test compile' command which runs the whole code generation pipeline. --- lib/cretonne/src/binemit/relaxation.rs | 9 ++- lib/cretonne/src/context.rs | 27 ++++++- src/filetest/binemit.rs | 9 ++- src/filetest/compile.rs | 98 ++++++++++++++++++++++++++ src/filetest/mod.rs | 8 ++- 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 src/filetest/compile.rs diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index d80d17d7bb..3420134d1b 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -31,11 +31,12 @@ use binemit::CodeOffset; use ir::{Function, DataFlowGraph, Cursor, InstructionData, Opcode, InstEncodings}; use isa::{TargetIsa, EncInfo}; use iterators::IteratorExtras; +use result::CtonError; /// Relax branches and compute the final layout of EBB headers in `func`. /// /// Fill in the `func.offsets` table so the function is ready for binary emission. -pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { +pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> Result { let encinfo = isa.encoding_info(); // Clear all offsets so we can recognize EBBs that haven't been visited yet. @@ -45,13 +46,15 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { // Start by inserting fall through instructions. fallthroughs(func); + let mut offset = 0; + // The relaxation algorithm iterates to convergence. let mut go_again = true; while go_again { go_again = false; + offset = 0; // Visit all instructions in layout order - let mut offset = 0; let mut pos = Cursor::new(&mut func.layout); while let Some(ebb) = pos.next_ebb() { // Record the offset for `ebb` and make sure we iterate until offsets are stable. @@ -90,6 +93,8 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { } } } + + Ok(offset) } /// Convert `jump` instructions to `fallthrough` instructions where possible and verify that any diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 5e23ed3efc..a87e35cd32 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -9,6 +9,7 @@ //! contexts concurrently. Typically, you would have one context per compilation thread and only a //! single ISA instance. +use binemit::{CodeOffset, relax_branches}; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; @@ -16,7 +17,7 @@ use loop_analysis::LoopAnalysis; use isa::TargetIsa; use legalize_function; use regalloc; -use result::CtonResult; +use result::{CtonError, CtonResult}; use verifier; use simple_gvn::do_simple_gvn; use licm::do_licm; @@ -54,6 +55,22 @@ impl Context { } } + /// Compile the function. + /// + /// Run the function through all the passes necessary to generate code for the target ISA + /// represented by `isa`. This does not include the final step of emitting machine code into a + /// code sink. + /// + /// Returns the size of the function's code. + pub fn compile(&mut self, isa: &TargetIsa) -> Result { + self.flowgraph(); + self.verify_if(isa)?; + + self.legalize(isa)?; + self.regalloc(isa)?; + self.relax_branches(isa) + } + /// Run the verifier on the function. /// /// Also check that the dominator tree and control flow graph are consistent with the function. @@ -107,4 +124,12 @@ impl Context { self.regalloc .run(isa, &mut self.func, &self.cfg, &self.domtree) } + + /// Run the branch relaxation pass and return the final code size. + pub fn relax_branches(&mut self, isa: &TargetIsa) -> Result { + let code_size = relax_branches(&mut self.func, isa)?; + self.verify_if(isa)?; + + Ok(code_size) + } } diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 15f5b0a982..d359cea794 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -12,7 +12,7 @@ use cretonne::ir::entities::AnyEntity; use cretonne::isa::TargetIsa; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result}; -use utils::match_directive; +use utils::{match_directive, pretty_error}; struct TestBinEmit; @@ -117,7 +117,8 @@ impl SubTest for TestBinEmit { } // Relax branches and compute EBB offsets based on the encodings. - binemit::relax_branches(&mut func, isa); + let code_size = binemit::relax_branches(&mut func, isa) + .map_err(|e| pretty_error(&func, context.isa, e))?; // Collect all of the 'bin:' directives on instructions. let mut bins = HashMap::new(); @@ -188,6 +189,10 @@ impl SubTest for TestBinEmit { } } + if sink.offset != code_size { + return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); + } + Ok(()) } } diff --git a/src/filetest/compile.rs b/src/filetest/compile.rs new file mode 100644 index 0000000000..15db119efd --- /dev/null +++ b/src/filetest/compile.rs @@ -0,0 +1,98 @@ +//! Test command for testing the code generator pipeline +//! +//! The `compile` test command runs each function through the full code generator pipeline + +use cretonne::binemit; +use cretonne::ir; +use cretonne; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result}; +use std::borrow::Cow; +use utils::pretty_error; + +struct TestCompile; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "compile"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestCompile)) + } +} + +impl SubTest for TestCompile { + fn name(&self) -> Cow { + Cow::from("compile") + } + + fn is_mutating(&self) -> bool { + true + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let isa = context.isa.expect("compile needs an ISA"); + + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + let code_size = comp_ctx + .compile(isa) + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; + + dbg!("Generated {} bytes of code:\n{}", + code_size, + comp_ctx.func.display(isa)); + + // Finally verify that the returned code size matches the emitted bytes. + let mut sink = SizeSink { offset: 0 }; + for ebb in comp_ctx.func.layout.ebbs() { + assert_eq!(sink.offset, comp_ctx.func.offsets[ebb]); + for inst in comp_ctx.func.layout.ebb_insts(ebb) { + isa.emit_inst(&comp_ctx.func, inst, &mut sink); + } + } + + if sink.offset != code_size { + return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); + } + + Ok(()) + } +} + +// Code sink that simply counts bytes. +struct SizeSink { + offset: binemit::CodeOffset, +} + +impl binemit::CodeSink for SizeSink { + fn offset(&self) -> binemit::CodeOffset { + self.offset + } + + fn put1(&mut self, _: u8) { + self.offset += 1; + } + + fn put2(&mut self, _: u16) { + self.offset += 2; + } + + fn put4(&mut self, _: u32) { + self.offset += 4; + } + + fn put8(&mut self, _: u64) { + self.offset += 8; + } + + fn reloc_ebb(&mut self, _reloc: binemit::Reloc, _ebb: ir::Ebb) {} + fn reloc_func(&mut self, _reloc: binemit::Reloc, _fref: ir::FuncRef) {} + fn reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable) {} +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 9c03cc6d4a..2d2a9c6cac 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -14,6 +14,7 @@ use filetest::runner::TestRunner; pub mod subtest; mod binemit; +mod compile; mod concurrent; mod domtree; mod legalizer; @@ -57,15 +58,16 @@ pub fn run(verbose: bool, files: Vec) -> CommandResult { /// a `.cton` test file. fn new_subtest(parsed: &TestCommand) -> subtest::Result> { match parsed.command { + "binemit" => binemit::subtest(parsed), "cat" => cat::subtest(parsed), - "print-cfg" => print_cfg::subtest(parsed), + "compile" => compile::subtest(parsed), "domtree" => domtree::subtest(parsed), - "verifier" => verifier::subtest(parsed), "legalizer" => legalizer::subtest(parsed), "licm" => licm::subtest(parsed), + "print-cfg" => print_cfg::subtest(parsed), "regalloc" => regalloc::subtest(parsed), - "binemit" => binemit::subtest(parsed), "simple-gvn" => simple_gvn::subtest(parsed), + "verifier" => verifier::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } From 3d738d01bb02bca73da5ef25d815e4c403a469c8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Jul 2017 17:42:21 -0700 Subject: [PATCH 868/968] Add a WebAssembly filetests directory. Start adding little 'test compile' test cases which check that the full compilation pipeline works for each WebAssembly instruction. --- filetests/wasm/i32-arith.cton | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 filetests/wasm/i32-arith.cton diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton new file mode 100644 index 0000000000..1096d70ddb --- /dev/null +++ b/filetests/wasm/i32-arith.cton @@ -0,0 +1,62 @@ +; Test basic code generation for i32 arithmetic WebAssembly instructions. +test compile + +set is_64bit +isa intel + +; Constants. + +function %i32_const() -> i32 { +ebb0: + v0 = iconst.i32 0x8765_4321 + return v0 +} + +; Unary operations. + +; function %i32_clz(i32) -> i32 +; function %i32_ctz(i32) -> i32 +; function %i32_popcnt(i32) -> i32 + +; Binary operations. + +function %i32_add(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = iadd v0, v1 + return v2 +} + +function %i32_sub(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = isub v0, v1 + return v2 +} + +; function %i32_mul(i32, i32) -> i32 +; function %i32_div(i32, i32) -> i32 +; function %i32_rem_s(i32, i32) -> i32 +; function %i32_rem_u(i32, i32) -> i32 + +function %i32_and(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = band v0, v1 + return v2 +} + +function %i32_or(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = bor v0, v1 + return v2 +} + +function %i32_xor(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = bxor v0, v1 + return v2 +} + +; function %i32_shl(i32, i32) -> i32 +; function %i32_shr_s(i32, i32) -> i32 +; function %i32_shr_u(i32, i32) -> i32 +; function %i32_rotl(i32, i32) -> i32 +; function %i32_rotr(i32, i32) -> i32 From 5615e4a9e7fdbab545cb3ac9903d7bb86c0e10c9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 12:53:41 -0700 Subject: [PATCH 869/968] Add Intel encodings for shift and rotate instructions. --- filetests/isa/intel/binary32.cton | 20 +++++-- filetests/isa/intel/binary64.cton | 70 ++++++++++++++++++++---- filetests/wasm/i32-arith.cton | 39 +++++++++++-- lib/cretonne/meta/isa/intel/encodings.py | 15 +++-- 4 files changed, 117 insertions(+), 27 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 14f050c2e7..4cc14b6905 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -55,11 +55,14 @@ ebb0: [-,%rsi] v24 = sshr v2, v1 ; bin: d3 fe ; asm: sarl %cl, %ecx [-,%rcx] v25 = sshr v1, v1 ; bin: d3 f9 - - ; asm: movl %esi, %ecx - [-,%rcx] v26 = copy v2 ; bin: 89 f1 - ; asm: movl %ecx, %esi - [-,%rsi] v27 = copy v1 ; bin: 89 ce + ; asm: roll %cl, %esi + [-,%rsi] v26 = rotl v2, v1 ; bin: d3 c6 + ; asm: roll %cl, %ecx + [-,%rcx] v27 = rotl v1, v1 ; bin: d3 c1 + ; asm: rorl %cl, %esi + [-,%rsi] v28 = rotr v2, v1 ; bin: d3 ce + ; asm: rorl %cl, %ecx + [-,%rcx] v29 = rotr v1, v1 ; bin: d3 c9 ; Integer Register - Immediate 8-bit operations. ; The 8-bit immediate is sign-extended. @@ -102,6 +105,13 @@ ebb0: ; asm: xorl $1000000, %esi [-,%rsi] v47 = bxor_imm v2, 1000000 ; bin: 81 f6 000f4240 + ; Register copies. + + ; asm: movl %esi, %ecx + [-,%rcx] v80 = copy v2 ; bin: 89 f1 + ; asm: movl %ecx, %esi + [-,%rsi] v81 = copy v1 ; bin: 89 ce + ; Load/Store instructions. ; Register indirect addressing with no displacement. diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 48e328b455..e9ee5d3941 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -65,13 +65,27 @@ ebb0: ; asm: xorq %rcx, %r10 [-,%r10] v52 = bxor v3, v1 ; bin: 49 31 ca - ; asm: movq %rsi, %rcx - [-,%rcx] v60 = copy v2 ; bin: 48 89 f1 - ; asm: movq %r10, %rsi - [-,%rsi] v61 = copy v3 ; bin: 4c 89 d6 - ; asm: movq %rcx, %r10 - [-,%r10] v62 = copy v1 ; bin: 49 89 ca + ; asm: shlq %cl, %rsi + [-,%rsi] v60 = ishl v2, v1 ; bin: 48 d3 e6 + ; asm: shlq %cl, %r10 + [-,%r10] v61 = ishl v3, v1 ; bin: 49 d3 e2 + ; asm: sarq %cl, %rsi + [-,%rsi] v62 = sshr v2, v1 ; bin: 48 d3 fe + ; asm: sarq %cl, %r10 + [-,%r10] v63 = sshr v3, v1 ; bin: 49 d3 fa + ; asm: shrq %cl, %rsi + [-,%rsi] v64 = ushr v2, v1 ; bin: 48 d3 ee + ; asm: shrq %cl, %r10 + [-,%r10] v65 = ushr v3, v1 ; bin: 49 d3 ea + ; asm: rolq %cl, %rsi + [-,%rsi] v66 = rotl v2, v1 ; bin: 48 d3 c6 + ; asm: rolq %cl, %r10 + [-,%r10] v67 = rotl v3, v1 ; bin: 49 d3 c2 + ; asm: rorq %cl, %rsi + [-,%rsi] v68 = rotr v2, v1 ; bin: 48 d3 ce + ; asm: rorq %cl, %r10 + [-,%r10] v69 = rotr v3, v1 ; bin: 49 d3 ca ; Integer Register-Immediate Operations. ; These 64-bit ops all use a 32-bit immediate that is sign-extended to 64 bits. @@ -122,6 +136,15 @@ ebb0: ; asm: xorq $-100, %r14 [-,%r14] v104 = bxor_imm v5, -100 ; bin: 49 83 f6 9c + ; Register copies. + + ; asm: movq %rsi, %rcx + [-,%rcx] v110 = copy v2 ; bin: 48 89 f1 + ; asm: movq %r10, %rsi + [-,%rsi] v111 = copy v3 ; bin: 4c 89 d6 + ; asm: movq %rcx, %r10 + [-,%r10] v112 = copy v1 ; bin: 49 89 ca + return ; bin: c3 } @@ -187,13 +210,27 @@ ebb0: ; asm: xorl %ecx, %r10d [-,%r10] v52 = bxor v3, v1 ; bin: 41 31 ca - ; asm: movl %esi, %ecx - [-,%rcx] v60 = copy v2 ; bin: 40 89 f1 - ; asm: movl %r10d, %esi - [-,%rsi] v61 = copy v3 ; bin: 44 89 d6 - ; asm: movl %ecx, %r10d - [-,%r10] v62 = copy v1 ; bin: 41 89 ca + ; asm: shll %cl, %esi + [-,%rsi] v60 = ishl v2, v1 ; bin: 40 d3 e6 + ; asm: shll %cl, %r10d + [-,%r10] v61 = ishl v3, v1 ; bin: 41 d3 e2 + ; asm: sarl %cl, %esi + [-,%rsi] v62 = sshr v2, v1 ; bin: 40 d3 fe + ; asm: sarl %cl, %r10d + [-,%r10] v63 = sshr v3, v1 ; bin: 41 d3 fa + ; asm: shrl %cl, %esi + [-,%rsi] v64 = ushr v2, v1 ; bin: 40 d3 ee + ; asm: shrl %cl, %r10d + [-,%r10] v65 = ushr v3, v1 ; bin: 41 d3 ea + ; asm: roll %cl, %esi + [-,%rsi] v66 = rotl v2, v1 ; bin: 40 d3 c6 + ; asm: roll %cl, %r10d + [-,%r10] v67 = rotl v3, v1 ; bin: 41 d3 c2 + ; asm: rorl %cl, %esi + [-,%rsi] v68 = rotr v2, v1 ; bin: 40 d3 ce + ; asm: rorl %cl, %r10d + [-,%r10] v69 = rotr v3, v1 ; bin: 41 d3 ca ; Integer Register-Immediate Operations. ; These 64-bit ops all use a 32-bit immediate that is sign-extended to 64 bits. @@ -244,5 +281,14 @@ ebb0: ; asm: xorl $-100, %r14d [-,%r14] v104 = bxor_imm v5, -100 ; bin: 41 83 f6 9c + ; Register copies. + + ; asm: movl %esi, %ecx + [-,%rcx] v110 = copy v2 ; bin: 40 89 f1 + ; asm: movl %r10d, %esi + [-,%rsi] v111 = copy v3 ; bin: 44 89 d6 + ; asm: movl %ecx, %r10d + [-,%r10] v112 = copy v1 ; bin: 41 89 ca + return ; bin: c3 } diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton index 1096d70ddb..f2fafffee3 100644 --- a/filetests/wasm/i32-arith.cton +++ b/filetests/wasm/i32-arith.cton @@ -1,7 +1,10 @@ ; Test basic code generation for i32 arithmetic WebAssembly instructions. test compile -set is_64bit +set is_64bit=0 +isa intel + +set is_64bit=1 isa intel ; Constants. @@ -55,8 +58,32 @@ ebb0(v0: i32, v1: i32): return v2 } -; function %i32_shl(i32, i32) -> i32 -; function %i32_shr_s(i32, i32) -> i32 -; function %i32_shr_u(i32, i32) -> i32 -; function %i32_rotl(i32, i32) -> i32 -; function %i32_rotr(i32, i32) -> i32 +function %i32_shl(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = ishl v0, v1 + return v2 +} + +function %i32_shr_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = sshr v0, v1 + return v2 +} + +function %i32_shr_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = ushr v0, v1 + return v2 +} + +function %i32_rotl(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = rotl v0, v1 + return v2 +} + +function %i32_rotr(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = rotr v0, v1 + return v2 +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index ab32e77303..df6631fe4c 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -66,12 +66,19 @@ I64.enc(base.iconst.i64, *r.uid.rex(0xc7, rrr=0, w=1)) # Finally, the 0xb8 opcode takes an 8-byte immediate with a REX.W prefix. I64.enc(base.iconst.i64, *r.puiq.rex(0xb8, w=1)) -# 32-bit shifts and rotates. +# Shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit # and 16-bit shifts would need explicit masking. -I32.enc(base.ishl.i32.i32, *r.rc(0xd3, rrr=4)) -I32.enc(base.ushr.i32.i32, *r.rc(0xd3, rrr=5)) -I32.enc(base.sshr.i32.i32, *r.rc(0xd3, rrr=7)) +for inst, rrr in [ + (base.rotl, 0), + (base.rotr, 1), + (base.ishl, 4), + (base.ushr, 5), + (base.sshr, 7)]: + I32.enc(inst.i32.i32, *r.rc(0xd3, rrr=rrr)) + I64.enc(inst.i64.i64, *r.rc.rex(0xd3, rrr=rrr, w=1)) + I64.enc(inst.i32.i32, *r.rc.rex(0xd3, rrr=rrr)) + I64.enc(inst.i32.i32, *r.rc(0xd3, rrr=rrr)) # Loads and stores. I32.enc(base.store.i32.i32, *r.st(0x89)) From b6f2f0d862c3e076255dd19d49fab81ad523a06b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 14:14:08 -0700 Subject: [PATCH 870/968] Add Intel encodings for popcnt. Change the result type for the bit-counting instructions from a fixed i8 to the iB type variable which is the type of the input. This matches the convention in WebAssembly, and at least Intel's instructions will set a full register's worth of count result, even if it is always < 64. Duplicate the Intel 'ur' encoding recipe into 'umr' and 'urm' variants corresponding to the RM and MR encoding variants. The difference is which register is encoded as 'reg' and which is 'r/m' in the ModR/M byte. A 'mov' register copy uses the MR variant, a unary popcnt uses the RM variant. --- filetests/isa/intel/binary32.cton | 7 ++++++ filetests/isa/intel/binary64.cton | 18 +++++++++++++++ filetests/wasm/i32-arith.cton | 7 +++++- lib/cretonne/meta/base/instructions.py | 4 ++-- lib/cretonne/meta/isa/intel/encodings.py | 14 ++++++++---- lib/cretonne/meta/isa/intel/recipes.py | 15 ++++++++++--- lib/cretonne/src/isa/intel/binemit.rs | 28 ++++++++++++++++++++---- 7 files changed, 79 insertions(+), 14 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 4cc14b6905..77e53ceeec 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -217,6 +217,13 @@ ebb0: ; asm: movsbl -50000(%esi), %edx [-,%rdx] v129 = sload8.i32 v2-50000 ; bin: 0f be 96 ffff3cb0 + ; Bit-counting instructions. + + ; asm: popcntl %esi, %ecx + [-,%rcx] v200 = popcnt v2 ; bin: f3 0f b8 ce + ; asm: popcntl %ecx, %esi + [-,%rsi] v201 = popcnt v1 ; bin: f3 0f b8 f1 + ; asm: call foo call fn0() ; bin: e8 PCRel4(fn0) 00000000 diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index e9ee5d3941..da350d1e45 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -145,6 +145,15 @@ ebb0: ; asm: movq %rcx, %r10 [-,%r10] v112 = copy v1 ; bin: 49 89 ca + ; Bit-counting instructions. + + ; asm: popcntq %rsi, %rcx + [-,%rcx] v200 = popcnt v2 ; bin: f3 48 0f b8 ce + ; asm: popcntq %r10, %rsi + [-,%rsi] v201 = popcnt v3 ; bin: f3 49 0f b8 f2 + ; asm: popcntq %rcx, %r10 + [-,%r10] v202 = popcnt v1 ; bin: f3 4c 0f b8 d1 + return ; bin: c3 } @@ -290,5 +299,14 @@ ebb0: ; asm: movl %ecx, %r10d [-,%r10] v112 = copy v1 ; bin: 41 89 ca + ; Bit-counting instructions. + + ; asm: popcntl %esi, %ecx + [-,%rcx] v200 = popcnt v2 ; bin: f3 40 0f b8 ce + ; asm: popcntl %r10d, %esi + [-,%rsi] v201 = popcnt v3 ; bin: f3 41 0f b8 f2 + ; asm: popcntl %ecx, %r10d + [-,%r10] v202 = popcnt v1 ; bin: f3 44 0f b8 d1 + return ; bin: c3 } diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton index f2fafffee3..fc730cbe23 100644 --- a/filetests/wasm/i32-arith.cton +++ b/filetests/wasm/i32-arith.cton @@ -19,7 +19,12 @@ ebb0: ; function %i32_clz(i32) -> i32 ; function %i32_ctz(i32) -> i32 -; function %i32_popcnt(i32) -> i32 + +function %i32_popcnt(i32) -> i32 { +ebb0(v0: i32): + v1 = popcnt v0 + return v1 +} ; Binary operations. diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index e1b38086e4..2aa9027a45 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -8,7 +8,7 @@ from __future__ import absolute_import from cdsl.operands import Operand, VARIABLE_ARGS from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup -from base.types import i8, f32, f64, b1 +from base.types import f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 from base.immediates import intcc, floatcc, memflags, regunit from base import entities @@ -1050,7 +1050,7 @@ sshr_imm = Instruction( # x = Operand('x', iB) -a = Operand('a', i8) +a = Operand('a', iB) clz = Instruction( 'clz', r""" diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index df6631fe4c..d8dfe53140 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -22,10 +22,10 @@ for inst, opc in [ # default. Otherwise reg-alloc would never use r8 and up. I64.enc(inst.i32, *r.rr(opc)) -I32.enc(base.copy.i32, *r.ur(0x89)) -I64.enc(base.copy.i64, *r.ur.rex(0x89, w=1)) -I64.enc(base.copy.i32, *r.ur.rex(0x89)) -I64.enc(base.copy.i32, *r.ur(0x89)) +I32.enc(base.copy.i32, *r.umr(0x89)) +I64.enc(base.copy.i64, *r.umr.rex(0x89, w=1)) +I64.enc(base.copy.i32, *r.umr.rex(0x89)) +I64.enc(base.copy.i32, *r.umr(0x89)) I32.enc(base.regmove.i32, *r.rmov(0x89)) I64.enc(base.regmove.i64, *r.rmov.rex(0x89, w=1)) @@ -80,6 +80,12 @@ for inst, rrr in [ I64.enc(inst.i32.i32, *r.rc.rex(0xd3, rrr=rrr)) I64.enc(inst.i32.i32, *r.rc(0xd3, rrr=rrr)) +# Population count. +I32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8)) +I64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1)) +I64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8)) +I64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8)) + # Loads and stores. I32.enc(base.store.i32.i32, *r.st(0x89)) I32.enc(base.store.i32.i32, *r.stDisp8(0x89)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index c6c7b3f2af..b7e6aa5575 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -198,14 +198,23 @@ rr = TailRecipe( ''') # XX /r, but for a unary operator with separate input/output register, like -# copies. -ur = TailRecipe( - 'ur', Unary, size=1, ins=GPR, outs=GPR, +# copies. MR form. +umr = TailRecipe( + 'umr', Unary, size=1, ins=GPR, outs=GPR, emit=''' PUT_OP(bits, rex2(out_reg0, in_reg0), sink); modrm_rr(out_reg0, in_reg0, sink); ''') +# XX /r, but for a unary operator with separate input/output register. +# RM form. +urm = TailRecipe( + 'urm', Unary, size=1, ins=GPR, outs=GPR, + emit=''' + PUT_OP(bits, rex2(in_reg0, out_reg0), sink); + modrm_rr(in_reg0, out_reg0, sink); + ''') + # XX /r, for regmove instructions. rmov = TailRecipe( 'ur', RegMove, size=1, ins=GPR, outs=(), diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 9db0ee1234..56f0c2f7f0 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -57,7 +57,7 @@ fn rex_prefix(bits: u16, rex: u8, sink: &mut CS) { // Emit a single-byte opcode with no REX prefix. fn put_op1(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8f00, 0, "Invalid encoding bits for Op1*"); - debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Op1 encoding"); sink.put1(bits as u8); } @@ -71,17 +71,37 @@ fn put_rexop1(bits: u16, rex: u8, sink: &mut CS) { // Emit two-byte opcode: 0F XX fn put_op2(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8f00, 0x0400, "Invalid encoding bits for Op2*"); - debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Op2 encoding"); sink.put1(0x0f); sink.put1(bits as u8); } // Emit single-byte opcode with mandatory prefix. fn put_mp1(bits: u16, rex: u8, sink: &mut CS) { - debug_assert_eq!(bits & 0x0c00, 0, "Invalid encoding bits for Mp1*"); + debug_assert_eq!(bits & 0x8c00, 0, "Invalid encoding bits for Mp1*"); let pp = (bits >> 8) & 3; sink.put1(PREFIX[(pp - 1) as usize]); - debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp1 encoding"); + sink.put1(bits as u8); +} + +// Emit two-byte opcode (0F XX) with mandatory prefix. +fn put_mp2(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x8c00, 0x0400, "Invalid encoding bits for Mp2*"); + let pp = (bits >> 8) & 3; + sink.put1(PREFIX[(pp - 1) as usize]); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp2 encoding"); + sink.put1(0x0f); + sink.put1(bits as u8); +} + +// Emit two-byte opcode (0F XX) with mandatory prefix and REX. +fn put_rexmp2(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x0c00, 0x0400, "Invalid encoding bits for Mp2*"); + let pp = (bits >> 8) & 3; + sink.put1(PREFIX[(pp - 1) as usize]); + rex_prefix(bits, rex, sink); + sink.put1(0x0f); sink.put1(bits as u8); } From d8e2cb2b4227747390483846dfe25d3b2f7d8df2 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 16:05:20 -0700 Subject: [PATCH 871/968] Add some ISA predicates for Intel CPUID features. Guard the popcnt instruction on the proper CPUID bits. --- filetests/isa/intel/binary32.cton | 2 +- filetests/isa/intel/binary64.cton | 2 +- filetests/wasm/i32-arith.cton | 4 +-- lib/cretonne/meta/isa/intel/encodings.py | 10 +++++--- lib/cretonne/meta/isa/intel/settings.py | 32 +++++++++++++++++++++++- 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 77e53ceeec..33df4794b3 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -1,6 +1,6 @@ ; binary emission of 32-bit code. test binemit -isa intel +isa intel has_sse42 has_popcnt ; The binary encodings can be verified with the command: ; diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index da350d1e45..908b42ac0e 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -1,7 +1,7 @@ ; binary emission of 64-bit code. test binemit set is_64bit -isa intel +isa intel has_sse42 has_popcnt ; The binary encodings can be verified with the command: ; diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton index fc730cbe23..2992dcf5b8 100644 --- a/filetests/wasm/i32-arith.cton +++ b/filetests/wasm/i32-arith.cton @@ -2,10 +2,10 @@ test compile set is_64bit=0 -isa intel +isa intel has_sse42 has_popcnt set is_64bit=1 -isa intel +isa intel has_sse42 has_popcnt ; Constants. diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index d8dfe53140..dc54819a8b 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -7,6 +7,7 @@ from base import instructions as base from base.formats import UnaryImm from .defs import I32, I64 from . import recipes as r +from . import settings as cfg for inst, opc in [ (base.iadd, 0x01), @@ -81,10 +82,11 @@ for inst, rrr in [ I64.enc(inst.i32.i32, *r.rc(0xd3, rrr=rrr)) # Population count. -I32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8)) -I64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1)) -I64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8)) -I64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8)) +I32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) +I64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1), + isap=cfg.use_popcnt) +I64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) +I64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) # Loads and stores. I32.enc(base.store.i32.i32, *r.st(0x89)) diff --git a/lib/cretonne/meta/isa/intel/settings.py b/lib/cretonne/meta/isa/intel/settings.py index 42e1a0ab99..9251f73c5d 100644 --- a/lib/cretonne/meta/isa/intel/settings.py +++ b/lib/cretonne/meta/isa/intel/settings.py @@ -2,10 +2,40 @@ Intel settings. """ from __future__ import absolute_import -from cdsl.settings import SettingGroup +from cdsl.settings import SettingGroup, BoolSetting +from cdsl.predicates import And import base.settings as shared from .defs import ISA ISA.settings = SettingGroup('intel', parent=shared.group) +# The has_* settings here correspond to CPUID bits. + +# CPUID.01H:EDX +has_sse2 = BoolSetting("SSE2: CPUID.01H:EDX.SSE2[bit 26]") + +# CPUID.01H:ECX +has_sse3 = BoolSetting("SSE3: CPUID.01H:ECX.SSE3[bit 0]") +has_ssse3 = BoolSetting("SSSE3: CPUID.01H:ECX.SSSE3[bit 9]") +has_sse41 = BoolSetting("SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]") +has_sse42 = BoolSetting("SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]") +has_popcnt = BoolSetting("POPCNT: CPUID.01H:ECX.POPCNT[bit 23]") +has_avx = BoolSetting("AVX: CPUID.01H:ECX.AVX[bit 28]") + +# CPUID.(EAX=07H, ECX=0H):EBX +has_bmi1 = BoolSetting("BMI1: CPUID.(EAX=07H, ECX=0H):EBX.BMI1[bit 3]") +has_bmi2 = BoolSetting("BMI2: CPUID.(EAX=07H, ECX=0H):EBX.BMI2[bit 8]") + +# CPUID.EAX=80000001H:ECX +has_lzcnt = BoolSetting("LZCNT: CPUID.EAX=80000001H:ECX.LZCNT[bit 5]") + + +# The use_* settings here are used to determine if a feature can be used. + +use_sse41 = And(has_sse41) +use_sse42 = And(has_sse42, use_sse41) +use_popcnt = And(has_popcnt, has_sse42) +use_bmi1 = And(has_bmi1) +use_lzcnt = And(has_lzcnt) + ISA.settings.close(globals()) From 3bcfb103b9da904fac3281a8524b7188151c3d83 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 13 Jul 2017 10:12:25 -0700 Subject: [PATCH 872/968] Add a bconst instruction. (#116) * Add a bconst instruction. --- docs/langref.rst | 1 + filetests/parser/tiny.cton | 17 +++++++++++++++++ lib/cretonne/meta/base/formats.py | 3 ++- lib/cretonne/meta/base/immediates.py | 7 +++++++ lib/cretonne/meta/base/instructions.py | 15 ++++++++++++++- lib/cretonne/src/ir/instructions.rs | 1 + lib/cretonne/src/verifier/mod.rs | 1 + lib/cretonne/src/write.rs | 1 + lib/reader/src/parser.rs | 20 ++++++++++++++++++++ 9 files changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index 7f8d3e038f..78953ed8b0 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -562,6 +562,7 @@ Constant materialization .. autoinst:: iconst .. autoinst:: f32const .. autoinst:: f64const +.. autoinst:: bconst Live range splitting -------------------- diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index ff3d386947..fff7ccd4c1 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -25,6 +25,23 @@ ebb0: ; nextln: $v2 = ishl $v0, $v1 ; nextln: } +; Create and use values. +; Polymorphic instructions with type suffix. +function %bvalues() { +ebb0: + v0 = bconst.b32 true + v1 = bconst.b8 false + v2 = bextend.b32 v1 + v3 = bxor v0, v2 +} +; sameln: function %bvalues() { +; nextln: ebb0: +; nextln: $v0 = bconst.b32 true +; nextln: $v1 = bconst.b8 false +; nextln: $v2 = bextend.b32 v1 +; nextln: $v3 = bxor v0, v2 +; nextln: } + ; Polymorphic istruction controlled by second operand. function %select() { ebb0(v90: i32, v91: i32, v92: b1): diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 368fb13078..16732c1153 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS from .immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from .immediates import intcc, floatcc, memflags, regunit +from .immediates import boolean, intcc, floatcc, memflags, regunit from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot Nullary = InstructionFormat() @@ -18,6 +18,7 @@ Unary = InstructionFormat(VALUE) UnaryImm = InstructionFormat(imm64) UnaryIeee32 = InstructionFormat(ieee32) UnaryIeee64 = InstructionFormat(ieee64) +UnaryBool = InstructionFormat(boolean) Binary = InstructionFormat(VALUE, VALUE) BinaryImm = InstructionFormat(VALUE, imm64) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 2458b76e70..5f64e73b80 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -45,6 +45,13 @@ ieee32 = ImmediateKind('ieee32', 'A 32-bit immediate floating point number.') #: IEEE 754-2008 binary64 interchange format. ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') +#: An immediate boolean operand. +#: +#: This type of immediate boolean can interact with SSA values with any +#: :py:class:`cretonne.BoolType` type. +boolean = ImmediateKind('boolean', 'An immediate boolean.', + rust_type='bool') + #: A condition code for comparing integer values. #: #: This enumerated operand kind is used for the :cton:inst:`icmp` instruction diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 2aa9027a45..8638e19368 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -10,7 +10,7 @@ from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from base.immediates import intcc, floatcc, memflags, regunit +from base.immediates import boolean, intcc, floatcc, memflags, regunit from base import entities from cdsl.ti import WiderOrEq import base.formats # noqa @@ -18,6 +18,8 @@ import base.formats # noqa GROUP = InstructionGroup("base", "Shared base instruction set") Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) +Bool = TypeVar('Bool', 'A scalar or vector boolean type', + bools=True, simd=True) iB = TypeVar('iB', 'A scalar integer type', ints=True) iAddr = TypeVar('iAddr', 'An integer address type', ints=(32, 64)) Testable = TypeVar( @@ -417,6 +419,17 @@ f64const = Instruction( """, ins=N, outs=a) +N = Operand('N', boolean) +a = Operand('a', Bool, doc='A constant boolean scalar or vector value') +bconst = Instruction( + 'bconst', r""" + Boolean constant. + + Create a scalar boolean SSA value with an immediate constant value, or + a boolean vector where all the lanes have the same value. + """, + ins=N, outs=a) + # # Generics. # diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index cf914b853a..808b7cc01a 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -107,6 +107,7 @@ pub enum InstructionData { UnaryImm { opcode: Opcode, imm: Imm64 }, UnaryIeee32 { opcode: Opcode, imm: Ieee32 }, UnaryIeee64 { opcode: Opcode, imm: Ieee64 }, + UnaryBool { opcode: Opcode, imm: bool }, Binary { opcode: Opcode, args: [Value; 2] }, BinaryImm { opcode: Opcode, diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 12b190e3da..164fa70289 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -273,6 +273,7 @@ impl<'a> Verifier<'a> { UnaryImm { .. } | UnaryIeee32 { .. } | UnaryIeee64 { .. } | + UnaryBool { .. } | Binary { .. } | BinaryImm { .. } | Ternary { .. } | diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index f015b7d6df..56a06a0c04 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -243,6 +243,7 @@ pub fn write_operands(w: &mut Write, UnaryImm { imm, .. } => write!(w, " {}", imm), UnaryIeee32 { imm, .. } => write!(w, " {}", imm), UnaryIeee64 { imm, .. } => write!(w, " {}", imm), + UnaryBool { imm, .. } => write!(w, " {}", imm), Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), BinaryImm { arg, imm, .. } => write!(w, " {}, {}", arg, imm), Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index f698b1f54f..620d02ca2b 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -498,6 +498,20 @@ impl<'a> Parser<'a> { } } + // Match and consume a boolean immediate. + fn match_bool(&mut self, err_msg: &str) -> Result { + if let Some(Token::Identifier(text)) = self.token() { + self.consume(); + match text { + "true" => Ok(true), + "false" => Ok(false), + _ => err!(self.loc, err_msg), + } + } else { + err!(self.loc, err_msg) + } + } + // Match and consume an enumerated immediate, like one of the condition codes. fn match_enum(&mut self, err_msg: &str) -> Result { if let Some(Token::Identifier(text)) = self.token() { @@ -1483,6 +1497,12 @@ impl<'a> Parser<'a> { imm: self.match_ieee64("expected immediate 64-bit float operand")?, } } + InstructionFormat::UnaryBool => { + InstructionData::UnaryBool { + opcode, + imm: self.match_bool("expected immediate boolean operand")?, + } + } InstructionFormat::Binary => { let lhs = self.match_value("expected SSA value first operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; From 52dae7c2e2f571cd90d4d6214aed187da59c7f0c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Jul 2017 13:18:18 -0700 Subject: [PATCH 873/968] CSSA verifier. During register allocation, the code must be kept in conventional SSA form. Add a verifier that checks this property. --- lib/cretonne/src/regalloc/context.rs | 6 +- lib/cretonne/src/regalloc/mod.rs | 2 +- lib/cretonne/src/regalloc/virtregs.rs | 8 +- lib/cretonne/src/verifier/cssa.rs | 122 ++++++++++++++++++++++++++ lib/cretonne/src/verifier/mod.rs | 2 + 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 lib/cretonne/src/verifier/cssa.rs diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index 1961add95c..0523984604 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -17,7 +17,7 @@ use regalloc::spilling::Spilling; use regalloc::virtregs::VirtRegs; use result::CtonResult; use topo_order::TopoOrder; -use verifier::{verify_context, verify_liveness}; +use verifier::{verify_context, verify_liveness, verify_cssa}; /// Persistent memory allocations for register allocation. pub struct Context { @@ -85,6 +85,7 @@ impl Context { if isa.flags().enable_verifier() { verify_context(func, cfg, domtree, Some(isa))?; verify_liveness(isa, func, cfg, &self.liveness)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; } @@ -101,6 +102,7 @@ impl Context { if isa.flags().enable_verifier() { verify_context(func, cfg, domtree, Some(isa))?; verify_liveness(isa, func, cfg, &self.liveness)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; } // Pass: Reload. @@ -115,6 +117,7 @@ impl Context { if isa.flags().enable_verifier() { verify_context(func, cfg, domtree, Some(isa))?; verify_liveness(isa, func, cfg, &self.liveness)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; } // Pass: Coloring. @@ -124,6 +127,7 @@ impl Context { if isa.flags().enable_verifier() { verify_context(func, cfg, domtree, Some(isa))?; verify_liveness(isa, func, cfg, &self.liveness)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; } Ok(()) } diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index f689503c03..6ae93c67b6 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -7,6 +7,7 @@ pub mod liveness; pub mod allocatable_set; pub mod live_value_tracker; pub mod coloring; +pub mod virtregs; mod affinity; mod coalescing; @@ -16,7 +17,6 @@ mod pressure; mod reload; mod solver; mod spilling; -mod virtregs; pub use self::allocatable_set::AllocatableSet; pub use self::context::Context; diff --git a/lib/cretonne/src/regalloc/virtregs.rs b/lib/cretonne/src/regalloc/virtregs.rs index 6efe196805..17fa641fcd 100644 --- a/lib/cretonne/src/regalloc/virtregs.rs +++ b/lib/cretonne/src/regalloc/virtregs.rs @@ -12,11 +12,12 @@ //! memory-to-memory copies when a spilled value is passed as an EBB argument. use entity_list::{EntityList, ListPool}; -use entity_map::{EntityMap, PrimaryEntityData}; +use entity_map::{EntityMap, PrimaryEntityData, Keys}; use ir::Value; use packed_option::PackedOption; use ref_slice::ref_slice; +/// A virtual register reference. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct VirtReg(u32); entity_impl!(VirtReg, "vreg"); @@ -71,6 +72,11 @@ impl VirtRegs { self.vregs[vreg].as_slice(&self.pool) } + /// Get an iterator over all virtual registers. + pub fn all_virtregs(&self) -> Keys { + self.vregs.keys() + } + /// Get the congruence class of `value`. /// /// If `value` belongs to a virtual register, the congruence class is the values of the virtual diff --git a/lib/cretonne/src/verifier/cssa.rs b/lib/cretonne/src/verifier/cssa.rs new file mode 100644 index 0000000000..fe9a2fe18e --- /dev/null +++ b/lib/cretonne/src/verifier/cssa.rs @@ -0,0 +1,122 @@ +//! Verify conventional SSA form. + +use dominator_tree::DominatorTree; +use flowgraph::ControlFlowGraph; +use ir::Function; +use regalloc::liveness::Liveness; +use regalloc::virtregs::VirtRegs; +use std::cmp::Ordering; +use verifier::Result; + +/// Verify conventional SSA form for `func`. +/// +/// Conventional SSA form is represented in Cretonne with the help of virtual registers: +/// +/// - Two values are said to be *PHI-related* if one is an EBB argument and the other is passed as +/// a branch argument in a location that matches the first value. +/// - PHI-related values must belong to the same virtual register. +/// - Two values in the same virtual register must not have overlapping live ranges. +/// +/// Additionally, we verify this property of virtual registers: +/// +/// - The values in a virtual register are ordered according to the dominator tree's `rpo_cmp()`. +/// +/// We don't verify that virtual registers are minimal. Minimal CSSA is not required. +pub fn verify_cssa(func: &Function, + cfg: &ControlFlowGraph, + domtree: &DominatorTree, + liveness: &Liveness, + virtregs: &VirtRegs) + -> Result { + let verifier = CssaVerifier { + func, + cfg, + domtree, + virtregs, + liveness, + }; + verifier.check_virtregs()?; + verifier.check_cssa()?; + Ok(()) +} + +struct CssaVerifier<'a> { + func: &'a Function, + cfg: &'a ControlFlowGraph, + domtree: &'a DominatorTree, + virtregs: &'a VirtRegs, + liveness: &'a Liveness, +} + +impl<'a> CssaVerifier<'a> { + fn check_virtregs(&self) -> Result { + for vreg in self.virtregs.all_virtregs() { + let values = self.virtregs.values(vreg); + + for (idx, &val) in values.iter().enumerate() { + if !self.func.dfg.value_is_valid(val) { + return err!(val, "Invalid value in {}", vreg); + } + if !self.func.dfg.value_is_attached(val) { + return err!(val, "Detached value in {}", vreg); + } + if self.liveness.get(val).is_none() { + return err!(val, "Value in {} has no live range", vreg); + }; + + // Check RPO ordering with the previous values in the virtual register. + let def = self.func.dfg.value_def(val).into(); + let def_ebb = self.func.layout.pp_ebb(def); + for &prev_val in &values[0..idx] { + let prev_def = self.func.dfg.value_def(prev_val); + + // Enforce RPO of defs in the virtual register. + match self.domtree.rpo_cmp(prev_def, def, &self.func.layout) { + Ordering::Less => {} + Ordering::Equal => { + return err!(val, "Value in {} has same def as {}", vreg, prev_val); + } + Ordering::Greater => { + return err!(val, + "Value in {} in wrong order relative to {}", + vreg, + prev_val); + } + } + + // Knowing that values are in RPO order, we can check for interference this + // way. + if self.liveness[prev_val].overlaps_def(def, def_ebb, &self.func.layout) { + return err!(val, "Value def in {} interferes with {}", vreg, prev_val); + } + } + } + } + + Ok(()) + } + + fn check_cssa(&self) -> Result { + for ebb in self.func.layout.ebbs() { + let ebb_args = self.func.dfg.ebb_args(ebb); + for &(_, pred) in self.cfg.get_predecessors(ebb) { + let pred_args = self.func.dfg.inst_variable_args(pred); + // This should have been caught by an earlier verifier pass. + assert_eq!(ebb_args.len(), + pred_args.len(), + "Wrong arguments on branch."); + + for (&ebb_arg, &pred_arg) in ebb_args.iter().zip(pred_args) { + if !self.virtregs.same_class(ebb_arg, pred_arg) { + return err!(pred, + "{} and {} must be in the same virtual register", + ebb_arg, + pred_arg); + } + } + } + } + + Ok(()) + } +} diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 164fa70289..2652579020 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -65,6 +65,7 @@ use std::result; use std::collections::BTreeSet; pub use self::liveness::verify_liveness; +pub use self::cssa::verify_cssa; // Create an `Err` variant of `Result` from a location and `format!` arguments. macro_rules! err { @@ -83,6 +84,7 @@ macro_rules! err { }; } +mod cssa; mod liveness; /// A verifier error. From 89634fa645e4fa8640a2eb0e9698e55a7646b5e4 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 13 Jul 2017 16:23:41 -0700 Subject: [PATCH 874/968] Add documentation for immediates with type bool. This makes the documentation for the new bconst instruction more complete. --- docs/langref.rst | 7 +++++++ lib/cretonne/meta/base/immediates.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/langref.rst b/docs/langref.rst index 78953ed8b0..49593e3f09 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -265,6 +265,13 @@ indicate the different kinds of immediate operands on an instruction. A 64-bit immediate floating point number in the IEEE 754-2008 binary64 interchange format. All bit patterns are allowed. +.. type:: bool + + A boolean immediate value, either false or true. + + In the textual format, :type:`bool` immediates appear as 'false' + and 'true'. + .. type:: intcc An integer condition code. See the :inst:`icmp` instruction for details. diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 5f64e73b80..1defe46f6d 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -49,7 +49,7 @@ ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.') #: #: This type of immediate boolean can interact with SSA values with any #: :py:class:`cretonne.BoolType` type. -boolean = ImmediateKind('boolean', 'An immediate boolean.', +boolean = ImmediateKind('bool', 'An immediate boolean.', rust_type='bool') #: A condition code for comparing integer values. From 4bb0e2014c79cc65b9c83577aab9292dcb09a663 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Jul 2017 14:49:17 -0700 Subject: [PATCH 875/968] Add support for setting presets. Fixes #11. Presets are groups of settings and values applied at once. This is used as a shorthand in test files, so for example "isa intel nehalem" enables all of the CPUID bits that the Nehalem micro-architecture provides. --- lib/cretonne/meta/cdsl/settings.py | 110 +++++++++++++++++++++++- lib/cretonne/meta/gen_settings.py | 32 +++++-- lib/cretonne/meta/isa/intel/settings.py | 8 +- lib/cretonne/src/isa/intel/settings.rs | 24 ++++++ lib/cretonne/src/isa/mod.rs | 4 +- lib/cretonne/src/isa/riscv/mod.rs | 8 +- lib/cretonne/src/isa/riscv/settings.rs | 10 +-- lib/cretonne/src/settings.rs | 46 +++++++--- lib/reader/src/isaspec.rs | 2 +- lib/reader/src/parser.rs | 2 +- 10 files changed, 213 insertions(+), 33 deletions(-) diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index d9404060dd..aca3ac296f 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -4,7 +4,8 @@ from collections import OrderedDict from .predicates import Predicate try: - from typing import Set, List, Dict, Any, TYPE_CHECKING # noqa + from typing import Tuple, Set, List, Dict, Any, Union, TYPE_CHECKING # noqa + BoolOrPresetOrDict = Union['BoolSetting', 'Preset', Dict['Setting', Any]] if TYPE_CHECKING: from .predicates import PredLeaf, PredNode # noqa except ImportError: @@ -47,6 +48,17 @@ class Setting(object): # type: () -> int raise NotImplementedError("default_byte is an abstract method") + def byte_for_value(self, value): + # type: (Any) -> int + """Get the setting byte value that corresponds to `value`""" + raise NotImplementedError("byte_for_value is an abstract method") + + def byte_mask(self): + # type: () -> int + """Get a mask of bits in our byte that are relevant to this setting.""" + # Only BoolSetting has a different mask. + return 0xff + class BoolSetting(Setting): """ @@ -73,6 +85,17 @@ class BoolSetting(Setting): else: return 0 + def byte_for_value(self, value): + # type: (Any) -> int + if value: + return 1 << self.bit_offset + else: + return 0 + + def byte_mask(self): + # type: () -> int + return 1 << self.bit_offset + def predicate_leafs(self, leafs): # type: (Set[PredLeaf]) -> None leafs.add(self) @@ -107,6 +130,12 @@ class NumSetting(Setting): # type: () -> int return self.default + def byte_for_value(self, value): + # type: (Any) -> int + assert isinstance(value, int), "NumSetting must be set to an int" + assert value >= 0 and value <= 255 + return value + class EnumSetting(Setting): """ @@ -129,6 +158,10 @@ class EnumSetting(Setting): # type: () -> int return 0 + def byte_for_value(self, value): + # type: (Any) -> int + return self.values.index(value) + class SettingGroup(object): """ @@ -160,6 +193,7 @@ class SettingGroup(object): # - Added parent predicates that are replicated in this group. # Maps predicate -> number. self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa + self.presets = [] # type: List[Preset] # Fully qualified Rust module name. See gen_settings.py. self.qual_mod = None # type: str @@ -199,6 +233,10 @@ class SettingGroup(object): assert obj.name is None obj.name = name self.named_predicates.append(obj) + if isinstance(obj, Preset): + assert obj.name is None, obj.name + obj.name = name + self.layout() @staticmethod @@ -209,6 +247,14 @@ class SettingGroup(object): g.settings.append(setting) return g + @staticmethod + def append_preset(preset): + # type: (Preset) -> SettingGroup + g = SettingGroup._current + assert g, "Open a setting group before defining presets." + g.presets.append(preset) + return g + def number_predicate(self, pred): # type: (PredNode) -> int """ @@ -295,3 +341,65 @@ class SettingGroup(object): predcate bits rounded up to a whole number of bytes. """ return self.boolean_offset + (len(self.predicate_number) + 7) // 8 + + +class Preset(object): + """ + A collection of setting values that are applied at once. + + A `Preset` represents a shorthand notation for applying a number of + settings at once. Example: + + nehalem = Preset(has_sse41, has_cmov, has_avx=0) + + Enabling the `nehalem` setting is equivalent to enabling `has_sse41` and + `has_cmov` while disabling the `has_avx` setting. + """ + + def __init__(self, *args): + # type: (*BoolOrPresetOrDict) -> None + self.name = None # type: str # Assigned later by `SettingGroup`. + # Each tuple provides the value for a setting. + self.values = list() # type: List[Tuple[Setting, Any]] + + for arg in args: + if isinstance(arg, Preset): + # Any presets in args are immediately expanded. + self.values.extend(arg.values) + elif isinstance(arg, dict): + # A dictionary of key: value pairs. + self.values.extend(arg.items()) + else: + # A BoolSetting to enable. + assert isinstance(arg, BoolSetting) + self.values.append((arg, True)) + + self.group = SettingGroup.append_preset(self) + # Index into the generated DESCRIPTORS table. + self.descriptor_index = None # type: int + + def layout(self): + # type: () -> List[Tuple[int, int]] + """ + Compute a list of (mask, byte) pairs that incorporate all values in + this preset. + + The list will have an entry for each setting byte in the settings + group. + """ + l = [(0, 0)] * self.group.settings_size + + # Apply setting values in order. + for s, v in self.values: + ofs = s.byte_offset + s_mask = s.byte_mask() + s_val = s.byte_for_value(v) + assert (s_val & ~s_mask) == 0 + l_mask, l_val = l[ofs] + # Accumulated mask of modified bits. + l_mask |= s_mask + # Overwrite the relevant bits with the new value. + l_val = (l_val & ~s_mask) | s_val + l[ofs] = (l_mask, l_val) + + return l diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index ae16b337c0..c56cc4df41 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -10,10 +10,10 @@ from cdsl.settings import BoolSetting, NumSetting, EnumSetting from base import settings try: - from typing import Sequence, Set, Tuple, List, TYPE_CHECKING # noqa + from typing import Sequence, Set, Tuple, List, Union, TYPE_CHECKING # noqa if TYPE_CHECKING: from cdsl.isa import TargetISA # noqa - from cdsl.settings import Setting, SettingGroup # noqa + from cdsl.settings import Setting, Preset, SettingGroup # noqa from cdsl.predicates import Predicate, PredContext # noqa except ImportError: pass @@ -106,14 +106,14 @@ def gen_getters(sgrp, fmt): def gen_descriptors(sgrp, fmt): # type: (SettingGroup, srcgen.Formatter) -> None """ - Generate the DESCRIPTORS and ENUMERATORS tables. + Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables. """ enums = UniqueSeqTable() with fmt.indented( 'static DESCRIPTORS: [detail::Descriptor; {}] = [' - .format(len(sgrp.settings)), + .format(len(sgrp.settings) + len(sgrp.presets)), '];'): for idx, setting in enumerate(sgrp.settings): setting.descriptor_index = idx @@ -135,6 +135,13 @@ def gen_descriptors(sgrp, fmt): else: raise AssertionError("Unknown setting kind") + for idx, preset in enumerate(sgrp.presets): + preset.descriptor_index = len(sgrp.settings) + idx + with fmt.indented('detail::Descriptor {', '},'): + fmt.line('name: "{}",'.format(preset.name)) + fmt.line('offset: {},'.format(idx * sgrp.settings_size)) + fmt.line('detail: detail::Detail::Preset,') + with fmt.indented( 'static ENUMERATORS: [&str; {}] = [' .format(len(enums.table)), @@ -143,10 +150,13 @@ def gen_descriptors(sgrp, fmt): fmt.line('"{}",'.format(txt)) def hash_setting(s): - # type: (Setting) -> int + # type: (Union[Setting, Preset]) -> int return constant_hash.simple_hash(s.name) - hash_table = constant_hash.compute_quadratic(sgrp.settings, hash_setting) + hash_elems = [] # type: List[Union[Setting, Preset]] + hash_elems.extend(sgrp.settings) + hash_elems.extend(sgrp.presets) + hash_table = constant_hash.compute_quadratic(hash_elems, hash_setting) with fmt.indented( 'static HASH_TABLE: [u16; {}] = [' .format(len(hash_table)), @@ -157,6 +167,15 @@ def gen_descriptors(sgrp, fmt): else: fmt.line('{},'.format(h.descriptor_index)) + with fmt.indented( + 'static PRESETS: [(u8, u8); {}] = [' + .format(len(sgrp.presets) * sgrp.settings_size), + '];'): + for preset in sgrp.presets: + fmt.comment(preset.name) + for mask, value in preset.layout(): + fmt.format('(0b{:08b}, 0b{:08b}),', mask, value) + def gen_template(sgrp, fmt): # type: (SettingGroup, srcgen.Formatter) -> None @@ -175,6 +194,7 @@ def gen_template(sgrp, fmt): fmt.line('hash_table: &HASH_TABLE,') vs = ', '.join('{:#04x}'.format(x) for x in v) fmt.line('defaults: &[ {} ],'.format(vs)) + fmt.line('presets: &PRESETS,') fmt.doc_comment( 'Create a `settings::Builder` for the {} settings group.' diff --git a/lib/cretonne/meta/isa/intel/settings.py b/lib/cretonne/meta/isa/intel/settings.py index 9251f73c5d..a16303a3b5 100644 --- a/lib/cretonne/meta/isa/intel/settings.py +++ b/lib/cretonne/meta/isa/intel/settings.py @@ -2,7 +2,7 @@ Intel settings. """ from __future__ import absolute_import -from cdsl.settings import SettingGroup, BoolSetting +from cdsl.settings import SettingGroup, BoolSetting, Preset from cdsl.predicates import And import base.settings as shared from .defs import ISA @@ -38,4 +38,10 @@ use_popcnt = And(has_popcnt, has_sse42) use_bmi1 = And(has_bmi1) use_lzcnt = And(has_lzcnt) +# Presets corresponding to Intel CPUs. + +nehalem = Preset( + has_sse2, has_sse3, has_ssse3, has_sse41, has_sse42, has_popcnt) +haswell = Preset(nehalem, has_bmi1, has_lzcnt) + ISA.settings.close(globals()) diff --git a/lib/cretonne/src/isa/intel/settings.rs b/lib/cretonne/src/isa/intel/settings.rs index 341eb2dcc9..7ca4886588 100644 --- a/lib/cretonne/src/isa/intel/settings.rs +++ b/lib/cretonne/src/isa/intel/settings.rs @@ -7,3 +7,27 @@ use std::fmt; // `Flags` struct with an impl for all of the settings defined in // `lib/cretonne/meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings-intel.rs")); + +#[cfg(test)] +mod tests { + use super::{builder, Flags}; + use settings::{self, Configurable}; + + #[test] + fn presets() { + let shared = settings::Flags::new(&settings::builder()); + + // Nehalem has SSE4.1 but not BMI1. + let mut b1 = builder(); + b1.enable("nehalem").unwrap(); + let f1 = Flags::new(&shared, &b1); + assert_eq!(f1.has_sse41(), true); + assert_eq!(f1.has_bmi1(), false); + + let mut b2 = builder(); + b2.enable("haswell").unwrap(); + let f2 = Flags::new(&shared, &b2); + assert_eq!(f2.has_sse41(), true); + assert_eq!(f2.has_bmi1(), true); + } +} diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 1ec80e790c..edd72b27af 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -108,8 +108,8 @@ impl settings::Configurable for Builder { self.setup.set(name, value) } - fn set_bool(&mut self, name: &str, value: bool) -> settings::Result<()> { - self.setup.set_bool(name, value) + fn enable(&mut self, name: &str) -> settings::Result<()> { + self.setup.enable(name) } } diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 5f9cd771a2..246ec13bd1 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -114,7 +114,7 @@ mod tests { #[test] fn test_64bitenc() { let mut shared_builder = settings::builder(); - shared_builder.set_bool("is_64bit", true).unwrap(); + shared_builder.enable("is_64bit").unwrap(); let shared_flags = settings::Flags::new(&shared_builder); let isa = isa::lookup("riscv").unwrap().finish(shared_flags); @@ -161,7 +161,7 @@ mod tests { #[test] fn test_32bitenc() { let mut shared_builder = settings::builder(); - shared_builder.set_bool("is_64bit", false).unwrap(); + shared_builder.set("is_64bit", "false").unwrap(); let shared_flags = settings::Flags::new(&shared_builder); let isa = isa::lookup("riscv").unwrap().finish(shared_flags); @@ -216,13 +216,13 @@ mod tests { #[test] fn test_rv32m() { let mut shared_builder = settings::builder(); - shared_builder.set_bool("is_64bit", false).unwrap(); + shared_builder.set("is_64bit", "false").unwrap(); let shared_flags = settings::Flags::new(&shared_builder); // Set the supports_m stting which in turn enables the use_m predicate that unlocks // encodings for imul. let mut isa_builder = isa::lookup("riscv").unwrap(); - isa_builder.set_bool("supports_m", true).unwrap(); + isa_builder.enable("supports_m").unwrap(); let isa = isa_builder.finish(shared_flags); diff --git a/lib/cretonne/src/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs index aa66d75fd9..69a47f8717 100644 --- a/lib/cretonne/src/isa/riscv/settings.rs +++ b/lib/cretonne/src/isa/riscv/settings.rs @@ -34,17 +34,17 @@ mod tests { fn predicates() { let shared = settings::Flags::new(&settings::builder()); let mut b = builder(); - b.set_bool("supports_f", true).unwrap(); - b.set_bool("supports_d", true).unwrap(); + b.enable("supports_f").unwrap(); + b.enable("supports_d").unwrap(); let f = Flags::new(&shared, &b); assert_eq!(f.full_float(), true); let mut sb = settings::builder(); - sb.set_bool("enable_simd", false).unwrap(); + sb.set("enable_simd", "false").unwrap(); let shared = settings::Flags::new(&sb); let mut b = builder(); - b.set_bool("supports_f", true).unwrap(); - b.set_bool("supports_d", true).unwrap(); + b.enable("supports_f").unwrap(); + b.enable("supports_d").unwrap(); let f = Flags::new(&shared, &b); assert_eq!(f.full_float(), false); } diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 204588c7c1..19b9fafc21 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -35,10 +35,10 @@ pub trait Configurable { /// This can set any type of setting whether it is numeric, boolean, or enumerated. fn set(&mut self, name: &str, value: &str) -> Result<()>; - /// Set the value of a boolean setting by name. + /// Enable a boolean setting or apply a preset. /// - /// If the identified setting isn't a boolean, a `BadType` error is returned. - fn set_bool(&mut self, name: &str, value: bool) -> Result<()>; + /// If the identified setting isn't a boolean or a preset, a `BadType` error is returned. + fn enable(&mut self, name: &str) -> Result<()>; } /// Collect settings values based on a template. @@ -73,6 +73,13 @@ impl Builder { } } + /// Apply a preset. The argument is a slice of (mask, value) bytes. + fn apply_preset(&mut self, values: &[(u8, u8)]) { + for (byte, &(mask, value)) in self.bytes.iter_mut().zip(values) { + *byte = (*byte & !mask) | value; + } + } + /// Look up a descriptor by name. fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> { match probe(self.template, name, simple_hash(name)) { @@ -101,14 +108,19 @@ fn parse_enum_value(value: &str, choices: &[&str]) -> Result { } impl Configurable for Builder { - fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { + fn enable(&mut self, name: &str) -> Result<()> { use self::detail::Detail; let (offset, detail) = self.lookup(name)?; - if let Detail::Bool { bit } = detail { - self.set_bit(offset, bit, value); - Ok(()) - } else { - Err(Error::BadType) + match detail { + Detail::Bool { bit } => { + self.set_bit(offset, bit, true); + Ok(()) + } + Detail::Preset => { + self.apply_preset(&self.template.presets[offset..]); + Ok(()) + } + _ => Err(Error::BadType), } } @@ -128,6 +140,7 @@ impl Configurable for Builder { self.bytes[offset] = parse_enum_value(value, self.template.enums(last, enumerators))?; } + Detail::Preset => return Err(Error::BadName), } Ok(()) } @@ -169,6 +182,8 @@ pub mod detail { pub hash_table: &'static [u16], /// Default values. pub defaults: &'static [u8], + /// Pairs of (mask, value) for presets. + pub presets: &'static [(u8, u8)], } impl Template { @@ -197,6 +212,8 @@ pub mod detail { write!(f, "{}", byte) } } + // Presets aren't printed. They are reflected in the other settings. + Detail::Preset { .. } => Ok(()), } } } @@ -251,6 +268,11 @@ pub mod detail { /// First enumerator in the ENUMERATORS table. enumerators: u16, }, + + /// A preset is not an individual setting, it is a collection of settings applied at once. + /// + /// The `Descriptor::offset` field refers to the `PRESETS` table. + Preset, } } @@ -284,9 +306,9 @@ mod tests { #[test] fn modify_bool() { let mut b = builder(); - assert_eq!(b.set_bool("not_there", true), Err(BadName)); - assert_eq!(b.set_bool("enable_simd", true), Ok(())); - assert_eq!(b.set_bool("enable_simd", false), Ok(())); + assert_eq!(b.enable("not_there"), Err(BadName)); + assert_eq!(b.enable("enable_simd"), Ok(())); + assert_eq!(b.set("enable_simd", "false"), Ok(())); let f = Flags::new(&b); assert_eq!(f.enable_simd(), false); diff --git a/lib/reader/src/isaspec.rs b/lib/reader/src/isaspec.rs index 3715f4ed6b..51748e284a 100644 --- a/lib/reader/src/isaspec.rs +++ b/lib/reader/src/isaspec.rs @@ -41,7 +41,7 @@ pub fn parse_options<'a, I>(iter: I, config: &mut Configurable, loc: &Location) for opt in iter.map(TestOption::new) { match opt { TestOption::Flag(name) => { - match config.set_bool(name, true) { + match config.enable(name) { Ok(_) => {} Err(SetError::BadName) => return err!(loc, "unknown flag '{}'", opt), Err(_) => return err!(loc, "not a boolean flag: '{}'", opt), diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 620d02ca2b..fb659a6b8e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -609,7 +609,7 @@ impl<'a> Parser<'a> { // would slow down normal compilation, but when we're reading IL from a text file we're // either testing or debugging Cretonne, and verification makes sense. flag_builder - .set_bool("enable_verifier", true) + .enable("enable_verifier") .expect("Missing enable_verifier setting"); while let Some(Token::Identifier(command)) = self.token() { From e3ff551c2b63706d614e5a61954b432f4884019b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 12 Jul 2017 16:28:33 -0700 Subject: [PATCH 876/968] Add Intel BMI1 ctz and clz encodings. --- filetests/isa/intel/binary32.cton | 12 +++++++- filetests/isa/intel/binary64.cton | 36 +++++++++++++++++++++--- filetests/wasm/i32-arith.cton | 17 ++++++++--- lib/cretonne/meta/isa/intel/encodings.py | 14 +++++++++ 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 33df4794b3..bd3ff06289 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -1,6 +1,6 @@ ; binary emission of 32-bit code. test binemit -isa intel has_sse42 has_popcnt +isa intel haswell ; The binary encodings can be verified with the command: ; @@ -224,6 +224,16 @@ ebb0: ; asm: popcntl %ecx, %esi [-,%rsi] v201 = popcnt v1 ; bin: f3 0f b8 f1 + ; asm: lzcntl %esi, %ecx + [-,%rcx] v202 = clz v2 ; bin: f3 0f bd ce + ; asm: lzcntl %ecx, %esi + [-,%rsi] v203 = clz v1 ; bin: f3 0f bd f1 + + ; asm: tzcntl %esi, %ecx + [-,%rcx] v204 = ctz v2 ; bin: f3 0f bc ce + ; asm: tzcntl %ecx, %esi + [-,%rsi] v205 = ctz v1 ; bin: f3 0f bc f1 + ; asm: call foo call fn0() ; bin: e8 PCRel4(fn0) 00000000 diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 908b42ac0e..ecb4b8a40d 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -1,7 +1,7 @@ ; binary emission of 64-bit code. test binemit set is_64bit -isa intel has_sse42 has_popcnt +isa intel haswell ; The binary encodings can be verified with the command: ; @@ -154,6 +154,20 @@ ebb0: ; asm: popcntq %rcx, %r10 [-,%r10] v202 = popcnt v1 ; bin: f3 4c 0f b8 d1 + ; asm: lzcntq %rsi, %rcx + [-,%rcx] v203 = clz v2 ; bin: f3 48 0f bd ce + ; asm: lzcntq %r10, %rsi + [-,%rsi] v204 = clz v3 ; bin: f3 49 0f bd f2 + ; asm: lzcntq %rcx, %r10 + [-,%r10] v205 = clz v1 ; bin: f3 4c 0f bd d1 + + ; asm: tzcntq %rsi, %rcx + [-,%rcx] v206 = ctz v2 ; bin: f3 48 0f bc ce + ; asm: tzcntq %r10, %rsi + [-,%rsi] v207 = ctz v3 ; bin: f3 49 0f bc f2 + ; asm: tzcntq %rcx, %r10 + [-,%r10] v208 = ctz v1 ; bin: f3 4c 0f bc d1 + return ; bin: c3 } @@ -302,11 +316,25 @@ ebb0: ; Bit-counting instructions. ; asm: popcntl %esi, %ecx - [-,%rcx] v200 = popcnt v2 ; bin: f3 40 0f b8 ce + [-,%rcx] v200 = popcnt v2 ; bin: f3 40 0f b8 ce ; asm: popcntl %r10d, %esi - [-,%rsi] v201 = popcnt v3 ; bin: f3 41 0f b8 f2 + [-,%rsi] v201 = popcnt v3 ; bin: f3 41 0f b8 f2 ; asm: popcntl %ecx, %r10d - [-,%r10] v202 = popcnt v1 ; bin: f3 44 0f b8 d1 + [-,%r10] v202 = popcnt v1 ; bin: f3 44 0f b8 d1 + + ; asm: lzcntl %esi, %ecx + [-,%rcx] v203 = clz v2 ; bin: f3 40 0f bd ce + ; asm: lzcntl %r10d, %esi + [-,%rsi] v204 = clz v3 ; bin: f3 41 0f bd f2 + ; asm: lzcntl %ecx, %r10d + [-,%r10] v205 = clz v1 ; bin: f3 44 0f bd d1 + + ; asm: tzcntl %esi, %ecx + [-,%rcx] v206 = ctz v2 ; bin: f3 40 0f bc ce + ; asm: tzcntl %r10d, %esi + [-,%rsi] v207 = ctz v3 ; bin: f3 41 0f bc f2 + ; asm: tzcntl %ecx, %r10d + [-,%r10] v208 = ctz v1 ; bin: f3 44 0f bc d1 return ; bin: c3 } diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton index 2992dcf5b8..bddec1b52b 100644 --- a/filetests/wasm/i32-arith.cton +++ b/filetests/wasm/i32-arith.cton @@ -2,10 +2,10 @@ test compile set is_64bit=0 -isa intel has_sse42 has_popcnt +isa intel haswell set is_64bit=1 -isa intel has_sse42 has_popcnt +isa intel haswell ; Constants. @@ -17,8 +17,17 @@ ebb0: ; Unary operations. -; function %i32_clz(i32) -> i32 -; function %i32_ctz(i32) -> i32 +function %i32_clz(i32) -> i32 { +ebb0(v0: i32): + v1 = clz v0 + return v1 +} + +function %i32_ctz(i32) -> i32 { +ebb0(v0: i32): + v1 = ctz v0 + return v1 +} function %i32_popcnt(i32) -> i32 { ebb0(v0: i32): diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index dc54819a8b..c2c361ea86 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -88,6 +88,20 @@ I64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1), I64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) I64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) +# Count leading zero bits. +I32.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) +I64.enc(base.clz.i64, *r.urm.rex(0xf3, 0x0f, 0xbd, w=1), + isap=cfg.use_lzcnt) +I64.enc(base.clz.i32, *r.urm.rex(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) +I64.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) + +# Count trailing zero bits. +I32.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) +I64.enc(base.ctz.i64, *r.urm.rex(0xf3, 0x0f, 0xbc, w=1), + isap=cfg.use_bmi1) +I64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) +I64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) + # Loads and stores. I32.enc(base.store.i32.i32, *r.st(0x89)) I32.enc(base.store.i32.i32, *r.stDisp8(0x89)) From 28457f82c3ebf06b9d6062546374481914cecc8f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Jul 2017 18:13:05 -0700 Subject: [PATCH 877/968] Add a Context::emit_to_memory function. This function will emit the binary machine code into contiguous raw memory while sending relocations to a RelocSink. Add a MemoryCodeSink for generating machine code directly into memory efficiently. Allow the TargetIsa to provide emit_function implementations that are specialized to the MemoryCodeSink type to avoid needless small virtual callbacks to put1() et etc. --- lib/cretonne/src/binemit/memorysink.rs | 108 +++++++++++++++++++++++++ lib/cretonne/src/binemit/mod.rs | 19 +++++ lib/cretonne/src/context.rs | 12 ++- lib/cretonne/src/isa/arm32/mod.rs | 6 +- lib/cretonne/src/isa/arm64/mod.rs | 6 +- lib/cretonne/src/isa/intel/mod.rs | 6 +- lib/cretonne/src/isa/mod.rs | 9 ++- lib/cretonne/src/isa/riscv/mod.rs | 6 +- src/filetest/compile.rs | 9 +-- 9 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 lib/cretonne/src/binemit/memorysink.rs diff --git a/lib/cretonne/src/binemit/memorysink.rs b/lib/cretonne/src/binemit/memorysink.rs new file mode 100644 index 0000000000..f3fd5cebba --- /dev/null +++ b/lib/cretonne/src/binemit/memorysink.rs @@ -0,0 +1,108 @@ +//! Code sink that writes binary machine code into contiguous memory. +//! +//! The `CodeSink` trait is the most general way of extracting binary machine code from Cretonne, +//! and it is implemented by things like the `test binemit` file test driver to generate +//! hexadecimal machine code. The `CodeSink` has some undesirable performance properties because of +//! the dual abstraction: `TargetIsa` is a trait object implemented by each supported ISA, so it +//! can't have any generic functions that could be specialized for each `CodeSink` implementation. +//! This results in many virtual function callbacks (one per `put*` call) when +//! `TargetIsa::emit_inst()` is used. +//! +//! The `MemoryCodeSink` type fixes the performance problem because it is a type known to +//! `TargetIsa` so it can specialize its machine code generation for the type. The trade-off is +//! that a `MemoryCodeSink` will always write binary machine code to raw memory. It forwards any +//! relocations to a `RelocSink` trait object. Relocations are less frequent than the +//! `CodeSink::put*` methods, so the performance impact of the virtual callbacks is less severe. + +use ir::{Ebb, FuncRef, JumpTable}; +use super::{CodeSink, CodeOffset, Reloc}; +use std::ptr::write_unaligned; + +/// A `CodeSink` that writes binary machine code directly into memory. +/// +/// A `MemoryCodeSink` object should be used when emitting a Cretonne IL function into executable +/// memory. It writes machine code directly to a raw pointer without any bounds checking, so make +/// sure to allocate enough memory for the whole function. The number of bytes required is returned +/// by the `Context::compile()` function. +/// +/// Any relocations in the function are forwarded to the `RelocSink` trait object. +/// +/// Note that `MemoryCodeSink` writes multi-byte values in the native byte order of the host. This +/// is not the right thing to do for cross compilation. +pub struct MemoryCodeSink<'a> { + data: *mut u8, + offset: isize, + relocs: &'a mut RelocSink, +} + +impl<'a> MemoryCodeSink<'a> { + /// Create a new memory code sink that writes a function to the memory pointed to by `data`. + pub fn new(data: *mut u8, relocs: &mut RelocSink) -> MemoryCodeSink { + MemoryCodeSink { + data, + offset: 0, + relocs, + } + } +} + +/// A trait for receiving relocations for code that is emitted directly into memory. +pub trait RelocSink { + /// Add a relocation referencing an EBB at the current offset. + fn reloc_ebb(&mut self, CodeOffset, Reloc, Ebb); + + /// Add a relocation referencing an external function at the current offset. + fn reloc_func(&mut self, CodeOffset, Reloc, FuncRef); + + /// Add a relocation referencing a jump table. + fn reloc_jt(&mut self, CodeOffset, Reloc, JumpTable); +} + +impl<'a> CodeSink for MemoryCodeSink<'a> { + fn offset(&self) -> CodeOffset { + self.offset as CodeOffset + } + + fn put1(&mut self, x: u8) { + unsafe { + write_unaligned(self.data.offset(self.offset), x); + } + self.offset += 1; + } + + fn put2(&mut self, x: u16) { + unsafe { + write_unaligned(self.data.offset(self.offset) as *mut u16, x); + } + self.offset += 2; + } + + fn put4(&mut self, x: u32) { + unsafe { + write_unaligned(self.data.offset(self.offset) as *mut u32, x); + } + self.offset += 4; + } + + fn put8(&mut self, x: u64) { + unsafe { + write_unaligned(self.data.offset(self.offset) as *mut u64, x); + } + self.offset += 8; + } + + fn reloc_ebb(&mut self, rel: Reloc, ebb: Ebb) { + let ofs = self.offset(); + self.relocs.reloc_ebb(ofs, rel, ebb); + } + + fn reloc_func(&mut self, rel: Reloc, func: FuncRef) { + let ofs = self.offset(); + self.relocs.reloc_func(ofs, rel, func); + } + + fn reloc_jt(&mut self, rel: Reloc, jt: JumpTable) { + let ofs = self.offset(); + self.relocs.reloc_jt(ofs, rel, jt); + } +} diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index eaa721d510..02076270d0 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -4,8 +4,10 @@ //! binary machine code. mod relaxation; +mod memorysink; pub use self::relaxation::relax_branches; +pub use self::memorysink::{MemoryCodeSink, RelocSink}; use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; @@ -55,3 +57,20 @@ pub fn bad_encoding(func: &Function, inst: Inst) -> ! { func.encodings[inst], func.dfg.display_inst(inst, None)); } + +/// Emit a function to `sink`, given an instruction emitter function. +/// +/// This function is called from the `TargetIsa::emit_function()` implementations with the +/// appropriate instruction emitter. +pub fn emit_function(func: &Function, emit_inst: EI, sink: &mut CS) + where CS: CodeSink, + EI: Fn(&Function, Inst, &mut CS) +{ + for ebb in func.layout.ebbs() { + assert_eq!(func.offsets[ebb], sink.offset()); + for inst in func.layout.ebb_insts(ebb) { + emit_inst(func, inst, sink); + } + + } +} diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index a87e35cd32..d0288789f6 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -9,7 +9,7 @@ //! contexts concurrently. Typically, you would have one context per compilation thread and only a //! single ISA instance. -use binemit::{CodeOffset, relax_branches}; +use binemit::{CodeOffset, relax_branches, MemoryCodeSink, RelocSink}; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; @@ -71,6 +71,16 @@ impl Context { self.relax_branches(isa) } + /// Emit machine code directly into raw memory. + /// + /// Write all of the function's machine code to the memory at `mem`. The size of the machine + /// code is returned by `compile` above. + /// + /// The machine code is not relocated. Instead, any relocations are emitted into `relocs`. + pub fn emit_to_memory(&self, mem: *mut u8, relocs: &mut RelocSink, isa: &TargetIsa) { + isa.emit_function(&self.func, &mut MemoryCodeSink::new(mem, relocs)); + } + /// Run the verifier on the function. /// /// Also check that the dominator tree and control flow graph are consistent with the function. diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index d986b9e0e2..5591a2aece 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -6,7 +6,7 @@ mod binemit; mod enc_tables; mod registers; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; @@ -95,6 +95,10 @@ impl TargetIsa for Isa { binemit::emit_inst(func, inst, sink) } + fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { + emit_function(func, binemit::emit_inst, sink) + } + fn reloc_names(&self) -> &'static [&'static str] { &binemit::RELOC_NAMES } diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index c8c2de3cf8..316d1957f5 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -6,7 +6,7 @@ mod binemit; mod enc_tables; mod registers; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; @@ -88,6 +88,10 @@ impl TargetIsa for Isa { binemit::emit_inst(func, inst, sink) } + fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { + emit_function(func, binemit::emit_inst, sink) + } + fn reloc_names(&self) -> &'static [&'static str] { &binemit::RELOC_NAMES } diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 88df62c8ac..81e3127811 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -6,7 +6,7 @@ mod binemit; mod enc_tables; mod registers; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; @@ -95,6 +95,10 @@ impl TargetIsa for Isa { binemit::emit_inst(func, inst, sink) } + fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { + emit_function(func, binemit::emit_inst, sink) + } + fn reloc_names(&self) -> &'static [&'static str] { &binemit::RELOC_NAMES } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index edd72b27af..0604c0f271 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -44,7 +44,7 @@ pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, pub use isa::encoding::{Encoding, EncInfo}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; -use binemit::CodeSink; +use binemit; use settings; use ir; use regalloc; @@ -215,7 +215,12 @@ pub trait TargetIsa { /// /// Note that this will call `put*` methods on the trait object via its vtable which is not the /// fastest way of emitting code. - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink); + fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut binemit::CodeSink); + + /// Emit a whole function into memory. + /// + /// This is more performant than calling `emit_inst` for each instruction. + fn emit_function(&self, func: &ir::Function, sink: &mut binemit::MemoryCodeSink); /// Get a static array of names associated with relocations in this ISA. /// diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 246ec13bd1..6be2432a35 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -7,7 +7,7 @@ mod enc_tables; mod registers; use super::super::settings as shared_settings; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; @@ -95,6 +95,10 @@ impl TargetIsa for Isa { binemit::emit_inst(func, inst, sink) } + fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { + emit_function(func, binemit::emit_inst, sink) + } + fn reloc_names(&self) -> &'static [&'static str] { &binemit::RELOC_NAMES } diff --git a/src/filetest/compile.rs b/src/filetest/compile.rs index 15db119efd..f3766f001f 100644 --- a/src/filetest/compile.rs +++ b/src/filetest/compile.rs @@ -51,12 +51,9 @@ impl SubTest for TestCompile { // Finally verify that the returned code size matches the emitted bytes. let mut sink = SizeSink { offset: 0 }; - for ebb in comp_ctx.func.layout.ebbs() { - assert_eq!(sink.offset, comp_ctx.func.offsets[ebb]); - for inst in comp_ctx.func.layout.ebb_insts(ebb) { - isa.emit_inst(&comp_ctx.func, inst, &mut sink); - } - } + binemit::emit_function(&comp_ctx.func, + |func, inst, sink| isa.emit_inst(func, inst, sink), + &mut sink); if sink.offset != code_size { return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); From 02fd83cd5c87866035352ed08afb1022fcd61bd8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Jul 2017 09:22:50 -0700 Subject: [PATCH 878/968] Add Intel encodings for imul. --- filetests/isa/intel/binary32.cton | 7 +++++++ filetests/isa/intel/binary64.cton | 18 ++++++++++++++++++ filetests/wasm/i32-arith.cton | 7 ++++++- lib/cretonne/meta/isa/intel/encodings.py | 5 +++++ lib/cretonne/meta/isa/intel/recipes.py | 8 ++++++++ lib/cretonne/src/isa/intel/binemit.rs | 8 ++++++++ 6 files changed, 52 insertions(+), 1 deletion(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index bd3ff06289..0eb136b5d6 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -105,6 +105,13 @@ ebb0: ; asm: xorl $1000000, %esi [-,%rsi] v47 = bxor_imm v2, 1000000 ; bin: 81 f6 000f4240 + ; More arithmetic. + + ; asm: imull %esi, %ecx + [-,%rcx] v50 = imul v1, v2 ; bin: 0f af ce + ; asm: imull %ecx, %esi + [-,%rsi] v51 = imul v2, v1 ; bin: 0f af f1 + ; Register copies. ; asm: movl %esi, %ecx diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index ecb4b8a40d..5e3507d473 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -145,6 +145,15 @@ ebb0: ; asm: movq %rcx, %r10 [-,%r10] v112 = copy v1 ; bin: 49 89 ca + ; More arithmetic. + + ; asm: imulq %rsi, %rcx + [-,%rcx] v120 = imul v1, v2 ; bin: 48 0f af ce + ; asm: imulq %r10, %rsi + [-,%rsi] v121 = imul v2, v3 ; bin: 49 0f af f2 + ; asm: imulq %rcx, %r10 + [-,%r10] v122 = imul v3, v1 ; bin: 4c 0f af d1 + ; Bit-counting instructions. ; asm: popcntq %rsi, %rcx @@ -313,6 +322,15 @@ ebb0: ; asm: movl %ecx, %r10d [-,%r10] v112 = copy v1 ; bin: 41 89 ca + ; More arithmetic. + + ; asm: imull %esi, %ecx + [-,%rcx] v120 = imul v1, v2 ; bin: 40 0f af ce + ; asm: imull %r10d, %esi + [-,%rsi] v121 = imul v2, v3 ; bin: 41 0f af f2 + ; asm: imull %ecx, %r10d + [-,%r10] v122 = imul v3, v1 ; bin: 44 0f af d1 + ; Bit-counting instructions. ; asm: popcntl %esi, %ecx diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton index bddec1b52b..b345847eb9 100644 --- a/filetests/wasm/i32-arith.cton +++ b/filetests/wasm/i32-arith.cton @@ -49,7 +49,12 @@ ebb0(v0: i32, v1: i32): return v2 } -; function %i32_mul(i32, i32) -> i32 +function %i32_mul(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = imul v0, v1 + return v2 +} + ; function %i32_div(i32, i32) -> i32 ; function %i32_rem_s(i32, i32) -> i32 ; function %i32_rem_u(i32, i32) -> i32 diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index c2c361ea86..f6f003bb1e 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -23,6 +23,11 @@ for inst, opc in [ # default. Otherwise reg-alloc would never use r8 and up. I64.enc(inst.i32, *r.rr(opc)) +I32.enc(base.imul.i32, *r.rrx(0x0f, 0xaf)) +I64.enc(base.imul.i64, *r.rrx.rex(0x0f, 0xaf, w=1)) +I64.enc(base.imul.i32, *r.rrx.rex(0x0f, 0xaf)) +I64.enc(base.imul.i32, *r.rrx(0x0f, 0xaf)) + I32.enc(base.copy.i32, *r.umr(0x89)) I64.enc(base.copy.i64, *r.umr.rex(0x89, w=1)) I64.enc(base.copy.i32, *r.umr.rex(0x89)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index b7e6aa5575..e521b205b1 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -197,6 +197,14 @@ rr = TailRecipe( modrm_rr(in_reg0, in_reg1, sink); ''') +# XX /r with operands swapped. (RM form). +rrx = TailRecipe( + 'rrx', Binary, size=1, ins=(GPR, GPR), outs=0, + emit=''' + PUT_OP(bits, rex2(in_reg1, in_reg0), sink); + modrm_rr(in_reg1, in_reg0, sink); + ''') + # XX /r, but for a unary operator with separate input/output register, like # copies. MR form. umr = TailRecipe( diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 56f0c2f7f0..d3c9716680 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -76,6 +76,14 @@ fn put_op2(bits: u16, rex: u8, sink: &mut CS) { sink.put1(bits as u8); } +// Emit two-byte opcode: 0F XX with REX prefix. +fn put_rexop2(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x0f00, 0x0400, "Invalid encoding bits for RexOp2*"); + rex_prefix(bits, rex, sink); + sink.put1(0x0f); + sink.put1(bits as u8); +} + // Emit single-byte opcode with mandatory prefix. fn put_mp1(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8c00, 0, "Invalid encoding bits for Mp1*"); From 306ef2095bdce9d0584e1266813b7432ee680946 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Jul 2017 10:09:02 -0700 Subject: [PATCH 879/968] Begin an Intel-specific instruction group. Add instructions representing Intel's division instructions which use a numerator that is twice as wide as the denominator and produce both the quotient and remainder. Add encodings for the x86_[su]divmodx instructions. --- docs/cton_domain.py | 5 ++- docs/langref.rst | 22 ++++++++-- filetests/isa/intel/binary32.cton | 13 ++++++ filetests/isa/intel/binary64.cton | 30 ++++++++++++++ lib/cretonne/meta/isa/intel/defs.py | 3 +- lib/cretonne/meta/isa/intel/encodings.py | 9 +++++ lib/cretonne/meta/isa/intel/instructions.py | 45 +++++++++++++++++++++ lib/cretonne/meta/isa/intel/recipes.py | 11 ++++- 8 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 lib/cretonne/meta/isa/intel/instructions.py diff --git a/docs/cton_domain.py b/docs/cton_domain.py index 3dba386d90..a03acf1160 100644 --- a/docs/cton_domain.py +++ b/docs/cton_domain.py @@ -271,7 +271,10 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter): return False def resolve_name(self, modname, parents, path, base): - return 'base.instructions', [base] + if path: + return path.rstrip('.'), [base] + else: + return 'base.instructions', [base] def format_signature(self): inst = self.object diff --git a/docs/langref.rst b/docs/langref.rst index 49593e3f09..dde7865486 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -775,15 +775,31 @@ the target ISA. .. autoinst:: isplit .. autoinst:: iconcat -Base instruction group -====================== +ISA-specific instructions +========================= + +Target ISAs can define supplemental instructions that do not make sense to +support generally. + +Intel +----- + +Instructions that can only be used by the Intel target ISA. + +.. autoinst:: isa.intel.instructions.sdivmodx +.. autoinst:: isa.intel.instructions.udivmodx + +Instruction groups +================== All of the shared instructions are part of the :instgroup:`base` instruction group. .. autoinstgroup:: base.instructions.GROUP -Target ISAs may define further instructions in their own instruction groups. +Target ISAs may define further instructions in their own instruction groups: + +.. autoinstgroup:: isa.intel.instructions.GROUP Implementation limits ===================== diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 0eb136b5d6..67f37db11b 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -112,6 +112,19 @@ ebb0: ; asm: imull %ecx, %esi [-,%rsi] v51 = imul v2, v1 ; bin: 0f af f1 + ; asm: movl $1, %eax + [-,%rax] v52 = iconst.i32 1 ; bin: b8 00000001 + ; asm: movl $2, %edx + [-,%rdx] v53 = iconst.i32 2 ; bin: ba 00000002 + ; asm: idivl %ecx + [-,%rax,%rdx] v54, v55 = x86_sdivmodx v52, v53, v1 ; bin: f7 f9 + ; asm: idivl %esi + [-,%rax,%rdx] v56, v57 = x86_sdivmodx v52, v53, v2 ; bin: f7 fe + ; asm: divl %ecx + [-,%rax,%rdx] v58, v59 = x86_udivmodx v52, v53, v1 ; bin: f7 f1 + ; asm: divl %esi + [-,%rax,%rdx] v60, v61 = x86_udivmodx v52, v53, v2 ; bin: f7 f6 + ; Register copies. ; asm: movl %esi, %ecx diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 5e3507d473..bed934d5ab 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -154,6 +154,21 @@ ebb0: ; asm: imulq %rcx, %r10 [-,%r10] v122 = imul v3, v1 ; bin: 4c 0f af d1 + [-,%rax] v130 = iconst.i64 1 + [-,%rdx] v131 = iconst.i64 2 + ; asm: idivq %rcx + [-,%rax,%rdx] v132, v133 = x86_sdivmodx v130, v131, v1 ; bin: 48 f7 f9 + ; asm: idivq %rsi + [-,%rax,%rdx] v134, v135 = x86_sdivmodx v130, v131, v2 ; bin: 48 f7 fe + ; asm: idivq %r10 + [-,%rax,%rdx] v136, v137 = x86_sdivmodx v130, v131, v3 ; bin: 49 f7 fa + ; asm: divq %rcx + [-,%rax,%rdx] v138, v139 = x86_udivmodx v130, v131, v1 ; bin: 48 f7 f1 + ; asm: divq %rsi + [-,%rax,%rdx] v140, v141 = x86_udivmodx v130, v131, v2 ; bin: 48 f7 f6 + ; asm: divq %r10 + [-,%rax,%rdx] v142, v143 = x86_udivmodx v130, v131, v3 ; bin: 49 f7 f2 + ; Bit-counting instructions. ; asm: popcntq %rsi, %rcx @@ -331,6 +346,21 @@ ebb0: ; asm: imull %ecx, %r10d [-,%r10] v122 = imul v3, v1 ; bin: 44 0f af d1 + [-,%rax] v130 = iconst.i32 1 + [-,%rdx] v131 = iconst.i32 2 + ; asm: idivl %rcx + [-,%rax,%rdx] v132, v133 = x86_sdivmodx v130, v131, v1 ; bin: 40 f7 f9 + ; asm: idivl %rsi + [-,%rax,%rdx] v134, v135 = x86_sdivmodx v130, v131, v2 ; bin: 40 f7 fe + ; asm: idivl %r10d + [-,%rax,%rdx] v136, v137 = x86_sdivmodx v130, v131, v3 ; bin: 41 f7 fa + ; asm: divl %rcx + [-,%rax,%rdx] v138, v139 = x86_udivmodx v130, v131, v1 ; bin: 40 f7 f1 + ; asm: divl %rsi + [-,%rax,%rdx] v140, v141 = x86_udivmodx v130, v131, v2 ; bin: 40 f7 f6 + ; asm: divl %r10d + [-,%rax,%rdx] v142, v143 = x86_udivmodx v130, v131, v3 ; bin: 41 f7 f2 + ; Bit-counting instructions. ; asm: popcntl %esi, %ecx diff --git a/lib/cretonne/meta/isa/intel/defs.py b/lib/cretonne/meta/isa/intel/defs.py index 50251b44af..5078bcec7e 100644 --- a/lib/cretonne/meta/isa/intel/defs.py +++ b/lib/cretonne/meta/isa/intel/defs.py @@ -6,8 +6,9 @@ Commonly used definitions. from __future__ import absolute_import from cdsl.isa import TargetISA, CPUMode import base.instructions +from . import instructions as x86 -ISA = TargetISA('intel', [base.instructions.GROUP]) +ISA = TargetISA('intel', [base.instructions.GROUP, x86.GROUP]) # CPU modes for 32-bit and 64-bit operation. I32 = CPUMode('I32', ISA) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index f6f003bb1e..25421be999 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -8,6 +8,7 @@ from base.formats import UnaryImm from .defs import I32, I64 from . import recipes as r from . import settings as cfg +from . import instructions as x86 for inst, opc in [ (base.iadd, 0x01), @@ -28,6 +29,14 @@ I64.enc(base.imul.i64, *r.rrx.rex(0x0f, 0xaf, w=1)) I64.enc(base.imul.i32, *r.rrx.rex(0x0f, 0xaf)) I64.enc(base.imul.i32, *r.rrx(0x0f, 0xaf)) +for inst, rrr in [ + (x86.sdivmodx, 7), + (x86.udivmodx, 6)]: + I32.enc(inst.i32, *r.div(0xf7, rrr=rrr)) + I64.enc(inst.i64, *r.div.rex(0xf7, rrr=rrr, w=1)) + I64.enc(inst.i32, *r.div.rex(0xf7, rrr=rrr)) + I64.enc(inst.i32, *r.div(0xf7, rrr=rrr)) + I32.enc(base.copy.i32, *r.umr(0x89)) I64.enc(base.copy.i64, *r.umr.rex(0x89, w=1)) I64.enc(base.copy.i32, *r.umr.rex(0x89)) diff --git a/lib/cretonne/meta/isa/intel/instructions.py b/lib/cretonne/meta/isa/intel/instructions.py new file mode 100644 index 0000000000..a5c2bdafb0 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/instructions.py @@ -0,0 +1,45 @@ +""" +Supplementary instruction definitions for Intel. + +This module defines additional instructions that are useful only to the Intel +target ISA. +""" + +from cdsl.operands import Operand +from cdsl.instructions import Instruction, InstructionGroup +from base.instructions import iB + + +GROUP = InstructionGroup("x86", "Intel-specific instruction set") + +nlo = Operand('nlo', iB, doc='Low part of numerator') +nhi = Operand('nhi', iB, doc='High part of numerator') +d = Operand('d', iB, doc='Denominator') +q = Operand('q', iB, doc='Quotient') +r = Operand('r', iB, doc='Remainder') + +udivmodx = Instruction( + 'x86_udivmodx', r""" + Extended unsigned division. + + Concatenate the bits in `nhi` and `nlo` to form the numerator. + Interpret the bits as an unsigned number and divide by the unsigned + denominator `d`. Trap when `d` is zero or if the quotient is larger + than the range of the output. + + Return both quotient and remainder. + """, + ins=(nlo, nhi, d), outs=(q, r), can_trap=True) + +sdivmodx = Instruction( + 'x86_sdivmodx', r""" + Extended signed division. + + Concatenate the bits in `nhi` and `nlo` to form the numerator. + Interpret the bits as a signed number and divide by the signed + denominator `d`. Trap when `d` is zero or if the quotient is outside + the range of the output. + + Return both quotient and remainder. + """, + ins=(nlo, nhi, d), outs=(q, r), can_trap=True) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index e521b205b1..9c48a529d2 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -6,7 +6,7 @@ from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry from base.formats import Call, IndirectCall, Store, Load -from base.formats import RegMove +from base.formats import RegMove, Ternary from .registers import GPR, ABCD try: @@ -239,6 +239,15 @@ rc = TailRecipe( modrm_r_bits(in_reg0, bits, sink); ''') +# XX /n for division: inputs in %rax, %rdx, r. Outputs in %rax, %rdx. +div = TailRecipe( + 'div', Ternary, size=1, + ins=(GPR.rax, GPR.rdx, GPR), outs=(GPR.rax, GPR.rdx), + emit=''' + PUT_OP(bits, rex1(in_reg2), sink); + modrm_r_bits(in_reg2, bits, sink); + ''') + # XX /n ib with 8-bit immediate sign-extended. rib = TailRecipe( 'rib', BinaryImm, size=2, ins=GPR, outs=0, From 2927878707b5ba9bf956fb25cadb63302c0b546f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Jul 2017 12:52:53 -0700 Subject: [PATCH 880/968] Track regmove instruction during binemit. Register locations can change throughout an EBB. Make sure the emit_inst() function considers this when encoding instructions and update the register diversion tracker. --- filetests/isa/riscv/regmove.cton | 15 +++++++++++++++ lib/cretonne/meta/gen_binemit.py | 20 +++++++++++++++----- lib/cretonne/src/binemit/mod.rs | 8 +++++--- lib/cretonne/src/isa/arm32/binemit.rs | 1 + lib/cretonne/src/isa/arm32/mod.rs | 8 ++++++-- lib/cretonne/src/isa/arm64/binemit.rs | 1 + lib/cretonne/src/isa/arm64/mod.rs | 8 ++++++-- lib/cretonne/src/isa/intel/binemit.rs | 1 + lib/cretonne/src/isa/intel/mod.rs | 8 ++++++-- lib/cretonne/src/isa/mod.rs | 6 +++++- lib/cretonne/src/isa/riscv/binemit.rs | 1 + lib/cretonne/src/isa/riscv/mod.rs | 8 ++++++-- src/filetest/binemit.rs | 5 ++++- src/filetest/compile.rs | 2 +- 14 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 filetests/isa/riscv/regmove.cton diff --git a/filetests/isa/riscv/regmove.cton b/filetests/isa/riscv/regmove.cton new file mode 100644 index 0000000000..c316f74f21 --- /dev/null +++ b/filetests/isa/riscv/regmove.cton @@ -0,0 +1,15 @@ +; Test tracking of register moves. +test binemit +isa riscv + +function %regmoves(i32 link [%x1]) -> i32 link [%x1] { +ebb0(v9999: i32): + [-,%x10] v1 = iconst.i32 1 + [-,%x7] v2 = iadd_imm v1, 1000 ; bin: 3e850393 + regmove v1, %x10 -> %x11 ; bin: 00050593 + [-,%x7] v3 = iadd_imm v1, 1000 ; bin: 3e858393 + regmove v1, %x11 -> %x10 ; bin: 00058513 + [-,%x7] v4 = iadd_imm v1, 1000 ; bin: 3e850393 + + return v9999 +} diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py index 858fc36b39..8e734c5ff1 100644 --- a/lib/cretonne/meta/gen_binemit.py +++ b/lib/cretonne/meta/gen_binemit.py @@ -31,6 +31,9 @@ def gen_recipe(recipe, fmt): want_outs = any(isinstance(o, RegClass) or isinstance(o, Stack) for o in recipe.outs) + # Regmove instructions get special treatment. + is_regmove = (recipe.format.name == 'RegMove') + # First unpack the instruction. with fmt.indented( 'if let InstructionData::{} {{'.format(iform.name), @@ -46,7 +49,7 @@ def gen_recipe(recipe, fmt): fmt.outdented_line('} = func.dfg[inst] {') # Normalize to an `args` array. - if want_args: + if want_args and not is_regmove: if iform.has_value_list: fmt.line('let args = args.as_slice(&func.dfg.value_lists);') elif nvops == 1: @@ -56,11 +59,11 @@ def gen_recipe(recipe, fmt): # Don't bother with fixed registers. args = '' for i, arg in enumerate(recipe.ins): - if isinstance(arg, RegClass): + if isinstance(arg, RegClass) and not is_regmove: v = 'in_reg{}'.format(i) args += ', ' + v fmt.line( - 'let {} = func.locations[args[{}]].unwrap_reg();' + 'let {} = divert.reg(args[{}], &func.locations);' .format(v, i)) elif isinstance(arg, Stack): v = 'in_ss{}'.format(i) @@ -93,6 +96,11 @@ def gen_recipe(recipe, fmt): 'let {} = func.locations[results[{}]].unwrap_stack();' .format(v, i)) + # Special handling for regmove instructions. Update the register + # diversion tracker. + if recipe.format.name == 'RegMove': + fmt.line('divert.regmove(arg, src, dst);') + # Call hand-written code. If the recipe contains a code snippet, use # that. Otherwise cal a recipe function in the target ISA's binemit # module. @@ -118,13 +126,15 @@ def gen_isa(isa, fmt): # No encoding recipes: Emit a stub. with fmt.indented( 'pub fn emit_inst' - '(func: &Function, inst: Inst, _sink: &mut CS) {', '}'): + '(func: &Function, inst: Inst, ' + '_divert: &mut RegDiversions, _sink: &mut CS) {', '}'): fmt.line('bad_encoding(func, inst)') else: fmt.line('#[allow(unused_variables, unreachable_code)]') with fmt.indented( 'pub fn emit_inst' - '(func: &Function, inst: Inst, sink: &mut CS) {', '}'): + '(func: &Function, inst: Inst, ' + 'divert: &mut RegDiversions, sink: &mut CS) {', '}'): fmt.line('let bits = func.encodings[inst].bits();') with fmt.indented('match func.encodings[inst].recipe() {', '}'): for i, recipe in enumerate(isa.all_recipes): diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index 02076270d0..a4a62f869f 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -10,6 +10,7 @@ pub use self::relaxation::relax_branches; pub use self::memorysink::{MemoryCodeSink, RelocSink}; use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; +use regalloc::RegDiversions; /// Offset in bytes from the beginning of the function. /// @@ -64,13 +65,14 @@ pub fn bad_encoding(func: &Function, inst: Inst) -> ! { /// appropriate instruction emitter. pub fn emit_function(func: &Function, emit_inst: EI, sink: &mut CS) where CS: CodeSink, - EI: Fn(&Function, Inst, &mut CS) + EI: Fn(&Function, Inst, &mut RegDiversions, &mut CS) { + let mut divert = RegDiversions::new(); for ebb in func.layout.ebbs() { + divert.clear(); assert_eq!(func.offsets[ebb], sink.offset()); for inst in func.layout.ebb_insts(ebb) { - emit_inst(func, inst, sink); + emit_inst(func, inst, &mut divert, sink); } - } } diff --git a/lib/cretonne/src/isa/arm32/binemit.rs b/lib/cretonne/src/isa/arm32/binemit.rs index cf12bdbde2..bbae03432c 100644 --- a/lib/cretonne/src/isa/arm32/binemit.rs +++ b/lib/cretonne/src/isa/arm32/binemit.rs @@ -2,6 +2,7 @@ use binemit::{CodeSink, bad_encoding}; use ir::{Function, Inst}; +use regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 5591a2aece..769adc376d 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -91,8 +91,12 @@ impl TargetIsa for Isa { abi::allocatable_registers(func) } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { - binemit::emit_inst(func, inst, sink) + fn emit_inst(&self, + func: &ir::Function, + inst: ir::Inst, + divert: &mut regalloc::RegDiversions, + sink: &mut CodeSink) { + binemit::emit_inst(func, inst, divert, sink) } fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { diff --git a/lib/cretonne/src/isa/arm64/binemit.rs b/lib/cretonne/src/isa/arm64/binemit.rs index 120115c0d8..ecff1662bc 100644 --- a/lib/cretonne/src/isa/arm64/binemit.rs +++ b/lib/cretonne/src/isa/arm64/binemit.rs @@ -2,6 +2,7 @@ use binemit::{CodeSink, bad_encoding}; use ir::{Function, Inst}; +use regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 316d1957f5..a1b0ac2478 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -84,8 +84,12 @@ impl TargetIsa for Isa { abi::allocatable_registers(func) } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { - binemit::emit_inst(func, inst, sink) + fn emit_inst(&self, + func: &ir::Function, + inst: ir::Inst, + divert: &mut regalloc::RegDiversions, + sink: &mut CodeSink) { + binemit::emit_inst(func, inst, divert, sink) } fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index d3c9716680..61c6f512a9 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -3,6 +3,7 @@ use binemit::{CodeSink, Reloc, bad_encoding}; use ir::{Function, Inst, InstructionData}; use isa::RegUnit; +use regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 81e3127811..93a2ed2c09 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -91,8 +91,12 @@ impl TargetIsa for Isa { abi::allocatable_registers(func, &self.shared_flags) } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { - binemit::emit_inst(func, inst, sink) + fn emit_inst(&self, + func: &ir::Function, + inst: ir::Inst, + divert: &mut regalloc::RegDiversions, + sink: &mut CodeSink) { + binemit::emit_inst(func, inst, divert, sink) } fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 0604c0f271..6665de5f42 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -215,7 +215,11 @@ pub trait TargetIsa { /// /// Note that this will call `put*` methods on the trait object via its vtable which is not the /// fastest way of emitting code. - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut binemit::CodeSink); + fn emit_inst(&self, + func: &ir::Function, + inst: ir::Inst, + divert: &mut regalloc::RegDiversions, + sink: &mut binemit::CodeSink); /// Emit a whole function into memory. /// diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index d193981887..86cbb6b82c 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -4,6 +4,7 @@ use binemit::{CodeSink, Reloc, bad_encoding}; use ir::{Function, Inst, InstructionData}; use isa::RegUnit; use predicates::is_signed_int; +use regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs")); diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 6be2432a35..b70f6185b8 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -91,8 +91,12 @@ impl TargetIsa for Isa { abi::allocatable_registers(func, &self.isa_flags) } - fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink) { - binemit::emit_inst(func, inst, sink) + fn emit_inst(&self, + func: &ir::Function, + inst: ir::Inst, + divert: &mut regalloc::RegDiversions, + sink: &mut CodeSink) { + binemit::emit_inst(func, inst, divert, sink) } fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index d359cea794..09a6955b5e 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -10,6 +10,7 @@ use cretonne::binemit; use cretonne::ir; use cretonne::ir::entities::AnyEntity; use cretonne::isa::TargetIsa; +use cretonne::regalloc::RegDiversions; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result}; use utils::{match_directive, pretty_error}; @@ -147,7 +148,9 @@ impl SubTest for TestBinEmit { // Now emit all instructions. let mut sink = TextSink::new(isa); + let mut divert = RegDiversions::new(); for ebb in func.layout.ebbs() { + divert.clear(); // Correct header offsets should have been computed by `relax_branches()`. assert_eq!(sink.offset, func.offsets[ebb], @@ -160,7 +163,7 @@ impl SubTest for TestBinEmit { // Send legal encodings into the emitter. if enc.is_legal() { let before = sink.offset; - isa.emit_inst(&func, inst, &mut sink); + isa.emit_inst(&func, inst, &mut divert, &mut sink); let emitted = sink.offset - before; // Verify the encoding recipe sizes against the ISAs emit_inst implementation. assert_eq!(emitted, diff --git a/src/filetest/compile.rs b/src/filetest/compile.rs index f3766f001f..5d00ee63c2 100644 --- a/src/filetest/compile.rs +++ b/src/filetest/compile.rs @@ -52,7 +52,7 @@ impl SubTest for TestCompile { // Finally verify that the returned code size matches the emitted bytes. let mut sink = SizeSink { offset: 0 }; binemit::emit_function(&comp_ctx.func, - |func, inst, sink| isa.emit_inst(func, inst, sink), + |func, inst, div, sink| isa.emit_inst(func, inst, div, sink), &mut sink); if sink.offset != code_size { From 0a7087732e234557780cdc87f0a424b379a2a1fe Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 18 Jul 2017 14:54:34 -0700 Subject: [PATCH 881/968] Add Intel encodings for jump and branch instructions. Just implement jump, brz, and brnz as needed for WebAssembly. --- filetests/isa/intel/binary32.cton | 22 ++++++++++ filetests/isa/intel/binary64.cton | 56 ++++++++++++++++++++++++ filetests/wasm/control.cton | 34 ++++++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 18 ++++++++ lib/cretonne/meta/isa/intel/recipes.py | 46 ++++++++++++++++++- lib/cretonne/src/isa/intel/binemit.rs | 14 +++++- 6 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 filetests/wasm/control.cton diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 67f37db11b..7b43ed7aa2 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -262,6 +262,28 @@ ebb0: ; asm: call *%esi call_indirect sig0, v2() ; bin: ff d6 + ; asm: testl %ecx, %ecx + ; asm: je ebb1 + brz v1, ebb1 ; bin: 85 c9 74 0e + ; asm: testl %esi, %esi + ; asm: je ebb1 + brz v2, ebb1 ; bin: 85 f6 74 0a + ; asm: testl %ecx, %ecx + ; asm: jne ebb1 + brnz v1, ebb1 ; bin: 85 c9 75 06 + ; asm: testl %esi, %esi + ; asm: jne ebb1 + brnz v2, ebb1 ; bin: 85 f6 75 02 + + ; asm: jmp ebb2 + jump ebb2 ; bin: eb 01 + + ; asm: ebb1: +ebb1: ; asm: ret return ; bin: c3 + + ; asm: ebb2: +ebb2: + jump ebb1 ; bin: eb fd } diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index bed934d5ab..1d88b69d50 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -192,7 +192,35 @@ ebb0: ; asm: tzcntq %rcx, %r10 [-,%r10] v208 = ctz v1 ; bin: f3 4c 0f bc d1 + ; asm: testq %rcx, %ecx + ; asm: je ebb1 + brz v1, ebb1 ; bin: 48 85 c9 74 1b + ; asm: testq %rsi, %esi + ; asm: je ebb1 + brz v2, ebb1 ; bin: 48 85 f6 74 16 + ; asm: testq %r10, %r10d + ; asm: je ebb1 + brz v3, ebb1 ; bin: 4d 85 d2 74 11 + ; asm: testq %rcx, %ecx + ; asm: jne ebb1 + brnz v1, ebb1 ; bin: 48 85 c9 75 0c + ; asm: test %rsi, %esi + ; asm: jne ebb1 + brnz v2, ebb1 ; bin: 48 85 f6 75 07 + ; asm: testq %r10, %r10d + ; asm: jne ebb1 + brnz v3, ebb1 ; bin: 4d 85 d2 75 02 + + ; asm: jmp ebb2 + jump ebb2 ; bin: eb 01 + + ; asm: ebb1: +ebb1: return ; bin: c3 + + ; asm: ebb2: +ebb2: + jump ebb1 ; bin: eb fd } ; Tests for i32 instructions in 64-bit mode. @@ -384,5 +412,33 @@ ebb0: ; asm: tzcntl %ecx, %r10d [-,%r10] v208 = ctz v1 ; bin: f3 44 0f bc d1 + ; asm: testl %ecx, %ecx + ; asm: je ebb1 + brz v1, ebb1 ; bin: 40 85 c9 74 1b + ; asm: testl %esi, %esi + ; asm: je ebb1 + brz v2, ebb1 ; bin: 40 85 f6 74 16 + ; asm: testl %r10d, %r10d + ; asm: je ebb1 + brz v3, ebb1 ; bin: 45 85 d2 74 11 + ; asm: testl %ecx, %ecx + ; asm: jne ebb1 + brnz v1, ebb1 ; bin: 40 85 c9 75 0c + ; asm: test %esi, %esi + ; asm: jne ebb1 + brnz v2, ebb1 ; bin: 40 85 f6 75 07 + ; asm: testl %r10d, %r10d + ; asm: jne ebb1 + brnz v3, ebb1 ; bin: 45 85 d2 75 02 + + ; asm: jmp ebb2 + jump ebb2 ; bin: eb 01 + + ; asm: ebb1: +ebb1: return ; bin: c3 + + ; asm: ebb2: +ebb2: + jump ebb1 ; bin: eb fd } diff --git a/filetests/wasm/control.cton b/filetests/wasm/control.cton new file mode 100644 index 0000000000..f29c19b5c5 --- /dev/null +++ b/filetests/wasm/control.cton @@ -0,0 +1,34 @@ +; Test basic code generation for control flow WebAssembly instructions. +test compile + +set is_64bit=0 +isa intel haswell + +set is_64bit=1 +isa intel haswell + +function %br_if(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 1 + brz v0, ebb1(v1) + jump ebb2 + +ebb1(v2: i32): + return v2 + +ebb2: + jump ebb1(v0) +} + +function %br_if_not(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 1 + brnz v0, ebb1(v0) + jump ebb2 + +ebb1(v2: i32): + return v2 + +ebb2: + jump ebb1(v0) +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 25421be999..f5e8aabd50 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -156,3 +156,21 @@ I32.enc(base.call, *r.call_id(0xe8)) I32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2)) I32.enc(base.x_return, *r.ret(0xc3)) I64.enc(base.x_return, *r.ret(0xc3)) + +# +# Branches +# +I32.enc(base.jump, *r.jmpb(0xeb)) +I32.enc(base.jump, *r.jmpd(0xe9)) +I64.enc(base.jump, *r.jmpb(0xeb)) +I64.enc(base.jump, *r.jmpd(0xe9)) + +I32.enc(base.brz.i32, *r.tjccb(0x74)) +I64.enc(base.brz.i64, *r.tjccb.rex(0x74, w=1)) +I64.enc(base.brz.i32, *r.tjccb.rex(0x74)) +I64.enc(base.brz.i32, *r.tjccb(0x74)) + +I32.enc(base.brnz.i32, *r.tjccb(0x75)) +I64.enc(base.brnz.i64, *r.tjccb.rex(0x75, w=1)) +I64.enc(base.brnz.i32, *r.tjccb.rex(0x75)) +I64.enc(base.brnz.i32, *r.tjccb(0x75)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 9c48a529d2..f68a8344b3 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -6,7 +6,7 @@ from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry from base.formats import Call, IndirectCall, Store, Load -from base.formats import RegMove, Ternary +from base.formats import RegMove, Ternary, Jump, Branch from .registers import GPR, ABCD try: @@ -420,3 +420,47 @@ ret = TailRecipe( emit=''' PUT_OP(bits, BASE_REX, sink); ''') + +# +# Branches +# +jmpb = TailRecipe( + 'jmpb', Jump, size=1, ins=(), outs=(), + branch_range=(2, 8), + emit=''' + PUT_OP(bits, BASE_REX, sink); + disp1(destination, func, sink); + ''') + +jmpd = TailRecipe( + 'jmpd', Jump, size=4, ins=(), outs=(), + branch_range=(5, 32), + emit=''' + PUT_OP(bits, BASE_REX, sink); + disp4(destination, func, sink); + ''') + +# Test-and-branch. +# +# This recipe represents the macro fusion of a test and a conditional branch. +# This serves two purposes: +# +# 1. Guarantee that the test and branch get scheduled next to each other so +# macro fusion is guaranteed to be possible. +# 2. Hide the status flags from Cretonne which doesn't currently model flags. +# +# The encoding bits affect both the test and the branch instruction: +# +# Bits 0-7 are the Jcc opcode. +# Bits 8-15 control the test instruction which always has opcode byte 0x85. +tjccb = TailRecipe( + 'tjcc', Branch, size=1 + 2, ins=GPR, outs=(), + branch_range=(2, 8), + emit=''' + // test r, r. + PUT_OP((bits & 0xff00) | 0x85, rex2(in_reg0, in_reg0), sink); + modrm_rr(in_reg0, in_reg0, sink); + // Jcc instruction. + sink.put1(bits as u8); + disp1(destination, func, sink); + ''') diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 61c6f512a9..0f3b1b76c6 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -1,7 +1,7 @@ //! Emitting binary Intel machine code. use binemit::{CodeSink, Reloc, bad_encoding}; -use ir::{Function, Inst, InstructionData}; +use ir::{Function, Inst, Ebb, InstructionData}; use isa::RegUnit; use regalloc::RegDiversions; @@ -169,3 +169,15 @@ fn modrm_disp32(rm: RegUnit, reg: RegUnit, sink: &mut CS) b |= rm; sink.put1(b); } + +/// Emit a single-byte branch displacement to `destination`. +fn disp1(destination: Ebb, func: &Function, sink: &mut CS) { + let delta = func.offsets[destination].wrapping_sub(sink.offset() + 1); + sink.put1(delta as u8); +} + +/// Emit a single-byte branch displacement to `destination`. +fn disp4(destination: Ebb, func: &Function, sink: &mut CS) { + let delta = func.offsets[destination].wrapping_sub(sink.offset() + 4); + sink.put4(delta); +} From 5a81831c698e113d468b780b67105dfc09858d41 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 09:30:04 -0700 Subject: [PATCH 882/968] Don't require that the fallthrough instruction has an encoding. A fallthrough jump is actually represented as 0 bytes, so no encoding is needed. Also allow for unencoded instructions in the generated emit_inst implementations. The verifier has stricter rules for when this is allowed. --- filetests/wasm/control.cton | 11 +++++++++++ lib/cretonne/meta/gen_binemit.py | 8 ++++++-- lib/cretonne/src/verifier/mod.rs | 8 +++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/filetests/wasm/control.cton b/filetests/wasm/control.cton index f29c19b5c5..0e82b05245 100644 --- a/filetests/wasm/control.cton +++ b/filetests/wasm/control.cton @@ -32,3 +32,14 @@ ebb1(v2: i32): ebb2: jump ebb1(v0) } + +function %br_if_fallthrough(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 1 + brz v0, ebb1(v1) + ; This jump gets converted to a fallthrough. + jump ebb1(v0) + +ebb1(v2: i32): + return v2 +} diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py index 8e734c5ff1..fddadf64e5 100644 --- a/lib/cretonne/meta/gen_binemit.py +++ b/lib/cretonne/meta/gen_binemit.py @@ -135,14 +135,18 @@ def gen_isa(isa, fmt): 'pub fn emit_inst' '(func: &Function, inst: Inst, ' 'divert: &mut RegDiversions, sink: &mut CS) {', '}'): - fmt.line('let bits = func.encodings[inst].bits();') + fmt.line('let encoding = func.encodings[inst];') + fmt.line('let bits = encoding.bits();') with fmt.indented('match func.encodings[inst].recipe() {', '}'): for i, recipe in enumerate(isa.all_recipes): fmt.comment(recipe.name) with fmt.indented('{} => {{'.format(i), '}'): gen_recipe(recipe, fmt) fmt.line('_ => {}') - fmt.line('bad_encoding(func, inst);') + # Allow for un-encoded ghost instructions. + # Verifier checks the details. + with fmt.indented('if encoding.is_legal() {', '}'): + fmt.line('bad_encoding(func, inst);') def generate(isas, out_dir): diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 2652579020..518c9a703f 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -57,7 +57,7 @@ use flowgraph::ControlFlowGraph; use ir::entities::AnyEntity; use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, - Value, Type}; + Value, Type, Opcode}; use isa::TargetIsa; use std::error as std_error; use std::fmt::{self, Display, Formatter}; @@ -711,6 +711,12 @@ impl<'a> Verifier<'a> { // Instructions with side effects are not allowed to be ghost instructions. let opcode = self.func.dfg[inst].opcode(); + // The `fallthrough` instruction is marked as a terminator and a branch, but it is not + // required to have an encoding. + if opcode == Opcode::Fallthrough { + return Ok(()); + } + if opcode.is_branch() { return err!(inst, "Branch must have an encoding"); } From 82fbc78f2fb95f7854ef26cad1c469ff0deafade Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 10:47:51 -0700 Subject: [PATCH 883/968] Add Intel encodings for the icmp instruction. This instruction returns a `b1` value which is represented as the output of a setCC instruction which is the low 8 bits of a GPR register. Use a cmp+setCC macro recipe to encode this. That is not ideal, but we can't represent CPU flags yet. --- filetests/isa/intel/binary32.cton | 72 +++++++++ filetests/isa/intel/binary64.cton | 184 ++++++++++++++++++++--- lib/cretonne/meta/base/legalize.py | 13 +- lib/cretonne/meta/isa/intel/encodings.py | 8 + lib/cretonne/meta/isa/intel/recipes.py | 42 ++++++ 5 files changed, 297 insertions(+), 22 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 7b43ed7aa2..2ee9396bf0 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -254,6 +254,78 @@ ebb0: ; asm: tzcntl %ecx, %esi [-,%rsi] v205 = ctz v1 ; bin: f3 0f bc f1 + ; Integer comparisons. + + ; asm: cmpl %esi, %ecx + ; asm: sete %bl + [-,%rbx] v300 = icmp eq v1, v2 ; bin: 39 f1 0f 94 c3 + ; asm: cmpl %ecx, %esi + ; asm: sete %dl + [-,%rdx] v301 = icmp eq v2, v1 ; bin: 39 ce 0f 94 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setne %bl + [-,%rbx] v302 = icmp ne v1, v2 ; bin: 39 f1 0f 95 c3 + ; asm: cmpl %ecx, %esi + ; asm: setne %dl + [-,%rdx] v303 = icmp ne v2, v1 ; bin: 39 ce 0f 95 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setl %bl + [-,%rbx] v304 = icmp slt v1, v2 ; bin: 39 f1 0f 9c c3 + ; asm: cmpl %ecx, %esi + ; asm: setl %dl + [-,%rdx] v305 = icmp slt v2, v1 ; bin: 39 ce 0f 9c c2 + + ; asm: cmpl %esi, %ecx + ; asm: setge %bl + [-,%rbx] v306 = icmp sge v1, v2 ; bin: 39 f1 0f 9d c3 + ; asm: cmpl %ecx, %esi + ; asm: setge %dl + [-,%rdx] v307 = icmp sge v2, v1 ; bin: 39 ce 0f 9d c2 + + ; asm: cmpl %esi, %ecx + ; asm: setg %bl + [-,%rbx] v308 = icmp sgt v1, v2 ; bin: 39 f1 0f 9f c3 + ; asm: cmpl %ecx, %esi + ; asm: setg %dl + [-,%rdx] v309 = icmp sgt v2, v1 ; bin: 39 ce 0f 9f c2 + + ; asm: cmpl %esi, %ecx + ; asm: setle %bl + [-,%rbx] v310 = icmp sle v1, v2 ; bin: 39 f1 0f 9e c3 + ; asm: cmpl %ecx, %esi + ; asm: setle %dl + [-,%rdx] v311 = icmp sle v2, v1 ; bin: 39 ce 0f 9e c2 + + ; asm: cmpl %esi, %ecx + ; asm: setb %bl + [-,%rbx] v312 = icmp ult v1, v2 ; bin: 39 f1 0f 92 c3 + ; asm: cmpl %ecx, %esi + ; asm: setb %dl + [-,%rdx] v313 = icmp ult v2, v1 ; bin: 39 ce 0f 92 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setae %bl + [-,%rbx] v314 = icmp uge v1, v2 ; bin: 39 f1 0f 93 c3 + ; asm: cmpl %ecx, %esi + ; asm: setae %dl + [-,%rdx] v315 = icmp uge v2, v1 ; bin: 39 ce 0f 93 c2 + + ; asm: cmpl %esi, %ecx + ; asm: seta %bl + [-,%rbx] v316 = icmp ugt v1, v2 ; bin: 39 f1 0f 97 c3 + ; asm: cmpl %ecx, %esi + ; asm: seta %dl + [-,%rdx] v317 = icmp ugt v2, v1 ; bin: 39 ce 0f 97 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setbe %bl + [-,%rbx] v318 = icmp ule v1, v2 ; bin: 39 f1 0f 96 c3 + ; asm: cmpl %ecx, %esi + ; asm: setbe %dl + [-,%rdx] v319 = icmp ule v2, v1 ; bin: 39 ce 0f 96 c2 + ; asm: call foo call fn0() ; bin: e8 PCRel4(fn0) 00000000 diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 1d88b69d50..00524afc4e 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -192,22 +192,94 @@ ebb0: ; asm: tzcntq %rcx, %r10 [-,%r10] v208 = ctz v1 ; bin: f3 4c 0f bc d1 - ; asm: testq %rcx, %ecx + ; Integer comparisons. + + ; asm: cmpq %rsi, %rcx + ; asm: sete %bl + [-,%rbx] v300 = icmp eq v1, v2 ; bin: 48 39 f1 0f 94 c3 + ; asm: cmpq %r10, %rsi + ; asm: sete %dl + [-,%rdx] v301 = icmp eq v2, v3 ; bin: 4c 39 d6 0f 94 c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setne %bl + [-,%rbx] v302 = icmp ne v1, v2 ; bin: 48 39 f1 0f 95 c3 + ; asm: cmpq %r10, %rsi + ; asm: setne %dl + [-,%rdx] v303 = icmp ne v2, v3 ; bin: 4c 39 d6 0f 95 c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setl %bl + [-,%rbx] v304 = icmp slt v1, v2 ; bin: 48 39 f1 0f 9c c3 + ; asm: cmpq %r10, %rsi + ; asm: setl %dl + [-,%rdx] v305 = icmp slt v2, v3 ; bin: 4c 39 d6 0f 9c c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setge %bl + [-,%rbx] v306 = icmp sge v1, v2 ; bin: 48 39 f1 0f 9d c3 + ; asm: cmpq %r10, %rsi + ; asm: setge %dl + [-,%rdx] v307 = icmp sge v2, v3 ; bin: 4c 39 d6 0f 9d c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setg %bl + [-,%rbx] v308 = icmp sgt v1, v2 ; bin: 48 39 f1 0f 9f c3 + ; asm: cmpq %r10, %rsi + ; asm: setg %dl + [-,%rdx] v309 = icmp sgt v2, v3 ; bin: 4c 39 d6 0f 9f c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setle %bl + [-,%rbx] v310 = icmp sle v1, v2 ; bin: 48 39 f1 0f 9e c3 + ; asm: cmpq %r10, %rsi + ; asm: setle %dl + [-,%rdx] v311 = icmp sle v2, v3 ; bin: 4c 39 d6 0f 9e c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setb %bl + [-,%rbx] v312 = icmp ult v1, v2 ; bin: 48 39 f1 0f 92 c3 + ; asm: cmpq %r10, %rsi + ; asm: setb %dl + [-,%rdx] v313 = icmp ult v2, v3 ; bin: 4c 39 d6 0f 92 c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setae %bl + [-,%rbx] v314 = icmp uge v1, v2 ; bin: 48 39 f1 0f 93 c3 + ; asm: cmpq %r10, %rsi + ; asm: setae %dl + [-,%rdx] v315 = icmp uge v2, v3 ; bin: 4c 39 d6 0f 93 c2 + + ; asm: cmpq %rsi, %rcx + ; asm: seta %bl + [-,%rbx] v316 = icmp ugt v1, v2 ; bin: 48 39 f1 0f 97 c3 + ; asm: cmpq %r10, %rsi + ; asm: seta %dl + [-,%rdx] v317 = icmp ugt v2, v3 ; bin: 4c 39 d6 0f 97 c2 + + ; asm: cmpq %rsi, %rcx + ; asm: setbe %bl + [-,%rbx] v318 = icmp ule v1, v2 ; bin: 48 39 f1 0f 96 c3 + ; asm: cmpq %r10, %rsi + ; asm: setbe %dl + [-,%rdx] v319 = icmp ule v2, v3 ; bin: 4c 39 d6 0f 96 c2 + + ; asm: testq %rcx, %rcx ; asm: je ebb1 brz v1, ebb1 ; bin: 48 85 c9 74 1b - ; asm: testq %rsi, %esi + ; asm: testq %rsi, %rsi ; asm: je ebb1 brz v2, ebb1 ; bin: 48 85 f6 74 16 - ; asm: testq %r10, %r10d + ; asm: testq %r10, %r10 ; asm: je ebb1 brz v3, ebb1 ; bin: 4d 85 d2 74 11 - ; asm: testq %rcx, %ecx + ; asm: testq %rcx, %rcx ; asm: jne ebb1 brnz v1, ebb1 ; bin: 48 85 c9 75 0c - ; asm: test %rsi, %esi + ; asm: testq %rsi, %rsi ; asm: jne ebb1 brnz v2, ebb1 ; bin: 48 85 f6 75 07 - ; asm: testq %r10, %r10d + ; asm: testq %r10, %r10 ; asm: jne ebb1 brnz v3, ebb1 ; bin: 4d 85 d2 75 02 @@ -376,15 +448,15 @@ ebb0: [-,%rax] v130 = iconst.i32 1 [-,%rdx] v131 = iconst.i32 2 - ; asm: idivl %rcx + ; asm: idivl %ecx [-,%rax,%rdx] v132, v133 = x86_sdivmodx v130, v131, v1 ; bin: 40 f7 f9 - ; asm: idivl %rsi + ; asm: idivl %esi [-,%rax,%rdx] v134, v135 = x86_sdivmodx v130, v131, v2 ; bin: 40 f7 fe ; asm: idivl %r10d [-,%rax,%rdx] v136, v137 = x86_sdivmodx v130, v131, v3 ; bin: 41 f7 fa - ; asm: divl %rcx + ; asm: divl %ecx [-,%rax,%rdx] v138, v139 = x86_udivmodx v130, v131, v1 ; bin: 40 f7 f1 - ; asm: divl %rsi + ; asm: divl %esi [-,%rax,%rdx] v140, v141 = x86_udivmodx v130, v131, v2 ; bin: 40 f7 f6 ; asm: divl %r10d [-,%rax,%rdx] v142, v143 = x86_udivmodx v130, v131, v3 ; bin: 41 f7 f2 @@ -412,33 +484,105 @@ ebb0: ; asm: tzcntl %ecx, %r10d [-,%r10] v208 = ctz v1 ; bin: f3 44 0f bc d1 + ; Integer comparisons. + + ; asm: cmpl %esi, %ecx + ; asm: sete %bl + [-,%rbx] v300 = icmp eq v1, v2 ; bin: 40 39 f1 0f 94 c3 + ; asm: cmpl %r10d, %esi + ; asm: sete %dl + [-,%rdx] v301 = icmp eq v2, v3 ; bin: 44 39 d6 0f 94 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setne %bl + [-,%rbx] v302 = icmp ne v1, v2 ; bin: 40 39 f1 0f 95 c3 + ; asm: cmpl %r10d, %esi + ; asm: setne %dl + [-,%rdx] v303 = icmp ne v2, v3 ; bin: 44 39 d6 0f 95 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setl %bl + [-,%rbx] v304 = icmp slt v1, v2 ; bin: 40 39 f1 0f 9c c3 + ; asm: cmpl %r10d, %esi + ; asm: setl %dl + [-,%rdx] v305 = icmp slt v2, v3 ; bin: 44 39 d6 0f 9c c2 + + ; asm: cmpl %esi, %ecx + ; asm: setge %bl + [-,%rbx] v306 = icmp sge v1, v2 ; bin: 40 39 f1 0f 9d c3 + ; asm: cmpl %r10d, %esi + ; asm: setge %dl + [-,%rdx] v307 = icmp sge v2, v3 ; bin: 44 39 d6 0f 9d c2 + + ; asm: cmpl %esi, %ecx + ; asm: setg %bl + [-,%rbx] v308 = icmp sgt v1, v2 ; bin: 40 39 f1 0f 9f c3 + ; asm: cmpl %r10d, %esi + ; asm: setg %dl + [-,%rdx] v309 = icmp sgt v2, v3 ; bin: 44 39 d6 0f 9f c2 + + ; asm: cmpl %esi, %ecx + ; asm: setle %bl + [-,%rbx] v310 = icmp sle v1, v2 ; bin: 40 39 f1 0f 9e c3 + ; asm: cmpl %r10d, %esi + ; asm: setle %dl + [-,%rdx] v311 = icmp sle v2, v3 ; bin: 44 39 d6 0f 9e c2 + + ; asm: cmpl %esi, %ecx + ; asm: setb %bl + [-,%rbx] v312 = icmp ult v1, v2 ; bin: 40 39 f1 0f 92 c3 + ; asm: cmpl %r10d, %esi + ; asm: setb %dl + [-,%rdx] v313 = icmp ult v2, v3 ; bin: 44 39 d6 0f 92 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setae %bl + [-,%rbx] v314 = icmp uge v1, v2 ; bin: 40 39 f1 0f 93 c3 + ; asm: cmpl %r10d, %esi + ; asm: setae %dl + [-,%rdx] v315 = icmp uge v2, v3 ; bin: 44 39 d6 0f 93 c2 + + ; asm: cmpl %esi, %ecx + ; asm: seta %bl + [-,%rbx] v316 = icmp ugt v1, v2 ; bin: 40 39 f1 0f 97 c3 + ; asm: cmpl %r10d, %esi + ; asm: seta %dl + [-,%rdx] v317 = icmp ugt v2, v3 ; bin: 44 39 d6 0f 97 c2 + + ; asm: cmpl %esi, %ecx + ; asm: setbe %bl + [-,%rbx] v318 = icmp ule v1, v2 ; bin: 40 39 f1 0f 96 c3 + ; asm: cmpl %r10d, %esi + ; asm: setbe %dl + [-,%rdx] v319 = icmp ule v2, v3 ; bin: 44 39 d6 0f 96 c2 + ; asm: testl %ecx, %ecx - ; asm: je ebb1 + ; asm: je ebb1x brz v1, ebb1 ; bin: 40 85 c9 74 1b ; asm: testl %esi, %esi - ; asm: je ebb1 + ; asm: je ebb1x brz v2, ebb1 ; bin: 40 85 f6 74 16 ; asm: testl %r10d, %r10d - ; asm: je ebb1 + ; asm: je ebb1x brz v3, ebb1 ; bin: 45 85 d2 74 11 ; asm: testl %ecx, %ecx - ; asm: jne ebb1 + ; asm: jne ebb1x brnz v1, ebb1 ; bin: 40 85 c9 75 0c - ; asm: test %esi, %esi - ; asm: jne ebb1 + ; asm: testl %esi, %esi + ; asm: jne ebb1x brnz v2, ebb1 ; bin: 40 85 f6 75 07 ; asm: testl %r10d, %r10d - ; asm: jne ebb1 + ; asm: jne ebb1x brnz v3, ebb1 ; bin: 45 85 d2 75 02 - ; asm: jmp ebb2 + ; asm: jmp ebb2x jump ebb2 ; bin: eb 01 - ; asm: ebb1: + ; asm: ebb1x: ebb1: return ; bin: c3 - ; asm: ebb2: + ; asm: ebb2x: ebb2: jump ebb1 ; bin: eb fd } diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index 8211a0039f..058c87f062 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -11,7 +11,8 @@ from .immediates import intcc from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm from .instructions import isub, isub_bin, isub_bout, isub_borrow from .instructions import band, bor, bxor, isplit, iconcat -from .instructions import icmp, iconst, bint +from .instructions import icmp, icmp_imm +from .instructions import iconst, bint from cdsl.ast import Var from cdsl.xform import Rtl, XFormGroup @@ -53,6 +54,7 @@ yl = Var('yl') yh = Var('yh') al = Var('al') ah = Var('ah') +cc = Var('cc') narrow.legalize( a << iadd(x, y), @@ -135,10 +137,17 @@ expand.legalize( b << bor(b1, b2) )) -# Expansions for immediates that are too large. +# Expansions for immediate operands that are out of range. expand.legalize( a << iadd_imm(x, y), Rtl( a1 << iconst(y), a << iadd(x, a1) )) + +expand.legalize( + a << icmp_imm(cc, x, y), + Rtl( + a1 << iconst(y), + a << icmp(cc, x, a1) + )) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index f5e8aabd50..9af8d6484d 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -174,3 +174,11 @@ I32.enc(base.brnz.i32, *r.tjccb(0x75)) I64.enc(base.brnz.i64, *r.tjccb.rex(0x75, w=1)) I64.enc(base.brnz.i32, *r.tjccb.rex(0x75)) I64.enc(base.brnz.i32, *r.tjccb(0x75)) + +# +# Comparisons +# +I32.enc(base.icmp.i32, *r.icscc(0x39)) +I64.enc(base.icmp.i64, *r.icscc.rex(0x39, w=1)) +I64.enc(base.icmp.i32, *r.icscc.rex(0x39)) +I64.enc(base.icmp.i32, *r.icscc(0x39)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index f68a8344b3..7fa7ca1bf3 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -6,6 +6,7 @@ from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry from base.formats import Call, IndirectCall, Store, Load +from base.formats import IntCompare from base.formats import RegMove, Ternary, Jump, Branch from .registers import GPR, ABCD @@ -464,3 +465,44 @@ tjccb = TailRecipe( sink.put1(bits as u8); disp1(destination, func, sink); ''') + +# Comparison that produces a `b1` result in a GPR. +# +# This is a macro of a `cmp` instruction followed by a `setCC` instruction. +# This is not a great solution because: +# +# - The cmp+setcc combination is not recognized by CPU's macro fusion. +# - The 64-bit encoding has issues with REX prefixes. The `cmp` and `setCC` +# instructions may need a REX independently. +# - Modeling CPU flags in the type system would be better. +# +# Since the `setCC` instructions only write an 8-bit register, we use that as +# our `b1` representation: A `b1` value is represented as a GPR where the low 8 +# bits are known to be 0 or 1. The high bits are undefined. +# +# This bandaid macro doesn't support a REX prefix for the final `setCC` +# instruction, so it is limited to the `ABCD` register class for booleans. +icscc = TailRecipe( + 'cscc', IntCompare, size=1 + 3, ins=(GPR, GPR), outs=ABCD, + emit=''' + // Comparison instruction. + PUT_OP(bits, rex2(in_reg0, in_reg1), sink); + modrm_rr(in_reg0, in_reg1, sink); + // `setCC` instruction, no REX. + use ir::condcodes::IntCC::*; + let setcc = match cond { + Equal => 0x94, + NotEqual => 0x95, + SignedLessThan => 0x9c, + SignedGreaterThanOrEqual => 0x9d, + SignedGreaterThan => 0x9f, + SignedLessThanOrEqual => 0x9e, + UnsignedLessThan => 0x92, + UnsignedGreaterThanOrEqual => 0x93, + UnsignedGreaterThan => 0x97, + UnsignedLessThanOrEqual => 0x96, + }; + sink.put1(0x0f); + sink.put1(setcc); + modrm_rr(out_reg0, 0, sink); + ''') From 265bd351bde107ce3f73ca664e8e709083519b29 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 12:01:28 -0700 Subject: [PATCH 884/968] Add Intel encodings for the bint instructions. Convert b1 to i32 or i64 by zero-extending the byte. --- filetests/isa/intel/binary32.cton | 7 +++++++ filetests/isa/intel/binary64.cton | 14 ++++++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 11 +++++++++++ lib/cretonne/meta/isa/intel/recipes.py | 8 ++++++++ 4 files changed, 40 insertions(+) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 2ee9396bf0..7e95f84818 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -326,6 +326,13 @@ ebb0: ; asm: setbe %dl [-,%rdx] v319 = icmp ule v2, v1 ; bin: 39 ce 0f 96 c2 + ; Bool-to-int conversions. + + ; asm: movzbl %bl, %ecx + [-,%rcx] v350 = bint.i32 v300 ; bin: 0f b6 cb + ; asm: movzbl %dl, %esi + [-,%rsi] v351 = bint.i32 v301 ; bin: 0f b6 f2 + ; asm: call foo call fn0() ; bin: e8 PCRel4(fn0) 00000000 diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 00524afc4e..6222a1e7be 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -264,6 +264,13 @@ ebb0: ; asm: setbe %dl [-,%rdx] v319 = icmp ule v2, v3 ; bin: 4c 39 d6 0f 96 c2 + ; Bool-to-int conversions. + + ; asm: movzbq %bl, %rcx + [-,%rcx] v350 = bint.i64 v300 ; bin: 48 0f b6 cb + ; asm: movzbq %dl, %rsi + [-,%rsi] v351 = bint.i64 v301 ; bin: 48 0f b6 f2 + ; asm: testq %rcx, %rcx ; asm: je ebb1 brz v1, ebb1 ; bin: 48 85 c9 74 1b @@ -556,6 +563,13 @@ ebb0: ; asm: setbe %dl [-,%rdx] v319 = icmp ule v2, v3 ; bin: 44 39 d6 0f 96 c2 + ; Bool-to-int conversions. + + ; asm: movzbl %bl, %ecx + [-,%rcx] v350 = bint.i32 v300 ; bin: 40 0f b6 cb + ; asm: movzbl %dl, %esi + [-,%rsi] v351 = bint.i32 v301 ; bin: 40 0f b6 f2 + ; asm: testl %ecx, %ecx ; asm: je ebb1x brz v1, ebb1 ; bin: 40 85 c9 74 1b diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 9af8d6484d..6b04ae110d 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -182,3 +182,14 @@ I32.enc(base.icmp.i32, *r.icscc(0x39)) I64.enc(base.icmp.i64, *r.icscc.rex(0x39, w=1)) I64.enc(base.icmp.i32, *r.icscc.rex(0x39)) I64.enc(base.icmp.i32, *r.icscc(0x39)) + +# +# Convert bool to int. +# +# This assumes that b1 is represented as an 8-bit low register with the value 0 +# or 1. +I32.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6)) +I64.enc(base.bint.i64.b1, *r.urm.rex(0x0f, 0xb6, w=1)) +I64.enc(base.bint.i64.b1, *r.urm_abcd(0x0f, 0xb6)) # zext to i64 implicit. +I64.enc(base.bint.i32.b1, *r.urm.rex(0x0f, 0xb6)) +I64.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 7fa7ca1bf3..8ae65f4723 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -224,6 +224,14 @@ urm = TailRecipe( modrm_rr(in_reg0, out_reg0, sink); ''') +# XX /r. Same as urm, but input limited to ABCD. +urm_abcd = TailRecipe( + 'urm_abcd', Unary, size=1, ins=ABCD, outs=GPR, + emit=''' + PUT_OP(bits, rex2(in_reg0, out_reg0), sink); + modrm_rr(in_reg0, out_reg0, sink); + ''') + # XX /r, for regmove instructions. rmov = TailRecipe( 'ur', RegMove, size=1, ins=GPR, outs=(), From bd55bd74cc926a8fe8e019f02fb98fe548cfc4c6 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 12:36:36 -0700 Subject: [PATCH 885/968] Add tests for WebAssembly i32 comparisons. One function for each comparison operator. --- filetests/wasm/i32-compares.cton | 85 ++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 filetests/wasm/i32-compares.cton diff --git a/filetests/wasm/i32-compares.cton b/filetests/wasm/i32-compares.cton new file mode 100644 index 0000000000..228258d279 --- /dev/null +++ b/filetests/wasm/i32-compares.cton @@ -0,0 +1,85 @@ +; Test code generation for WebAssembly i32 comparison operators. +test compile + +set is_64bit=0 +isa intel haswell + +set is_64bit=1 +isa intel haswell + +function %i32_eqz(i32) -> i32 { +ebb0(v0: i32): + v1 = icmp_imm eq v0, 0 + v2 = bint.i32 v1 + return v2 +} + +function %i32_eq(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp eq v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_ne(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp ne v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_lt_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp slt v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_lt_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp ult v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_gt_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp sgt v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_gt_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp ugt v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_le_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp sle v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_le_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp ule v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_ge_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp sge v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i32_ge_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = icmp uge v0, v1 + v3 = bint.i32 v2 + return v3 +} From 13190fd5124615aa1f69a53c45c47b1e1ced5c16 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 12:56:54 -0700 Subject: [PATCH 886/968] Add tests for WebAssembly i64 operators. This only works on 64-bit haswell for now. We need more legalization patterns for 32-bit ISAs. --- filetests/wasm/i64-arith.cton | 105 +++++++++++++++++++++++++++++++ filetests/wasm/i64-compares.cton | 82 ++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 filetests/wasm/i64-arith.cton create mode 100644 filetests/wasm/i64-compares.cton diff --git a/filetests/wasm/i64-arith.cton b/filetests/wasm/i64-arith.cton new file mode 100644 index 0000000000..a7cce18d3c --- /dev/null +++ b/filetests/wasm/i64-arith.cton @@ -0,0 +1,105 @@ +; Test basic code generation for i64 arithmetic WebAssembly instructions. +test compile + +set is_64bit=1 +isa intel haswell + +; Constants. + +function %i64_const() -> i64 { +ebb0: + v0 = iconst.i64 0x8765_4321 + return v0 +} + +; Unary operations. + +function %i64_clz(i64) -> i64 { +ebb0(v0: i64): + v1 = clz v0 + return v1 +} + +function %i64_ctz(i64) -> i64 { +ebb0(v0: i64): + v1 = ctz v0 + return v1 +} + +function %i64_popcnt(i64) -> i64 { +ebb0(v0: i64): + v1 = popcnt v0 + return v1 +} + +; Binary operations. + +function %i64_add(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = iadd v0, v1 + return v2 +} + +function %i64_sub(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = isub v0, v1 + return v2 +} + +function %i64_mul(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = imul v0, v1 + return v2 +} + +; function %i64_div(i64, i64) -> i64 +; function %i64_rem_s(i64, i64) -> i64 +; function %i64_rem_u(i64, i64) -> i64 + +function %i64_and(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = band v0, v1 + return v2 +} + +function %i64_or(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = bor v0, v1 + return v2 +} + +function %i64_xor(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = bxor v0, v1 + return v2 +} + +function %i64_shl(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = ishl v0, v1 + return v2 +} + +function %i64_shr_s(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = sshr v0, v1 + return v2 +} + +function %i64_shr_u(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = ushr v0, v1 + return v2 +} + +function %i64_rotl(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = rotl v0, v1 + return v2 +} + +function %i64_rotr(i64, i64) -> i64 { +ebb0(v0: i64, v1: i64): + v2 = rotr v0, v1 + return v2 +} diff --git a/filetests/wasm/i64-compares.cton b/filetests/wasm/i64-compares.cton new file mode 100644 index 0000000000..3406463f0d --- /dev/null +++ b/filetests/wasm/i64-compares.cton @@ -0,0 +1,82 @@ +; Test code generation for WebAssembly i64 comparison operators. +test compile + +set is_64bit=1 +isa intel haswell + +function %i64_eqz(i64) -> i32 { +ebb0(v0: i64): + v1 = icmp_imm eq v0, 0 + v2 = bint.i32 v1 + return v2 +} + +function %i64_eq(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp eq v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_ne(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp ne v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_lt_s(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp slt v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_lt_u(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp ult v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_gt_s(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp sgt v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_gt_u(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp ugt v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_le_s(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp sle v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_le_u(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp ule v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_ge_s(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp sge v0, v1 + v3 = bint.i32 v2 + return v3 +} + +function %i64_ge_u(i64, i64) -> i32 { +ebb0(v0: i64, v1: i64): + v2 = icmp uge v0, v1 + v3 = bint.i32 v2 + return v3 +} From 444f955466eccbdd6b75b41a7548e5ee20e4b9ad Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 13:11:11 -0700 Subject: [PATCH 887/968] Add a null encoding for ireduce.i32.i64. This conversion doesn't require any code, we're just looking at the bits differently. --- filetests/wasm/conversions.cton | 11 +++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 5 +++++ lib/cretonne/meta/isa/intel/recipes.py | 4 ++++ 3 files changed, 20 insertions(+) create mode 100644 filetests/wasm/conversions.cton diff --git a/filetests/wasm/conversions.cton b/filetests/wasm/conversions.cton new file mode 100644 index 0000000000..459e20e3bf --- /dev/null +++ b/filetests/wasm/conversions.cton @@ -0,0 +1,11 @@ +; Test code generation for WebAssembly type conversion operators. +test compile + +set is_64bit=1 +isa intel haswell + +function %i32_wrap_i64(i64) -> i32 { +ebb0(v0: i64): + v1 = ireduce.i32 v0 + return v1 +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 6b04ae110d..db039f36a1 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -193,3 +193,8 @@ I64.enc(base.bint.i64.b1, *r.urm.rex(0x0f, 0xb6, w=1)) I64.enc(base.bint.i64.b1, *r.urm_abcd(0x0f, 0xb6)) # zext to i64 implicit. I64.enc(base.bint.i32.b1, *r.urm.rex(0x0f, 0xb6)) I64.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6)) + +# Numerical conversions. + +# Converting i64 to i32 is a no-op in 64-bit mode. +I64.enc(base.ireduce.i32.i64, r.null, 0) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 8ae65f4723..5ff1e8b225 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -190,6 +190,10 @@ class TailRecipe: return (self.recipes[name], bits) +# A null unary instruction that takes a GPR register. Can be used for identity +# copies and no-op conversions. +null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='') + # XX /r rr = TailRecipe( 'rr', Binary, size=1, ins=(GPR, GPR), outs=0, From b804bc8fbc0eea1ab622ed898cdaa67f20718a90 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 13:40:11 -0700 Subject: [PATCH 888/968] Add Intel encodings for sextend and uextend. --- filetests/isa/intel/binary64.cton | 28 ++++++++++++++++++++++++ filetests/wasm/conversions.cton | 12 ++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 4 ++++ 3 files changed, 44 insertions(+) diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 6222a1e7be..3e332e2175 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -600,3 +600,31 @@ ebb1: ebb2: jump ebb1 ; bin: eb fd } + +; Tests for i64/i32 conversion instructions. +function %I64_I32() { +ebb0: + [-,%rcx] v1 = iconst.i64 1 + [-,%rsi] v2 = iconst.i64 2 + [-,%r10] v3 = iconst.i64 3 + + [-,%rcx] v11 = ireduce.i32 v1 ; bin: + [-,%rsi] v12 = ireduce.i32 v2 ; bin: + [-,%r10] v13 = ireduce.i32 v3 ; bin: + + ; asm: movslq %ecx, %rsi + [-,%rsi] v20 = sextend.i64 v11 ; bin: 48 63 f1 + ; asm: movslq %esi, %r10 + [-,%r10] v21 = sextend.i64 v12 ; bin: 4c 63 d6 + ; asm: movslq %r10d, %rcx + [-,%rcx] v22 = sextend.i64 v13 ; bin: 49 63 ca + + ; asm: movl %ecx, %esi + [-,%rsi] v30 = uextend.i64 v11 ; bin: 40 89 ce + ; asm: movl %esi, %r10d + [-,%r10] v31 = uextend.i64 v12 ; bin: 41 89 f2 + ; asm: movl %r10d, %ecx + [-,%rcx] v32 = uextend.i64 v13 ; bin: 44 89 d1 + + return +} diff --git a/filetests/wasm/conversions.cton b/filetests/wasm/conversions.cton index 459e20e3bf..a30cf97226 100644 --- a/filetests/wasm/conversions.cton +++ b/filetests/wasm/conversions.cton @@ -9,3 +9,15 @@ ebb0(v0: i64): v1 = ireduce.i32 v0 return v1 } + +function %i64_extend_s_i32(i32) -> i64 { +ebb0(v0: i32): + v1 = sextend.i64 v0 + return v1 +} + +function %i64_extend_u_i32(i32) -> i64 { +ebb0(v0: i32): + v1 = uextend.i64 v0 + return v1 +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index db039f36a1..16074cd849 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -198,3 +198,7 @@ I64.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6)) # Converting i64 to i32 is a no-op in 64-bit mode. I64.enc(base.ireduce.i32.i64, r.null, 0) +I64.enc(base.sextend.i64.i32, *r.urm.rex(0x63, w=1)) +# A 32-bit register copy clears the high 32 bits. +I64.enc(base.uextend.i64.i32, *r.umr.rex(0x89)) +I64.enc(base.uextend.i64.i32, *r.umr(0x89)) From e8acad5070d668b9e320e1dd747fbbeae74ad83a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 15:01:32 -0700 Subject: [PATCH 889/968] Intel encodings for trap. Use a ud2 instruction which generates an undefined instruction exception. --- filetests/isa/intel/binary32.cton | 2 +- filetests/isa/intel/binary64.cton | 2 +- filetests/wasm/control.cton | 5 +++++ lib/cretonne/meta/isa/intel/encodings.py | 6 ++++++ lib/cretonne/meta/isa/intel/recipes.py | 7 ++++++- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 7e95f84818..456623a235 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -364,5 +364,5 @@ ebb1: ; asm: ebb2: ebb2: - jump ebb1 ; bin: eb fd + trap ; bin: 0f 0b } diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 3e332e2175..3d93ba86ec 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -626,5 +626,5 @@ ebb0: ; asm: movl %r10d, %ecx [-,%rcx] v32 = uextend.i64 v13 ; bin: 44 89 d1 - return + trap ; bin: 0f 0b } diff --git a/filetests/wasm/control.cton b/filetests/wasm/control.cton index 0e82b05245..c8e37a536f 100644 --- a/filetests/wasm/control.cton +++ b/filetests/wasm/control.cton @@ -43,3 +43,8 @@ ebb0(v0: i32): ebb1(v2: i32): return v2 } + +function %undefined() { +ebb0: + trap +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 16074cd849..fcf114ef3e 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -175,6 +175,12 @@ I64.enc(base.brnz.i64, *r.tjccb.rex(0x75, w=1)) I64.enc(base.brnz.i32, *r.tjccb.rex(0x75)) I64.enc(base.brnz.i32, *r.tjccb(0x75)) +# +# Trap as ud2 +# +I32.enc(base.trap, *r.noop(0x0f, 0x0b)) +I64.enc(base.trap, *r.noop(0x0f, 0x0b)) + # # Comparisons # diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 5ff1e8b225..efffc47319 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -5,7 +5,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry -from base.formats import Call, IndirectCall, Store, Load +from base.formats import Nullary, Call, IndirectCall, Store, Load from base.formats import IntCompare from base.formats import RegMove, Ternary, Jump, Branch from .registers import GPR, ABCD @@ -194,6 +194,11 @@ class TailRecipe: # copies and no-op conversions. null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='') +# XX opcode, no ModR/M. +noop = TailRecipe( + 'noop', Nullary, size=0, ins=(), outs=(), + emit='PUT_OP(bits, 0, sink);') + # XX /r rr = TailRecipe( 'rr', Binary, size=1, ins=(GPR, GPR), outs=0, From 4df6741a904c3101285b0da4354a64ef9f060284 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 19 Jul 2017 14:44:01 -0700 Subject: [PATCH 890/968] Add some signed int to float conversions. These map to single Intel instructions. The i64 to float conversions are not tested yet. The encoding tables can't yet differentiate instructions on a secondary type variable alone. --- filetests/isa/intel/binary32-float.cton | 34 ++++++++++++++++ filetests/isa/intel/binary64-float.cton | 49 ++++++++++++++++++++++++ filetests/wasm/conversions.cton | 26 +++++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 14 +++++++ lib/cretonne/meta/isa/intel/recipes.py | 10 ++++- 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 filetests/isa/intel/binary32-float.cton create mode 100644 filetests/isa/intel/binary64-float.cton diff --git a/filetests/isa/intel/binary32-float.cton b/filetests/isa/intel/binary32-float.cton new file mode 100644 index 0000000000..c59bf10217 --- /dev/null +++ b/filetests/isa/intel/binary32-float.cton @@ -0,0 +1,34 @@ +; Binary emission of 32-bit floating point code. +test binemit +isa intel has_sse2 + +; The binary encodings can be verified with the command: +; +; sed -ne 's/^ *; asm: *//p' filetests/isa/intel/binary32-float.cton | llvm-mc -show-encoding -triple=i386 +; + +function %F32() { +ebb0: + [-,%rcx] v0 = iconst.i32 1 + [-,%rsi] v1 = iconst.i32 2 + + ; asm: cvtsi2ss %ecx, %xmm5 + [-,%xmm5] v10 = fcvt_from_sint.f32 v0 ; bin: f3 0f 2a e9 + ; asm: cvtsi2ss %esi, %xmm2 + [-,%xmm2] v11 = fcvt_from_sint.f32 v1 ; bin: f3 0f 2a d6 + + return +} + +function %F64() { +ebb0: + [-,%rcx] v0 = iconst.i32 1 + [-,%rsi] v1 = iconst.i32 2 + + ; asm: cvtsi2sd %ecx, %xmm5 + [-,%xmm5] v10 = fcvt_from_sint.f64 v0 ; bin: f2 0f 2a e9 + ; asm: cvtsi2sd %esi, %xmm2 + [-,%xmm2] v11 = fcvt_from_sint.f64 v1 ; bin: f2 0f 2a d6 + + return +} diff --git a/filetests/isa/intel/binary64-float.cton b/filetests/isa/intel/binary64-float.cton new file mode 100644 index 0000000000..cf523b0e5b --- /dev/null +++ b/filetests/isa/intel/binary64-float.cton @@ -0,0 +1,49 @@ +; Binary emission of 64-bit floating point code. +test binemit +set is_64bit +isa intel has_sse2 + +; The binary encodings can be verified with the command: +; +; sed -ne 's/^ *; asm: *//p' filetests/isa/intel/binary64-float.cton | llvm-mc -show-encoding -triple=x86_64 +; + +function %F32() { +ebb0: + [-,%r11] v0 = iconst.i32 1 + [-,%rsi] v1 = iconst.i32 2 + [-,%rax] v2 = iconst.i64 11 + [-,%r14] v3 = iconst.i64 12 + + ; asm: cvtsi2ssl %r11d, %xmm5 + [-,%xmm5] v10 = fcvt_from_sint.f32 v0 ; bin: f3 41 0f 2a eb + ; asm: cvtsi2ssl %esi, %xmm10 + [-,%xmm10] v11 = fcvt_from_sint.f32 v1 ; bin: f3 44 0f 2a d6 + + ; asm: cvtsi2ssq %rax, %xmm5 + [-,%xmm5] v12 = fcvt_from_sint.f32 v2 ; TODO: f3 48 0f 2a e8 + ; asm: cvtsi2ssq %r14, %xmm10 + [-,%xmm10] v13 = fcvt_from_sint.f32 v3 ; TODO: f3 4d 0f 2a d6 + + return +} + +function %F64() { +ebb0: + [-,%r11] v0 = iconst.i32 1 + [-,%rsi] v1 = iconst.i32 2 + [-,%rax] v2 = iconst.i64 11 + [-,%r14] v3 = iconst.i64 12 + + ; asm: cvtsi2sdl %r11d, %xmm5 + [-,%xmm5] v10 = fcvt_from_sint.f64 v0 ; bin: f2 41 0f 2a eb + ; asm: cvtsi2sdl %esi, %xmm10 + [-,%xmm10] v11 = fcvt_from_sint.f64 v1 ; bin: f2 44 0f 2a d6 + + ; asm: cvtsi2sdq %rax, %xmm5 + [-,%xmm5] v12 = fcvt_from_sint.f64 v2 ; TODO: f2 48 0f 2a e8 + ; asm: cvtsi2sdq %r14, %xmm10 + [-,%xmm10] v13 = fcvt_from_sint.f64 v3 ; TODO: f2 4d 0f 2a d6 + + return +} diff --git a/filetests/wasm/conversions.cton b/filetests/wasm/conversions.cton index a30cf97226..6f2354d624 100644 --- a/filetests/wasm/conversions.cton +++ b/filetests/wasm/conversions.cton @@ -21,3 +21,29 @@ ebb0(v0: i32): v1 = uextend.i64 v0 return v1 } + +function %f32_convert_s_i32(i32) -> f32 { +ebb0(v0: i32): + v1 = fcvt_from_sint.f32 v0 + return v1 +} + +function %f64_convert_s_i32(i32) -> f64 { +ebb0(v0: i32): + v1 = fcvt_from_sint.f64 v0 + return v1 +} + +function %f32_convert_s_i64(i64) -> f32 { +ebb0(v0: i64): + v1 = fcvt_from_sint.f32 v0 + return v1 +} + +function %f64_convert_s_i64(i64) -> f64 { +ebb0(v0: i64): + v1 = fcvt_from_sint.f64 v0 + return v1 +} + +; TODO: f*_convert_u_i* (Don't exist on Intel). diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index fcf114ef3e..264c07000e 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -208,3 +208,17 @@ I64.enc(base.sextend.i64.i32, *r.urm.rex(0x63, w=1)) # A 32-bit register copy clears the high 32 bits. I64.enc(base.uextend.i64.i32, *r.umr.rex(0x89)) I64.enc(base.uextend.i64.i32, *r.umr(0x89)) + +# +# Floating point +# + +# cvtsi2ss +I32.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) +I64.enc(base.fcvt_from_sint.f32.i32, *r.furm.rex(0xf3, 0x0f, 0x2A)) +I64.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) + +# cvtsi2sd +I32.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) +I64.enc(base.fcvt_from_sint.f64.i32, *r.furm.rex(0xf2, 0x0f, 0x2A)) +I64.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index efffc47319..642e335f6a 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -8,7 +8,7 @@ from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry from base.formats import Nullary, Call, IndirectCall, Store, Load from base.formats import IntCompare from base.formats import RegMove, Ternary, Jump, Branch -from .registers import GPR, ABCD +from .registers import GPR, ABCD, FPR try: from typing import Tuple, Dict # noqa @@ -241,6 +241,14 @@ urm_abcd = TailRecipe( modrm_rr(in_reg0, out_reg0, sink); ''') +# XX /r, RM form, GPR -> FPR. +furm = TailRecipe( + 'furm', Unary, size=1, ins=GPR, outs=FPR, + emit=''' + PUT_OP(bits, rex2(in_reg0, out_reg0), sink); + modrm_rr(in_reg0, out_reg0, sink); + ''') + # XX /r, for regmove instructions. rmov = TailRecipe( 'ur', RegMove, size=1, ins=GPR, outs=(), From 014d9a14fe54bb4677b98ae277c56224c650eaeb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Jul 2017 10:08:09 -0700 Subject: [PATCH 891/968] Intel encodings for fadd, fsub, fmul, fdiv. --- filetests/isa/intel/binary32-float.cton | 44 ++++++++++++++++++++ filetests/isa/intel/binary64-float.cton | 44 ++++++++++++++++++++ filetests/wasm/f32-arith.cton | 52 ++++++++++++++++++++++++ filetests/wasm/f64-arith.cton | 52 ++++++++++++++++++++++++ lib/cretonne/meta/isa/intel/encodings.py | 14 +++++++ lib/cretonne/meta/isa/intel/recipes.py | 10 ++++- 6 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 filetests/wasm/f32-arith.cton create mode 100644 filetests/wasm/f64-arith.cton diff --git a/filetests/isa/intel/binary32-float.cton b/filetests/isa/intel/binary32-float.cton index c59bf10217..a815ad3a52 100644 --- a/filetests/isa/intel/binary32-float.cton +++ b/filetests/isa/intel/binary32-float.cton @@ -17,6 +17,28 @@ ebb0: ; asm: cvtsi2ss %esi, %xmm2 [-,%xmm2] v11 = fcvt_from_sint.f32 v1 ; bin: f3 0f 2a d6 + ; Binary arithmetic. + + ; asm: addss %xmm2, %xmm5 + [-,%xmm5] v20 = fadd v10, v11 ; bin: f3 0f 58 ea + ; asm: addss %xmm5, %xmm2 + [-,%xmm2] v21 = fadd v11, v10 ; bin: f3 0f 58 d5 + + ; asm: subss %xmm2, %xmm5 + [-,%xmm5] v22 = fsub v10, v11 ; bin: f3 0f 5c ea + ; asm: subss %xmm5, %xmm2 + [-,%xmm2] v23 = fsub v11, v10 ; bin: f3 0f 5c d5 + + ; asm: mulss %xmm2, %xmm5 + [-,%xmm5] v24 = fmul v10, v11 ; bin: f3 0f 59 ea + ; asm: mulss %xmm5, %xmm2 + [-,%xmm2] v25 = fmul v11, v10 ; bin: f3 0f 59 d5 + + ; asm: divss %xmm2, %xmm5 + [-,%xmm5] v26 = fdiv v10, v11 ; bin: f3 0f 5e ea + ; asm: divss %xmm5, %xmm2 + [-,%xmm2] v27 = fdiv v11, v10 ; bin: f3 0f 5e d5 + return } @@ -25,10 +47,32 @@ ebb0: [-,%rcx] v0 = iconst.i32 1 [-,%rsi] v1 = iconst.i32 2 + ; Binary arithmetic. + ; asm: cvtsi2sd %ecx, %xmm5 [-,%xmm5] v10 = fcvt_from_sint.f64 v0 ; bin: f2 0f 2a e9 ; asm: cvtsi2sd %esi, %xmm2 [-,%xmm2] v11 = fcvt_from_sint.f64 v1 ; bin: f2 0f 2a d6 + ; asm: addsd %xmm2, %xmm5 + [-,%xmm5] v20 = fadd v10, v11 ; bin: f2 0f 58 ea + ; asm: addsd %xmm5, %xmm2 + [-,%xmm2] v21 = fadd v11, v10 ; bin: f2 0f 58 d5 + + ; asm: subsd %xmm2, %xmm5 + [-,%xmm5] v22 = fsub v10, v11 ; bin: f2 0f 5c ea + ; asm: subsd %xmm5, %xmm2 + [-,%xmm2] v23 = fsub v11, v10 ; bin: f2 0f 5c d5 + + ; asm: mulsd %xmm2, %xmm5 + [-,%xmm5] v24 = fmul v10, v11 ; bin: f2 0f 59 ea + ; asm: mulsd %xmm5, %xmm2 + [-,%xmm2] v25 = fmul v11, v10 ; bin: f2 0f 59 d5 + + ; asm: divsd %xmm2, %xmm5 + [-,%xmm5] v26 = fdiv v10, v11 ; bin: f2 0f 5e ea + ; asm: divsd %xmm5, %xmm2 + [-,%xmm2] v27 = fdiv v11, v10 ; bin: f2 0f 5e d5 + return } diff --git a/filetests/isa/intel/binary64-float.cton b/filetests/isa/intel/binary64-float.cton index cf523b0e5b..fcf78c71f1 100644 --- a/filetests/isa/intel/binary64-float.cton +++ b/filetests/isa/intel/binary64-float.cton @@ -25,6 +25,28 @@ ebb0: ; asm: cvtsi2ssq %r14, %xmm10 [-,%xmm10] v13 = fcvt_from_sint.f32 v3 ; TODO: f3 4d 0f 2a d6 + ; Binary arithmetic. + + ; asm: addss %xmm10, %xmm5 + [-,%xmm5] v20 = fadd v10, v11 ; bin: f3 41 0f 58 ea + ; asm: addss %xmm5, %xmm10 + [-,%xmm10] v21 = fadd v11, v10 ; bin: f3 44 0f 58 d5 + + ; asm: subss %xmm10, %xmm5 + [-,%xmm5] v22 = fsub v10, v11 ; bin: f3 41 0f 5c ea + ; asm: subss %xmm5, %xmm10 + [-,%xmm10] v23 = fsub v11, v10 ; bin: f3 44 0f 5c d5 + + ; asm: mulss %xmm10, %xmm5 + [-,%xmm5] v24 = fmul v10, v11 ; bin: f3 41 0f 59 ea + ; asm: mulss %xmm5, %xmm10 + [-,%xmm10] v25 = fmul v11, v10 ; bin: f3 44 0f 59 d5 + + ; asm: divss %xmm10, %xmm5 + [-,%xmm5] v26 = fdiv v10, v11 ; bin: f3 41 0f 5e ea + ; asm: divss %xmm5, %xmm10 + [-,%xmm10] v27 = fdiv v11, v10 ; bin: f3 44 0f 5e d5 + return } @@ -45,5 +67,27 @@ ebb0: ; asm: cvtsi2sdq %r14, %xmm10 [-,%xmm10] v13 = fcvt_from_sint.f64 v3 ; TODO: f2 4d 0f 2a d6 + ; Binary arithmetic. + + ; asm: addsd %xmm10, %xmm5 + [-,%xmm5] v20 = fadd v10, v11 ; bin: f2 41 0f 58 ea + ; asm: addsd %xmm5, %xmm10 + [-,%xmm10] v21 = fadd v11, v10 ; bin: f2 44 0f 58 d5 + + ; asm: subsd %xmm10, %xmm5 + [-,%xmm5] v22 = fsub v10, v11 ; bin: f2 41 0f 5c ea + ; asm: subsd %xmm5, %xmm10 + [-,%xmm10] v23 = fsub v11, v10 ; bin: f2 44 0f 5c d5 + + ; asm: mulsd %xmm10, %xmm5 + [-,%xmm5] v24 = fmul v10, v11 ; bin: f2 41 0f 59 ea + ; asm: mulsd %xmm5, %xmm10 + [-,%xmm10] v25 = fmul v11, v10 ; bin: f2 44 0f 59 d5 + + ; asm: divsd %xmm10, %xmm5 + [-,%xmm5] v26 = fdiv v10, v11 ; bin: f2 41 0f 5e ea + ; asm: divsd %xmm5, %xmm10 + [-,%xmm10] v27 = fdiv v11, v10 ; bin: f2 44 0f 5e d5 + return } diff --git a/filetests/wasm/f32-arith.cton b/filetests/wasm/f32-arith.cton new file mode 100644 index 0000000000..ad48b401b8 --- /dev/null +++ b/filetests/wasm/f32-arith.cton @@ -0,0 +1,52 @@ +; Test basic code generation for f32 arithmetic WebAssembly instructions. +test compile + +set is_64bit=0 +isa intel haswell + +set is_64bit=1 +isa intel haswell + +; Constants. + +; function %f32_const() -> f32 + +; Unary operations + +; function %f32_abs(f32) -> f32 +; function %f32_neg(f32) -> f32 +; function %f32_sqrt(f32) -> f32 +; function %f32_ceil(f32) -> f32 +; function %f32_floor(f32) -> f32 +; function %f32_trunc(f32) -> f32 +; function %f32_nearest (f32) -> f32 + +; Binary Operations + +function %f32_add(f32, f32) -> f32 { +ebb0(v0: f32, v1: f32): + v2 = fadd v0, v1 + return v2 +} + +function %f32_sub(f32, f32) -> f32 { +ebb0(v0: f32, v1: f32): + v2 = fsub v0, v1 + return v2 +} + +function %f32_mul(f32, f32) -> f32 { +ebb0(v0: f32, v1: f32): + v2 = fmul v0, v1 + return v2 +} + +function %f32_div(f32, f32) -> f32 { +ebb0(v0: f32, v1: f32): + v2 = fdiv v0, v1 + return v2 +} + +; function %f32_min(f32, f32) -> f32 +; function %f32_max(f32, f32) -> f32 +; function %f32_copysign(f32, f32) -> f32 diff --git a/filetests/wasm/f64-arith.cton b/filetests/wasm/f64-arith.cton new file mode 100644 index 0000000000..bc81d69c3e --- /dev/null +++ b/filetests/wasm/f64-arith.cton @@ -0,0 +1,52 @@ +; Test basic code generation for f64 arithmetic WebAssembly instructions. +test compile + +set is_64bit=0 +isa intel haswell + +set is_64bit=1 +isa intel haswell + +; Constants. + +; function %f64_const() -> f64 + +; Unary operations + +; function %f64_abs(f64) -> f64 +; function %f64_neg(f64) -> f64 +; function %f64_sqrt(f64) -> f64 +; function %f64_ceil(f64) -> f64 +; function %f64_floor(f64) -> f64 +; function %f64_trunc(f64) -> f64 +; function %f64_nearest (f64) -> f64 + +; Binary Operations + +function %f64_add(f64, f64) -> f64 { +ebb0(v0: f64, v1: f64): + v2 = fadd v0, v1 + return v2 +} + +function %f64_sub(f64, f64) -> f64 { +ebb0(v0: f64, v1: f64): + v2 = fsub v0, v1 + return v2 +} + +function %f64_mul(f64, f64) -> f64 { +ebb0(v0: f64, v1: f64): + v2 = fmul v0, v1 + return v2 +} + +function %f64_div(f64, f64) -> f64 { +ebb0(v0: f64, v1: f64): + v2 = fdiv v0, v1 + return v2 +} + +; function %f64_min(f64, f64) -> f64 +; function %f64_max(f64, f64) -> f64 +; function %f64_copysign(f64, f64) -> f64 diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 264c07000e..42d54a539f 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -222,3 +222,17 @@ I64.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) I32.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) I64.enc(base.fcvt_from_sint.f64.i32, *r.furm.rex(0xf2, 0x0f, 0x2A)) I64.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) + +# Binary arithmetic ops. +for inst, opc in [ + (base.fadd, 0x58), + (base.fsub, 0x5c), + (base.fmul, 0x59), + (base.fdiv, 0x5e)]: + I32.enc(inst.f32, *r.frm(0xf3, 0x0f, opc)) + I64.enc(inst.f32, *r.frm.rex(0xf3, 0x0f, opc)) + I64.enc(inst.f32, *r.frm(0xf3, 0x0f, opc)) + + I32.enc(inst.f64, *r.frm(0xf2, 0x0f, opc)) + I64.enc(inst.f64, *r.frm.rex(0xf2, 0x0f, opc)) + I64.enc(inst.f64, *r.frm(0xf2, 0x0f, opc)) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 642e335f6a..3eb3d51671 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -197,7 +197,7 @@ null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='') # XX opcode, no ModR/M. noop = TailRecipe( 'noop', Nullary, size=0, ins=(), outs=(), - emit='PUT_OP(bits, 0, sink);') + emit='PUT_OP(bits, BASE_REX, sink);') # XX /r rr = TailRecipe( @@ -215,6 +215,14 @@ rrx = TailRecipe( modrm_rr(in_reg1, in_reg0, sink); ''') +# XX /r with FPR ins and outs. RM form. +frm = TailRecipe( + 'frr', Binary, size=1, ins=(FPR, FPR), outs=0, + emit=''' + PUT_OP(bits, rex2(in_reg1, in_reg0), sink); + modrm_rr(in_reg1, in_reg0, sink); + ''') + # XX /r, but for a unary operator with separate input/output register, like # copies. MR form. umr = TailRecipe( From 6ba604125da4fc767b880bf1cdcea0ec00c066b1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Jul 2017 11:14:11 -0700 Subject: [PATCH 892/968] Add bitwise ops that invert the second operand. ARM has all of these as scalar integer instructions. Intel has band_not in SSE and as a scalar in BMI1. Add the trivial legalization patterns that use a bnot instruction. --- docs/langref.rst | 8 +++----- filetests/isa/riscv/expand-i32.cton | 8 ++++++++ lib/cretonne/meta/base/instructions.py | 24 ++++++++++++++++++++++++ lib/cretonne/meta/base/legalize.py | 13 +++++++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/docs/langref.rst b/docs/langref.rst index dde7865486..e7b0cad3d7 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -667,11 +667,9 @@ operating on boolean values, the bitwise operations work as logical operators. .. autoinst:: bxor .. autoinst:: bxor_imm .. autoinst:: bnot - -.. todo:: Redundant bitwise operators. - - ARM has instructions like ``bic(x,y) = x & ~y``, ``orn(x,y) = x | ~y``, and - ``eon(x,y) = x ^ ~y``. +.. autoinst:: band_not +.. autoinst:: bor_not +.. autoinst:: bxor_not The shift and rotate operations only work on integer types (scalar and vector). The shift amount does not have to be the same type as the value being shifted. diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton index 2bffad234c..885d97d309 100644 --- a/filetests/isa/riscv/expand-i32.cton +++ b/filetests/isa/riscv/expand-i32.cton @@ -28,3 +28,11 @@ ebb0(v0: i32): ; check: $(cst=$V) = iconst.i32 0x3b9a_ca00 ; check: $v1 = iadd $v0, $cst ; check: return $v1 + +function %bitclear(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = band_not v0, v1 + ; check: bnot + ; check: band + return v2 +} diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 8638e19368..763550a5ee 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -920,6 +920,30 @@ bnot = Instruction( """, ins=x, outs=a) +band_not = Instruction( + 'band_not', """ + Bitwise and not. + + Computes `x & ~y`. + """, + ins=(x, y), outs=a) + +bor_not = Instruction( + 'bor_not', """ + Bitwise or not. + + Computes `x | ~y`. + """, + ins=(x, y), outs=a) + +bxor_not = Instruction( + 'bxor_not', """ + Bitwise xor not. + + Computes `x ^ ~y`. + """, + ins=(x, y), outs=a) + # Bitwise binary ops with immediate arg. x = Operand('x', iB) Y = Operand('Y', imm64) diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index 058c87f062..91e1e1114a 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -11,6 +11,7 @@ from .immediates import intcc from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm from .instructions import isub, isub_bin, isub_bout, isub_borrow from .instructions import band, bor, bxor, isplit, iconcat +from .instructions import bnot, band_not, bor_not, bxor_not from .instructions import icmp, icmp_imm from .instructions import iconst, bint from cdsl.ast import Var @@ -151,3 +152,15 @@ expand.legalize( a1 << iconst(y), a << icmp(cc, x, a1) )) + +# Expansions for *_not variants of bitwise ops. +for inst_not, inst in [ + (band_not, band), + (bor_not, bor), + (bxor_not, bxor)]: + expand.legalize( + a << inst_not(x, y), + Rtl( + a1 << bnot(y), + a << inst(x, a1) + )) From 2b41f979cbbd26396f184ac9e717e9b5e850bac4 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 20 Jul 2017 11:45:06 -0700 Subject: [PATCH 893/968] Intel encodings for floating point bitwise ops. band, bor, bxor, band_not are all available on XMM registers. --- filetests/isa/intel/binary32-float.cton | 46 ++++++++++++++++++++ filetests/isa/intel/binary64-float.cton | 54 ++++++++++++++++++++++-- lib/cretonne/meta/isa/intel/encodings.py | 16 ++++++- 3 files changed, 111 insertions(+), 5 deletions(-) diff --git a/filetests/isa/intel/binary32-float.cton b/filetests/isa/intel/binary32-float.cton index a815ad3a52..4a4361707c 100644 --- a/filetests/isa/intel/binary32-float.cton +++ b/filetests/isa/intel/binary32-float.cton @@ -39,6 +39,29 @@ ebb0: ; asm: divss %xmm5, %xmm2 [-,%xmm2] v27 = fdiv v11, v10 ; bin: f3 0f 5e d5 + ; Bitwise ops. + ; We use the *ps SSE instructions for everything because they are smaller. + + ; asm: andps %xmm2, %xmm5 + [-,%xmm5] v30 = band v10, v11 ; bin: 0f 54 ea + ; asm: andps %xmm5, %xmm2 + [-,%xmm2] v31 = band v11, v10 ; bin: 0f 54 d5 + + ; asm: andnps %xmm2, %xmm5 + [-,%xmm5] v32 = band_not v10, v11 ; bin: 0f 55 ea + ; asm: andnps %xmm5, %xmm2 + [-,%xmm2] v33 = band_not v11, v10 ; bin: 0f 55 d5 + + ; asm: orps %xmm2, %xmm5 + [-,%xmm5] v34 = bor v10, v11 ; bin: 0f 56 ea + ; asm: orps %xmm5, %xmm2 + [-,%xmm2] v35 = bor v11, v10 ; bin: 0f 56 d5 + + ; asm: xorps %xmm2, %xmm5 + [-,%xmm5] v36 = bxor v10, v11 ; bin: 0f 57 ea + ; asm: xorps %xmm5, %xmm2 + [-,%xmm2] v37 = bxor v11, v10 ; bin: 0f 57 d5 + return } @@ -74,5 +97,28 @@ ebb0: ; asm: divsd %xmm5, %xmm2 [-,%xmm2] v27 = fdiv v11, v10 ; bin: f2 0f 5e d5 + ; Bitwise ops. + ; We use the *ps SSE instructions for everything because they are smaller. + + ; asm: andps %xmm2, %xmm5 + [-,%xmm5] v30 = band v10, v11 ; bin: 0f 54 ea + ; asm: andps %xmm5, %xmm2 + [-,%xmm2] v31 = band v11, v10 ; bin: 0f 54 d5 + + ; asm: andnps %xmm2, %xmm5 + [-,%xmm5] v32 = band_not v10, v11 ; bin: 0f 55 ea + ; asm: andnps %xmm5, %xmm2 + [-,%xmm2] v33 = band_not v11, v10 ; bin: 0f 55 d5 + + ; asm: orps %xmm2, %xmm5 + [-,%xmm5] v34 = bor v10, v11 ; bin: 0f 56 ea + ; asm: orps %xmm5, %xmm2 + [-,%xmm2] v35 = bor v11, v10 ; bin: 0f 56 d5 + + ; asm: xorps %xmm2, %xmm5 + [-,%xmm5] v36 = bxor v10, v11 ; bin: 0f 57 ea + ; asm: xorps %xmm5, %xmm2 + [-,%xmm2] v37 = bxor v11, v10 ; bin: 0f 57 d5 + return } diff --git a/filetests/isa/intel/binary64-float.cton b/filetests/isa/intel/binary64-float.cton index fcf78c71f1..ba604ad43c 100644 --- a/filetests/isa/intel/binary64-float.cton +++ b/filetests/isa/intel/binary64-float.cton @@ -47,6 +47,29 @@ ebb0: ; asm: divss %xmm5, %xmm10 [-,%xmm10] v27 = fdiv v11, v10 ; bin: f3 44 0f 5e d5 + ; Bitwise ops. + ; We use the *ps SSE instructions for everything because they are smaller. + + ; asm: andps %xmm10, %xmm5 + [-,%xmm5] v30 = band v10, v11 ; bin: 41 0f 54 ea + ; asm: andps %xmm5, %xmm10 + [-,%xmm10] v31 = band v11, v10 ; bin: 44 0f 54 d5 + + ; asm: andnps %xmm10, %xmm5 + [-,%xmm5] v32 = band_not v10, v11 ; bin: 41 0f 55 ea + ; asm: andnps %xmm5, %xmm10 + [-,%xmm10] v33 = band_not v11, v10 ; bin: 44 0f 55 d5 + + ; asm: orps %xmm10, %xmm5 + [-,%xmm5] v34 = bor v10, v11 ; bin: 41 0f 56 ea + ; asm: orps %xmm5, %xmm10 + [-,%xmm10] v35 = bor v11, v10 ; bin: 44 0f 56 d5 + + ; asm: xorps %xmm10, %xmm5 + [-,%xmm5] v36 = bxor v10, v11 ; bin: 41 0f 57 ea + ; asm: xorps %xmm5, %xmm10 + [-,%xmm10] v37 = bxor v11, v10 ; bin: 44 0f 57 d5 + return } @@ -72,22 +95,45 @@ ebb0: ; asm: addsd %xmm10, %xmm5 [-,%xmm5] v20 = fadd v10, v11 ; bin: f2 41 0f 58 ea ; asm: addsd %xmm5, %xmm10 - [-,%xmm10] v21 = fadd v11, v10 ; bin: f2 44 0f 58 d5 + [-,%xmm10] v21 = fadd v11, v10 ; bin: f2 44 0f 58 d5 ; asm: subsd %xmm10, %xmm5 [-,%xmm5] v22 = fsub v10, v11 ; bin: f2 41 0f 5c ea ; asm: subsd %xmm5, %xmm10 - [-,%xmm10] v23 = fsub v11, v10 ; bin: f2 44 0f 5c d5 + [-,%xmm10] v23 = fsub v11, v10 ; bin: f2 44 0f 5c d5 ; asm: mulsd %xmm10, %xmm5 [-,%xmm5] v24 = fmul v10, v11 ; bin: f2 41 0f 59 ea ; asm: mulsd %xmm5, %xmm10 - [-,%xmm10] v25 = fmul v11, v10 ; bin: f2 44 0f 59 d5 + [-,%xmm10] v25 = fmul v11, v10 ; bin: f2 44 0f 59 d5 ; asm: divsd %xmm10, %xmm5 [-,%xmm5] v26 = fdiv v10, v11 ; bin: f2 41 0f 5e ea ; asm: divsd %xmm5, %xmm10 - [-,%xmm10] v27 = fdiv v11, v10 ; bin: f2 44 0f 5e d5 + [-,%xmm10] v27 = fdiv v11, v10 ; bin: f2 44 0f 5e d5 + + ; Bitwise ops. + ; We use the *ps SSE instructions for everything because they are smaller. + + ; asm: andps %xmm10, %xmm5 + [-,%xmm5] v30 = band v10, v11 ; bin: 41 0f 54 ea + ; asm: andps %xmm5, %xmm10 + [-,%xmm10] v31 = band v11, v10 ; bin: 44 0f 54 d5 + + ; asm: andnps %xmm10, %xmm5 + [-,%xmm5] v32 = band_not v10, v11 ; bin: 41 0f 55 ea + ; asm: andnps %xmm5, %xmm10 + [-,%xmm10] v33 = band_not v11, v10 ; bin: 44 0f 55 d5 + + ; asm: orps %xmm10, %xmm5 + [-,%xmm5] v34 = bor v10, v11 ; bin: 41 0f 56 ea + ; asm: orps %xmm5, %xmm10 + [-,%xmm10] v35 = bor v11, v10 ; bin: 44 0f 56 d5 + + ; asm: xorps %xmm10, %xmm5 + [-,%xmm5] v36 = bxor v10, v11 ; bin: 41 0f 57 ea + ; asm: xorps %xmm5, %xmm10 + [-,%xmm10] v37 = bxor v11, v10 ; bin: 44 0f 57 d5 return } diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 42d54a539f..f155795b6f 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -224,7 +224,7 @@ I64.enc(base.fcvt_from_sint.f64.i32, *r.furm.rex(0xf2, 0x0f, 0x2A)) I64.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) # Binary arithmetic ops. -for inst, opc in [ +for inst, opc in [ (base.fadd, 0x58), (base.fsub, 0x5c), (base.fmul, 0x59), @@ -236,3 +236,17 @@ for inst, opc in [ I32.enc(inst.f64, *r.frm(0xf2, 0x0f, opc)) I64.enc(inst.f64, *r.frm.rex(0xf2, 0x0f, opc)) I64.enc(inst.f64, *r.frm(0xf2, 0x0f, opc)) + +# Binary bitwise ops. +for inst, opc in [ + (base.band, 0x54), + (base.band_not, 0x55), + (base.bor, 0x56), + (base.bxor, 0x57)]: + I32.enc(inst.f32, *r.frm(0x0f, opc)) + I64.enc(inst.f32, *r.frm.rex(0x0f, opc)) + I64.enc(inst.f32, *r.frm(0x0f, opc)) + + I32.enc(inst.f64, *r.frm(0x0f, opc)) + I64.enc(inst.f64, *r.frm.rex(0x0f, opc)) + I64.enc(inst.f64, *r.frm(0x0f, opc)) From df1bf7d57875da8f700016cf98f4b37025327572 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Jul 2017 21:10:32 -0700 Subject: [PATCH 894/968] Return a Result from constant_hash::probe. When a hash table probe fails, return the index of the failed entry. This can be used to store default values in the sentinel entries. --- lib/cretonne/src/constant_hash.rs | 12 ++++++--- lib/cretonne/src/ir/instructions.rs | 4 +-- lib/cretonne/src/isa/enc_tables.rs | 40 +++++++++++++++++++---------- lib/cretonne/src/settings.rs | 4 +-- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/lib/cretonne/src/constant_hash.rs b/lib/cretonne/src/constant_hash.rs index 08cc2944c8..a165654dce 100644 --- a/lib/cretonne/src/constant_hash.rs +++ b/lib/cretonne/src/constant_hash.rs @@ -24,8 +24,12 @@ pub trait Table { /// The provided `hash` value must have been computed from `key` using the same hash function that /// was used to construct the table. /// -/// Returns the table index containing the found entry, or `None` if no entry could be found. -pub fn probe + ?Sized>(table: &T, key: K, hash: usize) -> Option { +/// Returns `Ok(idx)` with the table index containing the found entry, or `Err(idx)` with the empty +/// sentinel entry if no entry could be found. +pub fn probe + ?Sized>(table: &T, + key: K, + hash: usize) + -> Result { debug_assert!(table.len().is_power_of_two()); let mask = table.len() - 1; @@ -36,8 +40,8 @@ pub fn probe + ?Sized>(table: &T, key: K, hash: usize) idx &= mask; match table.key(idx) { - None => return None, - Some(k) if k == key => return Some(idx), + None => return Err(idx), + Some(k) if k == key => return Ok(idx), _ => {} } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 808b7cc01a..279a7a37a9 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -85,10 +85,10 @@ impl FromStr for Opcode { } match probe::<&str, [Option]>(&OPCODE_HASH_TABLE, s, simple_hash(s)) { - None => Err("Unknown opcode"), + Err(_) => Err("Unknown opcode"), // We unwrap here because probe() should have ensured that the entry // at this index is not None. - Some(i) => Ok(OPCODE_HASH_TABLE[i].unwrap()), + Ok(i) => Ok(OPCODE_HASH_TABLE[i].unwrap()), } } } diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 3467a2bdfd..b53e80036e 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -5,6 +5,7 @@ use ir::{Type, Opcode, InstructionData}; use isa::{Encoding, Legalize}; use constant_hash::{Table, probe}; +use std::ops::Range; /// Level 1 hash table entry. /// @@ -27,6 +28,14 @@ pub struct Level1Entry + Copy> { pub offset: OffT, } +impl + Copy> Level1Entry { + /// Get the level 2 table range indicated by this entry. + fn range(&self) -> Range { + let b = self.offset.into() as usize; + b..b + (1 << self.log2len) + } +} + impl + Copy> Table for [Level1Entry] { fn len(&self) -> usize { self.len() @@ -83,20 +92,23 @@ pub fn lookup_enclist(ctrl_typevar: Type, OffT2: Into + Copy { // TODO: The choice of legalization actions here is naive. This needs to be configurable. - probe(level1_table, ctrl_typevar, ctrl_typevar.index()) - .ok_or_else(|| if ctrl_typevar.lane_type().bits() > 32 { - Legalize::Narrow - } else { - Legalize::Expand - }) - .and_then(|l1idx| { - let l1ent = &level1_table[l1idx]; - let l2off = l1ent.offset.into() as usize; - let l2tab = &level2_table[l2off..l2off + (1 << l1ent.log2len)]; - probe(l2tab, opcode, opcode as usize) - .map(|l2idx| l2tab[l2idx].offset.into() as usize) - .ok_or(Legalize::Expand) - }) + match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) { + Err(_) => { + // No level 1 entry for the type. + Err(if ctrl_typevar.lane_type().bits() > 32 { + Legalize::Narrow + } else { + Legalize::Expand + }) + } + Ok(l1idx) => { + let l1ent = &level1_table[l1idx]; + let l2tab = &level2_table[l1ent.range()]; + probe(l2tab, opcode, opcode as usize) + .map(|l2idx| l2tab[l2idx].offset.into() as usize) + .map_err(|_| Legalize::Expand) + } + } } /// Encoding list entry. diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 19b9fafc21..be5140ef34 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -83,8 +83,8 @@ impl Builder { /// Look up a descriptor by name. fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> { match probe(self.template, name, simple_hash(name)) { - None => Err(Error::BadName), - Some(entry) => { + Err(_) => Err(Error::BadName), + Ok(entry) => { let d = &self.template.descriptors[self.template.hash_table[entry] as usize]; Ok((d.offset as usize, d.detail)) } From f583511fb669ea5d0b5ac7da5e40f05b15db8d99 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Jul 2017 13:48:18 -0700 Subject: [PATCH 895/968] Add a PredicateView type to abstract the predicate bit vector a bit. The encoding tables contain references to numbered ISA predicates. - Give the ISA Flags types a predicate_view() method which returns a PredicateView. - Delete the old predicate_bytes() method which returned a raw &[u8]. - Use a 'static lifetime for the encoding list slice in the Encodings iterator, and a single 'a lifetime for everything else. --- lib/cretonne/meta/gen_settings.py | 22 +++++++++++++--------- lib/cretonne/src/isa/arm32/mod.rs | 12 ++++++------ lib/cretonne/src/isa/arm64/mod.rs | 12 ++++++------ lib/cretonne/src/isa/enc_tables.rs | 30 ++++++++++++++---------------- lib/cretonne/src/isa/intel/mod.rs | 12 ++++++------ lib/cretonne/src/isa/mod.rs | 10 +++++----- lib/cretonne/src/isa/riscv/mod.rs | 12 ++++++------ lib/cretonne/src/settings.rs | 22 ++++++++++++++++++++++ 8 files changed, 78 insertions(+), 54 deletions(-) diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index c56cc4df41..1a79026c36 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -87,16 +87,20 @@ def gen_getters(sgrp, fmt): """ fmt.doc_comment("User-defined settings.") with fmt.indented('impl Flags {', '}'): - fmt.doc_comment('Returns inner slice of bytes.') - fmt.doc_comment('The byte-sized settings are not included.') - with fmt.indented('pub fn predicate_bytes(&self) -> &[u8] {', '}'): - fmt.line('&self.bytes[{}..]'.format(sgrp.boolean_offset)) - fmt.doc_comment('Dynamic numbered predicate getter.') + fmt.doc_comment('Get a view of the boolean predicates.') with fmt.indented( - 'pub fn numbered_predicate(&self, p: usize) -> bool {', '}'): - fmt.line( - 'self.bytes[{} + p/8] & (1 << (p%8)) != 0' - .format(sgrp.boolean_offset)) + 'pub fn predicate_view(&self) -> ::settings::PredicateView {', + '}'): + fmt.format( + '::settings::PredicateView::new(&self.bytes[{}..])', + sgrp.boolean_offset) + if sgrp.settings: + fmt.doc_comment('Dynamic numbered predicate getter.') + with fmt.indented( + 'fn numbered_predicate(&self, p: usize) -> bool {', '}'): + fmt.line( + 'self.bytes[{} + p / 8] & (1 << (p % 8)) != 0' + .format(sgrp.boolean_offset)) for setting in sgrp.settings: gen_getter(setting, sgrp, fmt) for pred in sgrp.named_predicates: diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 769adc376d..5675d19d7a 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -61,11 +61,11 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn legal_encodings<'a, 'b>(&'a self, - _dfg: &'b ir::DataFlowGraph, - inst: &'b ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result, Legalize> { + fn legal_encodings<'a>(&'a self, + _dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, @@ -75,7 +75,7 @@ impl TargetIsa for Isa { &enc_tables::ENCLISTS[..], inst, enc_tables::check_instp, - self.isa_flags.predicate_bytes())) + self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index a1b0ac2478..e9aea5aacf 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -54,11 +54,11 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn legal_encodings<'a, 'b>(&'a self, - _dfg: &'b ir::DataFlowGraph, - inst: &'b ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result, Legalize> { + fn legal_encodings<'a>(&'a self, + _dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), &enc_tables::LEVEL1_A64[..], @@ -68,7 +68,7 @@ impl TargetIsa for Isa { &enc_tables::ENCLISTS[..], inst, enc_tables::check_instp, - self.isa_flags.predicate_bytes())) + self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index b53e80036e..1c6645002e 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -2,9 +2,11 @@ //! //! This module contains types and functions for working with the encoding tables generated by //! `lib/cretonne/meta/gen_encoding.py`. + +use constant_hash::{Table, probe}; use ir::{Type, Opcode, InstructionData}; use isa::{Encoding, Legalize}; -use constant_hash::{Table, probe}; +use settings::PredicateView; use std::ops::Range; /// Level 1 hash table entry. @@ -127,15 +129,15 @@ const CODE_ALWAYS: EncListEntry = PRED_MASK; const CODE_FAIL: EncListEntry = 0xffff; /// An iterator over legal encodings for the instruction. -pub struct Encodings<'a, 'b> { +pub struct Encodings<'a> { offset: usize, - enclist: &'b [EncListEntry], - inst: &'b InstructionData, + enclist: &'static [EncListEntry], + inst: &'a InstructionData, instp: fn(&InstructionData, EncListEntry) -> bool, - isa_predicate_bytes: &'a [u8], + isa_predicates: PredicateView<'a>, } -impl<'a, 'b> Encodings<'a, 'b> { +impl<'a> Encodings<'a> { /// Creates a new instance of `Encodings`. /// /// # Parameters @@ -151,29 +153,25 @@ impl<'a, 'b> Encodings<'a, 'b> { /// encoding lists are laid out such that first call to `next` returns valid entry in the list /// or `None`. pub fn new(offset: usize, - enclist: &'b [EncListEntry], - inst: &'b InstructionData, + enclist: &'static [EncListEntry], + inst: &'a InstructionData, instp: fn(&InstructionData, EncListEntry) -> bool, - isa_predicate_bytes: &'a [u8]) + isa_predicates: PredicateView<'a>) -> Self { Encodings { offset, enclist, inst, instp, - isa_predicate_bytes, + isa_predicates, } } } -impl<'a, 'b> Iterator for Encodings<'a, 'b> { +impl<'a> Iterator for Encodings<'a> { type Item = Encoding; fn next(&mut self) -> Option { - fn numbered_predicate(bytes: &[u8], p: usize) -> bool { - bytes[p / 8] & (1 << (p % 8)) != 0 - } - while self.enclist[self.offset] != CODE_FAIL { let pred = self.enclist[self.offset]; if pred <= CODE_ALWAYS { @@ -189,7 +187,7 @@ impl<'a, 'b> Iterator for Encodings<'a, 'b> { } else { // This is an ISA predicate entry. self.offset += 1; - if !numbered_predicate(self.isa_predicate_bytes, (pred & PRED_MASK) as usize) { + if !self.isa_predicates.test((pred & PRED_MASK) as usize) { // ISA predicate failed, skip the next N entries. self.offset += 3 * (pred >> PRED_BITS) as usize; } diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 93a2ed2c09..35d339a740 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -61,11 +61,11 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn legal_encodings<'a, 'b>(&'a self, - _dfg: &'b ir::DataFlowGraph, - inst: &'b ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result, Legalize> { + fn legal_encodings<'a>(&'a self, + _dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, @@ -75,7 +75,7 @@ impl TargetIsa for Isa { &enc_tables::ENCLISTS[..], inst, enc_tables::check_instp, - self.isa_flags.predicate_bytes())) + self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 6665de5f42..100c901ec0 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -138,11 +138,11 @@ pub trait TargetIsa { fn register_info(&self) -> RegInfo; /// Returns an iterartor over legal encodings for the instruction. - fn legal_encodings<'a, 'b>(&'a self, - dfg: &'b ir::DataFlowGraph, - inst: &'b ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result, Legalize>; + fn legal_encodings<'a>(&'a self, + dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize>; /// Encode an instruction after determining it is legal. /// diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index b70f6185b8..869ea4c9cb 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -61,11 +61,11 @@ impl TargetIsa for Isa { enc_tables::INFO.clone() } - fn legal_encodings<'a, 'b>(&'a self, - _dfg: &'b ir::DataFlowGraph, - inst: &'b ir::InstructionData, - ctrl_typevar: ir::Type) - -> Result, Legalize> { + fn legal_encodings<'a>(&'a self, + _dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Result, Legalize> { lookup_enclist(ctrl_typevar, inst.opcode(), self.cpumode, @@ -75,7 +75,7 @@ impl TargetIsa for Isa { &enc_tables::ENCLISTS[..], inst, enc_tables::check_instp, - self.isa_flags.predicate_bytes())) + self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index be5140ef34..660c833a4a 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -162,6 +162,28 @@ pub enum Error { /// A result returned when changing a setting. pub type Result = result::Result; +/// A reference to just the boolean predicates of a settings object. +/// +/// The settings objects themselves are generated and appear in the `isa/*/settings.rs` modules. +/// Each settings object provides a `predicate_view()` method that makes it possible to query +/// ISA predicates by number. +#[derive(Clone, Copy)] +pub struct PredicateView<'a>(&'a [u8]); + +impl<'a> PredicateView<'a> { + /// Create a new view of a precomputed predicate vector. + /// + /// See the `predicate_view()` method on the various `Flags` types defined for each ISA. + pub fn new(bits: &'a [u8]) -> PredicateView { + PredicateView(bits) + } + + /// Check a numbered predicate. + pub fn test(self, p: usize) -> bool { + self.0[p / 8] & (1 << (p % 8)) != 0 + } +} + /// Implementation details for generated code. /// /// This module holds definitions that need to be public so the can be instantiated by generated From a06964fc0ef4e0e617322f1d6199bef412b8ead5 Mon Sep 17 00:00:00 2001 From: Dimo Date: Mon, 24 Jul 2017 11:56:00 -0700 Subject: [PATCH 896/968] test-all.sh should print the versions for both python2 and python3 its using --- test-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-all.sh b/test-all.sh index 72b07f8856..33e47bc358 100755 --- a/test-all.sh +++ b/test-all.sh @@ -35,7 +35,7 @@ else needcheck=yes fi if [ -n "$needcheck" ]; then - banner $(python --version 2>&1) + banner "$(python --version 2>&1), $(python3 --version 2>&1)" $topdir/lib/cretonne/meta/check.sh touch $tsfile || echo no target directory fi From 127b22af5f47d1b4d87fd12ae55824637db56c78 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 24 Jul 2017 11:21:12 -0700 Subject: [PATCH 897/968] Make legalization actions configurable. When an instruction doesn't have a valid encoding for the target ISA, it needs to be legalized. Different legalization strategies can be expressed as separate XFormGroup objects. Make the choice of XFormGroup configurable per CPU mode, rather than depending on a hard-coded default. Add a CPUMode.legalize_type() method which assigns an XFormGroup to controlling type variables and lets you set a default. Add a `legalize` field to Level1Entry so the first-level hash table lookup gives us the configured default legalization action for the instruction's controlling type variable. --- lib/cretonne/meta/base/legalize.py | 7 ++ lib/cretonne/meta/cdsl/isa.py | 63 ++++++++++++++- lib/cretonne/meta/cdsl/xform.py | 4 + lib/cretonne/meta/gen_encoding.py | 97 ++++++++++++++++++------ lib/cretonne/meta/isa/arm32/defs.py | 5 ++ lib/cretonne/meta/isa/arm64/defs.py | 4 + lib/cretonne/meta/isa/intel/encodings.py | 14 ++++ lib/cretonne/meta/isa/riscv/encodings.py | 14 ++++ lib/cretonne/src/isa/enc_tables.rs | 48 +++++++----- lib/cretonne/src/isa/mod.rs | 14 ++++ 10 files changed, 229 insertions(+), 41 deletions(-) diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index 91e1e1114a..1afefcda89 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -27,6 +27,13 @@ narrow = XFormGroup('narrow', """ operations are expressed in terms of smaller integer types. """) +widen = XFormGroup('widen', """ + Legalize instructions by widening. + + The transformations in the 'widen' group work by expressing + instructions in terms of larger types. + """) + expand = XFormGroup('expand', """ Legalize instructions by expansion. diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 0562df10dc..ea10c64640 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,8 +1,10 @@ """Defining instruction set architectures.""" from __future__ import absolute_import +from collections import OrderedDict from .predicates import And from .registers import RegClass, Register, Stack from .ast import Apply +from .types import ValueType # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -12,8 +14,8 @@ try: from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa from .predicates import PredNode # noqa from .settings import SettingGroup # noqa - from .types import ValueType # noqa from .registers import RegBank # noqa + from .xform import XFormGroup # noqa OperandConstraint = Union[RegClass, Register, int, Stack] ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] # Instruction specification for encodings. Allows for predicated @@ -43,6 +45,11 @@ class TargetISA(object): self.cpumodes = list() # type: List[CPUMode] self.regbanks = list() # type: List[RegBank] self.regclasses = list() # type: List[RegClass] + self.legalize_codes = OrderedDict() # type: OrderedDict[XFormGroup, int] # noqa + + def __str__(self): + # type: () -> str + return self.name def finish(self): # type: () -> TargetISA @@ -138,6 +145,25 @@ class TargetISA(object): # `isa/registers.rs`. assert len(self.regclasses) <= 32, "Too many register classes" + def legalize_code(self, xgrp): + # type: (XFormGroup) -> int + """ + Get the legalization code for the transform group `xgrp`. Assign one if + necessary. + + Each target ISA has its own list of legalization actions with + associated legalize codes that appear in the encoding tables. + + This method is used to maintain the registry of legalization actions + and their table codes. + """ + if xgrp in self.legalize_codes: + code = self.legalize_codes[xgrp] + else: + code = len(self.legalize_codes) + self.legalize_codes[xgrp] = code + return code + class CPUMode(object): """ @@ -157,6 +183,11 @@ class CPUMode(object): self.encodings = [] # type: List[Encoding] isa.cpumodes.append(self) + # Tables for configuring legalization actions when no valid encoding + # exists for an instruction. + self.default_legalize = None # type: XFormGroup + self.type_legalize = dict() # type: Dict[ValueType, XFormGroup] + def __str__(self): # type: () -> str return self.name @@ -171,6 +202,36 @@ class CPUMode(object): """ self.encodings.append(Encoding(self, *args, **kwargs)) + def legalize_type(self, default=None, **kwargs): + # type: (XFormGroup, **XFormGroup) -> None + """ + Configure the legalization action per controlling type variable. + + Instructions that have a controlling type variable mentioned in one of + the arguments will be legalized according to the action specified here + instead of using the `legalize_default` action. + + The keyword arguments are value type names: + + mode.legalize_type(i8=widen, i16=widen, i32=expand) + + The `default` argument specifies the action to take for controlling + type variables that don't have an explicitly configured action. + """ + if default is not None: + self.default_legalize = default + + for name, xgrp in kwargs.items(): + ty = ValueType.by_name(name) + self.type_legalize[ty] = xgrp + + def get_legalize_action(self, ty): + # type: (ValueType) -> XFormGroup + """ + Get the legalization action to use for `ty`. + """ + return self.type_legalize.get(ty, self.default_legalize) + class EncRecipe(object): """ diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index f809a91ce6..6729267f7b 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -241,6 +241,10 @@ class XFormGroup(object): self.name = name self.__doc__ = doc + def __str__(self): + # type: () -> str + return self.name + def legalize(self, src, dst): # type: (Union[Def, Apply], Rtl) -> None """ diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 135940dd23..de8b138cf6 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -66,6 +66,7 @@ try: from cdsl.predicates import PredNode, PredLeaf # noqa from cdsl.types import ValueType # noqa from cdsl.instructions import Instruction # noqa + from cdsl.xform import XFormGroup # noqa except ImportError: pass @@ -261,12 +262,17 @@ class Level2Table(object): """ Level 2 table mapping instruction opcodes to `EncList` objects. + A level 2 table can be completely empty if it only holds a custom + legalization action for `ty`. + :param ty: Controlling type variable of all entries, or `None`. + :param legalize: Default legalize action for `ty`. """ - def __init__(self, ty): - # type: (ValueType) -> None + def __init__(self, ty, legalize): + # type: (ValueType, XFormGroup) -> None self.ty = ty + self.legalize = legalize # Maps inst -> EncList self.lists = OrderedDict() # type: OrderedDict[Instruction, EncList] @@ -278,6 +284,16 @@ class Level2Table(object): self.lists[inst] = ls return ls + def is_empty(self): + # type: () -> bool + """ + Check if this level 2 table is completely empty. + + This can happen if the associated type simply has an overridden + legalize action. + """ + return len(self.lists) == 0 + def enclists(self): # type: () -> Iterable[EncList] return iter(self.lists.values()) @@ -310,21 +326,32 @@ class Level1Table(object): Level 1 table mapping types to `Level2` objects. """ - def __init__(self): - # type: () -> None + def __init__(self, cpumode): + # type: (CPUMode) -> None + self.cpumode = cpumode self.tables = OrderedDict() # type: OrderedDict[ValueType, Level2Table] # noqa + if cpumode.default_legalize is None: + raise AssertionError( + 'CPU mode {}.{} needs a default legalize action' + .format(cpumode.isa, cpumode)) + self.legalize_code = cpumode.isa.legalize_code( + cpumode.default_legalize) + def __getitem__(self, ty): # type: (ValueType) -> Level2Table tbl = self.tables.get(ty) if not tbl: - tbl = Level2Table(ty) + legalize = self.cpumode.get_legalize_action(ty) + # Allocate a legalization code in a predictable order. + self.cpumode.isa.legalize_code(legalize) + tbl = Level2Table(ty, legalize) self.tables[ty] = tbl return tbl def l2tables(self): # type: () -> Iterable[Level2Table] - return iter(self.tables.values()) + return (l2 for l2 in self.tables.values() if not l2.is_empty()) def make_tables(cpumode): @@ -332,11 +359,17 @@ def make_tables(cpumode): """ Generate tables for `cpumode` as described above. """ - table = Level1Table() + table = Level1Table(cpumode) for enc in cpumode.encodings: ty = enc.ctrl_typevar() inst = enc.inst table[ty][inst].encodings.append(enc) + + # Ensure there are level 1 table entries for all types with a custom + # legalize action. Try to be stable relative to dict ordering. + for ty in sorted(cpumode.type_legalize.keys(), key=str): + table[ty] + return table @@ -412,22 +445,42 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): 'pub static LEVEL1_{}: [Level1Entry<{}>; {}] = [' .format(cpumode.name.upper(), offt, len(hash_table)), '];'): for level2 in hash_table: - if level2: - l2l = int(math.log(level2.hash_table_len, 2)) - assert l2l > 0, "Hash table too small" - tyname = level2.ty.name if level2.ty is not None else 'void' - fmt.line( - 'Level1Entry ' + - '{{ ty: types::{}, log2len: {}, offset: {:#08x} }},' - .format( - tyname.upper(), - l2l, - level2.hash_table_offset)) + # Empty hash table entry. Include the default legalization action. + if not level2: + fmt.format( + 'Level1Entry {{ ty: types::VOID, log2len: !0, ' + 'offset: 0, legalize: {} }},', + level1.legalize_code) + continue + + if level2.ty is not None: + tyname = level2.ty.rust_name() else: - # Empty entry. - fmt.line( - 'Level1Entry ' + - '{ ty: types::VOID, log2len: 0, offset: 0 },') + tyname = 'types::VOID' + + lcode = cpumode.isa.legalize_code(level2.legalize) + + # Empty level 2 table: Only a specialized legalization action, no + # actual table. + # Set an offset that is out of bounds, but make sure it doesn't + # overflow its type when adding `1< 0, "Level2 hash table too small" + fmt.format( + 'Level1Entry {{ ' + 'ty: {}, log2len: {}, offset: {:#08x}, ' + 'legalize: {} }}, // {}', + tyname, l2l, level2.hash_table_offset, + lcode, level2.legalize) def offset_type(length): diff --git a/lib/cretonne/meta/isa/arm32/defs.py b/lib/cretonne/meta/isa/arm32/defs.py index f90abc2001..6bba598d27 100644 --- a/lib/cretonne/meta/isa/arm32/defs.py +++ b/lib/cretonne/meta/isa/arm32/defs.py @@ -6,9 +6,14 @@ Commonly used definitions. from __future__ import absolute_import from cdsl.isa import TargetISA, CPUMode import base.instructions +from base.legalize import narrow ISA = TargetISA('arm32', [base.instructions.GROUP]) # CPU modes for 32-bit ARM and Thumb2. A32 = CPUMode('A32', ISA) T32 = CPUMode('T32', ISA) + +# TODO: Refine these. +A32.legalize_type(narrow) +T32.legalize_type(narrow) diff --git a/lib/cretonne/meta/isa/arm64/defs.py b/lib/cretonne/meta/isa/arm64/defs.py index e493b4424a..b1ed79b5d6 100644 --- a/lib/cretonne/meta/isa/arm64/defs.py +++ b/lib/cretonne/meta/isa/arm64/defs.py @@ -6,6 +6,10 @@ Commonly used definitions. from __future__ import absolute_import from cdsl.isa import TargetISA, CPUMode import base.instructions +from base.legalize import narrow ISA = TargetISA('arm64', [base.instructions.GROUP]) A64 = CPUMode('A64', ISA) + +# TODO: Refine these +A64.legalize_type(narrow) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index f155795b6f..df1a542101 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -9,6 +9,20 @@ from .defs import I32, I64 from . import recipes as r from . import settings as cfg from . import instructions as x86 +from base.legalize import narrow, expand + +I32.legalize_type( + default=narrow, + i32=expand, + f32=expand, + f64=expand) + +I64.legalize_type( + default=narrow, + i32=expand, + i64=expand, + f32=expand, + f64=expand) for inst, opc in [ (base.iadd, 0x01), diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 9ec6b34fc0..b9f1f8245d 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -11,6 +11,20 @@ from .recipes import R, Rshamt, Ricmp, I, Iz, Iicmp, Iret, Icall, Icopy from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi, Irmov from .settings import use_m from cdsl.ast import Var +from base.legalize import narrow, expand + +RV32.legalize_type( + default=narrow, + i32=expand, + f32=expand, + f64=expand) + +RV64.legalize_type( + default=narrow, + i32=expand, + i64=expand, + f32=expand, + f64=expand) # Dummies for instruction predicates. x = Var('x') diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 1c6645002e..33ec38d3c1 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -9,6 +9,11 @@ use isa::{Encoding, Legalize}; use settings::PredicateView; use std::ops::Range; +/// Legalization action to perform when no encoding can be found for an instruction. +/// +/// This is an index into an ISA-specific table of legalization actions. +pub type LegalizeCode = u8; + /// Level 1 hash table entry. /// /// One level 1 hash table is generated per CPU mode. This table is keyed by the controlling type @@ -19,14 +24,15 @@ use std::ops::Range; /// have a power-of-two size. /// /// Entries are generic over the offset type. It will typically be `u32` or `u16`, depending on the -/// size of the `LEVEL2` table. A `u16` offset allows entries to shrink to 32 bits each, but some -/// ISAs may have tables so large that `u32` offsets are needed. +/// size of the `LEVEL2` table. /// -/// Empty entries are encoded with a 0 `log2len`. This is on the assumption that no level 2 tables -/// have only a single entry. +/// Empty entries are encoded with a `!0` value for `log2len` which will always be out of range. +/// Entries that have a `legalize` value but no level 2 table have an `offset` field that is out f +/// bounds. pub struct Level1Entry + Copy> { pub ty: Type, pub log2len: u8, + pub legalize: LegalizeCode, pub offset: OffT, } @@ -44,7 +50,7 @@ impl + Copy> Table for [Level1Entry] { } fn key(&self, idx: usize) -> Option { - if self[idx].log2len != 0 { + if self[idx].log2len != !0 { Some(self[idx].ty) } else { None @@ -55,7 +61,7 @@ impl + Copy> Table for [Level1Entry] { /// Level 2 hash table entry. /// /// The second level hash tables are keyed by `Opcode`, and contain an offset into the `ENCLISTS` -/// table where the encoding recipes for the instrution are stored. +/// table where the encoding recipes for the instruction are stored. /// /// Entries are generic over the offset type which depends on the size of `ENCLISTS`. A `u16` /// offset allows the entries to be only 32 bits each. There is no benefit to dropping down to `u8` @@ -93,22 +99,28 @@ pub fn lookup_enclist(ctrl_typevar: Type, where OffT1: Into + Copy, OffT2: Into + Copy { - // TODO: The choice of legalization actions here is naive. This needs to be configurable. match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) { - Err(_) => { - // No level 1 entry for the type. - Err(if ctrl_typevar.lane_type().bits() > 32 { - Legalize::Narrow - } else { - Legalize::Expand - }) + Err(l1idx) => { + // No level 1 entry found for the type. + // We have a sentinel entry with the default legalization code. + let l1ent = &level1_table[l1idx]; + Err(l1ent.legalize.into()) } Ok(l1idx) => { + // We have a valid level 1 entry for this type. let l1ent = &level1_table[l1idx]; - let l2tab = &level2_table[l1ent.range()]; - probe(l2tab, opcode, opcode as usize) - .map(|l2idx| l2tab[l2idx].offset.into() as usize) - .map_err(|_| Legalize::Expand) + match level2_table.get(l1ent.range()) { + Some(l2tab) => { + probe(l2tab, opcode, opcode as usize) + .map(|l2idx| l2tab[l2idx].offset.into() as usize) + .map_err(|_| l1ent.legalize.into()) + } + None => { + // The l1ent range is invalid. This means that we just have a customized + // legalization code for this type. The level 2 table is empty. + Err(l1ent.legalize.into()) + } + } } } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 100c901ec0..7b25aec8ec 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -126,6 +126,20 @@ pub enum Legalize { Expand, } +/// Translate a legalization code into a `Legalize` enum. +/// +/// This mapping is going away soon. It depends on matching the `TargetISA.legalize_code()` +/// mapping. +impl From for Legalize { + fn from(x: u8) -> Legalize { + match x { + 0 => Legalize::Narrow, + 1 => Legalize::Expand, + _ => panic!("Unknown legalization code {}"), + } + } +} + /// Methods that are specialized to a target ISA. pub trait TargetIsa { /// Get the name of this ISA. From 15a7d50765887277c3edd373e973baa67be52157 Mon Sep 17 00:00:00 2001 From: Dimo Date: Thu, 20 Jul 2017 16:32:07 -0700 Subject: [PATCH 898/968] Rename Dict[Var, TypeVar] to VarTyping; Add VarMap (Dict[Var,Var]). Add {Ast, Def, Rtl}.{vars(), substitution()} and Def.uses(), Def.definitions() - these enable checking structural equivalence between Rtls and doing variable substitutions between compatible Rtls; Add TypeEnv.permits() routine - allows checking if a given TypeEnv allows a given concrete typing without enumerating all typings (will be useful for determing which semantic transform applies to a given concrete typing). --- lib/cretonne/meta/cdsl/ast.py | 79 +++++++++++++++++++++++++- lib/cretonne/meta/cdsl/instructions.py | 4 +- lib/cretonne/meta/cdsl/test_ti.py | 8 +-- lib/cretonne/meta/cdsl/ti.py | 46 +++++++++++++-- lib/cretonne/meta/cdsl/xform.py | 38 ++++++++++++- 5 files changed, 161 insertions(+), 14 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 0d87bf8914..fa9c359a62 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -11,15 +11,17 @@ from .predicates import IsEqual, And try: from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa + from typing import Optional, Set # noqa if TYPE_CHECKING: from .operands import ImmediateKind # noqa from .predicates import PredNode # noqa + VarMap = Dict["Var", "Var"] except ImportError: pass def replace_var(arg, m): - # type: (Expr, Dict[Var, Var]) -> Expr + # type: (Expr, VarMap) -> Expr """ Given a var v return either m[v] or a new variable v' (and remember m[v]=v'). Otherwise return the argument unchanged @@ -74,7 +76,7 @@ class Def(object): ', '.join(map(str, self.defs)), self.expr) def copy(self, m): - # type: (Dict[Var, Var]) -> Def + # type: (VarMap) -> Def """ Return a copy of this Def with vars replaced with fresh variables, in accordance with the map m. Update m as neccessary. @@ -88,6 +90,40 @@ class Def(object): return Def(tuple(new_defs), new_expr) + def definitions(self): + # type: () -> Set[Var] + """ Return the set of all Vars that are defined by self""" + return set(self.defs) + + def uses(self): + # type: () -> Set[Var] + """ Return the set of all Vars that are used(read) by self""" + return set(self.expr.vars()) + + def vars(self): + # type: () -> Set[Var] + """ Return the set of all Vars that appear in self""" + return self.definitions().union(self.uses()) + + def substitution(self, other, s): + # type: (Def, VarMap) -> Optional[VarMap] + """ + If the Defs self and other agree structurally, return a variable + substitution to transform self ot other. Two Defs agree structurally + if the contained Apply's agree structurally. + """ + s = self.expr.substitution(other.expr, s) + + if (s is None): + return s + + assert len(self.defs) == len(other.defs) + for (self_d, other_d) in zip(self.defs, other.defs): + assert self_d not in s # Guaranteed by SSA form + s[self_d] = other_d + + return s + class Expr(object): """ @@ -332,7 +368,7 @@ class Apply(Expr): return pred def copy(self, m): - # type: (Dict[Var, Var]) -> Apply + # type: (VarMap) -> Apply """ Return a copy of this Expr with vars replaced with fresh variables, in accordance with the map m. Update m as neccessary. @@ -340,6 +376,43 @@ class Apply(Expr): return Apply(self.inst, tuple(map(lambda e: replace_var(e, m), self.args))) + def vars(self): + # type: () -> Set[Var] + """ Return the set of all Vars that appear in self""" + res = set() + for i in self.inst.value_opnums: + arg = self.args[i] + assert isinstance(arg, Var) + res.add(arg) + return res + + def substitution(self, other, s): + # type: (Apply, VarMap) -> Optional[VarMap] + """ + If the application self and other agree structurally, return a variable + substitution to transform self ot other. Two applications agree + structurally if: + 1) They are over the same instruction + 2) Every Var v in self, maps to a single Var w in other. I.e for + each use of v in self, w is used in the corresponding place in + other. + """ + if self.inst != other.inst: + return None + + # TODO: Should we check imm/cond codes here as well? + for i in self.inst.value_opnums: + self_a = self.args[i] + other_a = other.args[i] + + assert isinstance(self_a, Var) and isinstance(other_a, Var) + if (self_a not in s): + s[self_a] = other_a + else: + if (s[self_a] != other_a): + return None + return s + class Enumerator(Expr): """ diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 7b8cc55b31..0bbaccb353 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -7,14 +7,16 @@ from .formats import InstructionFormat try: from typing import Union, Sequence, List, Tuple, Any, TYPE_CHECKING # noqa + from typing import Dict # noqa if TYPE_CHECKING: - from .ast import Expr, Apply # noqa + from .ast import Expr, Apply, Var # noqa from .typevar import TypeVar # noqa from .ti import TypeConstraint # noqa # List of operands for ins/outs: OpList = Union[Sequence[Operand], Operand] ConstrList = Union[Sequence[TypeConstraint], TypeConstraint] MaybeBoundInst = Union['Instruction', 'BoundInstruction'] + VarTyping = Dict[Var, TypeVar] except ImportError: pass diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index f60a9222f5..9351d9c3ea 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -13,7 +13,7 @@ from unittest import TestCase from functools import reduce try: - from .ti import TypeMap, ConstraintList, VarMap, TypingOrError # noqa + from .ti import TypeMap, ConstraintList, VarTyping, TypingOrError # noqa from typing import List, Dict, Tuple, TYPE_CHECKING, cast # noqa except ImportError: TYPE_CHECKING = False @@ -61,7 +61,7 @@ def agree(me, other): def check_typing(got_or_err, expected, symtab=None): - # type: (TypingOrError, Tuple[VarMap, ConstraintList], Dict[str, Var]) -> None # noqa + # type: (TypingOrError, Tuple[VarTyping, ConstraintList], Dict[str, Var]) -> None # noqa """ Check that a the typing we received (got_or_err) complies with the expected typing (expected). If symtab is specified, substitute the Vars in @@ -93,7 +93,7 @@ def check_typing(got_or_err, expected, symtab=None): def check_concrete_typing_rtl(var_types, rtl): - # type: (VarMap, Rtl) -> None + # type: (VarTyping, Rtl) -> None """ Check that a concrete type assignment var_types (Dict[Var, TypeVar]) is valid for an Rtl rtl. Specifically check that: @@ -136,7 +136,7 @@ def check_concrete_typing_rtl(var_types, rtl): def check_concrete_typing_xform(var_types, xform): - # type: (VarMap, XForm) -> None + # type: (VarTyping, XForm) -> None """ Check a concrete type assignment var_types for an XForm xform """ diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index 79023b8b34..ab4ae12f19 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -15,7 +15,7 @@ try: from .typevar import TypeSet # noqa if TYPE_CHECKING: TypeMap = Dict[TypeVar, TypeVar] - VarMap = Dict[Var, TypeVar] + VarTyping = Dict[Var, TypeVar] except ImportError: TYPE_CHECKING = False pass @@ -257,6 +257,7 @@ class TypeEnv(object): Lookup the canonical representative for a Var/TypeVar. """ if (isinstance(arg, Var)): + assert arg in self.vars tv = arg.get_typevar() else: assert (isinstance(arg, TypeVar)) @@ -290,8 +291,16 @@ class TypeEnv(object): """ Add a new constraint """ - if (constr not in self.constraints): - self.constraints.append(constr) + if (constr in self.constraints): + return + + # InTypeset constraints can be expressed by constraining the typeset of + # a variable. No need to add them to self.constraints + if (isinstance(constr, InTypeset)): + self[constr.tv].constrain_types_by_ts(constr.ts) + return + + self.constraints.append(constr) def get_uid(self): # type: () -> str @@ -436,7 +445,7 @@ class TypeEnv(object): return t def concrete_typings(self): - # type: () -> Iterable[VarMap] + # type: () -> Iterable[VarTyping] """ Return an iterable over all possible concrete typings permitted by this TypeEnv. @@ -464,6 +473,35 @@ class TypeEnv(object): yield concrete_var_map + def permits(self, concrete_typing): + # type: (VarTyping) -> bool + """ + Return true iff this TypeEnv permits the (possibly partial) concrete + variable type mapping concrete_typing. + """ + # Each variable has a concrete type, that is a subset of its inferred + # typeset. + for (v, typ) in concrete_typing.items(): + assert typ.singleton_type() is not None + if not typ.get_typeset().issubset(self[v].get_typeset()): + return False + + m = {self[v]: typ for (v, typ) in concrete_typing.items()} + + # Constraints involving vars in concrete_typing are satisfied + for constr in self.constraints: + try: + # If the constraint includes only vars in concrete_typing, we + # can translate it using m. Otherwise we encounter a KeyError + # and ignore it + constr = constr.translate(m) + if not constr.eval(): + return False + except KeyError: + pass + + return True + def dot(self): # type: () -> str """ diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 6729267f7b..a43846a372 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -4,10 +4,12 @@ Instruction transformations. from __future__ import absolute_import from .ast import Def, Var, Apply from .ti import ti_xform, TypeEnv, get_type_env +from functools import reduce try: from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa - from .ast import Expr # noqa + from typing import Optional, Set # noqa + from .ast import Expr, VarMap # noqa DefApply = Union[Def, Apply] except ImportError: pass @@ -42,13 +44,45 @@ class Rtl(object): self.rtl = tuple(map(canonicalize_defapply, args)) def copy(self, m): - # type: (Dict[Var, Var]) -> Rtl + # type: (VarMap) -> Rtl """ Return a copy of this rtl with all Vars substituted with copies or according to m. Update m as neccessary. """ return Rtl(*[d.copy(m) for d in self.rtl]) + def vars(self): + # type: () -> Set[Var] + """ Return the set of all Vars that appear in self""" + return reduce(lambda x, y: x.union(y), + [d.vars() for d in self.rtl], + set([])) + + def definitions(self): + # type: () -> Set[Var] + """ Return the set of all Vars defined in self""" + return reduce(lambda x, y: x.union(y), + [d.definitions() for d in self.rtl], + set([])) + + def substitution(self, other, s): + # type: (Rtl, VarMap) -> Optional[VarMap] + """ + If the Rtl self agrees structurally with the Rtl other, return a + substitution to transform self to other. Two Rtls agree structurally if + they have the same sequence of Defs, that agree structurally. + """ + if len(self.rtl) != len(other.rtl): + return None + + for i in range(len(self.rtl)): + s = self.rtl[i].substitution(other.rtl[i], s) + + if s is None: + return None + + return s + class XForm(object): """ From bd2e9e5d0b69fc44d2eac12395e2c6432b940f81 Mon Sep 17 00:00:00 2001 From: Dimo Date: Thu, 20 Jul 2017 17:20:23 -0700 Subject: [PATCH 899/968] Add the BVType; Add suport for bitvectors in TypeVar and TypeSet. --- lib/cretonne/meta/cdsl/ast.py | 2 +- lib/cretonne/meta/cdsl/types.py | 36 +++++++++- lib/cretonne/meta/cdsl/typevar.py | 110 +++++++++++++++++++++++++++--- 3 files changed, 136 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index fa9c359a62..c5e6d58def 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -239,7 +239,7 @@ class Var(Expr): 'typeof_{}'.format(self), 'Type of the pattern variable `{}`'.format(self), ints=True, floats=True, bools=True, - scalars=True, simd=True) + scalars=True, simd=True, bitvecs=True) self.original_typevar = tv self.typevar = tv return self.typevar diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index cebe472fbf..80b096cbc1 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -84,7 +84,10 @@ class ScalarType(ValueType): self._vectors = dict() # type: Dict[int, VectorType] # Assign numbers starting from 1. (0 is VOID). ValueType.all_scalars.append(self) - self.number = len(ValueType.all_scalars) + # Numbers are only valid for Cretone types that get emitted to Rust. + # This excludes BVTypes + self.number = len([x for x in ValueType.all_scalars + if not isinstance(x, BVType)]) assert self.number < 16, 'Too many scalar types' def __repr__(self): @@ -239,3 +242,34 @@ class BoolType(ScalarType): # type: () -> int """Return the number of bits in a lane.""" return self.bits + + +class BVType(ScalarType): + """A flat bitvector type. Used for semantics description only.""" + + def __init__(self, bits): + # type: (int) -> None + assert bits > 0, 'Must have positive number of bits' + super(BVType, self).__init__( + name='bv{:d}'.format(bits), + membytes=bits // 8, + doc="A bitvector type with {} bits.".format(bits)) + self.bits = bits + + def __repr__(self): + # type: () -> str + return 'BVType(bits={})'.format(self.bits) + + @staticmethod + def with_bits(bits): + # type: (int) -> BVType + typ = ValueType.by_name('bv{:d}'.format(bits)) + if TYPE_CHECKING: + return cast(BVType, typ) + else: + return typ + + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + return self.bits diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index b28502f61b..54a5c92cc0 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -22,6 +22,7 @@ except ImportError: MAX_LANES = 256 MAX_BITS = 64 +MAX_BITVEC = MAX_BITS * MAX_LANES def int_log2(x): @@ -169,15 +170,20 @@ class TypeSet(object): point widths. :param bools: `(min, max)` inclusive range of permitted scalar boolean widths. + :param bitvecs : `(min, max)` inclusive range of permitted bitvector + widths. """ - def __init__(self, lanes=None, ints=None, floats=None, bools=None): - # type: (BoolInterval, BoolInterval, BoolInterval, BoolInterval) -> None # noqa + def __init__(self, lanes=None, ints=None, floats=None, bools=None, + bitvecs=None): + # type: (BoolInterval, BoolInterval, BoolInterval, BoolInterval, BoolInterval) -> None # noqa self.lanes = interval_to_set(decode_interval(lanes, (1, MAX_LANES), 1)) self.ints = interval_to_set(decode_interval(ints, (8, MAX_BITS))) self.floats = interval_to_set(decode_interval(floats, (32, 64))) self.bools = interval_to_set(decode_interval(bools, (1, MAX_BITS))) self.bools = set(filter(legal_bool, self.bools)) + self.bitvecs = interval_to_set(decode_interval(bitvecs, + (1, MAX_BITVEC))) def copy(self): # type: (TypeSet) -> TypeSet @@ -188,12 +194,13 @@ class TypeSet(object): return deepcopy(self) def typeset_key(self): - # type: () -> Tuple[Tuple, Tuple, Tuple, Tuple] + # type: () -> Tuple[Tuple, Tuple, Tuple, Tuple, Tuple] """Key tuple used for hashing and equality.""" return (tuple(sorted(list(self.lanes))), tuple(sorted(list(self.ints))), tuple(sorted(list(self.floats))), - tuple(sorted(list(self.bools)))) + tuple(sorted(list(self.bools))), + tuple(sorted(list(self.bitvecs)))) def __hash__(self): # type: () -> int @@ -222,11 +229,14 @@ class TypeSet(object): s += ', floats={}'.format(pp_set(self.floats)) if len(self.bools) > 0: s += ', bools={}'.format(pp_set(self.bools)) + if len(self.bitvecs) > 0: + s += ', bitvecs={}'.format(pp_set(self.bitvecs)) return s + ')' def emit_fields(self, fmt): # type: (Formatter) -> None """Emit field initializers for this typeset.""" + assert len(self.bitvecs) == 0, "Bitvector types are not emitable." fmt.comment(repr(self)) fields = (('lanes', 16), @@ -262,6 +272,7 @@ class TypeSet(object): self.ints.intersection_update(other.ints) self.floats.intersection_update(other.floats) self.bools.intersection_update(other.bools) + self.bitvecs.intersection_update(other.bitvecs) return self @@ -273,7 +284,8 @@ class TypeSet(object): return self.lanes.issubset(other.lanes) and \ self.ints.issubset(other.ints) and \ self.floats.issubset(other.floats) and \ - self.bools.issubset(other.bools) + self.bools.issubset(other.bools) and \ + self.bitvecs.issubset(other.bitvecs) def lane_of(self): # type: () -> TypeSet @@ -282,6 +294,7 @@ class TypeSet(object): """ new = self.copy() new.lanes = set([1]) + new.bitvecs = set() return new def as_bool(self): @@ -292,6 +305,7 @@ class TypeSet(object): new = self.copy() new.ints = set() new.floats = set() + new.bitvecs = set() if len(self.lanes.difference(set([1]))) > 0: new.bools = self.ints.union(self.floats).union(self.bools) @@ -309,6 +323,7 @@ class TypeSet(object): new.ints = set([x//2 for x in self.ints if x > 8]) new.floats = set([x//2 for x in self.floats if x > 32]) new.bools = set([x//2 for x in self.bools if x > 8]) + new.bitvecs = set([x//2 for x in self.bitvecs if x > 1]) return new @@ -322,6 +337,7 @@ class TypeSet(object): new.floats = set([x*2 for x in self.floats if x < MAX_BITS]) new.bools = set(filter(legal_bool, set([x*2 for x in self.bools if x < MAX_BITS]))) + new.bitvecs = set([x*2 for x in self.bitvecs if x < MAX_BITVEC]) return new @@ -331,6 +347,7 @@ class TypeSet(object): Return a TypeSet describing the image of self across halfvector """ new = self.copy() + new.bitvecs = set() new.lanes = set([x//2 for x in self.lanes if x > 1]) return new @@ -341,10 +358,29 @@ class TypeSet(object): Return a TypeSet describing the image of self across doublevector """ new = self.copy() + new.bitvecs = set() new.lanes = set([x*2 for x in self.lanes if x < MAX_LANES]) return new + def to_bitvec(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across to_bitvec + """ + assert len(self.bitvecs) == 0 + all_scalars = self.ints.union(self.floats.union(self.bools)) + + new = self.copy() + new.lanes = set([1]) + new.ints = set() + new.bools = set() + new.floats = set() + new.bitvecs = set([lane_w * nlanes for lane_w in all_scalars + for nlanes in self.lanes]) + + return new + def image(self, func): # type: (str) -> TypeSet """ @@ -362,6 +398,8 @@ class TypeSet(object): return self.half_vector() elif (func == TypeVar.DOUBLEVECTOR): return self.double_vector() + elif (func == TypeVar.TOBITVEC): + return self.to_bitvec() else: assert False, "Unknown derived function: " + func @@ -376,10 +414,12 @@ class TypeSet(object): if (func == TypeVar.LANEOF): new = self.copy() + new.bitvecs = set() new.lanes = set([2**i for i in range(0, int_log2(MAX_LANES)+1)]) return new elif (func == TypeVar.ASBOOL): new = self.copy() + new.bitvecs = set() if 1 not in self.bools: new.ints = self.bools.difference(set([1])) @@ -400,6 +440,39 @@ class TypeSet(object): return self.double_vector() elif (func == TypeVar.DOUBLEVECTOR): return self.half_vector() + elif (func == TypeVar.TOBITVEC): + new = TypeSet() + + # Start with all possible lanes/ints/floats/bools + lanes = interval_to_set(decode_interval(True, (1, MAX_LANES), 1)) + ints = interval_to_set(decode_interval(True, (8, MAX_BITS))) + floats = interval_to_set(decode_interval(True, (32, 64))) + bools = interval_to_set(decode_interval(True, (1, MAX_BITS))) + + # See which combinations have a size that appears in self.bitvecs + has_t = set() # type: Set[Tuple[str, int, int]] + for l in lanes: + for i in ints: + if i * l in self.bitvecs: + has_t.add(('i', i, l)) + for i in bools: + if i * l in self.bitvecs: + has_t.add(('b', i, l)) + for i in floats: + if i * l in self.bitvecs: + has_t.add(('f', i, l)) + + for (t, width, lane) in has_t: + new.lanes.add(lane) + if (t == 'i'): + new.ints.add(width) + elif (t == 'b'): + new.bools.add(width) + else: + assert t == 'f' + new.floats.add(width) + + return new else: assert False, "Unknown derived function: " + func @@ -409,7 +482,7 @@ class TypeSet(object): Return the number of concrete types represented by this typeset """ return len(self.lanes) * (len(self.ints) + len(self.floats) + - len(self.bools)) + len(self.bools) + len(self.bitvecs)) def concrete_types(self): # type: () -> Iterable[types.ValueType] @@ -427,6 +500,8 @@ class TypeSet(object): yield by(types.FloatType.with_bits(bits), nlanes) for bits in self.bools: yield by(types.BoolType.with_bits(bits), nlanes) + for bits in self.bitvecs: + yield by(types.BVType.with_bits(bits), nlanes) def get_singleton(self): # type: () -> types.ValueType @@ -458,14 +533,15 @@ class TypeVar(object): :param scalars: Allow type variable to assume scalar types. :param simd: Allow type variable to assume vector types, or `(min, max)` lane count range. + :param bitvecs: Allow all BitVec base types, or `(min, max)` bit-range. """ def __init__( self, name, doc, ints=False, floats=False, bools=False, - scalars=True, simd=False, + scalars=True, simd=False, bitvecs=False, base=None, derived_func=None): - # type: (str, str, BoolInterval, BoolInterval, BoolInterval, bool, BoolInterval, TypeVar, str) -> None # noqa + # type: (str, str, BoolInterval, BoolInterval, BoolInterval, bool, BoolInterval, BoolInterval, TypeVar, str) -> None # noqa self.name = name self.__doc__ = doc self.is_derived = isinstance(base, TypeVar) @@ -482,7 +558,8 @@ class TypeVar(object): lanes=lanes, ints=ints, floats=floats, - bools=bools) + bools=bools, + bitvecs=bitvecs) @staticmethod def singleton(typ): @@ -498,6 +575,7 @@ class TypeVar(object): ints = None floats = None bools = None + bitvecs = None if isinstance(scalar, types.IntType): ints = (scalar.bits, scalar.bits) @@ -505,10 +583,13 @@ class TypeVar(object): floats = (scalar.bits, scalar.bits) elif isinstance(scalar, types.BoolType): bools = (scalar.bits, scalar.bits) + elif isinstance(scalar, types.BVType): + bitvecs = (scalar.bits, scalar.bits) tv = TypeVar( typ.name, typ.__doc__, - ints, floats, bools, simd=lanes) + ints=ints, floats=floats, bools=bools, + bitvecs=bitvecs, simd=lanes) return tv def __str__(self): @@ -558,6 +639,7 @@ class TypeVar(object): DOUBLEWIDTH = 'double_width' HALFVECTOR = 'half_vector' DOUBLEVECTOR = 'double_vector' + TOBITVEC = 'to_bitvec' @staticmethod def is_bijection(func): @@ -668,6 +750,14 @@ class TypeVar(object): """ return TypeVar.derived(self, self.DOUBLEVECTOR) + def to_bitvec(self): + # type: () -> TypeVar + """ + Return a derived type variable that represent a flat bitvector with + the same size as self + """ + return TypeVar.derived(self, self.TOBITVEC) + def singleton_type(self): # type: () -> ValueType """ From 40c86d58b9bc779fad43541054e720913ea97e53 Mon Sep 17 00:00:00 2001 From: Dimo Date: Thu, 20 Jul 2017 18:21:55 -0700 Subject: [PATCH 900/968] Add insturction semantics. Add semantics for vsplit,vconcat,iadd. Add initial tests --- lib/cretonne/meta/base/semantics.py | 57 +++ lib/cretonne/meta/cdsl/instructions.py | 15 +- lib/cretonne/meta/cdsl/ti.py | 56 ++- lib/cretonne/meta/cdsl/types.py | 5 + lib/cretonne/meta/cdsl/typevar.py | 7 + lib/cretonne/meta/cdsl/xform.py | 21 +- lib/cretonne/meta/semantics/__init__.py | 55 +++ lib/cretonne/meta/semantics/elaborate.py | 170 +++++++++ lib/cretonne/meta/semantics/primitives.py | 71 ++++ lib/cretonne/meta/semantics/test_elaborate.py | 337 ++++++++++++++++++ lib/cretonne/meta/semantics/types.py | 9 + 11 files changed, 797 insertions(+), 6 deletions(-) create mode 100644 lib/cretonne/meta/base/semantics.py create mode 100644 lib/cretonne/meta/semantics/__init__.py create mode 100644 lib/cretonne/meta/semantics/elaborate.py create mode 100644 lib/cretonne/meta/semantics/primitives.py create mode 100644 lib/cretonne/meta/semantics/test_elaborate.py create mode 100644 lib/cretonne/meta/semantics/types.py diff --git a/lib/cretonne/meta/base/semantics.py b/lib/cretonne/meta/base/semantics.py new file mode 100644 index 0000000000..d85dad0682 --- /dev/null +++ b/lib/cretonne/meta/base/semantics.py @@ -0,0 +1,57 @@ +from __future__ import absolute_import +from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\ + bvadd +from .instructions import vsplit, vconcat, iadd +from cdsl.xform import XForm, Rtl +from cdsl.ast import Var +from cdsl.typevar import TypeSet +from cdsl.ti import InTypeset +import semantics.types # noqa + +x = Var('x') +y = Var('y') +a = Var('a') +xhi = Var('xhi') +yhi = Var('yhi') +ahi = Var('ahi') +xlo = Var('xlo') +ylo = Var('ylo') +alo = Var('alo') +lo = Var('lo') +hi = Var('hi') +bvx = Var('bvx') +bvy = Var('bvy') +bva = Var('bva') +bvlo = Var('bvlo') +bvhi = Var('bvhi') + +ScalarTS = TypeSet(lanes=(1, 1), ints=True, floats=True, bools=True) + +vsplit.set_semantics( + XForm(Rtl((lo, hi) << vsplit(x)), + Rtl(bvx << prim_to_bv(x), + (bvlo, bvhi) << bvsplit(bvx), + lo << prim_from_bv(bvlo), + hi << prim_from_bv(bvhi)))) + +vconcat.set_semantics( + XForm(Rtl(x << vconcat(lo, hi)), + Rtl(bvlo << prim_to_bv(lo), + bvhi << prim_to_bv(hi), + bvx << bvconcat(bvlo, bvhi), + x << prim_from_bv(bvx)))) + +iadd.set_semantics([ + XForm(Rtl(a << iadd(x, y)), + Rtl(bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bva << bvadd(bvx, bvy), + a << prim_from_bv(bva)), + constraints=[InTypeset(x.get_typevar(), ScalarTS)]), + XForm(Rtl(a << iadd(x, y)), + Rtl((xlo, xhi) << vsplit(x), + (ylo, yhi) << vsplit(y), + alo << iadd(xlo, ylo), + ahi << iadd(xhi, yhi), + a << vconcat(alo, ahi))) +]) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 0bbaccb353..ee9bf15e5e 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -12,11 +12,12 @@ try: from .ast import Expr, Apply, Var # noqa from .typevar import TypeVar # noqa from .ti import TypeConstraint # noqa + from .xform import XForm # List of operands for ins/outs: OpList = Union[Sequence[Operand], Operand] ConstrList = Union[Sequence[TypeConstraint], TypeConstraint] MaybeBoundInst = Union['Instruction', 'BoundInstruction'] - VarTyping = Dict[Var, TypeVar] + InstructionSemantics = List[XForm] except ImportError: pass @@ -119,6 +120,7 @@ class Instruction(object): self.outs = self._to_operand_tuple(outs) self.constraints = self._to_constraint_tuple(constraints) self.format = InstructionFormat.lookup(self.ins, self.outs) + self.semantics = None # type: InstructionSemantics # Opcode number, assigned by gen_instr.py. self.number = None # type: int @@ -334,6 +336,17 @@ class Instruction(object): from .ast import Apply # noqa return Apply(self, args) + def set_semantics(self, sem): + # type: (Union[XForm, InstructionSemantics]) -> None + """Set our semantics.""" + from semantics import verify_semantics + + if not isinstance(sem, list): + sem = [sem] + + verify_semantics(self, sem) + self.semantics = sem + class BoundInstruction(object): """ diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index ab4ae12f19..b80396b428 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -54,8 +54,8 @@ class TypeConstraint(object): """ Return true iff all typevars in the constraint are singletons. """ - tvs = filter(lambda x: isinstance(x, TypeVar), self._args()) - return [] == list(filter(lambda x: x.singleton_type() is None, tvs)) + return [] == list(filter(lambda x: x.singleton_type() is None, + self.tvs())) def __hash__(self): # type: () -> int @@ -69,6 +69,13 @@ class TypeConstraint(object): """ assert False, "Abstract" + def tvs(self): + # type: () -> Iterable[TypeVar] + """ + Return the typevars contained in this constraint. + """ + return filter(lambda x: isinstance(x, TypeVar), self._args()) + def is_trivial(self): # type: () -> bool """ @@ -218,6 +225,47 @@ class WiderOrEq(TypeConstraint): return typ1.wider_or_equal(typ2) +class SameWidth(TypeConstraint): + """ + Constraint specifying that two types have the same width. E.g. i32x2 has + the same width as i64x1, i16x4, f32x2, f64, b1x64 etc. + """ + def __init__(self, tv1, tv2): + # type: (TypeVar, TypeVar) -> None + self.tv1 = tv1 + self.tv2 = tv2 + + def _args(self): + # type: () -> Tuple[Any,...] + """ See TypeConstraint._args() """ + return (self.tv1, self.tv2) + + def is_trivial(self): + # type: () -> bool + """ See TypeConstraint.is_trivial() """ + # Trivially true + if (self.tv1 == self.tv2): + return True + + ts1 = self.tv1.get_typeset() + ts2 = self.tv2.get_typeset() + + # Trivially False + if len(ts1.widths().intersection(ts2.widths())) == 0: + return True + + return self.is_concrete() + + def eval(self): + # type: () -> bool + """ See TypeConstraint.eval() """ + assert self.is_concrete() + typ1 = self.tv1.singleton_type() + typ2 = self.tv2.singleton_type() + + return (typ1.width() == typ2.width()) + + class TypeEnv(object): """ Class encapsulating the neccessary book keeping for type inference. @@ -537,6 +585,10 @@ class TypeEnv(object): elif isinstance(constr, WiderOrEq): assert constr.tv1 in nodes and constr.tv2 in nodes edges.add((constr.tv1, constr.tv2, "dashed", "forward", ">=")) + elif isinstance(constr, SameWidth): + assert constr.tv1 in nodes and constr.tv2 in nodes + edges.add((constr.tv1, constr.tv2, "dashed", "none", + "same_width")) else: assert False, "Can't display constraint {}".format(constr) diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index 80b096cbc1..846cc442c9 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -59,6 +59,11 @@ class ValueType(object): """Return the number of lanes.""" assert False, "Abstract" + def width(self): + # type: () -> int + """Return the total number of bits of an instance of this type.""" + return self.lane_count() * self.lane_bits() + def wider_or_equal(self, other): # type: (ValueType) -> bool """ diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 54a5c92cc0..2e7d4cb5cc 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -513,6 +513,13 @@ class TypeSet(object): assert len(types) == 1 return types[0] + def widths(self): + # type: () -> Set[int] + """ Return a set of the widths of all possible types in self""" + scalar_w = self.ints.union(self.floats.union(self.bools)) + scalar_w = scalar_w.union(self.bitvecs) + return set(w * l for l in self.lanes for w in scalar_w) + class TypeVar(object): """ diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index a43846a372..42655272b8 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -10,6 +10,8 @@ try: from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa from typing import Optional, Set # noqa from .ast import Expr, VarMap # noqa + from .ti import TypeConstraint # noqa + from .typevar import TypeVar # noqa DefApply = Union[Def, Apply] except ImportError: pass @@ -89,7 +91,8 @@ class XForm(object): An instruction transformation consists of a source and destination pattern. Patterns are expressed in *register transfer language* as tuples of - `ast.Def` or `ast.Expr` nodes. + `ast.Def` or `ast.Expr` nodes. A pattern may optionally have a sequence of + TypeConstraints, that additionally limit the set of cases when it applies. A legalization pattern must have a source pattern containing only a single instruction. @@ -111,8 +114,8 @@ class XForm(object): ) """ - def __init__(self, src, dst): - # type: (Rtl, Rtl) -> None + def __init__(self, src, dst, constraints=None): + # type: (Rtl, Rtl, Optional[Sequence[TypeConstraint]]) -> None self.src = src self.dst = dst # Variables that are inputs to the source pattern. @@ -146,6 +149,18 @@ class XForm(object): raw_ti.normalize() self.ti = raw_ti.extract() + def interp_tv(tv): + # type: (TypeVar) -> TypeVar + """ Convert typevars according to symtab """ + if not tv.name.startswith("typeof_"): + return tv + return symtab[tv.name[len("typeof_"):]].get_typevar() + + if constraints is not None: + for c in constraints: + type_m = {tv: interp_tv(tv) for tv in c.tvs()} + self.ti.add_constraint(c.translate(type_m)) + # Sanity: The set of inferred free typevars should be a subset of the # TVs corresponding to Vars appearing in src free_typevars = set(self.ti.free_typevars()) diff --git a/lib/cretonne/meta/semantics/__init__.py b/lib/cretonne/meta/semantics/__init__.py new file mode 100644 index 0000000000..d2c327c988 --- /dev/null +++ b/lib/cretonne/meta/semantics/__init__.py @@ -0,0 +1,55 @@ +"""Definitions for the semantics segment of the Cretonne language.""" +from cdsl.ti import TypeEnv, ti_rtl, get_type_env + +try: + from typing import List, Dict, Tuple # noqa + from cdsl.ast import Var # noqa + from cdsl.xform import XForm # noqa + from cdsl.ti import VarTyping # noqa + from cdsl.instructions import Instruction, InstructionSemantics # noqa +except ImportError: + pass + + +def verify_semantics(inst, sem): + # type: (Instruction, InstructionSemantics) -> None + """ + Verify that the semantics sem correctly describes the instruction inst. + This involves checking that: + 1) For all XForms x \in sem, x.src consists of a single instance of + inst + 2) For any possible concrete typing of inst there is exactly 1 XForm x + in sem that applies. + """ + # 1) The source rtl is always a single instance of inst. + for xform in sem: + assert len(xform.src.rtl) == 1 and\ + xform.src.rtl[0].expr.inst == inst,\ + "XForm {} doesn't describe instruction {}."\ + .format(xform, inst) + + # 2) Any possible typing for the instruction should be covered by + # exactly ONE semantic XForm + inst_rtl = sem[0].src + typenv = get_type_env(ti_rtl(inst_rtl, TypeEnv())) + + # This bit is awkward. Concrete typing is defined in terms of the vars + # of one Rtl. We arbitrarily picked that Rtl to be sem[0].src. For any + # other XForms in sem, we must build a substitution form + # sem[0].src->sem[N].src, before we can check if sem[N] permits one of + # the concrete typings of our Rtl. + # TODO: Can this be made cleaner? + subst = [inst_rtl.substitution(x.src, {}) for x in sem] + assert not any(x is None for x in subst) + sub_sem = list(zip(subst, sem)) # type: List[Tuple[Dict[Var, Var], XForm]] # noqa + + def subst_typing(typing, sub): + # type: (VarTyping, Dict[Var, Var]) -> VarTyping + return {sub[v]: tv for (v, tv) in typing.items()} + + for t in typenv.concrete_typings(): + matching_xforms = [x for (s, x) in sub_sem + if x.ti.permits(subst_typing(t, s))] + assert len(matching_xforms) == 1,\ + ("Possible typing {} of {} not matched by exactly one case " + + ": {}").format(t, inst, matching_xforms) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py new file mode 100644 index 0000000000..e6be0180e1 --- /dev/null +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -0,0 +1,170 @@ +""" +Tools to elaborate a given Rtl with concrete types into its semantically +equivalent primitive version. Its elaborated primitive version contains only +primitive cretonne instructions, which map well to SMTLIB functions. +""" +from .primitives import GROUP as PRIMITIVES, prim_to_bv, prim_from_bv +from cdsl.ti import ti_rtl, TypeEnv, get_type_env +from cdsl.typevar import TypeVar + +try: + from typing import TYPE_CHECKING, Dict, Union, List, Set, Tuple # noqa + from cdsl.xform import Rtl, XForm # noqa + from cdsl.ast import Var, Def, VarMap # noqa + from cdsl.ti import VarTyping # noqa +except ImportError: + TYPE_CHECKING = False + + +def is_rtl_concrete(r): + # type: (Rtl) -> bool + """Return True iff every Var in the Rtl r has a single type.""" + return all(v.get_typevar().singleton_type() is not None for v in r.vars()) + + +def cleanup_concrete_rtl(r): + # type: (Rtl) -> Rtl + """ + Given an Rtl r + 1) assert that there is only 1 possible concrete typing T for r + 2) Assign a singleton TV with the single type t \in T for each Var v \in r + """ + # 1) Infer the types of any of the remaining vars in res + typenv = get_type_env(ti_rtl(r, TypeEnv())) + typenv.normalize() + typenv = typenv.extract() + + # 2) Make sure there is only one possible type assignment + typings = list(typenv.concrete_typings()) + assert len(typings) == 1 + typing = typings[0] + + # 3) Assign the only possible type to each variable. + for v in typenv.vars: + if v.get_typevar().singleton_type() is not None: + continue + + v.set_typevar(TypeVar.singleton(typing[v].singleton_type())) + + return r + + +def apply(r, x, suffix=None): + # type: (Rtl, XForm, str) -> Rtl + """ + Given a concrete Rtl r and XForm x, s.t. r matches x.src, return the + corresponding concrete x.dst. If suffix is provided, any temporary defs are + renamed with '.suffix' appended to their old name. + """ + assert is_rtl_concrete(r) + s = x.src.substitution(r, {}) # type: VarMap + assert s is not None + + if (suffix is not None): + for v in x.dst.vars(): + if v.is_temp(): + assert v not in s + s[v] = Var(v.name + '.' + suffix) + + dst = x.dst.copy(s) + return cleanup_concrete_rtl(dst) + + +def find_matching_xform(d): + # type: (Def) -> XForm + """ + Given a concrete Def d, find the unique semantic XForm x in + d.expr.inst.semantics that applies to it. + """ + res = [] # type: List[XForm] + typing = {v: v.get_typevar() for v in d.vars()} # type: VarTyping + + for x in d.expr.inst.semantics: + subst = d.substitution(x.src.rtl[0], {}) + + if x.ti.permits({subst[v]: tv for (v, tv) in typing.items()}): + res.append(x) + + assert len(res) == 1 + return res[0] + + +def elaborate(r): + # type: (Rtl) -> Rtl + """ + Given an Rtl r, return a semantically equivalent Rtl r1 consisting only + primitive instructions. + """ + fp = False + primitives = set(PRIMITIVES.instructions) + idx = 0 + + while not fp: + assert is_rtl_concrete(r) + new_defs = [] # type: List[Def] + fp = True + + for d in r.rtl: + inst = d.expr.inst + + if (inst not in primitives): + transformed = apply(Rtl(d), find_matching_xform(d), str(idx)) + idx += 1 + new_defs.extend(transformed.rtl) + fp = False + else: + new_defs.append(d) + + r.rtl = tuple(new_defs) + + return r + + +def cleanup_semantics(r, outputs): + # type: (Rtl, Set[Var]) -> Rtl + """ + The elaboration process creates a lot of redundant instruction pairs of the + shape: + + a.0 << prim_from_bv(bva.0) + ... + bva.1 << prim_to_bv(a.0) + ... + + Contract these to ease manual inspection. + """ + new_defs = [] # type: List[Def] + subst_m = {v: v for v in r.vars()} # type: VarMap + definition = {} # type: Dict[Var, Def] + + # Pass 1: Remove redundant prim_to_bv + for d in r.rtl: + inst = d.expr.inst + + if (inst == prim_to_bv): + if d.expr.args[0] in definition: + assert isinstance(d.expr.args[0], Var) + def_loc = definition[d.expr.args[0]] + + if def_loc.expr.inst == prim_from_bv: + assert isinstance(def_loc.expr.args[0], Var) + subst_m[d.defs[0]] = def_loc.expr.args[0] + continue + + new_def = d.copy(subst_m) + + for v in new_def.defs: + assert v not in definition # Guaranteed by SSA + definition[v] = new_def + + new_defs.append(new_def) + + # Pass 2: Remove dead prim_from_bv + live = set(outputs) # type: Set[Var] + for d in new_defs: + live = live.union(d.uses()) + + new_defs = [d for d in new_defs if not (d.expr.inst == prim_from_bv and + d.defs[0] not in live)] + + return Rtl(*new_defs) diff --git a/lib/cretonne/meta/semantics/primitives.py b/lib/cretonne/meta/semantics/primitives.py new file mode 100644 index 0000000000..d5b6b35d8b --- /dev/null +++ b/lib/cretonne/meta/semantics/primitives.py @@ -0,0 +1,71 @@ +""" +Cretonne primitive instruction set. + +This module defines a primitive instruction set, in terms of which the base set +is described. Most instructions in this set correspond 1-1 with an SMTLIB +bitvector function. +""" +from __future__ import absolute_import +from cdsl.operands import Operand +from cdsl.typevar import TypeVar +from cdsl.instructions import Instruction, InstructionGroup +from cdsl.ti import SameWidth +import base.formats # noqa + +GROUP = InstructionGroup("primitive", "Primitive instruction set") + +BV = TypeVar('BV', 'A bitvector type.', bitvecs=True) +Real = TypeVar('Real', 'Any real type.', ints=True, floats=True, + bools=True, simd=True) + +x = Operand('x', BV, doc="A semantic value X") +y = Operand('x', BV, doc="A semantic value Y (same width as X)") +a = Operand('a', BV, doc="A semantic value A (same width as X)") + +real = Operand('real', Real, doc="A real cretonne value") +fromReal = Operand('fromReal', Real.to_bitvec(), + doc="A real cretonne value converted to a BV") + +prim_to_bv = Instruction( + 'prim_to_bv', r""" + Convert an SSA Value to a flat bitvector + """, + ins=(real), outs=(fromReal)) + +# Note that when converting from BV->real values, we use a constraint and not a +# derived function. This reflects that fact that to_bitvec() is not a +# bijection. +prim_from_bv = Instruction( + 'prim_from_bv', r""" + Convert a flat bitvector to a real SSA Value. + """, + ins=(x), outs=(real), + constraints=SameWidth(BV, Real)) + +xh = Operand('xh', BV.half_width(), + doc="A semantic value representing the upper half of X") +xl = Operand('xl', BV.half_width(), + doc="A semantic value representing the lower half of X") +bvsplit = Instruction( + 'bvsplit', r""" + """, + ins=(x), outs=(xh, xl)) + +xy = Operand('xy', BV.double_width(), + doc="A semantic value representing the concatenation of X and Y") +bvconcat = Instruction( + 'bvconcat', r""" + """, + ins=(x, y), outs=xy) + +bvadd = Instruction( + 'bvadd', r""" + Standard 2's complement addition. Equivalent to wrapping integer + addition: :math:`a := x + y \pmod{2^B}`. + + This instruction does not depend on the signed/unsigned interpretation + of the operands. + """, + ins=(x, y), outs=a) + +GROUP.close() diff --git a/lib/cretonne/meta/semantics/test_elaborate.py b/lib/cretonne/meta/semantics/test_elaborate.py new file mode 100644 index 0000000000..15d4178ae8 --- /dev/null +++ b/lib/cretonne/meta/semantics/test_elaborate.py @@ -0,0 +1,337 @@ +from __future__ import absolute_import +from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint +from base.instructions import b1, icmp, ireduce +from base.immediates import intcc +from base.types import i64, i8, b32, i32, i16, f32 +from cdsl.typevar import TypeVar +from cdsl.ast import Var +from cdsl.xform import Rtl +from unittest import TestCase +from .elaborate import cleanup_concrete_rtl, elaborate, is_rtl_concrete,\ + cleanup_semantics +from .primitives import prim_to_bv, bvsplit, prim_from_bv, bvconcat, bvadd +import base.semantics # noqa + + +def concrete_rtls_eq(r1, r2): + # type: (Rtl, Rtl) -> bool + """ + Check whether 2 concrete Rtls are equivalent. That is: + 1) They are structurally the same (i.e. there is a substitution between + them) + 2) Corresponding Vars between them have the same singleton type. + """ + assert is_rtl_concrete(r1) + assert is_rtl_concrete(r2) + + s = r1.substitution(r2, {}) + + if s is None: + return False + + for (v, v1) in s.items(): + if v.get_typevar().singleton_type() !=\ + v1.get_typevar().singleton_type(): + return False + + return True + + +class TestCleanupConcreteRtl(TestCase): + """ + Test cleanup_concrete_rtl(). cleanup_concrete_rtl() should take Rtls for + which we can infer a single concrete typing, and update the TypeVars + in-place to singleton TVs. + """ + def test_cleanup_concrete_rtl(self): + # type: () -> None + typ = i64.by(4) + x = Var('x') + lo = Var('lo') + hi = Var('hi') + + x.set_typevar(TypeVar.singleton(typ)) + r = Rtl( + (lo, hi) << vsplit(x), + ) + r1 = cleanup_concrete_rtl(r) + + s = r.substitution(r1, {}) + assert s is not None + assert s[x].get_typevar().singleton_type() == typ + assert s[lo].get_typevar().singleton_type() == i64.by(2) + assert s[hi].get_typevar().singleton_type() == i64.by(2) + + def test_cleanup_concrete_rtl_fail(self): + # type: () -> None + x = Var('x') + lo = Var('lo') + hi = Var('hi') + r = Rtl( + (lo, hi) << vsplit(x), + ) + + with self.assertRaises(AssertionError): + cleanup_concrete_rtl(r) + + def test_cleanup_concrete_rtl_ireduce(self): + # type: () -> None + x = Var('x') + y = Var('y') + x.set_typevar(TypeVar.singleton(i8.by(2))) + r = Rtl( + y << ireduce(x), + ) + + r1 = cleanup_concrete_rtl(r) + + s = r.substitution(r1, {}) + assert s is not None + assert s[x].get_typevar().singleton_type() == i8.by(2) + assert s[y].get_typevar().singleton_type() == i8.by(2) + + def test_cleanup_concrete_rtl_ireduce_bad(self): + # type: () -> None + x = Var('x') + y = Var('y') + x.set_typevar(TypeVar.singleton(i16.by(1))) + r = Rtl( + y << ireduce(x), + ) + + with self.assertRaises(AssertionError): + cleanup_concrete_rtl(r) + + def test_vselect_icmpimm(self): + # type: () -> None + x = Var('x') + y = Var('y') + z = Var('z') + w = Var('w') + v = Var('v') + zeroes = Var('zeroes') + imm0 = Var("imm0") + + zeroes.set_typevar(TypeVar.singleton(i32.by(4))) + z.set_typevar(TypeVar.singleton(f32.by(4))) + + r = Rtl( + zeroes << iconst(imm0), + y << icmp(intcc.eq, x, zeroes), + v << vselect(y, z, w), + ) + + r1 = cleanup_concrete_rtl(r) + + s = r.substitution(r1, {}) + assert s is not None + assert s[zeroes].get_typevar().singleton_type() == i32.by(4) + assert s[x].get_typevar().singleton_type() == i32.by(4) + assert s[y].get_typevar().singleton_type() == b32.by(4) + assert s[z].get_typevar().singleton_type() == f32.by(4) + assert s[w].get_typevar().singleton_type() == f32.by(4) + assert s[v].get_typevar().singleton_type() == f32.by(4) + + def test_bint(self): + # type: () -> None + x = Var('x') + y = Var('y') + z = Var('z') + w = Var('w') + v = Var('v') + u = Var('u') + + x.set_typevar(TypeVar.singleton(i32.by(8))) + z.set_typevar(TypeVar.singleton(i32.by(8))) + # TODO: Relax this to simd=True + v.set_typevar(TypeVar('v', '', bools=(1, 1), simd=(8, 8))) + + r = Rtl( + z << iadd(x, y), + w << bint(v), + u << iadd(z, w) + ) + + r1 = cleanup_concrete_rtl(r) + + s = r.substitution(r1, {}) + assert s is not None + assert s[x].get_typevar().singleton_type() == i32.by(8) + assert s[y].get_typevar().singleton_type() == i32.by(8) + assert s[z].get_typevar().singleton_type() == i32.by(8) + assert s[w].get_typevar().singleton_type() == i32.by(8) + assert s[u].get_typevar().singleton_type() == i32.by(8) + assert s[v].get_typevar().singleton_type() == b1.by(8) + + +class TestElaborate(TestCase): + """ + Test semantics elaboration. + """ + def setUp(self): + # type: () -> None + self.v0 = Var("v0") + self.v1 = Var("v1") + self.v2 = Var("v2") + self.v3 = Var("v3") + self.v4 = Var("v4") + self.v5 = Var("v5") + self.v6 = Var("v6") + self.v7 = Var("v7") + self.v8 = Var("v8") + self.v9 = Var("v9") + self.imm0 = Var("imm0") + self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True, + scalars=False, simd=True) + self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True, + scalars=False, simd=True) + self.b1 = TypeVar.singleton(b1) + + def test_elaborate_vsplit(self): + # type: () -> None + i32.by(4) # Make sure i32x4 exists. + i32.by(2) # Make sure i32x2 exists. + r = Rtl( + (self.v0, self.v1) << vsplit.i32x4(self.v2), + ) + sem = elaborate(cleanup_concrete_rtl(r)) + bvx = Var('bvx') + bvlo = Var('bvlo') + bvhi = Var('bvhi') + x = Var('x') + lo = Var('lo') + hi = Var('hi') + + assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + bvx << prim_to_bv.i32x4(x), + (bvlo, bvhi) << bvsplit.bv128(bvx), + lo << prim_from_bv.i32x2.bv64(bvlo), + hi << prim_from_bv.i32x2.bv64(bvhi)))) + + def test_elaborate_vconcat(self): + # type: () -> None + i32.by(4) # Make sure i32x4 exists. + i32.by(2) # Make sure i32x2 exists. + r = Rtl( + self.v0 << vconcat.i32x2(self.v1, self.v2), + ) + sem = elaborate(cleanup_concrete_rtl(r)) + bvx = Var('bvx') + bvlo = Var('bvlo') + bvhi = Var('bvhi') + x = Var('x') + lo = Var('lo') + hi = Var('hi') + + assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + bvlo << prim_to_bv.i32x2(lo), + bvhi << prim_to_bv.i32x2(hi), + bvx << bvconcat.bv64(bvlo, bvhi), + x << prim_from_bv.i32x4.bv128(bvx)))) + + def test_elaborate_iadd_simple(self): + # type: () -> None + i32.by(2) # Make sure i32x2 exists. + r = Rtl( + self.v0 << iadd.i32(self.v1, self.v2), + ) + sem = elaborate(cleanup_concrete_rtl(r)) + x = Var('x') + y = Var('y') + a = Var('a') + bvx = Var('bvx') + bvy = Var('bvy') + bva = Var('bva') + + assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + bvx << prim_to_bv.i32(x), + bvy << prim_to_bv.i32(y), + bva << bvadd.bv32(bvx, bvy), + a << prim_from_bv.i32.bv32(bva)))) + + def test_elaborate_iadd_elaborate_1(self): + # type: () -> None + i32.by(2) # Make sure i32x2 exists. + r = Rtl( + self.v0 << iadd.i32x2(self.v1, self.v2), + ) + sem = cleanup_semantics(elaborate(cleanup_concrete_rtl(r)), + set([self.v0])) + x = Var('x') + y = Var('y') + a = Var('a') + bvx_1 = Var('bvx_1') + bvx_2 = Var('bvx_2') + bvx_5 = Var('bvx_5') + bvlo_1 = Var('bvlo_1') + bvlo_2 = Var('bvlo_2') + bvhi_1 = Var('bvhi_1') + bvhi_2 = Var('bvhi_2') + + bva_3 = Var('bva_3') + bva_4 = Var('bva_4') + + assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + bvx_1 << prim_to_bv.i32x2(x), + (bvlo_1, bvhi_1) << bvsplit.bv64(bvx_1), + bvx_2 << prim_to_bv.i32x2(y), + (bvlo_2, bvhi_2) << bvsplit.bv64(bvx_2), + bva_3 << bvadd.bv32(bvlo_1, bvlo_2), + bva_4 << bvadd.bv32(bvhi_1, bvhi_2), + bvx_5 << bvconcat.bv32(bva_3, bva_4), + a << prim_from_bv.i32x2.bv64(bvx_5)))) + + def test_elaborate_iadd_elaborate_2(self): + # type: () -> None + i8.by(4) # Make sure i32x2 exists. + r = Rtl( + self.v0 << iadd.i8x4(self.v1, self.v2), + ) + + sem = cleanup_semantics(elaborate(cleanup_concrete_rtl(r)), + set([self.v0])) + x = Var('x') + y = Var('y') + a = Var('a') + bvx_1 = Var('bvx_1') + bvx_2 = Var('bvx_2') + bvx_5 = Var('bvx_5') + bvx_10 = Var('bvx_10') + bvx_15 = Var('bvx_15') + + bvlo_1 = Var('bvlo_1') + bvlo_2 = Var('bvlo_2') + bvlo_6 = Var('bvlo_6') + bvlo_7 = Var('bvlo_7') + bvlo_11 = Var('bvlo_11') + bvlo_12 = Var('bvlo_12') + + bvhi_1 = Var('bvhi_1') + bvhi_2 = Var('bvhi_2') + bvhi_6 = Var('bvhi_6') + bvhi_7 = Var('bvhi_7') + bvhi_11 = Var('bvhi_11') + bvhi_12 = Var('bvhi_12') + + bva_8 = Var('bva_8') + bva_9 = Var('bva_9') + bva_13 = Var('bva_13') + bva_14 = Var('bva_14') + + assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + bvx_1 << prim_to_bv.i8x4(x), + (bvlo_1, bvhi_1) << bvsplit.bv32(bvx_1), + bvx_2 << prim_to_bv.i8x4(y), + (bvlo_2, bvhi_2) << bvsplit.bv32(bvx_2), + (bvlo_6, bvhi_6) << bvsplit.bv16(bvlo_1), + (bvlo_7, bvhi_7) << bvsplit.bv16(bvlo_2), + bva_8 << bvadd.bv8(bvlo_6, bvlo_7), + bva_9 << bvadd.bv8(bvhi_6, bvhi_7), + bvx_10 << bvconcat.bv8(bva_8, bva_9), + (bvlo_11, bvhi_11) << bvsplit.bv16(bvhi_1), + (bvlo_12, bvhi_12) << bvsplit.bv16(bvhi_2), + bva_13 << bvadd.bv8(bvlo_11, bvlo_12), + bva_14 << bvadd.bv8(bvhi_11, bvhi_12), + bvx_15 << bvconcat.bv8(bva_13, bva_14), + bvx_5 << bvconcat.bv16(bvx_10, bvx_15), + a << prim_from_bv.i8x4.bv32(bvx_5)))) diff --git a/lib/cretonne/meta/semantics/types.py b/lib/cretonne/meta/semantics/types.py new file mode 100644 index 0000000000..1221804fbd --- /dev/null +++ b/lib/cretonne/meta/semantics/types.py @@ -0,0 +1,9 @@ +""" +The semantics.types module predefines all the Cretone primitive bitvector +types. +""" +from cdsl.types import BVType +from cdsl.typevar import MAX_BITVEC, int_log2 + +for width in range(0, int_log2(MAX_BITVEC)+1): + BVType(2**width) From 9258283e145d6a59dff5f25a503674065d84ad0d Mon Sep 17 00:00:00 2001 From: Dimo Date: Fri, 21 Jul 2017 16:46:20 -0700 Subject: [PATCH 901/968] Documentation nits; Sematnics syntax cleanup --- lib/cretonne/meta/base/semantics.py | 53 +++++++++-------- lib/cretonne/meta/cdsl/ast.py | 14 +++-- lib/cretonne/meta/cdsl/instructions.py | 25 +++++--- lib/cretonne/meta/cdsl/types.py | 18 ++++-- lib/cretonne/meta/cdsl/typevar.py | 8 ++- lib/cretonne/meta/cdsl/xform.py | 2 +- lib/cretonne/meta/semantics/__init__.py | 59 ++++++++----------- lib/cretonne/meta/semantics/elaborate.py | 1 + lib/cretonne/meta/semantics/test_elaborate.py | 8 +-- lib/cretonne/meta/semantics/types.py | 9 --- 10 files changed, 103 insertions(+), 94 deletions(-) delete mode 100644 lib/cretonne/meta/semantics/types.py diff --git a/lib/cretonne/meta/base/semantics.py b/lib/cretonne/meta/base/semantics.py index d85dad0682..b4315d8255 100644 --- a/lib/cretonne/meta/base/semantics.py +++ b/lib/cretonne/meta/base/semantics.py @@ -2,11 +2,10 @@ from __future__ import absolute_import from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\ bvadd from .instructions import vsplit, vconcat, iadd -from cdsl.xform import XForm, Rtl +from cdsl.xform import Rtl from cdsl.ast import Var from cdsl.typevar import TypeSet from cdsl.ti import InTypeset -import semantics.types # noqa x = Var('x') y = Var('y') @@ -28,30 +27,32 @@ bvhi = Var('bvhi') ScalarTS = TypeSet(lanes=(1, 1), ints=True, floats=True, bools=True) vsplit.set_semantics( - XForm(Rtl((lo, hi) << vsplit(x)), - Rtl(bvx << prim_to_bv(x), - (bvlo, bvhi) << bvsplit(bvx), - lo << prim_from_bv(bvlo), - hi << prim_from_bv(bvhi)))) + (lo, hi) << vsplit(x), + Rtl( + bvx << prim_to_bv(x), + (bvlo, bvhi) << bvsplit(bvx), + lo << prim_from_bv(bvlo), + hi << prim_from_bv(bvhi) + )) vconcat.set_semantics( - XForm(Rtl(x << vconcat(lo, hi)), - Rtl(bvlo << prim_to_bv(lo), - bvhi << prim_to_bv(hi), - bvx << bvconcat(bvlo, bvhi), - x << prim_from_bv(bvx)))) + x << vconcat(lo, hi), + Rtl( + bvlo << prim_to_bv(lo), + bvhi << prim_to_bv(hi), + bvx << bvconcat(bvlo, bvhi), + x << prim_from_bv(bvx) + )) -iadd.set_semantics([ - XForm(Rtl(a << iadd(x, y)), - Rtl(bvx << prim_to_bv(x), - bvy << prim_to_bv(y), - bva << bvadd(bvx, bvy), - a << prim_from_bv(bva)), - constraints=[InTypeset(x.get_typevar(), ScalarTS)]), - XForm(Rtl(a << iadd(x, y)), - Rtl((xlo, xhi) << vsplit(x), - (ylo, yhi) << vsplit(y), - alo << iadd(xlo, ylo), - ahi << iadd(xhi, yhi), - a << vconcat(alo, ahi))) -]) +iadd.set_semantics( + a << iadd(x, y), + (Rtl(bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bva << bvadd(bvx, bvy), + a << prim_from_bv(bva)), + [InTypeset(x.get_typevar(), ScalarTS)]), + Rtl((xlo, xhi) << vsplit(x), + (ylo, yhi) << vsplit(y), + alo << iadd(xlo, ylo), + ahi << iadd(xhi, yhi), + a << vconcat(alo, ahi))) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index c5e6d58def..86e2805497 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -102,15 +102,17 @@ class Def(object): def vars(self): # type: () -> Set[Var] - """ Return the set of all Vars that appear in self""" + """Return the set of all Vars in self that correspond to SSA values""" return self.definitions().union(self.uses()) def substitution(self, other, s): # type: (Def, VarMap) -> Optional[VarMap] """ If the Defs self and other agree structurally, return a variable - substitution to transform self ot other. Two Defs agree structurally - if the contained Apply's agree structurally. + substitution to transform self to other. Otherwise return None. Two + Defs agree structurally if there exists a Var substitution, that can + transform one into the other. See Apply.substitution() for more + details. """ s = self.expr.substitution(other.expr, s) @@ -378,7 +380,7 @@ class Apply(Expr): def vars(self): # type: () -> Set[Var] - """ Return the set of all Vars that appear in self""" + """Return the set of all Vars in self that correspond to SSA values""" res = set() for i in self.inst.value_opnums: arg = self.args[i] @@ -390,8 +392,8 @@ class Apply(Expr): # type: (Apply, VarMap) -> Optional[VarMap] """ If the application self and other agree structurally, return a variable - substitution to transform self ot other. Two applications agree - structurally if: + substitution to transform self to other. Otherwise return None. Two + applications agree structurally if: 1) They are over the same instruction 2) Every Var v in self, maps to a single Var w in other. I.e for each use of v in self, w is used in the corresponding place in diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index ee9bf15e5e..9d23dea89e 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -9,15 +9,16 @@ try: from typing import Union, Sequence, List, Tuple, Any, TYPE_CHECKING # noqa from typing import Dict # noqa if TYPE_CHECKING: - from .ast import Expr, Apply, Var # noqa + from .ast import Expr, Apply, Var, Def # noqa from .typevar import TypeVar # noqa from .ti import TypeConstraint # noqa - from .xform import XForm + from .xform import XForm, Rtl # List of operands for ins/outs: OpList = Union[Sequence[Operand], Operand] ConstrList = Union[Sequence[TypeConstraint], TypeConstraint] MaybeBoundInst = Union['Instruction', 'BoundInstruction'] - InstructionSemantics = List[XForm] + InstructionSemantics = Sequence[XForm] + RtlCase = Union[Rtl, Tuple[Rtl, Sequence[TypeConstraint]]] except ImportError: pass @@ -336,15 +337,23 @@ class Instruction(object): from .ast import Apply # noqa return Apply(self, args) - def set_semantics(self, sem): - # type: (Union[XForm, InstructionSemantics]) -> None + def set_semantics(self, src, *dsts): + # type: (Union[Def, Apply], *RtlCase) -> None """Set our semantics.""" from semantics import verify_semantics + from .xform import XForm, Rtl - if not isinstance(sem, list): - sem = [sem] + sem = [] # type: List[XForm] + for dst in dsts: + if isinstance(dst, Rtl): + sem.append(XForm(Rtl(src).copy({}), dst)) + else: + assert isinstance(dst, tuple) + sem.append(XForm(Rtl(src).copy({}), dst[0], + constraints=dst[1])) + + verify_semantics(self, Rtl(src), sem) - verify_semantics(self, sem) self.semantics = sem diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index 846cc442c9..53a1dd79ad 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -89,10 +89,7 @@ class ScalarType(ValueType): self._vectors = dict() # type: Dict[int, VectorType] # Assign numbers starting from 1. (0 is VOID). ValueType.all_scalars.append(self) - # Numbers are only valid for Cretone types that get emitted to Rust. - # This excludes BVTypes - self.number = len([x for x in ValueType.all_scalars - if not isinstance(x, BVType)]) + self.number = len(ValueType.all_scalars) assert self.number < 16, 'Too many scalar types' def __repr__(self): @@ -249,7 +246,7 @@ class BoolType(ScalarType): return self.bits -class BVType(ScalarType): +class BVType(ValueType): """A flat bitvector type. Used for semantics description only.""" def __init__(self, bits): @@ -268,7 +265,11 @@ class BVType(ScalarType): @staticmethod def with_bits(bits): # type: (int) -> BVType - typ = ValueType.by_name('bv{:d}'.format(bits)) + name = 'bv{:d}'.format(bits) + if name not in ValueType._registry: + return BVType(bits) + + typ = ValueType.by_name(name) if TYPE_CHECKING: return cast(BVType, typ) else: @@ -278,3 +279,8 @@ class BVType(ScalarType): # type: () -> int """Return the number of bits in a lane.""" return self.bits + + def lane_count(self): + # type: () -> int + """Return the number of lane. For BVtypes always 1.""" + return 1 diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index 2e7d4cb5cc..988d34aaf8 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -501,7 +501,8 @@ class TypeSet(object): for bits in self.bools: yield by(types.BoolType.with_bits(bits), nlanes) for bits in self.bitvecs: - yield by(types.BVType.with_bits(bits), nlanes) + assert nlanes == 1 + yield types.BVType.with_bits(bits) def get_singleton(self): # type: () -> types.ValueType @@ -572,12 +573,17 @@ class TypeVar(object): def singleton(typ): # type: (types.ValueType) -> TypeVar """Create a type variable that can only assume a single type.""" + scalar = None # type: ValueType if isinstance(typ, types.VectorType): scalar = typ.base lanes = (typ.lanes, typ.lanes) elif isinstance(typ, types.ScalarType): scalar = typ lanes = (1, 1) + else: + assert isinstance(typ, types.BVType) + scalar = typ + lanes = (1, 1) ints = None floats = None diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 42655272b8..3d3f25a3c7 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -55,7 +55,7 @@ class Rtl(object): def vars(self): # type: () -> Set[Var] - """ Return the set of all Vars that appear in self""" + """Return the set of all Vars in self that correspond to SSA values""" return reduce(lambda x, y: x.union(y), [d.vars() for d in self.rtl], set([])) diff --git a/lib/cretonne/meta/semantics/__init__.py b/lib/cretonne/meta/semantics/__init__.py index d2c327c988..8caf792259 100644 --- a/lib/cretonne/meta/semantics/__init__.py +++ b/lib/cretonne/meta/semantics/__init__.py @@ -4,52 +4,45 @@ from cdsl.ti import TypeEnv, ti_rtl, get_type_env try: from typing import List, Dict, Tuple # noqa from cdsl.ast import Var # noqa - from cdsl.xform import XForm # noqa + from cdsl.xform import XForm, Rtl # noqa from cdsl.ti import VarTyping # noqa from cdsl.instructions import Instruction, InstructionSemantics # noqa except ImportError: pass -def verify_semantics(inst, sem): - # type: (Instruction, InstructionSemantics) -> None +def verify_semantics(inst, src, xforms): + # type: (Instruction, Rtl, InstructionSemantics) -> None """ - Verify that the semantics sem correctly describes the instruction inst. - This involves checking that: - 1) For all XForms x \in sem, x.src consists of a single instance of - inst - 2) For any possible concrete typing of inst there is exactly 1 XForm x - in sem that applies. + Verify that the semantics transforms in xforms correctly describe the + instruction described by the src Rtl. This involves checking that: + 1) For all XForms x \in xforms, there is a Var substitution form src to + x.src + 2) For any possible concrete typing of src there is exactly 1 XForm x + in xforms that applies. """ - # 1) The source rtl is always a single instance of inst. - for xform in sem: - assert len(xform.src.rtl) == 1 and\ - xform.src.rtl[0].expr.inst == inst,\ - "XForm {} doesn't describe instruction {}."\ - .format(xform, inst) + # 0) The source rtl is always a single instruction + assert len(src.rtl) == 1 + + # 1) For all XForms x, x.src is structurally equivalent to src + for x in xforms: + assert src.substitution(x.src, {}) is not None,\ + "XForm {} doesn't describe instruction {}.".format(x, src) # 2) Any possible typing for the instruction should be covered by # exactly ONE semantic XForm - inst_rtl = sem[0].src - typenv = get_type_env(ti_rtl(inst_rtl, TypeEnv())) - - # This bit is awkward. Concrete typing is defined in terms of the vars - # of one Rtl. We arbitrarily picked that Rtl to be sem[0].src. For any - # other XForms in sem, we must build a substitution form - # sem[0].src->sem[N].src, before we can check if sem[N] permits one of - # the concrete typings of our Rtl. - # TODO: Can this be made cleaner? - subst = [inst_rtl.substitution(x.src, {}) for x in sem] - assert not any(x is None for x in subst) - sub_sem = list(zip(subst, sem)) # type: List[Tuple[Dict[Var, Var], XForm]] # noqa - - def subst_typing(typing, sub): - # type: (VarTyping, Dict[Var, Var]) -> VarTyping - return {sub[v]: tv for (v, tv) in typing.items()} + typenv = get_type_env(ti_rtl(src, TypeEnv())) + typenv.normalize() + typenv = typenv.extract() for t in typenv.concrete_typings(): - matching_xforms = [x for (s, x) in sub_sem - if x.ti.permits(subst_typing(t, s))] + matching_xforms = [] # type: List[XForm] + for x in xforms: + # Translate t using x.symtab + t = {x.symtab[str(v)]: tv for (v, tv) in t.items()} + if (x.ti.permits(t)): + matching_xforms.append(x) + assert len(matching_xforms) == 1,\ ("Possible typing {} of {} not matched by exactly one case " + ": {}").format(t, inst, matching_xforms) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index e6be0180e1..9cd92c1f06 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -81,6 +81,7 @@ def find_matching_xform(d): for x in d.expr.inst.semantics: subst = d.substitution(x.src.rtl[0], {}) + assert subst is not None if x.ti.permits({subst[v]: tv for (v, tv) in typing.items()}): res.append(x) diff --git a/lib/cretonne/meta/semantics/test_elaborate.py b/lib/cretonne/meta/semantics/test_elaborate.py index 15d4178ae8..4cbfe07bf9 100644 --- a/lib/cretonne/meta/semantics/test_elaborate.py +++ b/lib/cretonne/meta/semantics/test_elaborate.py @@ -232,16 +232,16 @@ class TestElaborate(TestCase): def test_elaborate_iadd_simple(self): # type: () -> None i32.by(2) # Make sure i32x2 exists. - r = Rtl( - self.v0 << iadd.i32(self.v1, self.v2), - ) - sem = elaborate(cleanup_concrete_rtl(r)) x = Var('x') y = Var('y') a = Var('a') bvx = Var('bvx') bvy = Var('bvy') bva = Var('bva') + r = Rtl( + a << iadd.i32(x, y), + ) + sem = elaborate(cleanup_concrete_rtl(r)) assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( bvx << prim_to_bv.i32(x), diff --git a/lib/cretonne/meta/semantics/types.py b/lib/cretonne/meta/semantics/types.py deleted file mode 100644 index 1221804fbd..0000000000 --- a/lib/cretonne/meta/semantics/types.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -The semantics.types module predefines all the Cretone primitive bitvector -types. -""" -from cdsl.types import BVType -from cdsl.typevar import MAX_BITVEC, int_log2 - -for width in range(0, int_log2(MAX_BITVEC)+1): - BVType(2**width) From 12db123606a27c01a6988054bdae7c2d12fa854c Mon Sep 17 00:00:00 2001 From: Dimo Date: Fri, 21 Jul 2017 16:56:15 -0700 Subject: [PATCH 902/968] TI failure due to misplaced import --- lib/cretonne/meta/semantics/elaborate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index 9cd92c1f06..0350c5c7dd 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -6,10 +6,11 @@ primitive cretonne instructions, which map well to SMTLIB functions. from .primitives import GROUP as PRIMITIVES, prim_to_bv, prim_from_bv from cdsl.ti import ti_rtl, TypeEnv, get_type_env from cdsl.typevar import TypeVar +from cdsl.xform import Rtl try: from typing import TYPE_CHECKING, Dict, Union, List, Set, Tuple # noqa - from cdsl.xform import Rtl, XForm # noqa + from cdsl.xform import XForm # noqa from cdsl.ast import Var, Def, VarMap # noqa from cdsl.ti import VarTyping # noqa except ImportError: From 736b6a44a74d7959e4c14ce121fa4639d013649e Mon Sep 17 00:00:00 2001 From: Dimo Date: Mon, 24 Jul 2017 10:58:57 -0700 Subject: [PATCH 903/968] Fix CI: Var was only imported when mypy was present. --- lib/cretonne/meta/semantics/elaborate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index 0350c5c7dd..03ca3195c4 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -7,11 +7,12 @@ from .primitives import GROUP as PRIMITIVES, prim_to_bv, prim_from_bv from cdsl.ti import ti_rtl, TypeEnv, get_type_env from cdsl.typevar import TypeVar from cdsl.xform import Rtl +from cdsl.ast import Var try: from typing import TYPE_CHECKING, Dict, Union, List, Set, Tuple # noqa from cdsl.xform import XForm # noqa - from cdsl.ast import Var, Def, VarMap # noqa + from cdsl.ast import Def, VarMap # noqa from cdsl.ti import VarTyping # noqa except ImportError: TYPE_CHECKING = False From 351d4af4ebf5f0e8770f9ce5636c2b0f7e684540 Mon Sep 17 00:00:00 2001 From: Dimo Date: Mon, 24 Jul 2017 13:41:04 -0700 Subject: [PATCH 904/968] Assert all InstructionGroups are closed in TargetIsa.__init__(); Close x86 group --- lib/cretonne/meta/cdsl/isa.py | 5 +++++ lib/cretonne/meta/isa/intel/instructions.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index ea10c64640..29a3698304 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -5,6 +5,7 @@ from .predicates import And from .registers import RegClass, Register, Stack from .ast import Apply from .types import ValueType +from .instructions import InstructionGroup # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -47,6 +48,10 @@ class TargetISA(object): self.regclasses = list() # type: List[RegClass] self.legalize_codes = OrderedDict() # type: OrderedDict[XFormGroup, int] # noqa + assert InstructionGroup._current is None,\ + "InstructionGroup {} is still open!"\ + .format(InstructionGroup._current.name) + def __str__(self): # type: () -> str return self.name diff --git a/lib/cretonne/meta/isa/intel/instructions.py b/lib/cretonne/meta/isa/intel/instructions.py index a5c2bdafb0..c9d87c43f3 100644 --- a/lib/cretonne/meta/isa/intel/instructions.py +++ b/lib/cretonne/meta/isa/intel/instructions.py @@ -43,3 +43,5 @@ sdivmodx = Instruction( Return both quotient and remainder. """, ins=(nlo, nhi, d), outs=(q, r), can_trap=True) + +GROUP.close() From a31dd3aa7a5a23fb88fccbebfd131a0d52e6ef95 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Jul 2017 15:33:35 -0700 Subject: [PATCH 905/968] Generate a RECIPE_PREDICATES table for each ISA. It turns out that most encoding predicates are expressed as recipe predicates. This means that the encoding tables can be more compact since we can check the recipe predicate separately from individual instruction predicates, and the recipe number is already present in the table. - Don't combine recipe and encoding-specific predicates when creating an Encoding. Keep them separate. - Generate a table of recipe predicates with function pointers. Many of these are null. - Check any recipe predicate before accepting a recipe+bits pair. This has the effect of making almost all instruction predicates CODE_ALWAYS. --- lib/cretonne/meta/cdsl/isa.py | 27 ++++++-- lib/cretonne/meta/gen_encoding.py | 85 +++++++++++++++++++----- lib/cretonne/src/isa/arm32/enc_tables.rs | 2 +- lib/cretonne/src/isa/arm32/mod.rs | 1 + lib/cretonne/src/isa/arm64/enc_tables.rs | 2 +- lib/cretonne/src/isa/arm64/mod.rs | 1 + lib/cretonne/src/isa/enc_tables.rs | 33 +++++++-- lib/cretonne/src/isa/intel/enc_tables.rs | 2 +- lib/cretonne/src/isa/intel/mod.rs | 1 + lib/cretonne/src/isa/riscv/enc_tables.rs | 2 +- lib/cretonne/src/isa/riscv/mod.rs | 1 + 11 files changed, 126 insertions(+), 31 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 29a3698304..f65c2ad1fc 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -23,6 +23,9 @@ try: # instructions. InstSpec = Union[MaybeBoundInst, Apply] BranchRange = Sequence[int] + # A recipe predicate consisting of an ISA predicate and an instruction + # predicate. + RecipePred = Tuple[PredNode, PredNode] except ImportError: pass @@ -62,7 +65,7 @@ class TargetISA(object): Finish the definition of a target ISA after adding all CPU modes and settings. - This computes some derived properties that are used in multilple + This computes some derived properties that are used in multiple places. :returns self: @@ -86,6 +89,9 @@ class TargetISA(object): recipe.number = len(rcps) rcps.add(recipe) self.all_recipes.append(recipe) + # Make sure ISA predicates are registered. + if recipe.isap: + self.settings.number_predicate(recipe.isap) def _collect_predicates(self): # type: () -> None @@ -336,6 +342,19 @@ class EncRecipe(object): o2i[o] = i return (i2o, o2i) + def recipe_pred(self): + # type: () -> RecipePred + """ + Get the combined recipe predicate which includes both the ISA predicate + and the instruction predicate. + + Return `None` if this recipe has neither predicate. + """ + if self.isap is None and self.instp is None: + return None + else: + return (self.isap, self.instp) + class Encoding(object): """ @@ -386,9 +405,9 @@ class Encoding(object): self.recipe = recipe self.encbits = encbits - # Combine recipe predicates with the manually specified ones. - self.instp = And.combine(recipe.instp, instp) - self.isap = And.combine(recipe.isap, isap) + # Record specific predicates. Note that the recipe also has predicates. + self.instp = instp + self.isap = isap def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index de8b138cf6..5f49cb42c6 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -11,11 +11,12 @@ are allocated. This is the information available to us: -- The instruction to be encoded as an `Inst` reference. -- The data-flow graph containing the instruction, giving us access to the - `InstructionData` representation and the types of all values involved. -- A target ISA instance with shared and ISA-specific settings for evaluating - ISA predicates. +- The instruction to be encoded as an `InstructionData` reference. +- The controlling type variable. +- The data-flow graph giving us access to the types of all values involved. + This is needed for testing any secondary type variables. +- A `PredicateView` reference for the ISA-specific settings for evaluating ISA + predicates. - The currently active CPU mode is determined by the ISA. ## Level 1 table lookup @@ -62,7 +63,7 @@ from cdsl.predicates import FieldPredicate try: from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa if TYPE_CHECKING: - from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode, EncRecipe # noqa + from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode, EncRecipe, RecipePred # noqa from cdsl.predicates import PredNode, PredLeaf # noqa from cdsl.types import ValueType # noqa from cdsl.instructions import Instruction # noqa @@ -77,8 +78,8 @@ def emit_instp(instp, fmt): Emit code for matching an instruction predicate against an `InstructionData` reference called `inst`. - The generated code is a pattern match that falls through if the instruction - has an unexpected format. This should lead to a panic. + The generated code is an `if let` pattern match that falls through if the + instruction has an unexpected format. This should lead to a panic. """ iform = instp.predicate_context() @@ -94,11 +95,10 @@ def emit_instp(instp, fmt): fnames.add(p.field.rust_name()) fields = ', '.join(sorted(fnames)) - with fmt.indented('{} => {{'.format(instp.number), '}'): - with fmt.indented( - 'if let InstructionData::{} {{ {}, .. }} = *inst {{' - .format(iform.name, fields), '}'): - fmt.line('return {};'.format(instp.rust_predicate(0))) + with fmt.indented( + 'if let InstructionData::{} {{ {}, .. }} = *inst {{' + .format(iform.name, fields), '}'): + fmt.line('return {};'.format(instp.rust_predicate(0))) def emit_instps(instps, fmt): @@ -122,7 +122,8 @@ def emit_instps(instps, fmt): fmt.line('use ir::instructions::InstructionFormat;') with fmt.indented('match instp_idx {', '}'): for instp in instps: - emit_instp(instp, fmt) + with fmt.indented('{} => {{'.format(instp.number), '}'): + emit_instp(instp, fmt) fmt.line('_ => panic!("Invalid instruction predicate")') # The match cases will fall through if the instruction format is wrong. @@ -132,6 +133,55 @@ def emit_instps(instps, fmt): fmt.line(' instp_idx);') +def emit_recipe_predicates(recipes, fmt): + # type: (Sequence[EncRecipe], srcgen.Formatter) -> None + """ + Emit private functions for checking recipe predicates as well as a static + `RECIPE_PREDICATES` array indexed by recipe number. + + A recipe predicate is a combination of an ISA predicate and an instruction + predicates. Many recipes have identical predicates. + """ + # Table for uniquing recipe predicates. Maps predicate to generated + # function name. + pname = dict() # type: Dict[RecipePred, str] + + # Generate unique recipe predicates. + for rcp in recipes: + p = rcp.recipe_pred() + if p is None or p in pname: + continue + name = 'recipe_predicate_{}'.format(rcp.name.lower()) + pname[p] = name + isap, instp = p + + # Generate the predicate function. + with fmt.indented( + 'fn {}({}: ::settings::PredicateView, ' + 'inst: &InstructionData) -> bool {{' + .format( + name, + 'isap' if isap else '_'), '}'): + if isap: + with fmt.indented( + 'if isap.test({})'.format(isap.number), + '}'): + fmt.line('return false;') + emit_instp(instp, fmt) + fmt.line('unreachable!();') + + # Generate the static table. + with fmt.indented( + 'pub static RECIPE_PREDICATES: [RecipePredicate; {}] = [' + .format(len(recipes)), '];'): + for rcp in recipes: + p = rcp.recipe_pred() + if p is None: + fmt.line('None,') + else: + fmt.format('Some({}),', pname[p]) + + # Encoding lists are represented as u16 arrays. CODE_BITS = 16 PRED_BITS = 12 @@ -604,8 +654,11 @@ def emit_recipe_sizing(isa, fmt): def gen_isa(isa, fmt): # type: (TargetISA, srcgen.Formatter) -> None - # First assign numbers to relevant instruction predicates and generate the - # check_instp() function.. + + # Make the `RECIPE_PREDICATES` table. + emit_recipe_predicates(isa.all_recipes, fmt) + + # Generate the check_instp() function.. emit_instps(isa.all_instps, fmt) # Level1 tables, one per CPU mode diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index 9fdfbcda95..e33d47f12d 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -4,7 +4,7 @@ use ir::InstructionData; use ir::types; use isa::EncInfo; use isa::constraints::*; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::enc_tables::*; use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 5675d19d7a..06852e4ad2 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -73,6 +73,7 @@ impl TargetIsa for Isa { .and_then(|enclist_offset| { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], inst, enc_tables::check_instp, self.isa_flags.predicate_view())) diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index cdfc255748..889a5a7dbe 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -4,7 +4,7 @@ use ir::InstructionData; use ir::types; use isa::EncInfo; use isa::constraints::*; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::enc_tables::*; use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index e9aea5aacf..26e4ea9dfe 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -66,6 +66,7 @@ impl TargetIsa for Isa { .and_then(|enclist_offset| { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], inst, enc_tables::check_instp, self.isa_flags.predicate_view())) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 33ec38d3c1..843cbf6a1d 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -9,6 +9,13 @@ use isa::{Encoding, Legalize}; use settings::PredicateView; use std::ops::Range; +/// A recipe predicate. +/// +/// This is a predicate function capable of testing ISA and instruction predicates simultaneously. +/// +/// A None predicate is always satisfied. +pub type RecipePredicate = Option bool>; + /// Legalization action to perform when no encoding can be found for an instruction. /// /// This is an index into an ISA-specific table of legalization actions. @@ -147,6 +154,7 @@ pub struct Encodings<'a> { inst: &'a InstructionData, instp: fn(&InstructionData, EncListEntry) -> bool, isa_predicates: PredicateView<'a>, + recipe_predicates: &'static [RecipePredicate], } impl<'a> Encodings<'a> { @@ -155,8 +163,9 @@ impl<'a> Encodings<'a> { /// # Parameters /// /// - `offset` an offset into encoding list returned by `lookup_enclist` function. - /// - `inst` the current instruction. /// - `enclist` a list of encoding entries. + /// - `recipe_predicates` is a slice of recipe predicate functions. + /// - `inst` the current instruction. /// - `instp` an instruction predicate number to be evaluated on the current instruction. /// - `isa_predicate_bytes` an ISA flags as a slice of bytes to evaluate an ISA predicate number /// on the current instruction. @@ -166,6 +175,7 @@ impl<'a> Encodings<'a> { /// or `None`. pub fn new(offset: usize, enclist: &'static [EncListEntry], + recipe_predicates: &'static [RecipePredicate], inst: &'a InstructionData, instp: fn(&InstructionData, EncListEntry) -> bool, isa_predicates: PredicateView<'a>) @@ -176,6 +186,15 @@ impl<'a> Encodings<'a> { inst, instp, isa_predicates, + recipe_predicates, + } + } + + /// Check if the predicate for `recipe` is satisfied. + fn check_recipe(&self, recipe: u16) -> bool { + match self.recipe_predicates[recipe as usize] { + Some(p) => p(self.isa_predicates, self.inst), + None => true, } } } @@ -188,13 +207,13 @@ impl<'a> Iterator for Encodings<'a> { let pred = self.enclist[self.offset]; if pred <= CODE_ALWAYS { // This is an instruction predicate followed by recipe and encbits entries. + self.offset += 3; if pred == CODE_ALWAYS || (self.instp)(self.inst, pred) { - let encoding = Encoding::new(self.enclist[self.offset + 1], - self.enclist[self.offset + 2]); - self.offset += 3; - return Some(encoding); - } else { - self.offset += 3; + let recipe = self.enclist[self.offset - 2]; + if self.check_recipe(recipe) { + let encoding = Encoding::new(recipe, self.enclist[self.offset - 1]); + return Some(encoding); + } } } else { // This is an ISA predicate entry. diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index e72da36b1e..47ea5dbe11 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -4,7 +4,7 @@ use ir::types; use ir::{Opcode, InstructionData}; use isa::EncInfo; use isa::constraints::*; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::enc_tables::*; use isa::encoding::RecipeSizing; use predicates; use super::registers::*; diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 35d339a740..c459b49bd7 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -73,6 +73,7 @@ impl TargetIsa for Isa { .and_then(|enclist_offset| { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], inst, enc_tables::check_instp, self.isa_flags.predicate_view())) diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 8ced0c681f..0dddebdbac 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -5,7 +5,7 @@ use ir::types; use ir::{Opcode, InstructionData}; use isa::EncInfo; use isa::constraints::*; -use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::enc_tables::*; use isa::encoding::RecipeSizing; use predicates; use super::registers::*; diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 869ea4c9cb..d09bfbd028 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -73,6 +73,7 @@ impl TargetIsa for Isa { .and_then(|enclist_offset| { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], inst, enc_tables::check_instp, self.isa_flags.predicate_view())) From f643e7e752cfccee2f2b9c55fa4169013b9eb151 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 24 Jul 2017 14:13:35 -0700 Subject: [PATCH 906/968] Generate an INST_PREDICATES table for each ISA. Instead of generating a single `check_instp()` function, create an array of individual function pointers for checking instruction predicates. This makes explicit the jump table in the old check_instp() method and it gives us a way of determining the number of instruction predicates that exists. --- lib/cretonne/meta/gen_encoding.py | 43 ++++++++++-------------- lib/cretonne/src/isa/arm32/enc_tables.rs | 1 - lib/cretonne/src/isa/arm32/mod.rs | 2 +- lib/cretonne/src/isa/arm64/enc_tables.rs | 1 - lib/cretonne/src/isa/arm64/mod.rs | 2 +- lib/cretonne/src/isa/enc_tables.rs | 18 +++++++--- lib/cretonne/src/isa/intel/mod.rs | 2 +- lib/cretonne/src/isa/riscv/mod.rs | 2 +- 8 files changed, 35 insertions(+), 36 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 5f49cb42c6..43f02d6416 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -101,36 +101,27 @@ def emit_instp(instp, fmt): fmt.line('return {};'.format(instp.rust_predicate(0))) -def emit_instps(instps, fmt): +def emit_inst_predicates(instps, fmt): # type: (Sequence[PredNode], srcgen.Formatter) -> None """ - Emit a function for matching instruction predicates. + Emit private functions for matching instruction predicates as well as a + static `INST_PREDICATES` array indexed by predicate number. """ - - if not instps: - # If the ISA has no predicates, just emit a stub. + for instp in instps: + name = 'inst_predicate_{}'.format(instp.number) with fmt.indented( - 'pub fn check_instp(_: &InstructionData, _: u16) ' + - '-> bool {', '}'): - fmt.line('unimplemented!()') - return + 'fn {}(inst: &InstructionData) -> bool {{' + .format(name), + '}'): + emit_instp(instp, fmt) + fmt.line('unreachable!();') + # Generate the static table. with fmt.indented( - 'pub fn check_instp(inst: &InstructionData, instp_idx: u16) ' + - '-> bool {', '}'): - # The matches emitted by `emit_instp` need this. - fmt.line('use ir::instructions::InstructionFormat;') - with fmt.indented('match instp_idx {', '}'): - for instp in instps: - with fmt.indented('{} => {{'.format(instp.number), '}'): - emit_instp(instp, fmt) - fmt.line('_ => panic!("Invalid instruction predicate")') - - # The match cases will fall through if the instruction format is wrong. - fmt.line('panic!("Bad format {:?}/{} for instp {}",') - fmt.line(' InstructionFormat::from(inst),') - fmt.line(' inst.opcode(),') - fmt.line(' instp_idx);') + 'pub static INST_PREDICATES: [InstPredicate; {}] = [' + .format(len(instps)), '];'): + for instp in instps: + fmt.format('inst_predicate_{},', instp.number) def emit_recipe_predicates(recipes, fmt): @@ -658,8 +649,8 @@ def gen_isa(isa, fmt): # Make the `RECIPE_PREDICATES` table. emit_recipe_predicates(isa.all_recipes, fmt) - # Generate the check_instp() function.. - emit_instps(isa.all_instps, fmt) + # Make the `INST_PREDICATES` table. + emit_inst_predicates(isa.all_instps, fmt) # Level1 tables, one per CPU mode level1_tables = dict() diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index e33d47f12d..adcc2fd915 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -1,6 +1,5 @@ //! Encoding tables for ARM32 ISA. -use ir::InstructionData; use ir::types; use isa::EncInfo; use isa::constraints::*; diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 06852e4ad2..1e5cde38db 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -74,8 +74,8 @@ impl TargetIsa for Isa { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], inst, - enc_tables::check_instp, self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index 889a5a7dbe..e57f3cc98c 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -1,6 +1,5 @@ //! Encoding tables for ARM64 ISA. -use ir::InstructionData; use ir::types; use isa::EncInfo; use isa::constraints::*; diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 26e4ea9dfe..4dd873e17b 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -67,8 +67,8 @@ impl TargetIsa for Isa { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], inst, - enc_tables::check_instp, self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 843cbf6a1d..d040dd9ca4 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -16,6 +16,12 @@ use std::ops::Range; /// A None predicate is always satisfied. pub type RecipePredicate = Option bool>; +/// An instruction predicate. +/// +/// This is a predicate function that needs to be tested in addition to the recipe predicate. It +/// can't depend on ISA settings. +pub type InstPredicate = fn(&InstructionData) -> bool; + /// Legalization action to perform when no encoding can be found for an instruction. /// /// This is an index into an ISA-specific table of legalization actions. @@ -152,9 +158,9 @@ pub struct Encodings<'a> { offset: usize, enclist: &'static [EncListEntry], inst: &'a InstructionData, - instp: fn(&InstructionData, EncListEntry) -> bool, isa_predicates: PredicateView<'a>, recipe_predicates: &'static [RecipePredicate], + inst_predicates: &'static [InstPredicate], } impl<'a> Encodings<'a> { @@ -176,17 +182,17 @@ impl<'a> Encodings<'a> { pub fn new(offset: usize, enclist: &'static [EncListEntry], recipe_predicates: &'static [RecipePredicate], + inst_predicates: &'static [InstPredicate], inst: &'a InstructionData, - instp: fn(&InstructionData, EncListEntry) -> bool, isa_predicates: PredicateView<'a>) -> Self { Encodings { offset, enclist, inst, - instp, isa_predicates, recipe_predicates, + inst_predicates, } } @@ -208,7 +214,11 @@ impl<'a> Iterator for Encodings<'a> { if pred <= CODE_ALWAYS { // This is an instruction predicate followed by recipe and encbits entries. self.offset += 3; - if pred == CODE_ALWAYS || (self.instp)(self.inst, pred) { + let satisfied = match self.inst_predicates.get(pred as usize) { + Some(p) => p(self.inst), + None => true, + }; + if satisfied { let recipe = self.enclist[self.offset - 2]; if self.check_recipe(recipe) { let encoding = Encoding::new(recipe, self.enclist[self.offset - 1]); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index c459b49bd7..85445ac701 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -74,8 +74,8 @@ impl TargetIsa for Isa { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], inst, - enc_tables::check_instp, self.isa_flags.predicate_view())) }) } diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index d09bfbd028..3fe675b7a4 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -74,8 +74,8 @@ impl TargetIsa for Isa { Ok(Encodings::new(enclist_offset, &enc_tables::ENCLISTS[..], &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], inst, - enc_tables::check_instp, self.isa_flags.predicate_view())) }) } From 7498d7a3f9e7664d80c1613a3333c97aa438767e Mon Sep 17 00:00:00 2001 From: Dimo Date: Mon, 24 Jul 2017 15:15:51 -0700 Subject: [PATCH 907/968] Handle non-ssa Vars and Enumerator constants in Rtl substitutions --- lib/cretonne/meta/cdsl/ast.py | 30 ++++++++++++++++------ lib/cretonne/meta/cdsl/test_xform.py | 37 +++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 86e2805497..e954ab01e9 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -402,16 +402,30 @@ class Apply(Expr): if self.inst != other.inst: return None - # TODO: Should we check imm/cond codes here as well? - for i in self.inst.value_opnums: - self_a = self.args[i] - other_a = other.args[i] + # Guaranteed by self.inst == other.inst + assert (len(self.args) == len(other.args)) - assert isinstance(self_a, Var) and isinstance(other_a, Var) - if (self_a not in s): - s[self_a] = other_a + for (self_a, other_a) in zip(self.args, other.args): + if (isinstance(self_a, Var)): + if not isinstance(other_a, Var): + return None + + if (self_a not in s): + s[self_a] = other_a + else: + if (s[self_a] != other_a): + return None else: - if (s[self_a] != other_a): + assert isinstance(self_a, Enumerator) + + if not isinstance(other_a, Enumerator): + # Currently don't support substitutions Var->Enumerator + return None + + # Guaranteed by self.inst == other.inst + assert self_a.kind == other_a.kind + + if (self_a.value != other_a.value): return None return s diff --git a/lib/cretonne/meta/cdsl/test_xform.py b/lib/cretonne/meta/cdsl/test_xform.py index 1609bb6c5f..952d8c90cb 100644 --- a/lib/cretonne/meta/cdsl/test_xform.py +++ b/lib/cretonne/meta/cdsl/test_xform.py @@ -1,7 +1,8 @@ from __future__ import absolute_import from unittest import TestCase from doctest import DocTestSuite -from base.instructions import iadd, iadd_imm, iconst +from base.instructions import iadd, iadd_imm, iconst, icmp +from base.immediates import intcc from . import xform from .ast import Var from .xform import Rtl, XForm @@ -14,9 +15,15 @@ def load_tests(loader, tests, ignore): x = Var('x') y = Var('y') +z = Var('z') +u = Var('u') a = Var('a') +b = Var('b') c = Var('c') +CC1 = Var('CC1') +CC2 = Var('CC2') + class TestXForm(TestCase): def test_macro_pattern(self): @@ -57,3 +64,31 @@ class TestXForm(TestCase): dst = Rtl(a << iadd(x, y)) with self.assertRaisesRegexp(AssertionError, "'a' multiply defined"): XForm(src, dst) + + def test_subst_imm(self): + src = Rtl(a << iconst(x)) + dst = Rtl(c << iconst(y)) + assert src.substitution(dst, {}) == {a: c, x: y} + + def test_subst_enum_var(self): + src = Rtl(a << icmp(CC1, x, y)) + dst = Rtl(b << icmp(CC2, z, u)) + assert src.substitution(dst, {}) == {a: b, CC1: CC2, x: z, y: u} + + def test_subst_enum_const(self): + src = Rtl(a << icmp(intcc.eq, x, y)) + dst = Rtl(b << icmp(intcc.eq, z, u)) + assert src.substitution(dst, {}) == {a: b, x: z, y: u} + + def test_subst_enum_bad(self): + src = Rtl(a << icmp(CC1, x, y)) + dst = Rtl(b << icmp(intcc.eq, z, u)) + assert src.substitution(dst, {}) is None + + src = Rtl(a << icmp(intcc.eq, x, y)) + dst = Rtl(b << icmp(CC1, z, u)) + assert src.substitution(dst, {}) is None + + src = Rtl(a << icmp(intcc.eq, x, y)) + dst = Rtl(b << icmp(intcc.sge, z, u)) + assert src.substitution(dst, {}) is None From 345d6754f58a51433bb2aa2bf565c0465913032e Mon Sep 17 00:00:00 2001 From: Dimo Date: Tue, 25 Jul 2017 15:09:22 -0700 Subject: [PATCH 908/968] Change TV ranking to select src vars as a representative during unification; Nit: cleanup dot() emitting code; Nit: fix small bug in verify_semantics() - make an internal copy of src rtl to avoid clobbering of typevars re-used in multiple definitions --- lib/cretonne/meta/cdsl/test_ti.py | 13 ++--- lib/cretonne/meta/cdsl/ti.py | 57 ++++++++++++------- lib/cretonne/meta/semantics/__init__.py | 1 + lib/cretonne/meta/semantics/primitives.py | 4 +- lib/cretonne/meta/semantics/test_elaborate.py | 12 ++-- lib/cretonne/meta/test_gen_legalizer.py | 5 +- 6 files changed, 51 insertions(+), 41 deletions(-) diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index 9351d9c3ea..31d9a349a8 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -158,8 +158,7 @@ class TypeCheckingBaseTest(TestCase): self.v8 = Var("v8") self.v9 = Var("v9") self.imm0 = Var("imm0") - self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True, - scalars=False, simd=True) + self.IxN = TypeVar("IxN", "", ints=True, scalars=True, simd=True) self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True, scalars=False, simd=True) self.b1 = TypeVar.singleton(b1) @@ -176,7 +175,7 @@ class TestRTL(TypeCheckingBaseTest): self.assertEqual(ti_rtl(r, ti), "On line 1: fail ti on `typeof_v2` <: `1`: " + "Error: empty type created when unifying " + - "`typeof_v2` and `half_vector(typeof_v2)`") + "`typeof_v3` and `half_vector(typeof_v3)`") def test_vselect(self): # type: () -> None @@ -202,11 +201,11 @@ class TestRTL(TypeCheckingBaseTest): ) ti = TypeEnv() typing = ti_rtl(r, ti) - ixn = self.IxN_nonscalar.get_fresh_copy("IxN1") + ixn = self.IxN.get_fresh_copy("IxN1") txn = self.TxN.get_fresh_copy("TxN1") check_typing(typing, ({ self.v0: ixn, - self.v1: ixn.as_bool(), + self.v1: txn.as_bool(), self.v2: ixn, self.v3: txn, self.v4: txn, @@ -319,7 +318,7 @@ class TestRTL(TypeCheckingBaseTest): self.assertEqual(typing, "On line 2: fail ti on `typeof_v4` <: `4`: " + "Error: empty type created when unifying " + - "`typeof_v4` and `typeof_v5`") + "`i16` and `i32`") def test_extend_reduce(self): # type: () -> None @@ -471,7 +470,7 @@ class TestXForm(TypeCheckingBaseTest): assert var_m[v0] == var_m[v2] and \ var_m[v3] == var_m[v4] and\ var_m[v5] == var_m[v3] and\ - var_m[v1] == var_m[v2].as_bool() and\ + var_m[v1] == var_m[v5].as_bool() and\ var_m[v1].get_typeset() == var_m[v3].as_bool().get_typeset() check_concrete_typing_xform(var_m, xform) diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index b80396b428..7a95daf425 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -104,7 +104,6 @@ class TypesEqual(TypeConstraint): """ def __init__(self, tv1, tv2): # type: (TypeVar, TypeVar) -> None - assert tv1.is_derived and tv2.is_derived (self.tv1, self.tv2) = sorted([tv1, tv2], key=repr) def _args(self): @@ -279,7 +278,7 @@ class TypeEnv(object): :attribute idx: counter used to get fresh ids """ - RANK_DERIVED = 5 + RANK_SINGLETON = 5 RANK_INPUT = 4 RANK_INTERMEDIATE = 3 RANK_OUTPUT = 2 @@ -364,12 +363,18 @@ class TypeEnv(object): # type: (TypeVar) -> int """ Get the rank of tv in the partial order. TVs directly associated with a - Var get their rank from the Var (see register()). - Internally generated non-derived TVs implicitly get the lowest rank (0) - Derived variables get the highest rank. + Var get their rank from the Var (see register()). Internally generated + non-derived TVs implicitly get the lowest rank (0). Derived variables + get their rank from their free typevar. Singletons have the highest + rank. TVs associated with vars in a source pattern have a higher rank + than TVs associted with temporary vars. """ - default_rank = TypeEnv.RANK_DERIVED if tv.is_derived else\ - TypeEnv.RANK_INTERNAL + default_rank = TypeEnv.RANK_INTERNAL if tv.singleton_type() is None \ + else TypeEnv.RANK_SINGLETON + + if tv.is_derived: + tv = tv.free_typevar() + return self.ranks.get(tv, default_rank) def register(self, v): @@ -565,28 +570,36 @@ class TypeEnv(object): # Add all registered TVs (as some of them may be singleton nodes not # appearing in the graph - nodes = set([v.get_typevar() for v in self.vars]) # type: Set[TypeVar] + nodes = set() # type: Set[TypeVar] edges = set() # type: Set[Tuple[TypeVar, TypeVar, str, str, Optional[str]]] # noqa - for (k, v) in self.type_map.items(): + def add_nodes(*args): + # type: (*TypeVar) -> None + for tv in args: + nodes.add(tv) + while (tv.is_derived): + nodes.add(tv.base) + edges.add((tv, tv.base, "solid", "forward", + tv.derived_func)) + tv = tv.base + + for v in self.vars: + add_nodes(v.get_typevar()) + + for (tv1, tv2) in self.type_map.items(): # Add all intermediate TVs appearing in edges - nodes.add(k) - nodes.add(v) - edges.add((k, v, "dotted", "forward", None)) - while (v.is_derived): - nodes.add(v.base) - edges.add((v, v.base, "solid", "forward", v.derived_func)) - v = v.base + add_nodes(tv1, tv2) + edges.add((tv1, tv2, "dotted", "forward", None)) for constr in self.constraints: if isinstance(constr, TypesEqual): - assert constr.tv1 in nodes and constr.tv2 in nodes + add_nodes(constr.tv1, constr.tv2) edges.add((constr.tv1, constr.tv2, "dashed", "none", "equal")) elif isinstance(constr, WiderOrEq): - assert constr.tv1 in nodes and constr.tv2 in nodes + add_nodes(constr.tv1, constr.tv2) edges.add((constr.tv1, constr.tv2, "dashed", "forward", ">=")) elif isinstance(constr, SameWidth): - assert constr.tv1 in nodes and constr.tv2 in nodes + add_nodes(constr.tv1, constr.tv2) edges.add((constr.tv1, constr.tv2, "dashed", "none", "same_width")) else: @@ -640,7 +653,9 @@ def get_type_env(typing_or_err): """ Helper function to appease mypy when checking the result of typing. """ - assert isinstance(typing_or_err, TypeEnv) + assert isinstance(typing_or_err, TypeEnv), \ + "Unexpected error: {}".format(typing_or_err) + if (TYPE_CHECKING): return cast(TypeEnv, typing_or_err) else: @@ -752,8 +767,6 @@ def unify(tv1, tv2, typ): typ.equivalent(tv1, tv2) return typ - assert tv2.is_derived, "Ordering gives us !tv1.is_derived==>tv2.is_derived" - if (tv1.is_derived and TypeVar.is_bijection(tv1.derived_func)): inv_f = TypeVar.inverse_func(tv1.derived_func) return unify(tv1.base, normalize_tv(TypeVar.derived(tv2, inv_f)), typ) diff --git a/lib/cretonne/meta/semantics/__init__.py b/lib/cretonne/meta/semantics/__init__.py index 8caf792259..1c1fee9b9f 100644 --- a/lib/cretonne/meta/semantics/__init__.py +++ b/lib/cretonne/meta/semantics/__init__.py @@ -31,6 +31,7 @@ def verify_semantics(inst, src, xforms): # 2) Any possible typing for the instruction should be covered by # exactly ONE semantic XForm + src = src.copy({}) typenv = get_type_env(ti_rtl(src, TypeEnv())) typenv.normalize() typenv = typenv.extract() diff --git a/lib/cretonne/meta/semantics/primitives.py b/lib/cretonne/meta/semantics/primitives.py index d5b6b35d8b..70fc196d5d 100644 --- a/lib/cretonne/meta/semantics/primitives.py +++ b/lib/cretonne/meta/semantics/primitives.py @@ -9,7 +9,6 @@ from __future__ import absolute_import from cdsl.operands import Operand from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup -from cdsl.ti import SameWidth import base.formats # noqa GROUP = InstructionGroup("primitive", "Primitive instruction set") @@ -39,8 +38,7 @@ prim_from_bv = Instruction( 'prim_from_bv', r""" Convert a flat bitvector to a real SSA Value. """, - ins=(x), outs=(real), - constraints=SameWidth(BV, Real)) + ins=(fromReal), outs=(real)) xh = Operand('xh', BV.half_width(), doc="A semantic value representing the upper half of X") diff --git a/lib/cretonne/meta/semantics/test_elaborate.py b/lib/cretonne/meta/semantics/test_elaborate.py index 4cbfe07bf9..8eafcb9e00 100644 --- a/lib/cretonne/meta/semantics/test_elaborate.py +++ b/lib/cretonne/meta/semantics/test_elaborate.py @@ -205,8 +205,8 @@ class TestElaborate(TestCase): assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( bvx << prim_to_bv.i32x4(x), (bvlo, bvhi) << bvsplit.bv128(bvx), - lo << prim_from_bv.i32x2.bv64(bvlo), - hi << prim_from_bv.i32x2.bv64(bvhi)))) + lo << prim_from_bv.i32x2(bvlo), + hi << prim_from_bv.i32x2(bvhi)))) def test_elaborate_vconcat(self): # type: () -> None @@ -227,7 +227,7 @@ class TestElaborate(TestCase): bvlo << prim_to_bv.i32x2(lo), bvhi << prim_to_bv.i32x2(hi), bvx << bvconcat.bv64(bvlo, bvhi), - x << prim_from_bv.i32x4.bv128(bvx)))) + x << prim_from_bv.i32x4(bvx)))) def test_elaborate_iadd_simple(self): # type: () -> None @@ -247,7 +247,7 @@ class TestElaborate(TestCase): bvx << prim_to_bv.i32(x), bvy << prim_to_bv.i32(y), bva << bvadd.bv32(bvx, bvy), - a << prim_from_bv.i32.bv32(bva)))) + a << prim_from_bv.i32(bva)))) def test_elaborate_iadd_elaborate_1(self): # type: () -> None @@ -279,7 +279,7 @@ class TestElaborate(TestCase): bva_3 << bvadd.bv32(bvlo_1, bvlo_2), bva_4 << bvadd.bv32(bvhi_1, bvhi_2), bvx_5 << bvconcat.bv32(bva_3, bva_4), - a << prim_from_bv.i32x2.bv64(bvx_5)))) + a << prim_from_bv.i32x2(bvx_5)))) def test_elaborate_iadd_elaborate_2(self): # type: () -> None @@ -334,4 +334,4 @@ class TestElaborate(TestCase): bva_14 << bvadd.bv8(bvhi_11, bvhi_12), bvx_15 << bvconcat.bv8(bva_13, bva_14), bvx_5 << bvconcat.bv16(bvx_10, bvx_15), - a << prim_from_bv.i8x4.bv32(bvx_5)))) + a << prim_from_bv.i8x4(bvx_5)))) diff --git a/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py index 538ff69b63..da4683413d 100644 --- a/lib/cretonne/meta/test_gen_legalizer.py +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -153,8 +153,7 @@ class TestRuntimeChecks(TestCase): def test_vselect_imm(self): # type: () -> None - ts = TypeSet(lanes=(2, 256), ints=(8, 64), - floats=(32, 64), bools=(8, 64)) + ts = TypeSet(lanes=(2, 256), ints=(8, 64)) r = Rtl( self.v0 << iconst(self.imm0), self.v1 << icmp(intcc.eq, self.v2, self.v0), @@ -167,7 +166,7 @@ class TestRuntimeChecks(TestCase): .format(self.v3.get_typevar().name) self.check_yo_check( - x, sequence(typeset_check(self.v3, ts), + x, sequence(typeset_check(self.v2, ts), equiv_check(tv2_exp, tv3_exp))) def test_reduce_extend(self): From 22d49c6510a3790219535c8ec030800aefae62af Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 25 Jul 2017 09:37:10 -0700 Subject: [PATCH 909/968] Use a more compact encoding list representation. Encodings has a 16-bit "recipe" field, but even Intel only has 57 recipes currently, so it is unlikely that we will ever need to full range. Use this to represent encoding lists more compactly. Change the encoding list to a format that: - Doesn't need a predicate entry before every encoding entry. - Doesn't need a terminator after the list for each instruction. - Supports multiple "stop codes" for configurable guidance of the legalizer. The encoding scheme has these limits: - 2*NR + NS <= 0x1000 - INSTP + ISAP <= 0x1000 Where: - NR is the number of recipes in an ISA, - NS is the number of stop codes (legalization actions). - INSTP is the number of instruction predicates. - ISAP is the number of discrete ISA predicates. --- lib/cretonne/meta/gen_encoding.py | 323 ++++++++++++++++++++++------- lib/cretonne/src/isa/enc_tables.rs | 94 +++++---- 2 files changed, 298 insertions(+), 119 deletions(-) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 43f02d6416..b8915367fc 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -39,13 +39,13 @@ types, so many of the level 2 tables will be cold. An encoding list is a non-empty sequence of list entries. Each entry has one of these forms: -1. Instruction predicate, encoding recipe, and encoding bits. If the - instruction predicate is true, use this recipe and bits. -2. ISA predicate and skip-count. If the ISA predicate is false, skip the next - *skip-count* entries in the list. If the skip count is zero, stop - completely. -3. Stop. End of list marker. If this is reached, the instruction does not have - a legal encoding. +1. Recipe + bits. Use this encoding if the recipe predicate is satisfied. +2. Recipe + bits, final entry. Use this encoding if the recipe predicate is + satisfied. Otherwise, stop with the default legalization code. +3. Stop with legalization code. +4. Predicate + skip count. Test predicate and skip N entries if it is false. +4. Predicate + stop. Test predicate and stop with the default legalization code + if it is false. The instruction predicate is also used to distinguish between polymorphic instructions with different types for secondary type variables. @@ -56,9 +56,10 @@ from constant_hash import compute_quadratic from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict import math -import itertools +from itertools import groupby from cdsl.registers import RegClass, Register, Stack from cdsl.predicates import FieldPredicate +from cdsl.settings import SettingGroup try: from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa @@ -173,42 +174,228 @@ def emit_recipe_predicates(recipes, fmt): fmt.format('Some({}),', pname[p]) -# Encoding lists are represented as u16 arrays. -CODE_BITS = 16 -PRED_BITS = 12 -PRED_MASK = (1 << PRED_BITS) - 1 - -# 0..CODE_ALWAYS means: Check instruction predicate and use the next two -# entries as a (recipe, encbits) pair if true. CODE_ALWAYS is the always-true -# predicate, smaller numbers refer to instruction predicates. -CODE_ALWAYS = PRED_MASK - -# Codes above CODE_ALWAYS indicate an ISA predicate to be tested. -# `x & PRED_MASK` is the ISA predicate number to test. -# `(x >> PRED_BITS)*3` is the number of u16 table entries to skip if the ISA -# predicate is false. (The factor of three corresponds to the (inst-pred, -# recipe, encbits) triples. +# The u16 values in an encoding list entry are interpreted as follows: # -# Finally, CODE_FAIL indicates the end of the list. -CODE_FAIL = (1 << CODE_BITS) - 1 +# NR = len(all_recipes) +# +# entry < 2*NR +# Try Encoding(entry/2, next_entry) if the recipe predicate is satisfied. +# If bit 0 is set, stop with the default legalization code. +# If bit 0 is clear, keep going down the list. +# entry < PRED_START +# Stop with legalization code `entry - 2*NR`. +# +# Remaining entries are interpreted as (skip, pred) pairs, where: +# +# skip = (entry - PRED_START) >> PRED_BITS +# pred = (entry - PRED_START) & PRED_MASK +# +# If the predicate is satisfied, keep going. Otherwise skip over the next +# `skip` entries. If skip == 0, stop with the default legalization code. +# +# The `pred` predicate number is interpreted as an instruction predicate if it +# is in range, otherwise an ISA predicate. -def seq_doc(enc): - # type: (Encoding) -> Tuple[Tuple[int, int, int], str] +class Encoder: """ - Return a tuple containing u16 representations of the instruction predicate - an recipe / encbits. + Encoder for the list format above. - Also return a doc string. + Two parameters are needed: + + :param NR: Number of recipes. + :param NI: Number of instruction predicates. """ - if enc.instp: - p = enc.instp.number - doc = '--> {} when {}'.format(enc, enc.instp) - else: - p = CODE_ALWAYS + + def __init__(self, isa): + # type: (TargetISA) -> None + self.isa = isa + self.NR = len(isa.all_recipes) + self.NI = len(isa.all_instps) + # u16 encoding list words. + self.words = list() # type: List[int] + # Documentation comments: Index into `words` + comment. + self.docs = list() # type: List[Tuple[int, str]] + + # Encoding lists are represented as u16 arrays. + CODE_BITS = 16 + + # Beginning of the predicate code words. + PRED_START = 0x1000 + + # Number of bits used to hold a predicate number (instruction + ISA + # predicates. + PRED_BITS = 12 + + # Mask for extracting the predicate number. + PRED_MASK = (1 << PRED_BITS) - 1 + + def max_skip(self): + # type: () -> int + """The maximum number of entries that a predicate can skip.""" + return (1 << (self.CODE_BITS - self.PRED_BITS)) - 1 + + def recipe(self, enc, final): + # type: (Encoding, bool) -> None + """Add a recipe+bits entry to the list.""" + offset = len(self.words) + code = 2 * enc.recipe.number doc = '--> {}'.format(enc) - assert p <= CODE_ALWAYS - return ((p, enc.recipe.number, enc.encbits), doc) + if final: + code += 1 + doc += ' and stop' + + assert(code < self.PRED_START) + self.words.extend((code, enc.encbits)) + self.docs.append((offset, doc)) + + def _pred(self, pred, skip, n): + # type: (PredNode, int, int) -> None + """Add a predicate entry.""" + assert n <= self.PRED_MASK + code = n | (skip << self.PRED_BITS) + code += self.PRED_START + assert code < (1 << self.CODE_BITS) + + if skip == 0: + doc = 'stop' + else: + doc = 'skip ' + str(skip) + doc = '{} unless {}'.format(doc, pred) + + self.docs.append((len(self.words), doc)) + self.words.append(code) + + def instp(self, pred, skip): + # type: (PredNode, int) -> None + """Add an instruction predicate entry.""" + self._pred(pred, skip, pred.number) + + def isap(self, pred, skip): + # type: (PredNode, int) -> None + """Add an ISA predicate entry.""" + n = self.isa.settings.predicate_number[pred] + # ISA predicates follow the instruction predicates. + self._pred(pred, skip, self.NI + n) + + +class EncNode(object): + """ + An abstract node in the encoder tree for an instruction. + + This tree is used to simplify the predicates guarding recipe+bits entries. + """ + + def size(self): + # type: () -> int + """Get the number of list entries needed to encode this tree.""" + raise NotImplementedError('EncNode.size() is abstract') + + def encode(self, encoder, final): + # type: (Encoder, bool) -> None + """Encode this tree.""" + raise NotImplementedError('EncNode.encode() is abstract') + + def optimize(self): + # type: () -> EncNode + """Transform this encoder tree into something simpler.""" + return self + + def predicate(self): + # type: () -> PredNode + """Get the predicate guarding this tree, or `None` for always""" + return None + + +class EncPred(EncNode): + """ + An encoder tree node which asserts a predicate on its child nodes. + + A `None` predicate is always satisfied. + """ + + def __init__(self, pred, children): + # type: (PredNode, List[EncNode]) -> None + self.pred = pred + self.children = children + + def size(self): + # type: () -> int + s = 1 if self.pred else 0 + s += sum(c.size() for c in self.children) + return s + + def encode(self, encoder, final): + # type: (Encoder, bool) -> None + if self.pred: + skip = 0 if final else self.size() - 1 + ctx = self.pred.predicate_context() + if isinstance(ctx, SettingGroup): + encoder.isap(self.pred, skip) + else: + encoder.instp(self.pred, skip) + + final_idx = len(self.children) - 1 if final else -1 + for idx, node in enumerate(self.children): + node.encode(encoder, idx == final_idx) + + def predicate(self): + # type: () -> PredNode + return self.pred + + def optimize(self): + # type: () -> EncNode + """ + Optimize a predicate node in the tree by combining child nodes that + have identical predicates. + """ + cnodes = list() # type: List[EncNode] + for pred, niter in groupby( + map(lambda c: c.optimize(), self.children), + key=lambda c: c.predicate()): + nodes = list(niter) + if pred is None or len(nodes) <= 1: + cnodes.extend(nodes) + continue + + # We have multiple children with identical predicates. + # Group them all into `n0`. + n0 = nodes[0] + assert isinstance(n0, EncPred) + for n in nodes[1:]: + assert isinstance(n, EncPred) + n0.children.extend(n.children) + + cnodes.append(n0) + + # Finally strip a redundant grouping node. + if self.pred is None and len(cnodes) == 1: + return cnodes[0] + else: + self.children = cnodes + return self + + +class EncLeaf(EncNode): + """ + A leaf in the encoder tree. + + This represents a single `Encoding`, without its predicates (they are + represented in the tree by parent nodes. + """ + + def __init__(self, encoding): + # type: (Encoding) -> None + self.encoding = encoding + + def size(self): + # type: () -> int + # recipe + bits. + return 2 + + def encode(self, encoder, final): + # type: (Encoder, bool) -> None + encoder.recipe(self.encoding, final) class EncList(object): @@ -239,25 +426,23 @@ class EncList(object): name += ' ({})'.format(self.encodings[0].cpumode) return name - def by_isap(self): - # type: () -> Iterable[Tuple[PredNode, Tuple[Encoding, ...]]] + def encoder_tree(self): + # type: () -> EncNode """ - Group the encodings by ISA predicate without reordering them. + Generate an optimized encoder tree for this list. The tree represents + all of the encodings with parent nodes for the predicates that need + checking. + """ + forest = list() # type: List[EncNode] + for enc in self.encodings: + n = EncLeaf(enc) # type: EncNode + if enc.instp: + n = EncPred(enc.instp, [n]) + if enc.isap: + n = EncPred(enc.isap, [n]) + forest.append(n) - Yield a sequence of `(isap, (encs...))` tuples where `isap` is the ISA - predicate or `None`, and `(encs...)` is a tuple of encodings that all - have the same ISA predicate. - """ - maxlen = CODE_FAIL >> PRED_BITS - for isap, groupi in itertools.groupby( - self.encodings, lambda enc: enc.isap): - group = tuple(groupi) - # This probably never happens, but we can't express more than - # maxlen encodings per isap. - while len(group) > maxlen: - yield (isap, group[0:maxlen]) - group = group[maxlen:] - yield (isap, group) + return EncPred(None, forest).optimize() def encode(self, seq_table, doc_table, isa): # type: (UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa @@ -269,34 +454,20 @@ class EncList(object): Adds comment lines to `doc_table` keyed by seq_table offsets. """ - words = list() # type: List[int] - docs = list() # type: List[Tuple[int, str]] + # Use an encoder object to hold the parameters. + encoder = Encoder(isa) + tree = self.encoder_tree() + tree.encode(encoder, True) - # Group our encodings by isap. - for isap, group in self.by_isap(): - if isap: - # We have an ISA predicate covering `glen` encodings. - pnum = isa.settings.predicate_number[isap] - glen = len(group) - doc = 'skip {}x3 unless {}'.format(glen, isap) - docs.append((len(words), doc)) - words.append((glen << PRED_BITS) | pnum) - - for enc in group: - seq, doc = seq_doc(enc) - docs.append((len(words), doc)) - words.extend(seq) - - # Terminate the list. - words.append(CODE_FAIL) - - self.offset = seq_table.add(words) + self.offset = seq_table.add(encoder.words) # Add doc comments. doc_table[self.offset].append( '{:06x}: {}'.format(self.offset, self.name())) - for pos, doc in docs: + for pos, doc in encoder.docs: doc_table[self.offset + pos].append(doc) + doc_table[self.offset + len(encoder.words)].insert( + 0, 'end of: {}'.format(self.name())) class Level2Table(object): diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index d040dd9ca4..95e79b2982 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -143,22 +143,19 @@ pub fn lookup_enclist(ctrl_typevar: Type, /// Encoding lists are represented as sequences of u16 words. pub type EncListEntry = u16; -/// Number of bits used to represent a predicate. c.f. `meta.gen_encoding.py`. +/// Number of bits used to represent a predicate. c.f. `meta/gen_encoding.py`. const PRED_BITS: u8 = 12; -const PRED_MASK: EncListEntry = (1 << PRED_BITS) - 1; - -/// The match-always instruction predicate. c.f. `meta.gen_encoding.py`. -const CODE_ALWAYS: EncListEntry = PRED_MASK; - -/// The encoding list terminator. -const CODE_FAIL: EncListEntry = 0xffff; +const PRED_MASK: usize = (1 << PRED_BITS) - 1; +/// First code word representing a predicate check. c.f. `meta/gen_encoding.py`. +const PRED_START: usize = 0x1000; /// An iterator over legal encodings for the instruction. pub struct Encodings<'a> { + // Current offset into `enclist`, or out of bounds after we've reached the end. offset: usize, - enclist: &'static [EncListEntry], inst: &'a InstructionData, isa_predicates: PredicateView<'a>, + enclist: &'static [EncListEntry], recipe_predicates: &'static [RecipePredicate], inst_predicates: &'static [InstPredicate], } @@ -166,16 +163,6 @@ pub struct Encodings<'a> { impl<'a> Encodings<'a> { /// Creates a new instance of `Encodings`. /// - /// # Parameters - /// - /// - `offset` an offset into encoding list returned by `lookup_enclist` function. - /// - `enclist` a list of encoding entries. - /// - `recipe_predicates` is a slice of recipe predicate functions. - /// - `inst` the current instruction. - /// - `instp` an instruction predicate number to be evaluated on the current instruction. - /// - `isa_predicate_bytes` an ISA flags as a slice of bytes to evaluate an ISA predicate number - /// on the current instruction. - /// /// This iterator provides search for encodings that applies to the given instruction. The /// encoding lists are laid out such that first call to `next` returns valid entry in the list /// or `None`. @@ -196,42 +183,63 @@ impl<'a> Encodings<'a> { } } - /// Check if the predicate for `recipe` is satisfied. - fn check_recipe(&self, recipe: u16) -> bool { - match self.recipe_predicates[recipe as usize] { + /// Check if the `rpred` recipe predicate s satisfied. + fn check_recipe(&self, rpred: RecipePredicate) -> bool { + match rpred { Some(p) => p(self.isa_predicates, self.inst), None => true, } } + + /// Check an instruction or isa predicate. + fn check_pred(&self, pred: usize) -> bool { + if let Some(&p) = self.inst_predicates.get(pred) { + p(self.inst) + } else { + let pred = pred - self.inst_predicates.len(); + self.isa_predicates.test(pred) + } + } } impl<'a> Iterator for Encodings<'a> { type Item = Encoding; fn next(&mut self) -> Option { - while self.enclist[self.offset] != CODE_FAIL { - let pred = self.enclist[self.offset]; - if pred <= CODE_ALWAYS { - // This is an instruction predicate followed by recipe and encbits entries. - self.offset += 3; - let satisfied = match self.inst_predicates.get(pred as usize) { - Some(p) => p(self.inst), - None => true, - }; - if satisfied { - let recipe = self.enclist[self.offset - 2]; - if self.check_recipe(recipe) { - let encoding = Encoding::new(recipe, self.enclist[self.offset - 1]); - return Some(encoding); - } + while let Some(entryref) = self.enclist.get(self.offset) { + let entry = *entryref as usize; + + // Check for "recipe+bits". + let recipe = entry >> 1; + if let Some(&rpred) = self.recipe_predicates.get(recipe) { + let bits = self.offset + 1; + if entry & 1 == 0 { + self.offset += 2; // Next entry. + } else { + self.offset = !0; // Stop. } - } else { - // This is an ISA predicate entry. + if self.check_recipe(rpred) { + return Some(Encoding::new(recipe as u16, self.enclist[bits])); + } + continue; + } + + // Check for "stop with legalize". + if entry < PRED_START { + unimplemented!(); + } + + // Finally, this must be a predicate entry. + let pred_entry = entry - PRED_START; + let skip = pred_entry >> PRED_BITS; + let pred = pred_entry & PRED_MASK; + + if self.check_pred(pred) { self.offset += 1; - if !self.isa_predicates.test((pred & PRED_MASK) as usize) { - // ISA predicate failed, skip the next N entries. - self.offset += 3 * (pred >> PRED_BITS) as usize; - } + } else if skip == 0 { + self.offset = !0 // This means stop. + } else { + self.offset += 1 + skip; } } None From 629bfe7ba928bc9650217b90190409708740ee82 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 21 Jul 2017 13:04:18 -0700 Subject: [PATCH 910/968] Define I64 before I32 for better encoding table compression. The encoding list compression algorithm is not the sharpest knife in the drawer. It can reuse subsets of I64 encoding lists for I32 instructions, but only when the I64 lists are defined first. With this change and the previous change to the encoding list format, we get the following table sizes for the Intel ISA: ENCLISTS: 1478 B -> 662 B LEVEL2: 1072 B (unchanged) LEVEL1: 32 B -> 48 B Total: 2582 B -> 1782 B (-31%) --- lib/cretonne/meta/isa/intel/defs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/meta/isa/intel/defs.py b/lib/cretonne/meta/isa/intel/defs.py index 5078bcec7e..ad13741ebc 100644 --- a/lib/cretonne/meta/isa/intel/defs.py +++ b/lib/cretonne/meta/isa/intel/defs.py @@ -11,5 +11,5 @@ from . import instructions as x86 ISA = TargetISA('intel', [base.instructions.GROUP, x86.GROUP]) # CPU modes for 32-bit and 64-bit operation. -I32 = CPUMode('I32', ISA) I64 = CPUMode('I64', ISA) +I32 = CPUMode('I32', ISA) From 9067fe7f990da33d7c5919738016245354ccb9cb Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 25 Jul 2017 16:33:35 -0700 Subject: [PATCH 911/968] Add support for legalization codes in the encoding tables. The new encoding format allows entries that mean "stop with this legalization code" which makes it possible to configure legalization actions per instruction, instead of only per controlling type variable. This patch adds the Rust side of the legalization codes: - Add an `Encodings::legalize()` method on the encoding iterator which can be called after the iterator has returned `None`. The returned code is either the default legalization action for the type, or a specific code encountered in the encoding list. - Change `lookup_enclist` to return a full iterator instead of just an offset. The two-phase lookup can bail at multiple points, each time with a default legalization code from the level 1 table. This default legalization code is stored in the returned iterator. - Change all the implementations of legal_encodings() in the ISA implementations. This change means that we don't need to return a Result any longer. The `Encodings` iterator can be empty with an associated legalization code. --- lib/cretonne/src/isa/arm32/mod.rs | 23 +++--- lib/cretonne/src/isa/arm64/mod.rs | 23 +++--- lib/cretonne/src/isa/enc_tables.rs | 115 ++++++++++++++++++----------- lib/cretonne/src/isa/intel/mod.rs | 23 +++--- lib/cretonne/src/isa/mod.rs | 6 +- lib/cretonne/src/isa/riscv/mod.rs | 23 +++--- 6 files changed, 116 insertions(+), 97 deletions(-) diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 1e5cde38db..f9a44db258 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -10,7 +10,7 @@ use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; use ir; use regalloc; @@ -62,22 +62,19 @@ impl TargetIsa for Isa { } fn legal_encodings<'a>(&'a self, - _dfg: &'a ir::DataFlowGraph, + dfg: &'a ir::DataFlowGraph, inst: &'a ir::InstructionData, ctrl_typevar: ir::Type) - -> Result, Legalize> { + -> Encodings<'a> { lookup_enclist(ctrl_typevar, - inst.opcode(), + inst, + dfg, self.cpumode, - &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - Ok(Encodings::new(enclist_offset, - &enc_tables::ENCLISTS[..], - &enc_tables::RECIPE_PREDICATES[..], - &enc_tables::INST_PREDICATES[..], - inst, - self.isa_flags.predicate_view())) - }) + &enc_tables::LEVEL2[..], + &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], + self.isa_flags.predicate_view()) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 4dd873e17b..f530d4b0b2 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -10,7 +10,7 @@ use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; use ir; use regalloc; @@ -55,22 +55,19 @@ impl TargetIsa for Isa { } fn legal_encodings<'a>(&'a self, - _dfg: &'a ir::DataFlowGraph, + dfg: &'a ir::DataFlowGraph, inst: &'a ir::InstructionData, ctrl_typevar: ir::Type) - -> Result, Legalize> { + -> Encodings<'a> { lookup_enclist(ctrl_typevar, - inst.opcode(), + inst, + dfg, &enc_tables::LEVEL1_A64[..], - &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - Ok(Encodings::new(enclist_offset, - &enc_tables::ENCLISTS[..], - &enc_tables::RECIPE_PREDICATES[..], - &enc_tables::INST_PREDICATES[..], - inst, - self.isa_flags.predicate_view())) - }) + &enc_tables::LEVEL2[..], + &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], + self.isa_flags.predicate_view()) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 95e79b2982..402bf8f06d 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -4,8 +4,8 @@ //! `lib/cretonne/meta/gen_encoding.py`. use constant_hash::{Table, probe}; -use ir::{Type, Opcode, InstructionData}; -use isa::{Encoding, Legalize}; +use ir::{Type, Opcode, DataFlowGraph, InstructionData}; +use isa::Encoding; use settings::PredicateView; use std::ops::Range; @@ -97,45 +97,59 @@ impl + Copy> Table for [Level2Entry] { } } -/// Two-level hash table lookup. +/// Two-level hash table lookup and iterator construction. /// /// Given the controlling type variable and instruction opcode, find the corresponding encoding /// list. /// -/// Returns an offset into the ISA's `ENCLIST` table, or `None` if the opcode/type combination is -/// not legal. -pub fn lookup_enclist(ctrl_typevar: Type, - opcode: Opcode, - level1_table: &[Level1Entry], - level2_table: &[Level2Entry]) - -> Result +/// Returns an iterator that produces legal encodings for `inst`. +pub fn lookup_enclist<'a, OffT1, OffT2>(ctrl_typevar: Type, + inst: &'a InstructionData, + _dfg: &'a DataFlowGraph, + level1_table: &'static [Level1Entry], + level2_table: &'static [Level2Entry], + enclist: &'static [EncListEntry], + recipe_preds: &'static [RecipePredicate], + inst_preds: &'static [InstPredicate], + isa_preds: PredicateView<'a>) + -> Encodings<'a> where OffT1: Into + Copy, OffT2: Into + Copy { - match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) { + let (offset, legalize) = match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) { Err(l1idx) => { // No level 1 entry found for the type. // We have a sentinel entry with the default legalization code. - let l1ent = &level1_table[l1idx]; - Err(l1ent.legalize.into()) + (!0, level1_table[l1idx].legalize) } Ok(l1idx) => { // We have a valid level 1 entry for this type. let l1ent = &level1_table[l1idx]; - match level2_table.get(l1ent.range()) { + let offset = match level2_table.get(l1ent.range()) { Some(l2tab) => { - probe(l2tab, opcode, opcode as usize) - .map(|l2idx| l2tab[l2idx].offset.into() as usize) - .map_err(|_| l1ent.legalize.into()) + let opcode = inst.opcode(); + match probe(l2tab, opcode, opcode as usize) { + Ok(l2idx) => l2tab[l2idx].offset.into() as usize, + Err(_) => !0, + } } - None => { - // The l1ent range is invalid. This means that we just have a customized - // legalization code for this type. The level 2 table is empty. - Err(l1ent.legalize.into()) - } - } + // The l1ent range is invalid. This means that we just have a customized + // legalization code for this type. The level 2 table is empty. + None => !0, + }; + (offset, l1ent.legalize) } - } + }; + + // Now we have an offset into `enclist` that is `!0` when no encoding list could be found. + // The default legalization code is always valid. + Encodings::new(offset, + legalize, + inst, + enclist, + recipe_preds, + inst_preds, + isa_preds) } /// Encoding list entry. @@ -153,11 +167,13 @@ const PRED_START: usize = 0x1000; pub struct Encodings<'a> { // Current offset into `enclist`, or out of bounds after we've reached the end. offset: usize, + // Legalization code to use of no encoding is found. + legalize: LegalizeCode, inst: &'a InstructionData, - isa_predicates: PredicateView<'a>, enclist: &'static [EncListEntry], - recipe_predicates: &'static [RecipePredicate], - inst_predicates: &'static [InstPredicate], + recipe_preds: &'static [RecipePredicate], + inst_preds: &'static [InstPredicate], + isa_preds: PredicateView<'a>, } impl<'a> Encodings<'a> { @@ -167,37 +183,49 @@ impl<'a> Encodings<'a> { /// encoding lists are laid out such that first call to `next` returns valid entry in the list /// or `None`. pub fn new(offset: usize, - enclist: &'static [EncListEntry], - recipe_predicates: &'static [RecipePredicate], - inst_predicates: &'static [InstPredicate], + legalize: LegalizeCode, inst: &'a InstructionData, - isa_predicates: PredicateView<'a>) + enclist: &'static [EncListEntry], + recipe_preds: &'static [RecipePredicate], + inst_preds: &'static [InstPredicate], + isa_preds: PredicateView<'a>) -> Self { Encodings { offset, - enclist, inst, - isa_predicates, - recipe_predicates, - inst_predicates, + legalize, + isa_preds, + recipe_preds, + inst_preds, + enclist, } } + /// Get the legalization action that caused the enumeration of encodings to stop. + /// This can be the default legalization action for the type or a custom code for the + /// instruction. + /// + /// This method must only be called after the iterator returns `None`. + pub fn legalize(&self) -> LegalizeCode { + debug_assert_eq!(self.offset, !0, "Premature Encodings::legalize()"); + self.legalize + } + /// Check if the `rpred` recipe predicate s satisfied. fn check_recipe(&self, rpred: RecipePredicate) -> bool { match rpred { - Some(p) => p(self.isa_predicates, self.inst), + Some(p) => p(self.isa_preds, self.inst), None => true, } } /// Check an instruction or isa predicate. fn check_pred(&self, pred: usize) -> bool { - if let Some(&p) = self.inst_predicates.get(pred) { + if let Some(&p) = self.inst_preds.get(pred) { p(self.inst) } else { - let pred = pred - self.inst_predicates.len(); - self.isa_predicates.test(pred) + let pred = pred - self.inst_preds.len(); + self.isa_preds.test(pred) } } } @@ -211,7 +239,7 @@ impl<'a> Iterator for Encodings<'a> { // Check for "recipe+bits". let recipe = entry >> 1; - if let Some(&rpred) = self.recipe_predicates.get(recipe) { + if let Some(&rpred) = self.recipe_preds.get(recipe) { let bits = self.offset + 1; if entry & 1 == 0 { self.offset += 2; // Next entry. @@ -226,7 +254,9 @@ impl<'a> Iterator for Encodings<'a> { // Check for "stop with legalize". if entry < PRED_START { - unimplemented!(); + self.legalize = (entry - 2 * self.recipe_preds.len()) as LegalizeCode; + self.offset = !0; // Stop. + return None; } // Finally, this must be a predicate entry. @@ -237,7 +267,8 @@ impl<'a> Iterator for Encodings<'a> { if self.check_pred(pred) { self.offset += 1; } else if skip == 0 { - self.offset = !0 // This means stop. + self.offset = !0; // Stop. + return None; } else { self.offset += 1 + skip; } diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 85445ac701..9d31727b14 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -10,7 +10,7 @@ use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; use ir; use regalloc; @@ -62,22 +62,19 @@ impl TargetIsa for Isa { } fn legal_encodings<'a>(&'a self, - _dfg: &'a ir::DataFlowGraph, + dfg: &'a ir::DataFlowGraph, inst: &'a ir::InstructionData, ctrl_typevar: ir::Type) - -> Result, Legalize> { + -> Encodings<'a> { lookup_enclist(ctrl_typevar, - inst.opcode(), + inst, + dfg, self.cpumode, - &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - Ok(Encodings::new(enclist_offset, - &enc_tables::ENCLISTS[..], - &enc_tables::RECIPE_PREDICATES[..], - &enc_tables::INST_PREDICATES[..], - inst, - self.isa_flags.predicate_view())) - }) + &enc_tables::LEVEL2[..], + &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], + self.isa_flags.predicate_view()) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 7b25aec8ec..6c43040825 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -156,7 +156,7 @@ pub trait TargetIsa { dfg: &'a ir::DataFlowGraph, inst: &'a ir::InstructionData, ctrl_typevar: ir::Type) - -> Result, Legalize>; + -> Encodings<'a>; /// Encode an instruction after determining it is legal. /// @@ -169,8 +169,8 @@ pub trait TargetIsa { inst: &ir::InstructionData, ctrl_typevar: ir::Type) -> Result { - self.legal_encodings(dfg, inst, ctrl_typevar) - .and_then(|mut iter| iter.next().ok_or(Legalize::Expand)) + let mut iter = self.legal_encodings(dfg, inst, ctrl_typevar); + iter.next().ok_or(iter.legalize().into()) } /// Get a data structure describing the instruction encodings in this ISA. diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 3fe675b7a4..18f0172f86 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -10,7 +10,7 @@ use super::super::settings as shared_settings; use binemit::{CodeSink, MemoryCodeSink, emit_function}; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; +use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; use ir; use regalloc; @@ -62,22 +62,19 @@ impl TargetIsa for Isa { } fn legal_encodings<'a>(&'a self, - _dfg: &'a ir::DataFlowGraph, + dfg: &'a ir::DataFlowGraph, inst: &'a ir::InstructionData, ctrl_typevar: ir::Type) - -> Result, Legalize> { + -> Encodings<'a> { lookup_enclist(ctrl_typevar, - inst.opcode(), + inst, + dfg, self.cpumode, - &enc_tables::LEVEL2[..]) - .and_then(|enclist_offset| { - Ok(Encodings::new(enclist_offset, - &enc_tables::ENCLISTS[..], - &enc_tables::RECIPE_PREDICATES[..], - &enc_tables::INST_PREDICATES[..], - inst, - self.isa_flags.predicate_view())) - }) + &enc_tables::LEVEL2[..], + &enc_tables::ENCLISTS[..], + &enc_tables::RECIPE_PREDICATES[..], + &enc_tables::INST_PREDICATES[..], + self.isa_flags.predicate_view()) } fn legalize_signature(&self, sig: &mut ir::Signature, current: bool) { From 6da734221a50c27e8fed10601742dcc890295338 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Jul 2017 08:12:16 -0700 Subject: [PATCH 912/968] Generate type check predicates for secondary type variables. The encoding tables are keyed by the controlling type variable only. We need to distinguish different encodings for instructions with multiple type variables. Add a TypePredicate instruction predicate which can check the type of an instruction value operand. Combine type checks into the instruction predicate for instructions with more than one type variable. Add Intel encodings for fcvt_from_sint.f32.i64 which can now be distinguished from fcvt_from_sint.f32.i32. --- filetests/isa/intel/binary64-float.cton | 8 +-- lib/cretonne/meta/cdsl/formats.py | 24 +++++++ lib/cretonne/meta/cdsl/isa.py | 9 ++- lib/cretonne/meta/cdsl/predicates.py | 79 ++++++++++++++++++++++-- lib/cretonne/meta/gen_encoding.py | 50 ++++++++++----- lib/cretonne/meta/isa/intel/encodings.py | 2 + lib/cretonne/src/isa/enc_tables.rs | 10 ++- lib/cretonne/src/isa/intel/enc_tables.rs | 3 +- lib/cretonne/src/isa/riscv/enc_tables.rs | 3 +- 9 files changed, 156 insertions(+), 32 deletions(-) diff --git a/filetests/isa/intel/binary64-float.cton b/filetests/isa/intel/binary64-float.cton index ba604ad43c..e8a7c574c1 100644 --- a/filetests/isa/intel/binary64-float.cton +++ b/filetests/isa/intel/binary64-float.cton @@ -21,9 +21,9 @@ ebb0: [-,%xmm10] v11 = fcvt_from_sint.f32 v1 ; bin: f3 44 0f 2a d6 ; asm: cvtsi2ssq %rax, %xmm5 - [-,%xmm5] v12 = fcvt_from_sint.f32 v2 ; TODO: f3 48 0f 2a e8 + [-,%xmm5] v12 = fcvt_from_sint.f32 v2 ; bin: f3 48 0f 2a e8 ; asm: cvtsi2ssq %r14, %xmm10 - [-,%xmm10] v13 = fcvt_from_sint.f32 v3 ; TODO: f3 4d 0f 2a d6 + [-,%xmm10] v13 = fcvt_from_sint.f32 v3 ; bin: f3 4d 0f 2a d6 ; Binary arithmetic. @@ -86,9 +86,9 @@ ebb0: [-,%xmm10] v11 = fcvt_from_sint.f64 v1 ; bin: f2 44 0f 2a d6 ; asm: cvtsi2sdq %rax, %xmm5 - [-,%xmm5] v12 = fcvt_from_sint.f64 v2 ; TODO: f2 48 0f 2a e8 + [-,%xmm5] v12 = fcvt_from_sint.f64 v2 ; bin: f2 48 0f 2a e8 ; asm: cvtsi2sdq %r14, %xmm10 - [-,%xmm10] v13 = fcvt_from_sint.f64 v3 ; TODO: f2 4d 0f 2a d6 + [-,%xmm10] v13 = fcvt_from_sint.f64 v3 ; bin: f2 4d 0f 2a d6 ; Binary arithmetic. diff --git a/lib/cretonne/meta/cdsl/formats.py b/lib/cretonne/meta/cdsl/formats.py index 0b7a72fe0a..aba83ed7a2 100644 --- a/lib/cretonne/meta/cdsl/formats.py +++ b/lib/cretonne/meta/cdsl/formats.py @@ -11,6 +11,29 @@ except ImportError: pass +class InstructionContext(object): + """ + Most instruction predicates refer to immediate fields of a specific + instruction format, so their `predicate_context()` method returns the + specific instruction format. + + Predicates that only care about the types of SSA values are independent of + the instruction format. They can be evaluated in the context of any + instruction. + + The singleton `InstructionContext` class serves as the predicate context + for these predicates. + """ + + def __init__(self): + # type: () -> None + self.name = 'inst' + + +# Singleton instance. +instruction_context = InstructionContext() + + class InstructionFormat(object): """ Every instruction opcode has a corresponding instruction format which @@ -48,6 +71,7 @@ class InstructionFormat(object): def __init__(self, *kinds, **kwargs): # type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa self.name = kwargs.get('name', None) # type: str + self.parent = instruction_context # The number of value operands stored in the format, or `None` when # `has_value_list` is set. diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index f65c2ad1fc..563cf91e92 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,7 +1,7 @@ """Defining instruction set architectures.""" from __future__ import absolute_import from collections import OrderedDict -from .predicates import And +from .predicates import And, TypePredicate from .registers import RegClass, Register, Stack from .ast import Apply from .types import ValueType @@ -405,6 +405,13 @@ class Encoding(object): self.recipe = recipe self.encbits = encbits + + # Add secondary type variables to the instruction predicate. + if len(self.typevars) > 1: + for tv, vt in zip(self.inst.other_typevars, self.typevars[1:]): + typred = TypePredicate.typevar_check(self.inst, tv, vt) + instp = And.combine(instp, typred) + # Record specific predicates. Note that the recipe also has predicates. self.instp = instp self.isap = isap diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 8a1cc147f9..62779c2d15 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -23,14 +23,19 @@ predicate, the context is the instruction format. """ from __future__ import absolute_import from functools import reduce +from .formats import instruction_context try: from typing import Sequence, Tuple, Set, Any, Union, TYPE_CHECKING # noqa if TYPE_CHECKING: - from .formats import InstructionFormat, FormatField # noqa + from .formats import InstructionFormat, InstructionContext, FormatField # noqa + from .instructions import Instruction # noqa from .settings import BoolSetting, SettingGroup # noqa - PredContext = Union[SettingGroup, InstructionFormat] - PredLeaf = Union[BoolSetting, 'FieldPredicate'] + from .types import ValueType # noqa + from .typevar import TypeVar # noqa + PredContext = Union[SettingGroup, InstructionFormat, + InstructionContext] + PredLeaf = Union[BoolSetting, 'FieldPredicate', 'TypePredicate'] PredNode = Union[PredLeaf, 'Predicate'] except ImportError: pass @@ -52,7 +57,7 @@ def _descendant(a, b): If a is a parent of b or b is a parent of a, return the descendant of the two. - If neiher is a parent of the other, return None. + If neither is a parent of the other, return None. """ if _is_parent(a, b): return b @@ -293,3 +298,69 @@ class IsUnsignedInt(FieldPredicate): self.scale = scale assert width >= 0 and width <= 64 assert scale >= 0 and scale < width + + +class TypePredicate(object): + """ + An instruction predicate that checks the type of an SSA argument value. + + Type predicates are used to implement encodings for instructions with + multiple type variables. The encoding tables are keyed by the controlling + type variable, type predicates check any secondary type variables. + + A type predicate is not bound to any specific instruction format. + + :param value_arg: Index of the value argument to type check. + :param value_type: The required value type. + """ + + def __init__(self, value_arg, value_type): + # type: (int, ValueType) -> None + assert value_arg >= 0 + assert value_type is not None + self.value_arg = value_arg + self.value_type = value_type + self.number = None # type: int + # All PredNode members must have a name field. This will never be set. + self.name = None # type: str + + def __str__(self): + # type: () -> str + return 'args[{}]:{}'.format(self.value_arg, self.value_type) + + def predicate_context(self): + # type: () -> PredContext + return instruction_context + + def predicate_leafs(self, leafs): + # type: (Set[PredLeaf]) -> None + leafs.add(self) + + @staticmethod + def typevar_check(inst, typevar, value_type): + # type: (Instruction, TypeVar, ValueType) -> TypePredicate + """ + Return a type check predicate for the given type variable in `inst`. + + The type variable must appear directly as the type of one of the + operands to `inst`, so this is only guaranteed to work for secondary + type variables. + + Find an `inst` value operand whose type is determined by `typevar` and + create a `TypePredicate` that checks that the type variable has the + value `value_type`. + """ + # Find the first value operand whose type is `typevar`. + value_arg = next(i for i, opnum in enumerate(inst.value_opnums) + if inst.ins[opnum].typevar == typevar) + return TypePredicate(value_arg, value_type) + + def rust_predicate(self, prec): + # type: (int) -> str + """ + Return Rust code for evaluating this predicate. + + It is assumed that the context has `dfg` and `args` variables. + """ + return 'dfg.value_type(args[{}]) == {}'.format( + self.value_arg, self.value_type.rust_name()) diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index b8915367fc..6f88aa70fb 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -58,8 +58,9 @@ from collections import OrderedDict, defaultdict import math from itertools import groupby from cdsl.registers import RegClass, Register, Stack -from cdsl.predicates import FieldPredicate +from cdsl.predicates import FieldPredicate, TypePredicate from cdsl.settings import SettingGroup +from cdsl.formats import instruction_context, InstructionFormat try: from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa @@ -73,8 +74,8 @@ except ImportError: pass -def emit_instp(instp, fmt): - # type: (PredNode, srcgen.Formatter) -> None +def emit_instp(instp, fmt, has_dfg=False): + # type: (PredNode, srcgen.Formatter, bool) -> None """ Emit code for matching an instruction predicate against an `InstructionData` reference called `inst`. @@ -84,22 +85,42 @@ def emit_instp(instp, fmt): """ iform = instp.predicate_context() + # Deal with pure type check predicates which apply to any instruction. + if iform == instruction_context: + fmt.line('let args = inst.arguments(&dfg.value_lists);') + fmt.format('return {};', instp.rust_predicate(0)) + return + + assert isinstance(iform, InstructionFormat) + # Which fields do we need in the InstructionData pattern match? + has_type_check = False # Collect the leaf predicates. leafs = set() # type: Set[PredLeaf] instp.predicate_leafs(leafs) - # All the leafs are FieldPredicate instances. Here we just care about - # the field names. + # All the leafs are FieldPredicate or TypePredicate instances. Here we just + # care about the field names. fnames = set() # type: Set[str] for p in leafs: - assert isinstance(p, FieldPredicate) - fnames.add(p.field.rust_name()) + if isinstance(p, FieldPredicate): + fnames.add(p.field.rust_name()) + else: + assert isinstance(p, TypePredicate) + has_type_check = True fields = ', '.join(sorted(fnames)) with fmt.indented( - 'if let InstructionData::{} {{ {}, .. }} = *inst {{' + 'if let ir::InstructionData::{} {{ {}, .. }} = *inst {{' .format(iform.name, fields), '}'): - fmt.line('return {};'.format(instp.rust_predicate(0))) + if has_type_check: + # We could implement this if we need to. + assert has_dfg, "Recipe predicates can't check type variables." + fmt.line('let args = inst.arguments(&dfg.value_lists);') + elif has_dfg: + # Silence dead argument warning. + fmt.line('let _ = dfg;') + fmt.format('return {};', instp.rust_predicate(0)) + fmt.line('unreachable!();') def emit_inst_predicates(instps, fmt): @@ -111,11 +132,9 @@ def emit_inst_predicates(instps, fmt): for instp in instps: name = 'inst_predicate_{}'.format(instp.number) with fmt.indented( - 'fn {}(inst: &InstructionData) -> bool {{' - .format(name), - '}'): - emit_instp(instp, fmt) - fmt.line('unreachable!();') + 'fn {}(dfg: &ir::DataFlowGraph, inst: &ir::InstructionData)' + '-> bool {{'.format(name), '}'): + emit_instp(instp, fmt, has_dfg=True) # Generate the static table. with fmt.indented( @@ -150,7 +169,7 @@ def emit_recipe_predicates(recipes, fmt): # Generate the predicate function. with fmt.indented( 'fn {}({}: ::settings::PredicateView, ' - 'inst: &InstructionData) -> bool {{' + 'inst: &ir::InstructionData) -> bool {{' .format( name, 'isap' if isap else '_'), '}'): @@ -160,7 +179,6 @@ def emit_recipe_predicates(recipes, fmt): '}'): fmt.line('return false;') emit_instp(instp, fmt) - fmt.line('unreachable!();') # Generate the static table. with fmt.indented( diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index df1a542101..b59dd8d9af 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -229,11 +229,13 @@ I64.enc(base.uextend.i64.i32, *r.umr(0x89)) # cvtsi2ss I32.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) +I64.enc(base.fcvt_from_sint.f32.i64, *r.furm.rex(0xf3, 0x0f, 0x2A, w=1)) I64.enc(base.fcvt_from_sint.f32.i32, *r.furm.rex(0xf3, 0x0f, 0x2A)) I64.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) # cvtsi2sd I32.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) +I64.enc(base.fcvt_from_sint.f64.i64, *r.furm.rex(0xf2, 0x0f, 0x2A, w=1)) I64.enc(base.fcvt_from_sint.f64.i32, *r.furm.rex(0xf2, 0x0f, 0x2A)) I64.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 402bf8f06d..d8bd9756d6 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -20,7 +20,7 @@ pub type RecipePredicate = Option bool>; /// /// This is a predicate function that needs to be tested in addition to the recipe predicate. It /// can't depend on ISA settings. -pub type InstPredicate = fn(&InstructionData) -> bool; +pub type InstPredicate = fn(&DataFlowGraph, &InstructionData) -> bool; /// Legalization action to perform when no encoding can be found for an instruction. /// @@ -105,7 +105,7 @@ impl + Copy> Table for [Level2Entry] { /// Returns an iterator that produces legal encodings for `inst`. pub fn lookup_enclist<'a, OffT1, OffT2>(ctrl_typevar: Type, inst: &'a InstructionData, - _dfg: &'a DataFlowGraph, + dfg: &'a DataFlowGraph, level1_table: &'static [Level1Entry], level2_table: &'static [Level2Entry], enclist: &'static [EncListEntry], @@ -146,6 +146,7 @@ pub fn lookup_enclist<'a, OffT1, OffT2>(ctrl_typevar: Type, Encodings::new(offset, legalize, inst, + dfg, enclist, recipe_preds, inst_preds, @@ -170,6 +171,7 @@ pub struct Encodings<'a> { // Legalization code to use of no encoding is found. legalize: LegalizeCode, inst: &'a InstructionData, + dfg: &'a DataFlowGraph, enclist: &'static [EncListEntry], recipe_preds: &'static [RecipePredicate], inst_preds: &'static [InstPredicate], @@ -185,6 +187,7 @@ impl<'a> Encodings<'a> { pub fn new(offset: usize, legalize: LegalizeCode, inst: &'a InstructionData, + dfg: &'a DataFlowGraph, enclist: &'static [EncListEntry], recipe_preds: &'static [RecipePredicate], inst_preds: &'static [InstPredicate], @@ -193,6 +196,7 @@ impl<'a> Encodings<'a> { Encodings { offset, inst, + dfg, legalize, isa_preds, recipe_preds, @@ -222,7 +226,7 @@ impl<'a> Encodings<'a> { /// Check an instruction or isa predicate. fn check_pred(&self, pred: usize) -> bool { if let Some(&p) = self.inst_preds.get(pred) { - p(self.inst) + p(self.dfg, self.inst) } else { let pred = pred - self.inst_preds.len(); self.isa_preds.test(pred) diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 47ea5dbe11..b3144f11e3 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,7 +1,6 @@ //! Encoding tables for Intel ISAs. -use ir::types; -use ir::{Opcode, InstructionData}; +use ir::{self, types, Opcode}; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::*; diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 0dddebdbac..7e05879a3b 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -1,8 +1,7 @@ //! Encoding tables for RISC-V. use ir::condcodes::IntCC; -use ir::types; -use ir::{Opcode, InstructionData}; +use ir::{self, types, Opcode}; use isa::EncInfo; use isa::constraints::*; use isa::enc_tables::*; From 136cfe00dd515a5cc6bf9cab4a73b457683e4428 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Jul 2017 09:30:29 -0700 Subject: [PATCH 913/968] Add a predicate_key() method to all predicates. This enables interning of predicates to avoid duplicates. Add a predicate registry to TargetIsa for interning predicates per ISA. --- lib/cretonne/meta/cdsl/isa.py | 27 ++++++++++++++++++++++++--- lib/cretonne/meta/cdsl/predicates.py | 21 +++++++++++++++++++++ lib/cretonne/meta/cdsl/settings.py | 23 ++++++++++++++--------- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 563cf91e92..cdb1923a05 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -13,7 +13,7 @@ try: from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, Dict, TYPE_CHECKING # noqa if TYPE_CHECKING: from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa - from .predicates import PredNode # noqa + from .predicates import PredNode, PredKey # noqa from .settings import SettingGroup # noqa from .registers import RegBank # noqa from .xform import XFormGroup # noqa @@ -50,6 +50,8 @@ class TargetISA(object): self.regbanks = list() # type: List[RegBank] self.regclasses = list() # type: List[RegClass] self.legalize_codes = OrderedDict() # type: OrderedDict[XFormGroup, int] # noqa + # Unique copies of all predicates. + self._predicates = dict() # type: Dict[PredKey, PredNode] assert InstructionGroup._current is None,\ "InstructionGroup {} is still open!"\ @@ -86,12 +88,15 @@ class TargetISA(object): for enc in cpumode.encodings: recipe = enc.recipe if recipe not in rcps: + assert recipe.number is None recipe.number = len(rcps) rcps.add(recipe) self.all_recipes.append(recipe) # Make sure ISA predicates are registered. if recipe.isap: + recipe.isap = self.unique_pred(recipe.isap) self.settings.number_predicate(recipe.isap) + recipe.instp = self.unique_pred(recipe.instp) def _collect_predicates(self): # type: () -> None @@ -111,6 +116,7 @@ class TargetISA(object): instp = enc.instp if instp and instp not in instps: # assign predicate number starting from 0. + assert instp.number is None instp.number = len(instps) instps.add(instp) self.all_instps.append(instp) @@ -175,6 +181,21 @@ class TargetISA(object): self.legalize_codes[xgrp] = code return code + def unique_pred(self, pred): + # type: (PredNode) -> PredNode + """ + Get a unique predicate that is equivalent to `pred`. + """ + if pred is None: + return pred + # TODO: We could actually perform some algebraic simplifications. It's + # not clear if it is worthwhile. + k = pred.predicate_key() + if k in self._predicates: + return self._predicates[k] + self._predicates[k] = pred + return pred + class CPUMode(object): """ @@ -413,8 +434,8 @@ class Encoding(object): instp = And.combine(instp, typred) # Record specific predicates. Note that the recipe also has predicates. - self.instp = instp - self.isap = isap + self.instp = self.cpumode.isa.unique_pred(instp) + self.isap = self.cpumode.isa.unique_pred(isap) def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 62779c2d15..ca5d87aa9a 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -37,6 +37,9 @@ try: InstructionContext] PredLeaf = Union[BoolSetting, 'FieldPredicate', 'TypePredicate'] PredNode = Union[PredLeaf, 'Predicate'] + # A predicate key is a (recursive) tuple of primitive types that + # uniquely describes a predicate. It is used for interning. + PredKey = Tuple[Any, ...] except ImportError: pass @@ -84,6 +87,7 @@ class Predicate(object): _descendant, (p.predicate_context() for p in parts)) assert self.context, "Incompatible predicate parts" + self.predkey = None # type: PredKey def __str__(self): # type: () -> str @@ -110,6 +114,14 @@ class Predicate(object): # type: (int) -> str raise NotImplementedError("rust_predicate is an abstract method") + def predicate_key(self): + # type: () -> PredKey + """Tuple uniquely identifying a predicate.""" + if not self.predkey: + p = tuple(p.predicate_key() for p in self.parts) # type: PredKey + self.predkey = (type(self).__name__,) + p + return self.predkey + class And(Predicate): """ @@ -224,6 +236,11 @@ class FieldPredicate(object): iform = self.field.format # type: InstructionFormat return iform + def predicate_key(self): + # type: () -> PredKey + a = tuple(map(str, self.args)) + return (self.function, str(self.field)) + a + def predicate_leafs(self, leafs): # type: (Set[PredLeaf]) -> None leafs.add(self) @@ -332,6 +349,10 @@ class TypePredicate(object): # type: () -> PredContext return instruction_context + def predicate_key(self): + # type: () -> PredKey + return ('typecheck', self.value_arg, self.value_type.name) + def predicate_leafs(self, leafs): # type: (Set[PredLeaf]) -> None leafs.add(self) diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index aca3ac296f..bf3d37dc00 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -7,7 +7,7 @@ try: from typing import Tuple, Set, List, Dict, Any, Union, TYPE_CHECKING # noqa BoolOrPresetOrDict = Union['BoolSetting', 'Preset', Dict['Setting', Any]] if TYPE_CHECKING: - from .predicates import PredLeaf, PredNode # noqa + from .predicates import PredLeaf, PredNode, PredKey # noqa except ImportError: pass @@ -36,14 +36,6 @@ class Setting(object): # type: () -> str return '{}.{}'.format(self.group.name, self.name) - def predicate_context(self): - # type: () -> SettingGroup - """ - Return the context where this setting can be evaluated as a (leaf) - predicate. - """ - return self.group - def default_byte(self): # type: () -> int raise NotImplementedError("default_byte is an abstract method") @@ -96,6 +88,19 @@ class BoolSetting(Setting): # type: () -> int return 1 << self.bit_offset + def predicate_context(self): + # type: () -> SettingGroup + """ + Return the context where this setting can be evaluated as a (leaf) + predicate. + """ + return self.group + + def predicate_key(self): + # type: () -> PredKey + assert self.name, "Can't compute key before setting is named" + return ('setting', self.group.name, self.name) + def predicate_leafs(self, leafs): # type: (Set[PredLeaf]) -> None leafs.add(self) From 84fffa79f6a27426b700efcf07a50a16a9553571 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Jul 2017 10:14:26 -0700 Subject: [PATCH 914/968] Remove the name field from the PredNode union type. The name of a predicate was only ever used for named settings that are computed as a boolean expression of other settings. - Record the names of these settings in named_predicates instead. - Remove the name field from all predicates. Named predicates does not interact well with the interning of predicates through isa.unique_pred(). --- lib/cretonne/meta/cdsl/predicates.py | 13 ++----------- lib/cretonne/meta/cdsl/settings.py | 10 ++++------ lib/cretonne/meta/gen_settings.py | 18 +++++++----------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index ca5d87aa9a..3b375549c7 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -80,7 +80,6 @@ class Predicate(object): def __init__(self, parts): # type: (Sequence[PredNode]) -> None - self.name = None # type: str self.number = None # type: int self.parts = parts self.context = reduce( @@ -91,12 +90,8 @@ class Predicate(object): def __str__(self): # type: () -> str - if self.name: - return '{}.{}'.format(self.context.name, self.name) - else: - return '{}({})'.format( - type(self).__name__, - ', '.join(map(str, self.parts))) + return '{}({})'.format(type(self).__name__, + ', '.join(map(str, self.parts))) def predicate_context(self): # type: () -> PredContext @@ -219,8 +214,6 @@ class FieldPredicate(object): self.field = field self.function = function self.args = args - # All PredNode members must have a name field. This will never be set. - self.name = None # type: str def __str__(self): # type: () -> str @@ -338,8 +331,6 @@ class TypePredicate(object): self.value_arg = value_arg self.value_type = value_type self.number = None # type: int - # All PredNode members must have a name field. This will never be set. - self.name = None # type: str def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index bf3d37dc00..331696ccf0 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -190,7 +190,7 @@ class SettingGroup(object): self.settings = [] # type: List[Setting] # Named predicates computed from settings in this group or its # parents. - self.named_predicates = [] # type: List[Predicate] + self.named_predicates = OrderedDict() # type: OrderedDict[str, Predicate] # noqa # All boolean predicates that can be accessed by number. This includes: # - All boolean settings in this group. # - All named predicates. @@ -235,9 +235,7 @@ class SettingGroup(object): assert obj.name is None, obj.name obj.name = name if isinstance(obj, Predicate): - assert obj.name is None - obj.name = name - self.named_predicates.append(obj) + self.named_predicates[name] = obj if isinstance(obj, Preset): assert obj.name is None, obj.name obj.name = name @@ -333,8 +331,8 @@ class SettingGroup(object): self.settings_size = self.byte_size() # Now assign numbers to all our named predicates. - for p in self.named_predicates: - self.number_predicate(p) + for name, pred in self.named_predicates.items(): + self.number_predicate(pred) def byte_size(self): # type: () -> int diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 1a79026c36..223e33f478 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -67,13 +67,13 @@ def gen_getter(setting, sgrp, fmt): raise AssertionError("Unknown setting kind") -def gen_pred_getter(pred, sgrp, fmt): - # type: (Predicate, SettingGroup, srcgen.Formatter) -> None +def gen_pred_getter(name, pred, sgrp, fmt): + # type: (str, Predicate, SettingGroup, srcgen.Formatter) -> None """ - Emit a getter for a pre-computed predicate. + Emit a getter for a named pre-computed predicate. """ fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0))) - proto = 'pub fn {}(&self) -> bool'.format(pred.name) + proto = 'pub fn {}(&self) -> bool'.format(name) with fmt.indented(proto + ' {', '}'): fmt.line( 'self.numbered_predicate({})' @@ -103,8 +103,8 @@ def gen_getters(sgrp, fmt): .format(sgrp.boolean_offset)) for setting in sgrp.settings: gen_getter(setting, sgrp, fmt) - for pred in sgrp.named_predicates: - gen_pred_getter(pred, sgrp, fmt) + for name, pred in sgrp.named_predicates.items(): + gen_pred_getter(name, pred, sgrp, fmt) def gen_descriptors(sgrp, fmt): @@ -262,11 +262,7 @@ def gen_constructor(sgrp, parent, fmt): # Don't compute our own settings. if number < sgrp.boolean_settings: continue - if pred.name: - fmt.comment( - 'Precompute #{} ({}).'.format(number, pred.name)) - else: - fmt.comment('Precompute #{}.'.format(number)) + fmt.comment('Precompute #{}.'.format(number)) with fmt.indented( 'if {} {{'.format(pred.rust_predicate(0)), '}'): From ac830e0446fd66f4f125d313f7447c97f98e8c92 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Jul 2017 11:01:47 -0700 Subject: [PATCH 915/968] Remove the number field from the PredNode union type. Predicate numbers are available in the maps isa.settings.predicate_number and isa.instp_number instead. Like the name field, predicate numbers don't interact well with unique_pred(). --- lib/cretonne/meta/cdsl/isa.py | 14 ++++-------- lib/cretonne/meta/cdsl/predicates.py | 3 --- lib/cretonne/meta/cdsl/settings.py | 1 - lib/cretonne/meta/gen_encoding.py | 34 ++++++++++++++-------------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index cdb1923a05..1e08ca0e4f 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -103,23 +103,17 @@ class TargetISA(object): """ Collect and number all predicates in use. - Sets `instp.number` for all used instruction predicates and places them - in `self.all_instps` in numerical order. - Ensures that all ISA predicates have an assigned bit number in `self.settings`. """ - self.all_instps = list() # type: List[PredNode] - instps = set() # type: Set[PredNode] + self.instp_number = OrderedDict() # type: OrderedDict[PredNode, int] for cpumode in self.cpumodes: for enc in cpumode.encodings: instp = enc.instp - if instp and instp not in instps: + if instp and instp not in self.instp_number: # assign predicate number starting from 0. - assert instp.number is None - instp.number = len(instps) - instps.add(instp) - self.all_instps.append(instp) + n = len(self.instp_number) + self.instp_number[instp] = n # All referenced ISA predicates must have a number in # `self.settings`. This may cause some parent predicates to be diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py index 3b375549c7..f1f5232272 100644 --- a/lib/cretonne/meta/cdsl/predicates.py +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -80,7 +80,6 @@ class Predicate(object): def __init__(self, parts): # type: (Sequence[PredNode]) -> None - self.number = None # type: int self.parts = parts self.context = reduce( _descendant, @@ -210,7 +209,6 @@ class FieldPredicate(object): def __init__(self, field, function, args): # type: (FormatField, str, Sequence[Any]) -> None - self.number = None # type: int self.field = field self.function = function self.args = args @@ -330,7 +328,6 @@ class TypePredicate(object): assert value_type is not None self.value_arg = value_arg self.value_type = value_type - self.number = None # type: int def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index 331696ccf0..cea8ec7eee 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -23,7 +23,6 @@ class Setting(object): def __init__(self, doc): # type: (str) -> None self.name = None # type: str # Assigned later by `extract_names()`. - self.number = None # type: int self.__doc__ = doc # Offset of byte in settings vector containing this setting. self.byte_offset = None # type: int diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 6f88aa70fb..a7f60f488f 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -124,13 +124,13 @@ def emit_instp(instp, fmt, has_dfg=False): def emit_inst_predicates(instps, fmt): - # type: (Sequence[PredNode], srcgen.Formatter) -> None + # type: (OrderedDict[PredNode, int], srcgen.Formatter) -> None """ Emit private functions for matching instruction predicates as well as a static `INST_PREDICATES` array indexed by predicate number. """ - for instp in instps: - name = 'inst_predicate_{}'.format(instp.number) + for instp, number in instps.items(): + name = 'inst_predicate_{}'.format(number) with fmt.indented( 'fn {}(dfg: &ir::DataFlowGraph, inst: &ir::InstructionData)' '-> bool {{'.format(name), '}'): @@ -140,12 +140,12 @@ def emit_inst_predicates(instps, fmt): with fmt.indented( 'pub static INST_PREDICATES: [InstPredicate; {}] = [' .format(len(instps)), '];'): - for instp in instps: - fmt.format('inst_predicate_{},', instp.number) + for instp, number in instps.items(): + fmt.format('inst_predicate_{},', number) -def emit_recipe_predicates(recipes, fmt): - # type: (Sequence[EncRecipe], srcgen.Formatter) -> None +def emit_recipe_predicates(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None """ Emit private functions for checking recipe predicates as well as a static `RECIPE_PREDICATES` array indexed by recipe number. @@ -158,7 +158,7 @@ def emit_recipe_predicates(recipes, fmt): pname = dict() # type: Dict[RecipePred, str] # Generate unique recipe predicates. - for rcp in recipes: + for rcp in isa.all_recipes: p = rcp.recipe_pred() if p is None or p in pname: continue @@ -174,17 +174,16 @@ def emit_recipe_predicates(recipes, fmt): name, 'isap' if isap else '_'), '}'): if isap: - with fmt.indented( - 'if isap.test({})'.format(isap.number), - '}'): + n = isa.settings.predicate_number[isap] + with fmt.indented('if isap.test({})'.format(n), '}'): fmt.line('return false;') emit_instp(instp, fmt) # Generate the static table. with fmt.indented( 'pub static RECIPE_PREDICATES: [RecipePredicate; {}] = [' - .format(len(recipes)), '];'): - for rcp in recipes: + .format(len(isa.all_recipes)), '];'): + for rcp in isa.all_recipes: p = rcp.recipe_pred() if p is None: fmt.line('None,') @@ -229,7 +228,7 @@ class Encoder: # type: (TargetISA) -> None self.isa = isa self.NR = len(isa.all_recipes) - self.NI = len(isa.all_instps) + self.NI = len(isa.instp_number) # u16 encoding list words. self.words = list() # type: List[int] # Documentation comments: Index into `words` + comment. @@ -287,7 +286,8 @@ class Encoder: def instp(self, pred, skip): # type: (PredNode, int) -> None """Add an instruction predicate entry.""" - self._pred(pred, skip, pred.number) + number = self.isa.instp_number[pred] + self._pred(pred, skip, number) def isap(self, pred, skip): # type: (PredNode, int) -> None @@ -836,10 +836,10 @@ def gen_isa(isa, fmt): # type: (TargetISA, srcgen.Formatter) -> None # Make the `RECIPE_PREDICATES` table. - emit_recipe_predicates(isa.all_recipes, fmt) + emit_recipe_predicates(isa, fmt) # Make the `INST_PREDICATES` table. - emit_inst_predicates(isa.all_instps, fmt) + emit_inst_predicates(isa.instp_number, fmt) # Level1 tables, one per CPU mode level1_tables = dict() From 06bab60fcc765954545fd12d58ad5ebb0a9214b1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 26 Jul 2017 14:55:26 -0700 Subject: [PATCH 916/968] Add support for type variable wildcards in bound instructions. Instructions will multiple type variables can now use `any` to indicate encodings that don't care about the value of a secondary type variable: ishl.i32.any instead of ishl.i32.i32 This is only allowed for secondary type variables (which are converted to instruction predicates). The controlling type variable must still be fully specified because it is used to key the encoding tables. --- lib/cretonne/meta/cdsl/instructions.py | 5 +++ lib/cretonne/meta/cdsl/isa.py | 3 ++ lib/cretonne/meta/isa/intel/encodings.py | 56 ++++++++++++------------ 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 9d23dea89e..6dec0b7dff 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -314,6 +314,7 @@ class Instruction(object): >>> iadd.i32 """ + assert name != 'any', 'Wildcard not allowed for ctrl_typevar' return self.bind(ValueType.by_name(name)) def fully_bound(self): @@ -386,6 +387,10 @@ class BoundInstruction(object): >>> uext.i32.i8 """ + if name == 'any': + # This is a wild card bind represented as a None type variable. + return self.bind(None) + return self.bind(ValueType.by_name(name)) def fully_bound(self): diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 1e08ca0e4f..487001d9e1 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -424,6 +424,9 @@ class Encoding(object): # Add secondary type variables to the instruction predicate. if len(self.typevars) > 1: for tv, vt in zip(self.inst.other_typevars, self.typevars[1:]): + # A None tv is an 'any' wild card: `ishl.i32.any`. + if vt is None: + continue typred = TypePredicate.typevar_check(self.inst, tv, vt) instp = And.combine(instp, typred) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index b59dd8d9af..df95cf7f48 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -104,10 +104,10 @@ for inst, rrr in [ (base.ishl, 4), (base.ushr, 5), (base.sshr, 7)]: - I32.enc(inst.i32.i32, *r.rc(0xd3, rrr=rrr)) - I64.enc(inst.i64.i64, *r.rc.rex(0xd3, rrr=rrr, w=1)) - I64.enc(inst.i32.i32, *r.rc.rex(0xd3, rrr=rrr)) - I64.enc(inst.i32.i32, *r.rc(0xd3, rrr=rrr)) + I32.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr)) + I64.enc(inst.i64.any, *r.rc.rex(0xd3, rrr=rrr, w=1)) + I64.enc(inst.i32.any, *r.rc.rex(0xd3, rrr=rrr)) + I64.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr)) # Population count. I32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) @@ -131,37 +131,37 @@ I64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) I64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) # Loads and stores. -I32.enc(base.store.i32.i32, *r.st(0x89)) -I32.enc(base.store.i32.i32, *r.stDisp8(0x89)) -I32.enc(base.store.i32.i32, *r.stDisp32(0x89)) +I32.enc(base.store.i32.any, *r.st(0x89)) +I32.enc(base.store.i32.any, *r.stDisp8(0x89)) +I32.enc(base.store.i32.any, *r.stDisp32(0x89)) -I32.enc(base.istore16.i32.i32, *r.st(0x66, 0x89)) -I32.enc(base.istore16.i32.i32, *r.stDisp8(0x66, 0x89)) -I32.enc(base.istore16.i32.i32, *r.stDisp32(0x66, 0x89)) +I32.enc(base.istore16.i32.any, *r.st(0x66, 0x89)) +I32.enc(base.istore16.i32.any, *r.stDisp8(0x66, 0x89)) +I32.enc(base.istore16.i32.any, *r.stDisp32(0x66, 0x89)) -I32.enc(base.istore8.i32.i32, *r.st_abcd(0x88)) -I32.enc(base.istore8.i32.i32, *r.stDisp8_abcd(0x88)) -I32.enc(base.istore8.i32.i32, *r.stDisp32_abcd(0x88)) +I32.enc(base.istore8.i32.any, *r.st_abcd(0x88)) +I32.enc(base.istore8.i32.any, *r.stDisp8_abcd(0x88)) +I32.enc(base.istore8.i32.any, *r.stDisp32_abcd(0x88)) -I32.enc(base.load.i32.i32, *r.ld(0x8b)) -I32.enc(base.load.i32.i32, *r.ldDisp8(0x8b)) -I32.enc(base.load.i32.i32, *r.ldDisp32(0x8b)) +I32.enc(base.load.i32.any, *r.ld(0x8b)) +I32.enc(base.load.i32.any, *r.ldDisp8(0x8b)) +I32.enc(base.load.i32.any, *r.ldDisp32(0x8b)) -I32.enc(base.uload16.i32.i32, *r.ld(0x0f, 0xb7)) -I32.enc(base.uload16.i32.i32, *r.ldDisp8(0x0f, 0xb7)) -I32.enc(base.uload16.i32.i32, *r.ldDisp32(0x0f, 0xb7)) +I32.enc(base.uload16.i32.any, *r.ld(0x0f, 0xb7)) +I32.enc(base.uload16.i32.any, *r.ldDisp8(0x0f, 0xb7)) +I32.enc(base.uload16.i32.any, *r.ldDisp32(0x0f, 0xb7)) -I32.enc(base.sload16.i32.i32, *r.ld(0x0f, 0xbf)) -I32.enc(base.sload16.i32.i32, *r.ldDisp8(0x0f, 0xbf)) -I32.enc(base.sload16.i32.i32, *r.ldDisp32(0x0f, 0xbf)) +I32.enc(base.sload16.i32.any, *r.ld(0x0f, 0xbf)) +I32.enc(base.sload16.i32.any, *r.ldDisp8(0x0f, 0xbf)) +I32.enc(base.sload16.i32.any, *r.ldDisp32(0x0f, 0xbf)) -I32.enc(base.uload8.i32.i32, *r.ld(0x0f, 0xb6)) -I32.enc(base.uload8.i32.i32, *r.ldDisp8(0x0f, 0xb6)) -I32.enc(base.uload8.i32.i32, *r.ldDisp32(0x0f, 0xb6)) +I32.enc(base.uload8.i32.any, *r.ld(0x0f, 0xb6)) +I32.enc(base.uload8.i32.any, *r.ldDisp8(0x0f, 0xb6)) +I32.enc(base.uload8.i32.any, *r.ldDisp32(0x0f, 0xb6)) -I32.enc(base.sload8.i32.i32, *r.ld(0x0f, 0xbe)) -I32.enc(base.sload8.i32.i32, *r.ldDisp8(0x0f, 0xbe)) -I32.enc(base.sload8.i32.i32, *r.ldDisp32(0x0f, 0xbe)) +I32.enc(base.sload8.i32.any, *r.ld(0x0f, 0xbe)) +I32.enc(base.sload8.i32.any, *r.ldDisp8(0x0f, 0xbe)) +I32.enc(base.sload8.i32.any, *r.ldDisp32(0x0f, 0xbe)) # # Call/return From ebf5c8095944bee2fe432e1a1d286c78e8c744ea Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Jul 2017 10:58:00 -0700 Subject: [PATCH 917/968] Add Intel encodings for more conversion instructions. The following instructions have simple encodings: - bitcast.f32.i32 - bitcast.i32.f32 - bitcast.f64.i64 - bitcast.i64.f64 - fpromote.f64.f32 - fdemote.f32.f64 Also add helper functions enc_flt() and enc_i32_i64 to intel.encodings.py for generating the common set of encodings for an instruction: I32, I64 w/REX, I64 w/o REX. --- filetests/isa/intel/binary32-float.cton | 26 +++- filetests/isa/intel/binary64-float.cton | 40 ++++++- filetests/wasm/conversions.cton | 45 +++++++ lib/cretonne/meta/isa/intel/encodings.py | 145 ++++++++++++----------- lib/cretonne/meta/isa/intel/recipes.py | 20 +++- 5 files changed, 195 insertions(+), 81 deletions(-) diff --git a/filetests/isa/intel/binary32-float.cton b/filetests/isa/intel/binary32-float.cton index 4a4361707c..5c0dc43b18 100644 --- a/filetests/isa/intel/binary32-float.cton +++ b/filetests/isa/intel/binary32-float.cton @@ -17,6 +17,21 @@ ebb0: ; asm: cvtsi2ss %esi, %xmm2 [-,%xmm2] v11 = fcvt_from_sint.f32 v1 ; bin: f3 0f 2a d6 + ; asm: cvtss2sd %xmm2, %xmm5 + [-,%xmm5] v12 = fpromote.f64 v11 ; bin: f3 0f 5a ea + ; asm: cvtss2sd %xmm5, %xmm2 + [-,%xmm2] v13 = fpromote.f64 v10 ; bin: f3 0f 5a d5 + + ; asm: movd %ecx, %xmm5 + [-,%xmm5] v14 = bitcast.f32 v0 ; bin: 66 0f 6e e9 + ; asm: movd %esi, %xmm2 + [-,%xmm2] v15 = bitcast.f32 v1 ; bin: 66 0f 6e d6 + + ; asm: movd %xmm5, %ecx + [-,%rcx] v16 = bitcast.i32 v10 ; bin: 66 0f 7e e9 + ; asm: movd %xmm2, %esi + [-,%rsi] v17 = bitcast.i32 v11 ; bin: 66 0f 7e d6 + ; Binary arithmetic. ; asm: addss %xmm2, %xmm5 @@ -70,13 +85,20 @@ ebb0: [-,%rcx] v0 = iconst.i32 1 [-,%rsi] v1 = iconst.i32 2 - ; Binary arithmetic. - ; asm: cvtsi2sd %ecx, %xmm5 [-,%xmm5] v10 = fcvt_from_sint.f64 v0 ; bin: f2 0f 2a e9 ; asm: cvtsi2sd %esi, %xmm2 [-,%xmm2] v11 = fcvt_from_sint.f64 v1 ; bin: f2 0f 2a d6 + ; asm: cvtsd2ss %xmm2, %xmm5 + [-,%xmm5] v12 = fdemote.f32 v11 ; bin: f2 0f 5a ea + ; asm: cvtsd2ss %xmm5, %xmm2 + [-,%xmm2] v13 = fdemote.f32 v10 ; bin: f2 0f 5a d5 + + ; No i64 <-> f64 bitcasts in 32-bit mode. + + ; Binary arithmetic. + ; asm: addsd %xmm2, %xmm5 [-,%xmm5] v20 = fadd v10, v11 ; bin: f2 0f 58 ea ; asm: addsd %xmm5, %xmm2 diff --git a/filetests/isa/intel/binary64-float.cton b/filetests/isa/intel/binary64-float.cton index e8a7c574c1..64dd1ebd05 100644 --- a/filetests/isa/intel/binary64-float.cton +++ b/filetests/isa/intel/binary64-float.cton @@ -25,27 +25,42 @@ ebb0: ; asm: cvtsi2ssq %r14, %xmm10 [-,%xmm10] v13 = fcvt_from_sint.f32 v3 ; bin: f3 4d 0f 2a d6 + ; asm: cvtss2sd %xmm10, %xmm5 + [-,%xmm5] v14 = fpromote.f64 v11 ; bin: f3 41 0f 5a ea + ; asm: cvtss2sd %xmm5, %xmm10 + [-,%xmm10] v15 = fpromote.f64 v10 ; bin: f3 44 0f 5a d5 + + ; asm: movd %r11d, %xmm5 + [-,%xmm5] v16 = bitcast.f32 v0 ; bin: 66 41 0f 6e eb + ; asm: movd %esi, %xmm10 + [-,%xmm10] v17 = bitcast.f32 v1 ; bin: 66 44 0f 6e d6 + + ; asm: movd %xmm5, %ecx + [-,%rcx] v18 = bitcast.i32 v10 ; bin: 66 40 0f 7e e9 + ; asm: movd %xmm10, %esi + [-,%rsi] v19 = bitcast.i32 v11 ; bin: 66 44 0f 7e d6 + ; Binary arithmetic. ; asm: addss %xmm10, %xmm5 [-,%xmm5] v20 = fadd v10, v11 ; bin: f3 41 0f 58 ea ; asm: addss %xmm5, %xmm10 - [-,%xmm10] v21 = fadd v11, v10 ; bin: f3 44 0f 58 d5 + [-,%xmm10] v21 = fadd v11, v10 ; bin: f3 44 0f 58 d5 ; asm: subss %xmm10, %xmm5 [-,%xmm5] v22 = fsub v10, v11 ; bin: f3 41 0f 5c ea ; asm: subss %xmm5, %xmm10 - [-,%xmm10] v23 = fsub v11, v10 ; bin: f3 44 0f 5c d5 + [-,%xmm10] v23 = fsub v11, v10 ; bin: f3 44 0f 5c d5 ; asm: mulss %xmm10, %xmm5 [-,%xmm5] v24 = fmul v10, v11 ; bin: f3 41 0f 59 ea ; asm: mulss %xmm5, %xmm10 - [-,%xmm10] v25 = fmul v11, v10 ; bin: f3 44 0f 59 d5 + [-,%xmm10] v25 = fmul v11, v10 ; bin: f3 44 0f 59 d5 ; asm: divss %xmm10, %xmm5 [-,%xmm5] v26 = fdiv v10, v11 ; bin: f3 41 0f 5e ea ; asm: divss %xmm5, %xmm10 - [-,%xmm10] v27 = fdiv v11, v10 ; bin: f3 44 0f 5e d5 + [-,%xmm10] v27 = fdiv v11, v10 ; bin: f3 44 0f 5e d5 ; Bitwise ops. ; We use the *ps SSE instructions for everything because they are smaller. @@ -90,12 +105,27 @@ ebb0: ; asm: cvtsi2sdq %r14, %xmm10 [-,%xmm10] v13 = fcvt_from_sint.f64 v3 ; bin: f2 4d 0f 2a d6 + ; asm: cvtsd2ss %xmm10, %xmm5 + [-,%xmm5] v14 = fdemote.f32 v11 ; bin: f2 41 0f 5a ea + ; asm: cvtsd2ss %xmm5, %xmm10 + [-,%xmm10] v15 = fdemote.f32 v10 ; bin: f2 44 0f 5a d5 + + ; asm: movq %rax, %xmm5 + [-,%xmm5] v16 = bitcast.f64 v2 ; bin: 66 48 0f 6e e8 + ; asm: movq %r14, %xmm10 + [-,%xmm10] v17 = bitcast.f64 v3 ; bin: 66 4d 0f 6e d6 + + ; asm: movq %xmm5, %rcx + [-,%rcx] v18 = bitcast.i64 v10 ; bin: 66 48 0f 7e e9 + ; asm: movq %xmm10, %rsi + [-,%rsi] v19 = bitcast.i64 v11 ; bin: 66 4c 0f 7e d6 + ; Binary arithmetic. ; asm: addsd %xmm10, %xmm5 [-,%xmm5] v20 = fadd v10, v11 ; bin: f2 41 0f 58 ea ; asm: addsd %xmm5, %xmm10 - [-,%xmm10] v21 = fadd v11, v10 ; bin: f2 44 0f 58 d5 + [-,%xmm10] v21 = fadd v11, v10 ; bin: f2 44 0f 58 d5 ; asm: subsd %xmm10, %xmm5 [-,%xmm5] v22 = fsub v10, v11 ; bin: f2 41 0f 5c ea diff --git a/filetests/wasm/conversions.cton b/filetests/wasm/conversions.cton index 6f2354d624..e742ebba27 100644 --- a/filetests/wasm/conversions.cton +++ b/filetests/wasm/conversions.cton @@ -22,6 +22,27 @@ ebb0(v0: i32): return v1 } +; function %i32_trunc_s_f32(f32) -> i32 +; function %i32_trunc_u_f32(f32) -> i32 +; function %i32_trunc_s_f64(f64) -> i32 +; function %i32_trunc_u_f64(f64) -> i32 +; function %i64_trunc_s_f32(f32) -> i64 +; function %i64_trunc_u_f32(f32) -> i64 +; function %i64_trunc_s_f64(f64) -> i64 +; function %i64_trunc_u_f64(f64) -> i64 + +function %f32_trunc_f64(f64) -> f32 { +ebb0(v0: f64): + v1 = fdemote.f32 v0 + return v1 +} + +function %f64_promote_f32(f32) -> f64 { +ebb0(v0: f32): + v1 = fpromote.f64 v0 + return v1 +} + function %f32_convert_s_i32(i32) -> f32 { ebb0(v0: i32): v1 = fcvt_from_sint.f32 v0 @@ -47,3 +68,27 @@ ebb0(v0: i64): } ; TODO: f*_convert_u_i* (Don't exist on Intel). + +function %i32_reinterpret_f32(f32) -> i32 { +ebb0(v0: f32): + v1 = bitcast.i32 v0 + return v1 +} + +function %f32_reinterpret_i32(i32) -> f32 { +ebb0(v0: i32): + v1 = bitcast.f32 v0 + return v1 +} + +function %i64_reinterpret_f64(f64) -> i64 { +ebb0(v0: f64): + v1 = bitcast.i64 v0 + return v1 +} + +function %f64_reinterpret_i64(i64) -> f64 { +ebb0(v0: i64): + v1 = bitcast.f64 v0 + return v1 +} diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index df95cf7f48..44f8c16677 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -11,6 +11,14 @@ from . import settings as cfg from . import instructions as x86 from base.legalize import narrow, expand +try: + from typing import TYPE_CHECKING + if TYPE_CHECKING: + from cdsl.instructions import MaybeBoundInst # noqa +except ImportError: + pass + + I32.legalize_type( default=narrow, i32=expand, @@ -24,42 +32,52 @@ I64.legalize_type( f32=expand, f64=expand) + +# +# Helper functions for generating encodings. +# + +def enc_i32_i64(inst, recipe, *args, **kwargs): + # type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None + """ + Add encodings for `inst.i32` to I32. + Add encodings for `inst.i32` to I64 with and without REX. + Add encodings for `inst.i64` to I64 with a REX.W prefix. + """ + I32.enc(inst.i32, *recipe(*args, **kwargs)) + + # REX-less encoding must come after REX encoding so we don't use it by + # default. Otherwise reg-alloc would never use r8 and up. + I64.enc(inst.i32, *recipe.rex(*args, **kwargs)) + I64.enc(inst.i32, *recipe(*args, **kwargs)) + + I64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs)) + + +def enc_flt(inst, recipe, *args, **kwargs): + # type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None + """ + Add encodings for floating point instruction `inst` to both I32 and I64. + """ + I32.enc(inst, *recipe(*args, **kwargs)) + I64.enc(inst, *recipe.rex(*args, **kwargs)) + I64.enc(inst, *recipe(*args, **kwargs)) + + for inst, opc in [ (base.iadd, 0x01), (base.isub, 0x29), (base.band, 0x21), (base.bor, 0x09), (base.bxor, 0x31)]: - I32.enc(inst.i32, *r.rr(opc)) + enc_i32_i64(inst, r.rr, opc) - I64.enc(inst.i64, *r.rr.rex(opc, w=1)) - I64.enc(inst.i32, *r.rr.rex(opc)) - # REX-less encoding must come after REX encoding so we don't use it by - # default. Otherwise reg-alloc would never use r8 and up. - I64.enc(inst.i32, *r.rr(opc)) +enc_i32_i64(base.imul, r.rrx, 0x0f, 0xaf) +enc_i32_i64(x86.sdivmodx, r.div, 0xf7, rrr=7) +enc_i32_i64(x86.udivmodx, r.div, 0xf7, rrr=6) -I32.enc(base.imul.i32, *r.rrx(0x0f, 0xaf)) -I64.enc(base.imul.i64, *r.rrx.rex(0x0f, 0xaf, w=1)) -I64.enc(base.imul.i32, *r.rrx.rex(0x0f, 0xaf)) -I64.enc(base.imul.i32, *r.rrx(0x0f, 0xaf)) - -for inst, rrr in [ - (x86.sdivmodx, 7), - (x86.udivmodx, 6)]: - I32.enc(inst.i32, *r.div(0xf7, rrr=rrr)) - I64.enc(inst.i64, *r.div.rex(0xf7, rrr=rrr, w=1)) - I64.enc(inst.i32, *r.div.rex(0xf7, rrr=rrr)) - I64.enc(inst.i32, *r.div(0xf7, rrr=rrr)) - -I32.enc(base.copy.i32, *r.umr(0x89)) -I64.enc(base.copy.i64, *r.umr.rex(0x89, w=1)) -I64.enc(base.copy.i32, *r.umr.rex(0x89)) -I64.enc(base.copy.i32, *r.umr(0x89)) - -I32.enc(base.regmove.i32, *r.rmov(0x89)) -I64.enc(base.regmove.i64, *r.rmov.rex(0x89, w=1)) -I64.enc(base.regmove.i32, *r.rmov.rex(0x89)) -I64.enc(base.regmove.i32, *r.rmov(0x89)) +enc_i32_i64(base.copy, r.umr, 0x89) +enc_i32_i64(base.regmove, r.rmov, 0x89) # Immediate instructions with sign-extended 8-bit and 32-bit immediate. for inst, rrr in [ @@ -67,15 +85,8 @@ for inst, rrr in [ (base.band_imm, 4), (base.bor_imm, 1), (base.bxor_imm, 6)]: - I32.enc(inst.i32, *r.rib(0x83, rrr=rrr)) - I32.enc(inst.i32, *r.rid(0x81, rrr=rrr)) - - I64.enc(inst.i64, *r.rib.rex(0x83, rrr=rrr, w=1)) - I64.enc(inst.i64, *r.rid.rex(0x81, rrr=rrr, w=1)) - I64.enc(inst.i32, *r.rib.rex(0x83, rrr=rrr)) - I64.enc(inst.i32, *r.rid.rex(0x81, rrr=rrr)) - I64.enc(inst.i32, *r.rib(0x83, rrr=rrr)) - I64.enc(inst.i32, *r.rid(0x81, rrr=rrr)) + enc_i32_i64(inst, r.rib, 0x83, rrr=rrr) + enc_i32_i64(inst, r.rid, 0x81, rrr=rrr) # TODO: band_imm.i64 with an unsigned 32-bit immediate can be encoded as # band_imm.i32. Can even use the single-byte immediate for 0xffff_ffXX masks. @@ -179,15 +190,8 @@ I32.enc(base.jump, *r.jmpd(0xe9)) I64.enc(base.jump, *r.jmpb(0xeb)) I64.enc(base.jump, *r.jmpd(0xe9)) -I32.enc(base.brz.i32, *r.tjccb(0x74)) -I64.enc(base.brz.i64, *r.tjccb.rex(0x74, w=1)) -I64.enc(base.brz.i32, *r.tjccb.rex(0x74)) -I64.enc(base.brz.i32, *r.tjccb(0x74)) - -I32.enc(base.brnz.i32, *r.tjccb(0x75)) -I64.enc(base.brnz.i64, *r.tjccb.rex(0x75, w=1)) -I64.enc(base.brnz.i32, *r.tjccb.rex(0x75)) -I64.enc(base.brnz.i32, *r.tjccb(0x75)) +enc_i32_i64(base.brz, r.tjccb, 0x74) +enc_i32_i64(base.brnz, r.tjccb, 0x75) # # Trap as ud2 @@ -198,10 +202,7 @@ I64.enc(base.trap, *r.noop(0x0f, 0x0b)) # # Comparisons # -I32.enc(base.icmp.i32, *r.icscc(0x39)) -I64.enc(base.icmp.i64, *r.icscc.rex(0x39, w=1)) -I64.enc(base.icmp.i32, *r.icscc.rex(0x39)) -I64.enc(base.icmp.i32, *r.icscc(0x39)) +enc_i32_i64(base.icmp, r.icscc, 0x39) # # Convert bool to int. @@ -223,21 +224,31 @@ I64.enc(base.sextend.i64.i32, *r.urm.rex(0x63, w=1)) I64.enc(base.uextend.i64.i32, *r.umr.rex(0x89)) I64.enc(base.uextend.i64.i32, *r.umr(0x89)) + # # Floating point # +# movd +enc_flt(base.bitcast.f32.i32, r.frurm, 0x66, 0x0f, 0x6e) +enc_flt(base.bitcast.i32.f32, r.rfumr, 0x66, 0x0f, 0x7e) + +# movq +I64.enc(base.bitcast.f64.i64, *r.frurm.rex(0x66, 0x0f, 0x6e, w=1)) +I64.enc(base.bitcast.i64.f64, *r.rfumr.rex(0x66, 0x0f, 0x7e, w=1)) + # cvtsi2ss -I32.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) -I64.enc(base.fcvt_from_sint.f32.i64, *r.furm.rex(0xf3, 0x0f, 0x2A, w=1)) -I64.enc(base.fcvt_from_sint.f32.i32, *r.furm.rex(0xf3, 0x0f, 0x2A)) -I64.enc(base.fcvt_from_sint.f32.i32, *r.furm(0xf3, 0x0f, 0x2A)) +enc_i32_i64(base.fcvt_from_sint.f32, r.frurm, 0xf3, 0x0f, 0x2a) # cvtsi2sd -I32.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) -I64.enc(base.fcvt_from_sint.f64.i64, *r.furm.rex(0xf2, 0x0f, 0x2A, w=1)) -I64.enc(base.fcvt_from_sint.f64.i32, *r.furm.rex(0xf2, 0x0f, 0x2A)) -I64.enc(base.fcvt_from_sint.f64.i32, *r.furm(0xf2, 0x0f, 0x2A)) +enc_i32_i64(base.fcvt_from_sint.f64, r.frurm, 0xf2, 0x0f, 0x2a) + +# cvtss2sd +enc_flt(base.fpromote.f64.f32, r.furm, 0xf3, 0x0f, 0x5a) + +# cvtsd2ss +enc_flt(base.fdemote.f32.f64, r.furm, 0xf2, 0x0f, 0x5a) + # Binary arithmetic ops. for inst, opc in [ @@ -245,13 +256,8 @@ for inst, opc in [ (base.fsub, 0x5c), (base.fmul, 0x59), (base.fdiv, 0x5e)]: - I32.enc(inst.f32, *r.frm(0xf3, 0x0f, opc)) - I64.enc(inst.f32, *r.frm.rex(0xf3, 0x0f, opc)) - I64.enc(inst.f32, *r.frm(0xf3, 0x0f, opc)) - - I32.enc(inst.f64, *r.frm(0xf2, 0x0f, opc)) - I64.enc(inst.f64, *r.frm.rex(0xf2, 0x0f, opc)) - I64.enc(inst.f64, *r.frm(0xf2, 0x0f, opc)) + enc_flt(inst.f32, r.frm, 0xf3, 0x0f, opc) + enc_flt(inst.f64, r.frm, 0xf2, 0x0f, opc) # Binary bitwise ops. for inst, opc in [ @@ -259,10 +265,5 @@ for inst, opc in [ (base.band_not, 0x55), (base.bor, 0x56), (base.bxor, 0x57)]: - I32.enc(inst.f32, *r.frm(0x0f, opc)) - I64.enc(inst.f32, *r.frm.rex(0x0f, opc)) - I64.enc(inst.f32, *r.frm(0x0f, opc)) - - I32.enc(inst.f64, *r.frm(0x0f, opc)) - I64.enc(inst.f64, *r.frm.rex(0x0f, opc)) - I64.enc(inst.f64, *r.frm(0x0f, opc)) + enc_flt(inst.f32, r.frm, 0x0f, opc) + enc_flt(inst.f64, r.frm, 0x0f, opc) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 3eb3d51671..3e038e1208 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -232,6 +232,14 @@ umr = TailRecipe( modrm_rr(out_reg0, in_reg0, sink); ''') +# Same as umr, but with FPR -> GPR registers. +rfumr = TailRecipe( + 'rfumr', Unary, size=1, ins=FPR, outs=GPR, + emit=''' + PUT_OP(bits, rex2(out_reg0, in_reg0), sink); + modrm_rr(out_reg0, in_reg0, sink); + ''') + # XX /r, but for a unary operator with separate input/output register. # RM form. urm = TailRecipe( @@ -249,9 +257,17 @@ urm_abcd = TailRecipe( modrm_rr(in_reg0, out_reg0, sink); ''') -# XX /r, RM form, GPR -> FPR. +# XX /r, RM form, FPR -> FPR. furm = TailRecipe( - 'furm', Unary, size=1, ins=GPR, outs=FPR, + 'furm', Unary, size=1, ins=FPR, outs=FPR, + emit=''' + PUT_OP(bits, rex2(in_reg0, out_reg0), sink); + modrm_rr(in_reg0, out_reg0, sink); + ''') + +# XX /r, RM form, GPR -> FPR. +frurm = TailRecipe( + 'frurm', Unary, size=1, ins=GPR, outs=FPR, emit=''' PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_rr(in_reg0, out_reg0, sink); From f6af7be205161de83a537e1ec25a857215240acd Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Thu, 27 Jul 2017 16:30:48 -0700 Subject: [PATCH 918/968] Bugfix: encode function wasn't calling legalize function properly --- lib/cretonne/src/isa/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 6c43040825..f85c98df53 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -170,7 +170,7 @@ pub trait TargetIsa { ctrl_typevar: ir::Type) -> Result { let mut iter = self.legal_encodings(dfg, inst, ctrl_typevar); - iter.next().ok_or(iter.legalize().into()) + iter.next().ok_or_else(|| iter.legalize().into()) } /// Get a data structure describing the instruction encodings in this ISA. From 1bbc06e2d6791c4f2ab80555d14bf77151eba321 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Jul 2017 13:33:45 -0700 Subject: [PATCH 919/968] Assign legalization codes early. Make sure legalization codes are assigned by TargetIsa::finish() such that they can be accessed by multiple gen_* drivers. --- lib/cretonne/meta/cdsl/isa.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 487001d9e1..3e903408ef 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -75,6 +75,7 @@ class TargetISA(object): self._collect_encoding_recipes() self._collect_predicates() self._collect_regclasses() + self._collect_legalize_codes() return self def _collect_encoding_recipes(self): @@ -156,6 +157,17 @@ class TargetISA(object): # `isa/registers.rs`. assert len(self.regclasses) <= 32, "Too many register classes" + def _collect_legalize_codes(self): + # type: () -> None + """ + Make sure all legalization transforms have been assigned a code. + """ + for cpumode in self.cpumodes: + self.legalize_code(cpumode.default_legalize) + for x in sorted(cpumode.type_legalize.values(), + key=lambda x: x.name): + self.legalize_code(x) + def legalize_code(self, xgrp): # type: (XFormGroup) -> int """ From b04a2c30d22839da9170e12a94f6619e50c6c36c Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 27 Jul 2017 14:46:56 -0700 Subject: [PATCH 920/968] Return a function pointer from TargetIsa::encode(). Replace the isa::Legalize enumeration with a function pointer. This allows an ISA to define its own specific legalization actions instead of relying on the default two. Generate a LEGALIZE_ACTIONS table for each ISA which contains legalization function pointers indexed by the legalization codes that are already in the encoding tables. Include this table in isa/*/enc_tables.rs. Give the `Encodings` iterator a reference to the action table and change its `legalize()` method to return a function pointer instead of an ISA-specific code. The Result<> returned from TargetIsa::encode() no longer implements Debug, so eliminate uses of unwrap and expect on that type. --- lib/cretonne/meta/cdsl/xform.py | 27 +++++++++-- lib/cretonne/meta/gen_encoding.py | 2 +- lib/cretonne/meta/gen_instr.py | 2 + lib/cretonne/meta/gen_legalizer.py | 60 +++++++++++++++++++----- lib/cretonne/src/isa/arm32/enc_tables.rs | 3 +- lib/cretonne/src/isa/arm32/mod.rs | 1 + lib/cretonne/src/isa/arm64/enc_tables.rs | 3 +- lib/cretonne/src/isa/arm64/mod.rs | 1 + lib/cretonne/src/isa/enc_tables.rs | 11 +++-- lib/cretonne/src/isa/intel/enc_tables.rs | 3 +- lib/cretonne/src/isa/intel/mod.rs | 1 + lib/cretonne/src/isa/mod.rs | 29 +++--------- lib/cretonne/src/isa/riscv/enc_tables.rs | 3 +- lib/cretonne/src/isa/riscv/mod.rs | 32 ++++++------- lib/cretonne/src/legalizer/mod.rs | 21 ++------- lib/cretonne/src/regalloc/coalescing.rs | 14 +++--- lib/cretonne/src/regalloc/reload.rs | 14 +++--- lib/cretonne/src/regalloc/spilling.rs | 8 ++-- lib/cretonne/src/verifier/mod.rs | 7 ++- 19 files changed, 140 insertions(+), 102 deletions(-) diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 3d3f25a3c7..2cae1c1236 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -10,6 +10,7 @@ try: from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa from typing import Optional, Set # noqa from .ast import Expr, VarMap # noqa + from .isa import TargetISA # noqa from .ti import TypeConstraint # noqa from .typevar import TypeVar # noqa DefApply = Union[Def, Apply] @@ -282,17 +283,37 @@ class XForm(object): class XFormGroup(object): """ A group of related transformations. + + :param isa: A target ISA whose instructions are allowed. + :param chain: A next level group to try if this one doesn't match. """ - def __init__(self, name, doc): - # type: (str, str) -> None + def __init__(self, name, doc, isa=None, chain=None): + # type: (str, str, TargetISA, XFormGroup) -> None self.xforms = list() # type: List[XForm] self.name = name self.__doc__ = doc + self.isa = isa + self.chain = chain def __str__(self): # type: () -> str - return self.name + if self.isa: + return '{}.{}'.format(self.isa.name, self.name) + else: + return self.name + + def rust_name(self): + # type: () -> str + """ + Get the Rust name of this function implementing this transform. + """ + if self.isa: + # This is a function in the same module as the LEGALIZE_ACTION + # table referring to it. + return self.name + else: + return '::legalizer::{}'.format(self.name) def legalize(self, src, dst): # type: (Union[Def, Apply], Rtl) -> None diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index a7f60f488f..c3b3ddb87c 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -875,7 +875,7 @@ def gen_isa(isa, fmt): emit_recipe_sizing(isa, fmt) # Finally, tie it all together in an `EncInfo`. - with fmt.indented('pub static INFO: EncInfo = EncInfo {', '};'): + with fmt.indented('pub static INFO: isa::EncInfo = isa::EncInfo {', '};'): fmt.line('constraints: &RECIPE_CONSTRAINTS,') fmt.line('sizing: &RECIPE_SIZING,') fmt.line('names: &RECIPE_NAMES,') diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 242e5152d3..b0c9943806 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -345,6 +345,8 @@ def gen_typesets_table(fmt, type_sets): """ Generate the table of ValueTypeSets described by type_sets. """ + if len(type_sets.table) == 0: + return fmt.comment('Table of value type sets.') assert len(type_sets.table) <= typeset_limit, "Too many type sets" with fmt.indented( diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index eeb567722e..10c5bcf3ac 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -9,7 +9,7 @@ the input instruction. """ from __future__ import absolute_import from srcgen import Formatter -from base import legalize, instructions +from base import instructions from cdsl.ast import Var from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\ InTypeset, WiderOrEq @@ -18,7 +18,7 @@ from gen_instr import gen_typesets_table from cdsl.typevar import TypeVar try: - from typing import Sequence, List, Dict # noqa + from typing import Sequence, List, Dict, Set # noqa from cdsl.isa import TargetISA # noqa from cdsl.ast import Def # noqa from cdsl.xform import XForm, XFormGroup # noqa @@ -167,7 +167,7 @@ def unwrap_inst(iref, node, fmt): # The tuple of locals we're extracting is `expr.args`. with fmt.indented( - 'let ({}) = if let InstructionData::{} {{' + 'let ({}) = if let ir::InstructionData::{} {{' .format(', '.join(map(str, expr.args)), iform.name), '};'): # Fields are encoded directly. for f in iform.imm_fields: @@ -348,9 +348,11 @@ def gen_xform_group(xgrp, fmt, type_sets): fmt.doc_comment("Legalize the instruction pointed to by `pos`.") fmt.line('#[allow(unused_variables,unused_assignments)]') with fmt.indented( - 'fn {}(dfg: &mut DataFlowGraph, ' - 'cfg: &mut ControlFlowGraph, pos: &mut Cursor) -> ' + 'pub fn {}(dfg: &mut ir::DataFlowGraph, ' + 'cfg: &mut ::flowgraph::ControlFlowGraph, ' + 'pos: &mut ir::Cursor) -> ' 'bool {{'.format(xgrp.name), '}'): + fmt.line('use ir::InstBuilder;') # Gen the instruction to be legalized. The cursor we're passed must be # pointing at an instruction. @@ -360,21 +362,55 @@ def gen_xform_group(xgrp, fmt, type_sets): for xform in xgrp.xforms: inst = xform.src.rtl[0].expr.inst with fmt.indented( - 'Opcode::{} => {{'.format(inst.camel_name), '}'): + 'ir::Opcode::{} => {{'.format(inst.camel_name), '}'): gen_xform(xform, fmt, type_sets) # We'll assume there are uncovered opcodes. - fmt.line('_ => return false,') + if xgrp.chain: + fmt.format('_ => return {}(dfg, cfg, pos),', + xgrp.chain.rust_name()) + else: + fmt.line('_ => return false,') fmt.line('true') +def gen_isa(isa, fmt, shared_groups): + # type: (TargetISA, Formatter, Set[XFormGroup]) -> None + """ + Generate legalization functions for `isa` and add any shared `XFormGroup`s + encountered to `shared_groups`. + + Generate `TYPE_SETS` and `LEGALIZE_ACTION` tables. + """ + type_sets = UniqueTable() + for xgrp in isa.legalize_codes.keys(): + if xgrp.isa is None: + shared_groups.add(xgrp) + else: + assert xgrp.isa == isa + gen_xform_group(xgrp, fmt, type_sets) + + gen_typesets_table(fmt, type_sets) + + with fmt.indented( + 'pub static LEGALIZE_ACTIONS: [isa::Legalize; {}] = [' + .format(len(isa.legalize_codes)), '];'): + for xgrp in isa.legalize_codes.keys(): + fmt.format('{},', xgrp.rust_name()) + + def generate(isas, out_dir): # type: (Sequence[TargetISA], str) -> None + shared_groups = set() # type: Set[XFormGroup] + + for isa in isas: + fmt = Formatter() + gen_isa(isa, fmt, shared_groups) + fmt.update_file('legalize-{}.rs'.format(isa.name), out_dir) + + # Shared xform groups. fmt = Formatter() - # Table of TypeSet instances type_sets = UniqueTable() - - gen_xform_group(legalize.narrow, fmt, type_sets) - gen_xform_group(legalize.expand, fmt, type_sets) - + for xgrp in sorted(shared_groups, key=lambda g: g.name): + gen_xform_group(xgrp, fmt, type_sets) gen_typesets_table(fmt, type_sets) fmt.update_file('legalizer.rs', out_dir) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index adcc2fd915..eeb8466669 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -1,9 +1,10 @@ //! Encoding tables for ARM32 ISA. use ir::types; -use isa::EncInfo; +use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); +include!(concat!(env!("OUT_DIR"), "/legalize-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index f9a44db258..fafd5d9871 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -72,6 +72,7 @@ impl TargetIsa for Isa { self.cpumode, &enc_tables::LEVEL2[..], &enc_tables::ENCLISTS[..], + &enc_tables::LEGALIZE_ACTIONS[..], &enc_tables::RECIPE_PREDICATES[..], &enc_tables::INST_PREDICATES[..], self.isa_flags.predicate_view()) diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index e57f3cc98c..c2c087e039 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -1,9 +1,10 @@ //! Encoding tables for ARM64 ISA. use ir::types; -use isa::EncInfo; +use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); +include!(concat!(env!("OUT_DIR"), "/legalize-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index f530d4b0b2..f4b21bf42d 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -65,6 +65,7 @@ impl TargetIsa for Isa { &enc_tables::LEVEL1_A64[..], &enc_tables::LEVEL2[..], &enc_tables::ENCLISTS[..], + &enc_tables::LEGALIZE_ACTIONS[..], &enc_tables::RECIPE_PREDICATES[..], &enc_tables::INST_PREDICATES[..], self.isa_flags.predicate_view()) diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index d8bd9756d6..b9fa09dd65 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -5,7 +5,7 @@ use constant_hash::{Table, probe}; use ir::{Type, Opcode, DataFlowGraph, InstructionData}; -use isa::Encoding; +use isa::{Encoding, Legalize}; use settings::PredicateView; use std::ops::Range; @@ -109,6 +109,7 @@ pub fn lookup_enclist<'a, OffT1, OffT2>(ctrl_typevar: Type, level1_table: &'static [Level1Entry], level2_table: &'static [Level2Entry], enclist: &'static [EncListEntry], + legalize_actions: &'static [Legalize], recipe_preds: &'static [RecipePredicate], inst_preds: &'static [InstPredicate], isa_preds: PredicateView<'a>) @@ -148,6 +149,7 @@ pub fn lookup_enclist<'a, OffT1, OffT2>(ctrl_typevar: Type, inst, dfg, enclist, + legalize_actions, recipe_preds, inst_preds, isa_preds) @@ -173,6 +175,7 @@ pub struct Encodings<'a> { inst: &'a InstructionData, dfg: &'a DataFlowGraph, enclist: &'static [EncListEntry], + legalize_actions: &'static [Legalize], recipe_preds: &'static [RecipePredicate], inst_preds: &'static [InstPredicate], isa_preds: PredicateView<'a>, @@ -189,6 +192,7 @@ impl<'a> Encodings<'a> { inst: &'a InstructionData, dfg: &'a DataFlowGraph, enclist: &'static [EncListEntry], + legalize_actions: &'static [Legalize], recipe_preds: &'static [RecipePredicate], inst_preds: &'static [InstPredicate], isa_preds: PredicateView<'a>) @@ -202,6 +206,7 @@ impl<'a> Encodings<'a> { recipe_preds, inst_preds, enclist, + legalize_actions, } } @@ -210,9 +215,9 @@ impl<'a> Encodings<'a> { /// instruction. /// /// This method must only be called after the iterator returns `None`. - pub fn legalize(&self) -> LegalizeCode { + pub fn legalize(&self) -> Legalize { debug_assert_eq!(self.offset, !0, "Premature Encodings::legalize()"); - self.legalize + self.legalize_actions[self.legalize as usize] } /// Check if the `rpred` recipe predicate s satisfied. diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index b3144f11e3..62ef7b3cd6 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,7 +1,7 @@ //! Encoding tables for Intel ISAs. use ir::{self, types, Opcode}; -use isa::EncInfo; +use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; @@ -9,3 +9,4 @@ use predicates; use super::registers::*; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); +include!(concat!(env!("OUT_DIR"), "/legalize-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 9d31727b14..1cce45b12e 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -72,6 +72,7 @@ impl TargetIsa for Isa { self.cpumode, &enc_tables::LEVEL2[..], &enc_tables::ENCLISTS[..], + &enc_tables::LEGALIZE_ACTIONS[..], &enc_tables::RECIPE_PREDICATES[..], &enc_tables::INST_PREDICATES[..], self.isa_flags.predicate_view()) diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index f85c98df53..b808b88746 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -45,6 +45,7 @@ pub use isa::encoding::{Encoding, EncInfo}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; use binemit; +use flowgraph; use settings; use ir; use regalloc; @@ -116,29 +117,11 @@ impl settings::Configurable for Builder { /// After determining that an instruction doesn't have an encoding, how should we proceed to /// legalize it? /// -/// These actions correspond to the transformation groups defined in `meta/cretonne/legalize.py`. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Legalize { - /// Legalize in terms of narrower types. - Narrow, - - /// Expanding in terms of other instructions using the same types. - Expand, -} - -/// Translate a legalization code into a `Legalize` enum. -/// -/// This mapping is going away soon. It depends on matching the `TargetISA.legalize_code()` -/// mapping. -impl From for Legalize { - fn from(x: u8) -> Legalize { - match x { - 0 => Legalize::Narrow, - 1 => Legalize::Expand, - _ => panic!("Unknown legalization code {}"), - } - } -} +/// The `Encodings` iterator returns a legalization function to call. +pub type Legalize = fn(&mut ir::DataFlowGraph, + &mut flowgraph::ControlFlowGraph, + &mut ir::Cursor) + -> bool; /// Methods that are specialized to a target ISA. pub trait TargetIsa { diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 7e05879a3b..0d11d006c5 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -2,7 +2,7 @@ use ir::condcodes::IntCC; use ir::{self, types, Opcode}; -use isa::EncInfo; +use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; @@ -16,3 +16,4 @@ use super::registers::*; // - `ENCLIST` // - `INFO` include!(concat!(env!("OUT_DIR"), "/encoding-riscv.rs")); +include!(concat!(env!("OUT_DIR"), "/legalize-riscv.rs")); diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 18f0172f86..03482b920c 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -72,6 +72,7 @@ impl TargetIsa for Isa { self.cpumode, &enc_tables::LEVEL2[..], &enc_tables::ENCLISTS[..], + &enc_tables::LEGALIZE_ACTIONS[..], &enc_tables::RECIPE_PREDICATES[..], &enc_tables::INST_PREDICATES[..], self.isa_flags.predicate_view()) @@ -113,8 +114,11 @@ mod tests { use ir::{DataFlowGraph, InstructionData, Opcode}; use ir::{types, immediates}; - fn encstr(isa: &isa::TargetIsa, enc: isa::Encoding) -> String { - isa.encoding_info().display(enc).to_string() + fn encstr(isa: &isa::TargetIsa, enc: Result) -> String { + match enc { + Ok(e) => isa.encoding_info().display(e).to_string(), + Err(_) => "no encoding".to_string(), + } } #[test] @@ -137,8 +141,7 @@ mod tests { }; // ADDI is I/0b00100 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64, types::I64).unwrap()), - "I#04"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst64, types::I64)), "I#04"); // Try to encode iadd_imm.i64 v1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -148,8 +151,7 @@ mod tests { }; // Immediate is out of range for ADDI. - assert_eq!(isa.encode(&dfg, &inst64_large, types::I64), - Err(isa::Legalize::Expand)); + assert!(isa.encode(&dfg, &inst64_large, types::I64).is_err()); // Create an iadd_imm.i32 which is encodable in RV64. let inst32 = InstructionData::BinaryImm { @@ -159,8 +161,7 @@ mod tests { }; // ADDIW is I/0b00110 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32).unwrap()), - "I#06"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32)), "I#06"); } // Same as above, but for RV32. @@ -184,8 +185,7 @@ mod tests { }; // In 32-bit mode, an i64 bit add should be narrowed. - assert_eq!(isa.encode(&dfg, &inst64, types::I64), - Err(isa::Legalize::Narrow)); + assert!(isa.encode(&dfg, &inst64, types::I64).is_err()); // Try to encode iadd_imm.i64 v1, -10000. let inst64_large = InstructionData::BinaryImm { @@ -195,8 +195,7 @@ mod tests { }; // In 32-bit mode, an i64 bit add should be narrowed. - assert_eq!(isa.encode(&dfg, &inst64_large, types::I64), - Err(isa::Legalize::Narrow)); + assert!(isa.encode(&dfg, &inst64_large, types::I64).is_err()); // Create an iadd_imm.i32 which is encodable in RV32. let inst32 = InstructionData::BinaryImm { @@ -206,8 +205,7 @@ mod tests { }; // ADDI is I/0b00100 - assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32).unwrap()), - "I#04"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32)), "I#04"); // Create an imul.i32 which is encodable in RV32, but only when use_m is true. let mul32 = InstructionData::Binary { @@ -215,8 +213,7 @@ mod tests { args: [arg32, arg32], }; - assert_eq!(isa.encode(&dfg, &mul32, types::I32), - Err(isa::Legalize::Expand)); + assert!(isa.encode(&dfg, &mul32, types::I32).is_err()); } #[test] @@ -241,7 +238,6 @@ mod tests { opcode: Opcode::Imul, args: [arg32, arg32], }; - assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32, types::I32).unwrap()), - "R#10c"); + assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32, types::I32)), "R#10c"); } } diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index f21f7e1d91..c5178a97ee 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -15,9 +15,9 @@ use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; -use ir::{Function, Cursor, DataFlowGraph, InstructionData, Opcode, InstBuilder}; +use ir::{self, Function, Cursor}; use ir::condcodes::IntCC; -use isa::{TargetIsa, Legalize}; +use isa::TargetIsa; use bitset::BitSet; use ir::instructions::ValueTypeSet; @@ -73,22 +73,7 @@ pub fn legalize_function(func: &mut Function, Ok(encoding) => *func.encodings.ensure(inst) = encoding, Err(action) => { // We should transform the instruction into legal equivalents. - // Possible strategies are: - // 1. Legalize::Expand: Expand instruction into sequence of legal instructions. - // Possibly iteratively. () - // 2. Legalize::Narrow: Split the controlling type variable into high and low - // parts. This applies both to SIMD vector types which can be halved and to - // integer types such as `i64` used on a 32-bit ISA. (). - // 3. TODO: Promote the controlling type variable to a larger type. This - // typically means expressing `i8` and `i16` arithmetic in terms if `i32` - // operations on RISC targets. (It may or may not be beneficial to promote - // small vector types versus splitting them.) - // 4. TODO: Convert to library calls. For example, floating point operations on - // an ISA with no IEEE 754 support. - let changed = match action { - Legalize::Expand => expand(&mut func.dfg, cfg, &mut pos), - Legalize::Narrow => narrow(&mut func.dfg, cfg, &mut pos), - }; + let changed = action(&mut func.dfg, cfg, &mut pos); // If the current instruction was replaced, we need to double back and revisit // the expanded sequence. This is both to assign encodings and possible to // expand further. diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 08ecf12589..964644fad3 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -479,9 +479,10 @@ impl<'a> Context<'a> { self.func.dfg.display_inst(pred_inst, self.isa)); // Give it an encoding. - let encoding = self.isa - .encode(&self.func.dfg, &self.func.dfg[inst], ty) - .expect("Can't encode copy"); + let encoding = match self.isa.encode(&self.func.dfg, &self.func.dfg[inst], ty) { + Ok(e) => e, + Err(_) => panic!("Can't encode copy.{}", ty), + }; *self.func.encodings.ensure(inst) = encoding; // Create a live range for the new value. @@ -525,9 +526,10 @@ impl<'a> Context<'a> { ty); // Give it an encoding. - let encoding = self.isa - .encode(&self.func.dfg, &self.func.dfg[inst], ty) - .expect("Can't encode copy"); + let encoding = match self.isa.encode(&self.func.dfg, &self.func.dfg[inst], ty) { + Ok(e) => e, + Err(_) => panic!("Can't encode copy.{}", ty), + }; *self.func.encodings.ensure(inst) = encoding; // Create a live range for the new value. diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 93860071d3..e22978938b 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -220,9 +220,10 @@ impl<'a> Context<'a> { let reg = dfg.ins(pos).fill(cand.value); let fill = dfg.value_def(reg).unwrap_inst(); - *encodings.ensure(fill) = self.isa - .encode(dfg, &dfg[fill], dfg.value_type(reg)) - .expect("Can't encode fill"); + match self.isa.encode(dfg, &dfg[fill], dfg.value_type(reg)) { + Ok(e) => *encodings.ensure(fill) = e, + Err(_) => panic!("Can't encode fill {}", cand.value), + } self.reloads .insert(ReloadedValue { @@ -351,9 +352,10 @@ impl<'a> Context<'a> { .Unary(Opcode::Spill, ty, reg); // Give it an encoding. - *encodings.ensure(inst) = self.isa - .encode(dfg, &dfg[inst], ty) - .expect("Can't encode spill"); + match self.isa.encode(dfg, &dfg[inst], ty) { + Ok(e) => *encodings.ensure(inst) = e, + Err(_) => panic!("Can't encode spill.{}", ty), + } // Update live ranges. self.liveness.move_def_locally(stack, inst); diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 5733f6e80f..49f6d67d09 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -533,10 +533,10 @@ impl<'a> Context<'a> { let ty = dfg.value_type(copy); // Give it an encoding. - let encoding = self.isa - .encode(dfg, &dfg[inst], ty) - .expect("Can't encode copy"); - *self.encodings.ensure(inst) = encoding; + match self.isa.encode(dfg, &dfg[inst], ty) { + Ok(e) => *self.encodings.ensure(inst) = e, + Err(_) => panic!("Can't encode {}", dfg.display_inst(inst, self.isa)), + } // Update live ranges. self.liveness.create_dead(copy, inst, Affinity::Reg(rci)); diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 518c9a703f..a1e04fbb79 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -697,11 +697,10 @@ impl<'a> Verifier<'a> { isa.encoding_info().display(encoding)); } } - Err(e) => { + Err(_) => { return err!(inst, - "Instruction failed to re-encode {}: {:?}", - isa.encoding_info().display(encoding), - e) + "Instruction failed to re-encode {}", + isa.encoding_info().display(encoding)) } } return Ok(()); From 23877458474ac26d62fc9ef8593b8187fb8b27cd Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 14:41:59 -0700 Subject: [PATCH 921/968] bextend/breduce need constraints --- lib/cretonne/meta/base/instructions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 763550a5ee..a26d791719 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -1382,7 +1382,7 @@ breduce = Instruction( The result type must have the same number of vector lanes as the input, and each lane must not have more bits that the input lanes. If the input and output types are the same, this is a no-op. - """, ins=x, outs=a) + """, ins=x, outs=a, constraints=WiderOrEq(Bool, BoolTo)) BoolTo = TypeVar( 'BoolTo', @@ -1399,7 +1399,7 @@ bextend = Instruction( The result type must have the same number of vector lanes as the input, and each lane must not have fewer bits that the input lanes. If the input and output types are the same, this is a no-op. - """, ins=x, outs=a) + """, ins=x, outs=a, constraints=WiderOrEq(BoolTo, Bool)) IntTo = TypeVar( 'IntTo', 'An integer type with the same number of lanes', From 7d1a9c7d81c332145df665d017cfa592d017e17b Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 14:47:57 -0700 Subject: [PATCH 922/968] When doing ti on a polymorphic definition first unify the control variable, then the rest. --- lib/cretonne/meta/cdsl/ti.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index 7a95daf425..028579857d 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -8,12 +8,13 @@ from itertools import product try: from typing import Dict, TYPE_CHECKING, Union, Tuple, Optional, Set # noqa - from typing import Iterable, List, Any # noqa + from typing import Iterable, List, Any, TypeVar as MTypeVar # noqa from typing import cast from .xform import Rtl, XForm # noqa from .ast import Expr # noqa from .typevar import TypeSet # noqa if TYPE_CHECKING: + T = MTypeVar('T') TypeMap = Dict[TypeVar, TypeVar] VarTyping = Dict[Var, TypeVar] except ImportError: @@ -775,6 +776,11 @@ def unify(tv1, tv2, typ): return typ +def move_first(l, i): + # type: (List[T], int) -> List[T] + return [l[i]] + l[:i] + l[i+1:] + + def ti_def(definition, typ): # type: (Def, TypeEnv) -> TypingOrError """ @@ -821,6 +827,12 @@ def ti_def(definition, typ): typ.register(v) actual_tvs.append(v.get_typevar()) + # Make sure we unify the control typevar first. + if inst.is_polymorphic: + idx = fresh_formal_tvs.index(m[inst.ctrl_typevar]) + fresh_formal_tvs = move_first(fresh_formal_tvs, idx) + actual_tvs = move_first(actual_tvs, idx) + # Unify each actual typevar with the correpsonding fresh formal tv for (actual_tv, formal_tv) in zip(actual_tvs, fresh_formal_tvs): typ_or_err = unify(actual_tv, formal_tv, typ) From a2b60108fdabac80c877f47118406916086a880c Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 15:26:59 -0700 Subject: [PATCH 923/968] Fix up a couple of test changed by unifying control tv first --- lib/cretonne/meta/cdsl/test_ti.py | 11 ++++++----- lib/cretonne/meta/test_gen_legalizer.py | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/cretonne/meta/cdsl/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py index 31d9a349a8..bffbbd527d 100644 --- a/lib/cretonne/meta/cdsl/test_ti.py +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -158,7 +158,8 @@ class TypeCheckingBaseTest(TestCase): self.v8 = Var("v8") self.v9 = Var("v9") self.imm0 = Var("imm0") - self.IxN = TypeVar("IxN", "", ints=True, scalars=True, simd=True) + self.IxN_nonscalar = TypeVar("IxN", "", ints=True, scalars=False, + simd=True) self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True, scalars=False, simd=True) self.b1 = TypeVar.singleton(b1) @@ -175,7 +176,7 @@ class TestRTL(TypeCheckingBaseTest): self.assertEqual(ti_rtl(r, ti), "On line 1: fail ti on `typeof_v2` <: `1`: " + "Error: empty type created when unifying " + - "`typeof_v3` and `half_vector(typeof_v3)`") + "`typeof_v2` and `half_vector(typeof_v2)`") def test_vselect(self): # type: () -> None @@ -201,11 +202,11 @@ class TestRTL(TypeCheckingBaseTest): ) ti = TypeEnv() typing = ti_rtl(r, ti) - ixn = self.IxN.get_fresh_copy("IxN1") + ixn = self.IxN_nonscalar.get_fresh_copy("IxN1") txn = self.TxN.get_fresh_copy("TxN1") check_typing(typing, ({ self.v0: ixn, - self.v1: txn.as_bool(), + self.v1: ixn.as_bool(), self.v2: ixn, self.v3: txn, self.v4: txn, @@ -470,7 +471,7 @@ class TestXForm(TypeCheckingBaseTest): assert var_m[v0] == var_m[v2] and \ var_m[v3] == var_m[v4] and\ var_m[v5] == var_m[v3] and\ - var_m[v1] == var_m[v5].as_bool() and\ + var_m[v1] == var_m[v2].as_bool() and\ var_m[v1].get_typeset() == var_m[v3].as_bool().get_typeset() check_concrete_typing_xform(var_m, xform) diff --git a/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py index da4683413d..6a317d887b 100644 --- a/lib/cretonne/meta/test_gen_legalizer.py +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -153,7 +153,7 @@ class TestRuntimeChecks(TestCase): def test_vselect_imm(self): # type: () -> None - ts = TypeSet(lanes=(2, 256), ints=(8, 64)) + ts = TypeSet(lanes=(2, 256), ints=True, floats=True, bools=(8, 64)) r = Rtl( self.v0 << iconst(self.imm0), self.v1 << icmp(intcc.eq, self.v2, self.v0), @@ -166,7 +166,7 @@ class TestRuntimeChecks(TestCase): .format(self.v3.get_typevar().name) self.check_yo_check( - x, sequence(typeset_check(self.v2, ts), + x, sequence(typeset_check(self.v3, ts), equiv_check(tv2_exp, tv3_exp))) def test_reduce_extend(self): From a92021ebced012be41b88ffdb296c84411b5ecbb Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 15:28:52 -0700 Subject: [PATCH 924/968] With multiple semantic transforms mentioning Enumerators, it may be possible for there not to be a substitution from the concrete rtl to some of the transforms. This is not an error - just a case where a given semantic transform doesnt apply. (e.g. icmp being described by different transforms with concrete intcc condition codes) --- lib/cretonne/meta/semantics/elaborate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index 03ca3195c4..0f2d10641d 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -83,7 +83,12 @@ def find_matching_xform(d): for x in d.expr.inst.semantics: subst = d.substitution(x.src.rtl[0], {}) - assert subst is not None + + # There may not be a substitution if there are concrete Enumerator + # values in the src pattern. (e.g. specifying the semantics of icmp.eq, + # icmp.ge... as separate transforms) + if (subst is None): + continue if x.ti.permits({subst[v]: tv for (v, tv) in typing.items()}): res.append(x) From 80a42fdeaab05ff77234eb1957e234ca6872eca5 Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 16:30:31 -0700 Subject: [PATCH 925/968] Move apply() -> Xform.apply(); is_concrete_rtl() -> Rtl.is_concrete(); cleanup_concrete_rtl() -> Rtl.cleanup_concrete_rtl(). Documnetation nits in semantics.elaborate --- lib/cretonne/meta/cdsl/xform.py | 54 +++++++++- lib/cretonne/meta/semantics/elaborate.py | 123 +++++++---------------- 2 files changed, 88 insertions(+), 89 deletions(-) diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 2cae1c1236..f25068480c 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -5,6 +5,7 @@ from __future__ import absolute_import from .ast import Def, Var, Apply from .ti import ti_xform, TypeEnv, get_type_env from functools import reduce +from .typevar import TypeVar try: from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa @@ -12,7 +13,6 @@ try: from .ast import Expr, VarMap # noqa from .isa import TargetISA # noqa from .ti import TypeConstraint # noqa - from .typevar import TypeVar # noqa DefApply = Union[Def, Apply] except ImportError: pass @@ -86,6 +86,37 @@ class Rtl(object): return s + def is_concrete(self): + # type: (Rtl) -> bool + """Return True iff every Var in the self has a singleton type.""" + return all(v.get_typevar().singleton_type() is not None + for v in self.vars()) + + def cleanup_concrete_rtl(self): + # type: (Rtl) -> None + """ + Given that there is only 1 possible concrete typing T for self, assign + a singleton TV with the single type t=T[v] for each Var v \in self. + Its an error to call this on an Rtl with more than 1 possible typing. + """ + from .ti import ti_rtl, TypeEnv + # 1) Infer the types of all vars in res + typenv = get_type_env(ti_rtl(self, TypeEnv())) + typenv.normalize() + typenv = typenv.extract() + + # 2) Make sure there is only one possible type assignment + typings = list(typenv.concrete_typings()) + assert len(typings) == 1 + typing = typings[0] + + # 3) Assign the only possible type to each variable. + for v in typenv.vars: + if v.get_typevar().singleton_type() is not None: + continue + + v.set_typevar(TypeVar.singleton(typing[v].singleton_type())) + class XForm(object): """ @@ -279,6 +310,27 @@ class XForm(object): raise AssertionError( '{} not defined in dest pattern'.format(d)) + def apply(self, r, suffix=None): + # type: (Rtl, str) -> Rtl + """ + Given a concrete Rtl r s.t. r matches self.src, return the + corresponding concrete self.dst. If suffix is provided, any temporary + defs are renamed with '.suffix' appended to their old name. + """ + assert r.is_concrete() + s = self.src.substitution(r, {}) # type: VarMap + assert s is not None + + if (suffix is not None): + for v in self.dst.vars(): + if v.is_temp(): + assert v not in s + s[v] = Var(v.name + '.' + suffix) + + dst = self.dst.copy(s) + dst.cleanup_concrete_rtl() + return dst + class XFormGroup(object): """ diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index 0f2d10641d..4b7dc14ae3 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -4,8 +4,6 @@ equivalent primitive version. Its elaborated primitive version contains only primitive cretonne instructions, which map well to SMTLIB functions. """ from .primitives import GROUP as PRIMITIVES, prim_to_bv, prim_from_bv -from cdsl.ti import ti_rtl, TypeEnv, get_type_env -from cdsl.typevar import TypeVar from cdsl.xform import Rtl from cdsl.ast import Var @@ -18,60 +16,6 @@ except ImportError: TYPE_CHECKING = False -def is_rtl_concrete(r): - # type: (Rtl) -> bool - """Return True iff every Var in the Rtl r has a single type.""" - return all(v.get_typevar().singleton_type() is not None for v in r.vars()) - - -def cleanup_concrete_rtl(r): - # type: (Rtl) -> Rtl - """ - Given an Rtl r - 1) assert that there is only 1 possible concrete typing T for r - 2) Assign a singleton TV with the single type t \in T for each Var v \in r - """ - # 1) Infer the types of any of the remaining vars in res - typenv = get_type_env(ti_rtl(r, TypeEnv())) - typenv.normalize() - typenv = typenv.extract() - - # 2) Make sure there is only one possible type assignment - typings = list(typenv.concrete_typings()) - assert len(typings) == 1 - typing = typings[0] - - # 3) Assign the only possible type to each variable. - for v in typenv.vars: - if v.get_typevar().singleton_type() is not None: - continue - - v.set_typevar(TypeVar.singleton(typing[v].singleton_type())) - - return r - - -def apply(r, x, suffix=None): - # type: (Rtl, XForm, str) -> Rtl - """ - Given a concrete Rtl r and XForm x, s.t. r matches x.src, return the - corresponding concrete x.dst. If suffix is provided, any temporary defs are - renamed with '.suffix' appended to their old name. - """ - assert is_rtl_concrete(r) - s = x.src.substitution(r, {}) # type: VarMap - assert s is not None - - if (suffix is not None): - for v in x.dst.vars(): - if v.is_temp(): - assert v not in s - s[v] = Var(v.name + '.' + suffix) - - dst = x.dst.copy(s) - return cleanup_concrete_rtl(dst) - - def find_matching_xform(d): # type: (Def) -> XForm """ @@ -93,41 +37,10 @@ def find_matching_xform(d): if x.ti.permits({subst[v]: tv for (v, tv) in typing.items()}): res.append(x) - assert len(res) == 1 + assert len(res) == 1, "Couldn't find semantic transform for {}".format(d) return res[0] -def elaborate(r): - # type: (Rtl) -> Rtl - """ - Given an Rtl r, return a semantically equivalent Rtl r1 consisting only - primitive instructions. - """ - fp = False - primitives = set(PRIMITIVES.instructions) - idx = 0 - - while not fp: - assert is_rtl_concrete(r) - new_defs = [] # type: List[Def] - fp = True - - for d in r.rtl: - inst = d.expr.inst - - if (inst not in primitives): - transformed = apply(Rtl(d), find_matching_xform(d), str(idx)) - idx += 1 - new_defs.extend(transformed.rtl) - fp = False - else: - new_defs.append(d) - - r.rtl = tuple(new_defs) - - return r - - def cleanup_semantics(r, outputs): # type: (Rtl, Set[Var]) -> Rtl """ @@ -176,3 +89,37 @@ def cleanup_semantics(r, outputs): d.defs[0] not in live)] return Rtl(*new_defs) + + +def elaborate(r): + # type: (Rtl) -> Rtl + """ + Given a concrete Rtl r, return a semantically equivalent Rtl r1 containing + only primitive instructions. + """ + fp = False + primitives = set(PRIMITIVES.instructions) + idx = 0 + + outputs = r.definitions() + + while not fp: + assert r.is_concrete() + new_defs = [] # type: List[Def] + fp = True + + for d in r.rtl: + inst = d.expr.inst + + if (inst not in primitives): + t = find_matching_xform(d) + transformed = t.apply(Rtl(d), str(idx)) + idx += 1 + new_defs.extend(transformed.rtl) + fp = False + else: + new_defs.append(d) + + r.rtl = tuple(new_defs) + + return cleanup_semantics(r, outputs) From 93b57a5209979b7344b23a2ddbcf54233b3770a8 Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 16:49:12 -0700 Subject: [PATCH 926/968] Nit: Make elaborate return a new Rtl instead of modifying the existing rtl inplace --- lib/cretonne/meta/semantics/elaborate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index 4b7dc14ae3..de59de35e5 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -101,14 +101,15 @@ def elaborate(r): primitives = set(PRIMITIVES.instructions) idx = 0 - outputs = r.definitions() + res = Rtl(*r.rtl) + outputs = res.definitions() while not fp: - assert r.is_concrete() + assert res.is_concrete() new_defs = [] # type: List[Def] fp = True - for d in r.rtl: + for d in res.rtl: inst = d.expr.inst if (inst not in primitives): @@ -120,6 +121,6 @@ def elaborate(r): else: new_defs.append(d) - r.rtl = tuple(new_defs) + res.rtl = tuple(new_defs) - return cleanup_semantics(r, outputs) + return cleanup_semantics(res, outputs) From ec9e9bd1ca7e819241c3182b63cb63d3f1a7a5da Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 19:10:14 -0700 Subject: [PATCH 927/968] cleanup_semantics() should remove repeated prim_from_bv(x) --- lib/cretonne/meta/semantics/elaborate.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index de59de35e5..335c4ebb86 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -57,21 +57,30 @@ def cleanup_semantics(r, outputs): new_defs = [] # type: List[Def] subst_m = {v: v for v in r.vars()} # type: VarMap definition = {} # type: Dict[Var, Def] + prim_to_bv_map = {} # type: Dict[Var, Def] # Pass 1: Remove redundant prim_to_bv for d in r.rtl: inst = d.expr.inst if (inst == prim_to_bv): - if d.expr.args[0] in definition: - assert isinstance(d.expr.args[0], Var) - def_loc = definition[d.expr.args[0]] + arg = d.expr.args[0] + df = d.defs[0] + assert isinstance(arg, Var) + if arg in definition: + def_loc = definition[arg] if def_loc.expr.inst == prim_from_bv: assert isinstance(def_loc.expr.args[0], Var) - subst_m[d.defs[0]] = def_loc.expr.args[0] + subst_m[df] = def_loc.expr.args[0] continue + if arg in prim_to_bv_map: + subst_m[df] = prim_to_bv_map[arg].defs[0] + continue + + prim_to_bv_map[arg] = d + new_def = d.copy(subst_m) for v in new_def.defs: From 3fd43fd00635aa3e2de244e795ba946fe3406454 Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 19:10:39 -0700 Subject: [PATCH 928/968] Add Rtl.free_vars() --- lib/cretonne/meta/cdsl/xform.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index f25068480c..1d74eaccf9 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -68,6 +68,17 @@ class Rtl(object): [d.definitions() for d in self.rtl], set([])) + def free_vars(self): + # type: () -> Set[Var] + """ Return the set of free Vars used in self""" + def flow_f(s, d): + # type: (Set[Var], Def): Set[Var] + """Compute the change in the set of free vars across a Def""" + s = s.difference(set(d.defs)) + return s.union(set(a for a in d.expr.args if isinstance(a, Var))) + + return reduce(flow_f, reversed(self.rtl), set([])) + def substitution(self, other, s): # type: (Rtl, VarMap) -> Optional[VarMap] """ From e346bd50c89cddbc520a8c4d98d7d11de08cc5fc Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 19:12:11 -0700 Subject: [PATCH 929/968] Add primitive bvult, bvzeroext; Add semantics for bextend, icmp (partial - only for <) iadd_cout --- lib/cretonne/meta/base/semantics.py | 48 ++++++++++++++++++++++- lib/cretonne/meta/semantics/primitives.py | 16 ++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/meta/base/semantics.py b/lib/cretonne/meta/base/semantics.py index b4315d8255..cdf071f7d9 100644 --- a/lib/cretonne/meta/base/semantics.py +++ b/lib/cretonne/meta/base/semantics.py @@ -1,7 +1,8 @@ from __future__ import absolute_import from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\ - bvadd -from .instructions import vsplit, vconcat, iadd + bvadd, bvult, bvzeroext +from .instructions import vsplit, vconcat, iadd, iadd_cout, icmp, bextend +from .immediates import intcc from cdsl.xform import Rtl from cdsl.ast import Var from cdsl.typevar import TypeSet @@ -10,6 +11,9 @@ from cdsl.ti import InTypeset x = Var('x') y = Var('y') a = Var('a') +b = Var('b') +c_out = Var('c_out') +bvc_out = Var('bvc_out') xhi = Var('xhi') yhi = Var('yhi') ahi = Var('ahi') @@ -21,6 +25,7 @@ hi = Var('hi') bvx = Var('bvx') bvy = Var('bvy') bva = Var('bva') +bva_wide = Var('bva_wide') bvlo = Var('bvlo') bvhi = Var('bvhi') @@ -56,3 +61,42 @@ iadd.set_semantics( alo << iadd(xlo, ylo), ahi << iadd(xhi, yhi), a << vconcat(alo, ahi))) + +iadd_cout.set_semantics( + (a, c_out) << iadd_cout(x, y), + Rtl( + bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bva << bvadd(bvx, bvy), + bvc_out << bvult(bva, bvx), + a << prim_from_bv(bva), + c_out << prim_from_bv(bvc_out) + )) + +bextend.set_semantics( + a << bextend(x), + (Rtl( + bvx << prim_to_bv(x), + bvy << bvzeroext(bvx), + a << prim_from_bv(bvy) + ), [InTypeset(x.get_typevar(), ScalarTS)]), + Rtl((xlo, xhi) << vsplit(x), + alo << bextend(xlo), + ahi << bextend(xhi), + a << vconcat(alo, ahi))) + +icmp.set_semantics( + a << icmp(intcc.ult, x, y), + (Rtl( + bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bva << bvult(bvx, bvy), + bva_wide << bvzeroext(bva), + a << prim_from_bv(bva_wide), + ), [InTypeset(x.get_typevar(), ScalarTS)]), + Rtl((xlo, xhi) << vsplit(x), + (ylo, yhi) << vsplit(y), + alo << icmp(intcc.ult, xlo, ylo), + ahi << icmp(intcc.ult, xhi, yhi), + b << vconcat(alo, ahi), + a << bextend(b))) diff --git a/lib/cretonne/meta/semantics/primitives.py b/lib/cretonne/meta/semantics/primitives.py index 70fc196d5d..62d936bc31 100644 --- a/lib/cretonne/meta/semantics/primitives.py +++ b/lib/cretonne/meta/semantics/primitives.py @@ -9,11 +9,13 @@ from __future__ import absolute_import from cdsl.operands import Operand from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup +from cdsl.ti import WiderOrEq import base.formats # noqa GROUP = InstructionGroup("primitive", "Primitive instruction set") BV = TypeVar('BV', 'A bitvector type.', bitvecs=True) +BV1 = TypeVar('BV1', 'A single bit bitvector.', bitvecs=(1, 1)) Real = TypeVar('Real', 'Any real type.', ints=True, floats=True, bools=True, simd=True) @@ -66,4 +68,18 @@ bvadd = Instruction( """, ins=(x, y), outs=a) +# Bitvector comparisons +cmp_res = Operand('cmp_res', BV1, doc="Single bit boolean") +bvult = Instruction( + 'bvult', r"""Unsigned bitvector comparison""", + ins=(x, y), outs=cmp_res) + +# Extensions +ToBV = TypeVar('ToBV', 'A bitvector type.', bitvecs=True) +x1 = Operand('x1', ToBV, doc="") + +bvzeroext = Instruction( + 'bvzeroext', r"""Unsigned bitvector extension""", + ins=x, outs=x1, constraints=WiderOrEq(ToBV, BV)) + GROUP.close() From 59e204cec2bcb5713d3aabc7999fceed6574548b Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 19:12:41 -0700 Subject: [PATCH 930/968] Fix broken test_elaborate tests after the moving of is_concrete/cleanup_concrete_rtl --- lib/cretonne/meta/semantics/test_elaborate.py | 142 ++++++++++++------ 1 file changed, 96 insertions(+), 46 deletions(-) diff --git a/lib/cretonne/meta/semantics/test_elaborate.py b/lib/cretonne/meta/semantics/test_elaborate.py index 8eafcb9e00..cb798295b9 100644 --- a/lib/cretonne/meta/semantics/test_elaborate.py +++ b/lib/cretonne/meta/semantics/test_elaborate.py @@ -1,15 +1,15 @@ from __future__ import absolute_import from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint -from base.instructions import b1, icmp, ireduce +from base.instructions import b1, icmp, ireduce, iadd_cout from base.immediates import intcc from base.types import i64, i8, b32, i32, i16, f32 from cdsl.typevar import TypeVar from cdsl.ast import Var from cdsl.xform import Rtl from unittest import TestCase -from .elaborate import cleanup_concrete_rtl, elaborate, is_rtl_concrete,\ - cleanup_semantics -from .primitives import prim_to_bv, bvsplit, prim_from_bv, bvconcat, bvadd +from .elaborate import elaborate +from .primitives import prim_to_bv, bvsplit, prim_from_bv, bvconcat, bvadd, \ + bvult import base.semantics # noqa @@ -21,8 +21,8 @@ def concrete_rtls_eq(r1, r2): them) 2) Corresponding Vars between them have the same singleton type. """ - assert is_rtl_concrete(r1) - assert is_rtl_concrete(r2) + assert r1.is_concrete() + assert r2.is_concrete() s = r1.substitution(r2, {}) @@ -50,13 +50,14 @@ class TestCleanupConcreteRtl(TestCase): lo = Var('lo') hi = Var('hi') - x.set_typevar(TypeVar.singleton(typ)) r = Rtl( (lo, hi) << vsplit(x), ) - r1 = cleanup_concrete_rtl(r) - + r1 = r.copy({}) s = r.substitution(r1, {}) + + s[x].set_typevar(TypeVar.singleton(typ)) + r1.cleanup_concrete_rtl() assert s is not None assert s[x].get_typevar().singleton_type() == typ assert s[lo].get_typevar().singleton_type() == i64.by(2) @@ -72,20 +73,20 @@ class TestCleanupConcreteRtl(TestCase): ) with self.assertRaises(AssertionError): - cleanup_concrete_rtl(r) + r.cleanup_concrete_rtl() def test_cleanup_concrete_rtl_ireduce(self): # type: () -> None x = Var('x') y = Var('y') - x.set_typevar(TypeVar.singleton(i8.by(2))) r = Rtl( y << ireduce(x), ) - - r1 = cleanup_concrete_rtl(r) - + r1 = r.copy({}) s = r.substitution(r1, {}) + s[x].set_typevar(TypeVar.singleton(i8.by(2))) + r1.cleanup_concrete_rtl() + assert s is not None assert s[x].get_typevar().singleton_type() == i8.by(2) assert s[y].get_typevar().singleton_type() == i8.by(2) @@ -100,7 +101,7 @@ class TestCleanupConcreteRtl(TestCase): ) with self.assertRaises(AssertionError): - cleanup_concrete_rtl(r) + r.cleanup_concrete_rtl() def test_vselect_icmpimm(self): # type: () -> None @@ -112,18 +113,19 @@ class TestCleanupConcreteRtl(TestCase): zeroes = Var('zeroes') imm0 = Var("imm0") - zeroes.set_typevar(TypeVar.singleton(i32.by(4))) - z.set_typevar(TypeVar.singleton(f32.by(4))) - r = Rtl( zeroes << iconst(imm0), y << icmp(intcc.eq, x, zeroes), v << vselect(y, z, w), ) - - r1 = cleanup_concrete_rtl(r) + r1 = r.copy({}) s = r.substitution(r1, {}) + s[zeroes].set_typevar(TypeVar.singleton(i32.by(4))) + s[z].set_typevar(TypeVar.singleton(f32.by(4))) + + r1.cleanup_concrete_rtl() + assert s is not None assert s[zeroes].get_typevar().singleton_type() == i32.by(4) assert s[x].get_typevar().singleton_type() == i32.by(4) @@ -141,20 +143,20 @@ class TestCleanupConcreteRtl(TestCase): v = Var('v') u = Var('u') - x.set_typevar(TypeVar.singleton(i32.by(8))) - z.set_typevar(TypeVar.singleton(i32.by(8))) - # TODO: Relax this to simd=True - v.set_typevar(TypeVar('v', '', bools=(1, 1), simd=(8, 8))) - r = Rtl( z << iadd(x, y), w << bint(v), u << iadd(z, w) ) - - r1 = cleanup_concrete_rtl(r) - + r1 = r.copy({}) s = r.substitution(r1, {}) + + s[x].set_typevar(TypeVar.singleton(i32.by(8))) + s[z].set_typevar(TypeVar.singleton(i32.by(8))) + # TODO: Relax this to simd=True + s[v].set_typevar(TypeVar('v', '', bools=(1, 1), simd=(8, 8))) + r1.cleanup_concrete_rtl() + assert s is not None assert s[x].get_typevar().singleton_type() == i32.by(8) assert s[y].get_typevar().singleton_type() == i32.by(8) @@ -194,7 +196,8 @@ class TestElaborate(TestCase): r = Rtl( (self.v0, self.v1) << vsplit.i32x4(self.v2), ) - sem = elaborate(cleanup_concrete_rtl(r)) + r.cleanup_concrete_rtl() + sem = elaborate(r) bvx = Var('bvx') bvlo = Var('bvlo') bvhi = Var('bvhi') @@ -202,11 +205,15 @@ class TestElaborate(TestCase): lo = Var('lo') hi = Var('hi') - assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + exp = Rtl( bvx << prim_to_bv.i32x4(x), (bvlo, bvhi) << bvsplit.bv128(bvx), lo << prim_from_bv.i32x2(bvlo), - hi << prim_from_bv.i32x2(bvhi)))) + hi << prim_from_bv.i32x2(bvhi) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) def test_elaborate_vconcat(self): # type: () -> None @@ -215,7 +222,8 @@ class TestElaborate(TestCase): r = Rtl( self.v0 << vconcat.i32x2(self.v1, self.v2), ) - sem = elaborate(cleanup_concrete_rtl(r)) + r.cleanup_concrete_rtl() + sem = elaborate(r) bvx = Var('bvx') bvlo = Var('bvlo') bvhi = Var('bvhi') @@ -223,11 +231,15 @@ class TestElaborate(TestCase): lo = Var('lo') hi = Var('hi') - assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + exp = Rtl( bvlo << prim_to_bv.i32x2(lo), bvhi << prim_to_bv.i32x2(hi), bvx << bvconcat.bv64(bvlo, bvhi), - x << prim_from_bv.i32x4(bvx)))) + x << prim_from_bv.i32x4(bvx) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) def test_elaborate_iadd_simple(self): # type: () -> None @@ -241,13 +253,17 @@ class TestElaborate(TestCase): r = Rtl( a << iadd.i32(x, y), ) - sem = elaborate(cleanup_concrete_rtl(r)) - - assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + r.cleanup_concrete_rtl() + sem = elaborate(r) + exp = Rtl( bvx << prim_to_bv.i32(x), bvy << prim_to_bv.i32(y), bva << bvadd.bv32(bvx, bvy), - a << prim_from_bv.i32(bva)))) + a << prim_from_bv.i32(bva) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) def test_elaborate_iadd_elaborate_1(self): # type: () -> None @@ -255,8 +271,8 @@ class TestElaborate(TestCase): r = Rtl( self.v0 << iadd.i32x2(self.v1, self.v2), ) - sem = cleanup_semantics(elaborate(cleanup_concrete_rtl(r)), - set([self.v0])) + r.cleanup_concrete_rtl() + sem = elaborate(r) x = Var('x') y = Var('y') a = Var('a') @@ -271,7 +287,7 @@ class TestElaborate(TestCase): bva_3 = Var('bva_3') bva_4 = Var('bva_4') - assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + exp = Rtl( bvx_1 << prim_to_bv.i32x2(x), (bvlo_1, bvhi_1) << bvsplit.bv64(bvx_1), bvx_2 << prim_to_bv.i32x2(y), @@ -279,7 +295,11 @@ class TestElaborate(TestCase): bva_3 << bvadd.bv32(bvlo_1, bvlo_2), bva_4 << bvadd.bv32(bvhi_1, bvhi_2), bvx_5 << bvconcat.bv32(bva_3, bva_4), - a << prim_from_bv.i32x2(bvx_5)))) + a << prim_from_bv.i32x2(bvx_5) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) def test_elaborate_iadd_elaborate_2(self): # type: () -> None @@ -287,9 +307,9 @@ class TestElaborate(TestCase): r = Rtl( self.v0 << iadd.i8x4(self.v1, self.v2), ) + r.cleanup_concrete_rtl() - sem = cleanup_semantics(elaborate(cleanup_concrete_rtl(r)), - set([self.v0])) + sem = elaborate(r) x = Var('x') y = Var('y') a = Var('a') @@ -318,7 +338,7 @@ class TestElaborate(TestCase): bva_13 = Var('bva_13') bva_14 = Var('bva_14') - assert concrete_rtls_eq(sem, cleanup_concrete_rtl(Rtl( + exp = Rtl( bvx_1 << prim_to_bv.i8x4(x), (bvlo_1, bvhi_1) << bvsplit.bv32(bvx_1), bvx_2 << prim_to_bv.i8x4(y), @@ -334,4 +354,34 @@ class TestElaborate(TestCase): bva_14 << bvadd.bv8(bvhi_11, bvhi_12), bvx_15 << bvconcat.bv8(bva_13, bva_14), bvx_5 << bvconcat.bv16(bvx_10, bvx_15), - a << prim_from_bv.i8x4(bvx_5)))) + a << prim_from_bv.i8x4(bvx_5) + ) + exp.cleanup_concrete_rtl() + assert concrete_rtls_eq(sem, exp) + + def test_elaborate_iadd_cout_simple(self): + # type: () -> None + x = Var('x') + y = Var('y') + a = Var('a') + c_out = Var('c_out') + bvc_out = Var('bvc_out') + bvx = Var('bvx') + bvy = Var('bvy') + bva = Var('bva') + r = Rtl( + (a, c_out) << iadd_cout.i32(x, y), + ) + r.cleanup_concrete_rtl() + sem = elaborate(r) + exp = Rtl( + bvx << prim_to_bv.i32(x), + bvy << prim_to_bv.i32(y), + bva << bvadd.bv32(bvx, bvy), + bvc_out << bvult.bv32(bva, bvx), + a << prim_from_bv.i32(bva), + c_out << prim_from_bv.b1(bvc_out) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) From c3092d680f666a9923c5a4f4f03a60259163fde6 Mon Sep 17 00:00:00 2001 From: Dimo Date: Wed, 26 Jul 2017 19:25:24 -0700 Subject: [PATCH 931/968] Add smtlib.py --- lib/cretonne/meta/cdsl/ast.py | 8 +- lib/cretonne/meta/cdsl/xform.py | 2 +- lib/cretonne/meta/semantics/smtlib.py | 150 ++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 lib/cretonne/meta/semantics/smtlib.py diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index e954ab01e9..4207253684 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -156,19 +156,19 @@ class Var(Expr): Values that are defined only in the destination pattern. """ - def __init__(self, name): - # type: (str) -> None + def __init__(self, name, typevar=None): + # type: (str, TypeVar) -> None self.name = name # The `Def` defining this variable in a source pattern. self.src_def = None # type: Def # The `Def` defining this variable in a destination pattern. self.dst_def = None # type: Def # TypeVar representing the type of this variable. - self.typevar = None # type: TypeVar + self.typevar = typevar # type: TypeVar # The original 'typeof(x)' type variable that was created for this Var. # This one doesn't change. `self.typevar` above may be changed to # another typevar by type inference. - self.original_typevar = None # type: TypeVar + self.original_typevar = self.typevar # type: TypeVar def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 1d74eaccf9..ecbb144e16 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -72,7 +72,7 @@ class Rtl(object): # type: () -> Set[Var] """ Return the set of free Vars used in self""" def flow_f(s, d): - # type: (Set[Var], Def): Set[Var] + # type: (Set[Var], Def) -> Set[Var] """Compute the change in the set of free vars across a Def""" s = s.difference(set(d.defs)) return s.union(set(a for a in d.expr.args if isinstance(a, Var))) diff --git a/lib/cretonne/meta/semantics/smtlib.py b/lib/cretonne/meta/semantics/smtlib.py new file mode 100644 index 0000000000..53dac682e5 --- /dev/null +++ b/lib/cretonne/meta/semantics/smtlib.py @@ -0,0 +1,150 @@ +""" +Tools to emit SMTLIB bitvector queries encoding concrete RTLs containing only +primitive instructions. +""" +from .primitives import GROUP as PRIMITIVES, prim_from_bv, prim_to_bv, bvadd,\ + bvult, bvzeroext +from cdsl.ast import Var +from cdsl.types import BVType + +try: + from typing import TYPE_CHECKING, Tuple # noqa + from cdsl.xform import Rtl # noqa + from cdsl.ast import VarMap # noqa +except ImportError: + TYPE_CHECKING = False + + +def bvtype_to_sort(typ): + # type: (BVType) -> str + """Return the BitVec sort corresponding to a BVType""" + return "(_ BitVec {})".format(typ.bits) + + +def to_smt(r): + # type: (Rtl) -> Tuple[str, VarMap] + """ + Encode a concrete primitive Rtl r sa SMTLIB 2.0 query. + Returns a tuple (query, var_m) where: + - query is the resulting query. + - var_m is a map from Vars v with non-BVType to their Vars v' with + BVType s.t. v' holds the flattend bitvector value of v. + """ + assert r.is_concrete() + # Should contain only primitives + primitives = set(PRIMITIVES.instructions) + assert all(d.expr.inst in primitives for d in r.rtl) + + q = "" + m = {} # type: VarMap + for v in r.vars(): + typ = v.get_typevar().singleton_type() + if not isinstance(typ, BVType): + continue + + q += "(declare-fun {} () {})\n".format(v.name, bvtype_to_sort(typ)) + + for d in r.rtl: + inst = d.expr.inst + + if inst == prim_to_bv: + assert isinstance(d.expr.args[0], Var) + m[d.expr.args[0]] = d.defs[0] + continue + + if inst == prim_from_bv: + assert isinstance(d.expr.args[0], Var) + m[d.defs[0]] = d.expr.args[0] + continue + + if inst in [bvadd, bvult]: # Binary instructions + assert len(d.expr.args) == 2 and len(d.defs) == 1 + lhs = d.expr.args[0] + rhs = d.expr.args[1] + df = d.defs[0] + assert isinstance(lhs, Var) and isinstance(rhs, Var) + + if inst in [bvadd]: # Normal binary - output type same as args + exp = "(= {} ({} {} {}))".format(df, inst.name, lhs, rhs) + else: + # Comparison binary - need to convert bool to BitVec 1 + exp = "(= {} (ite ({} {} {}) #b1 #b0))"\ + .format(df, inst.name, lhs, rhs) + elif inst == bvzeroext: + arg = d.expr.args[0] + df = d.defs[0] + assert isinstance(arg, Var) + fromW = arg.get_typevar().singleton_type().width() + toW = df.get_typevar().singleton_type().width() + + exp = "(= {} ((_ zero_extend {}) {}))"\ + .format(df, toW-fromW, arg, df) + else: + assert False, "Unknown primitive instruction {}".format(inst) + + q += "(assert {})\n".format(exp) + + return (q, m) + + +def equivalent(r1, r2, m): + # type: (Rtl, Rtl, VarMap) -> str + """ + Given concrete primitive Rtls r1 and r2, and a VarMap m, mapping all + non-primitive vars in r1 onto r2, return a query checking that the + two Rtls are semantically equivalent. + + If the returned query is unsatisfiable, then r1 and r2 are equivalent. + Otherwise, the satisfying example for the query gives us values + for which the two Rtls disagree. + """ + # Rename the vars in r1 and r2 to avoid conflicts + src_m = {v: Var(v.name + ".a", v.get_typevar()) for v in r1.vars()} + dst_m = {v: Var(v.name + ".b", v.get_typevar()) for v in r2.vars()} + m = {src_m[k]: dst_m[v] for (k, v) in m.items()} + + r1 = r1.copy(src_m) + r2 = r2.copy(dst_m) + + r1_nonprim_vars = set( + [v for v in r1.vars() + if not isinstance(v.get_typevar().singleton_type(), BVType)]) + + r2_nonprim_vars = set( + [v for v in r2.vars() + if not isinstance(v.get_typevar().singleton_type(), BVType)]) + + # Check that the map m maps all non real Cretone Vars from r1 onto r2 + assert r1_nonprim_vars == set(m.keys()) + assert r2_nonprim_vars == set(m.values()) + + (q1, m1) = to_smt(r1) + (q2, m2) = to_smt(r2) + + # Build an expression for the equality of real Cretone inputs + args_eq_exp = "(and \n" + + for v in r1.free_vars(): + assert v in r1_nonprim_vars + args_eq_exp += "(= {} {})\n".format(m1[v], m2[m[v]]) + args_eq_exp += ")" + + # Build an expression for the equality of real Cretone defs + results_eq_exp = "(and \n" + for v in r1.definitions(): + if (v not in r1_nonprim_vars): + continue + + results_eq_exp += "(= {} {})\n".format(m1[v], m2[m[v]]) + results_eq_exp += ")" + + q = '; Rtl 1 declarations and assertions\n' + q1 + q += '; Rtl 2 declarations and assertions\n' + q2 + + q += '; Assert that the inputs of Rtl1 and Rtl2 are equal\n' + \ + '(assert {})\n'.format(args_eq_exp) + + q += '; Assert that the outputs of Rtl1 and Rtl2 are not equal\n' + \ + '(assert (not {}))\n'.format(results_eq_exp) + + return q From 9767654dd7525b17e89e95a859b2af05204ef5bc Mon Sep 17 00:00:00 2001 From: Dimo Date: Thu, 27 Jul 2017 17:16:26 -0700 Subject: [PATCH 932/968] Add semantics for several more iadd with carry; Add xform_correct() and doc cleanup --- lib/cretonne/meta/base/semantics.py | 103 +++++++++++++++++++++--- lib/cretonne/meta/semantics/smtlib.py | 110 +++++++++++++++++++------- 2 files changed, 171 insertions(+), 42 deletions(-) diff --git a/lib/cretonne/meta/base/semantics.py b/lib/cretonne/meta/base/semantics.py index cdf071f7d9..582fdc0889 100644 --- a/lib/cretonne/meta/base/semantics.py +++ b/lib/cretonne/meta/base/semantics.py @@ -1,7 +1,8 @@ from __future__ import absolute_import from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\ bvadd, bvult, bvzeroext -from .instructions import vsplit, vconcat, iadd, iadd_cout, icmp, bextend +from .instructions import vsplit, vconcat, iadd, iadd_cout, icmp, bextend, \ + isplit, iconcat, iadd_cin, iadd_carry from .immediates import intcc from cdsl.xform import Rtl from cdsl.ast import Var @@ -13,18 +14,24 @@ y = Var('y') a = Var('a') b = Var('b') c_out = Var('c_out') +c_in = Var('c_in') bvc_out = Var('bvc_out') +bvc_in = Var('bvc_in') xhi = Var('xhi') yhi = Var('yhi') ahi = Var('ahi') +bhi = Var('bhi') xlo = Var('xlo') ylo = Var('ylo') alo = Var('alo') +blo = Var('blo') lo = Var('lo') hi = Var('hi') bvx = Var('bvx') bvy = Var('bvy') bva = Var('bva') +bvt = Var('bvt') +bvs = Var('bvs') bva_wide = Var('bva_wide') bvlo = Var('bvlo') bvhi = Var('bvhi') @@ -51,16 +58,34 @@ vconcat.set_semantics( iadd.set_semantics( a << iadd(x, y), - (Rtl(bvx << prim_to_bv(x), - bvy << prim_to_bv(y), - bva << bvadd(bvx, bvy), - a << prim_from_bv(bva)), - [InTypeset(x.get_typevar(), ScalarTS)]), - Rtl((xlo, xhi) << vsplit(x), + (Rtl( + bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bva << bvadd(bvx, bvy), + a << prim_from_bv(bva) + ), [InTypeset(x.get_typevar(), ScalarTS)]), + Rtl( + (xlo, xhi) << vsplit(x), (ylo, yhi) << vsplit(y), alo << iadd(xlo, ylo), ahi << iadd(xhi, yhi), - a << vconcat(alo, ahi))) + a << vconcat(alo, ahi) + )) + +# +# Integer arithmetic with carry and/or borrow. +# +iadd_cin.set_semantics( + a << iadd_cin(x, y, c_in), + Rtl( + bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bvc_in << prim_to_bv(c_in), + bvs << bvzeroext(bvc_in), + bvt << bvadd(bvx, bvy), + bva << bvadd(bvt, bvs), + a << prim_from_bv(bva) + )) iadd_cout.set_semantics( (a, c_out) << iadd_cout(x, y), @@ -73,6 +98,20 @@ iadd_cout.set_semantics( c_out << prim_from_bv(bvc_out) )) +iadd_carry.set_semantics( + (a, c_out) << iadd_carry(x, y, c_in), + Rtl( + bvx << prim_to_bv(x), + bvy << prim_to_bv(y), + bvc_in << prim_to_bv(c_in), + bvs << bvzeroext(bvc_in), + bvt << bvadd(bvx, bvy), + bva << bvadd(bvt, bvs), + bvc_out << bvult(bva, bvx), + a << prim_from_bv(bva), + c_out << prim_from_bv(bvc_out) + )) + bextend.set_semantics( a << bextend(x), (Rtl( @@ -80,10 +119,12 @@ bextend.set_semantics( bvy << bvzeroext(bvx), a << prim_from_bv(bvy) ), [InTypeset(x.get_typevar(), ScalarTS)]), - Rtl((xlo, xhi) << vsplit(x), + Rtl( + (xlo, xhi) << vsplit(x), alo << bextend(xlo), ahi << bextend(xhi), - a << vconcat(alo, ahi))) + a << vconcat(alo, ahi) + )) icmp.set_semantics( a << icmp(intcc.ult, x, y), @@ -94,9 +135,47 @@ icmp.set_semantics( bva_wide << bvzeroext(bva), a << prim_from_bv(bva_wide), ), [InTypeset(x.get_typevar(), ScalarTS)]), - Rtl((xlo, xhi) << vsplit(x), + Rtl( + (xlo, xhi) << vsplit(x), (ylo, yhi) << vsplit(y), alo << icmp(intcc.ult, xlo, ylo), ahi << icmp(intcc.ult, xhi, yhi), b << vconcat(alo, ahi), - a << bextend(b))) + a << bextend(b) + )) + +# +# Legalization helper instructions. +# + +isplit.set_semantics( + (xlo, xhi) << isplit(x), + (Rtl( + bvx << prim_to_bv(x), + (bvlo, bvhi) << bvsplit(bvx), + xlo << prim_from_bv(bvlo), + xhi << prim_from_bv(bvhi) + ), [InTypeset(x.get_typevar(), ScalarTS)]), + Rtl( + (a, b) << vsplit(x), + (alo, ahi) << isplit(a), + (blo, bhi) << isplit(b), + xlo << vconcat(alo, blo), + xhi << vconcat(bhi, bhi) + )) + +iconcat.set_semantics( + x << iconcat(xlo, xhi), + (Rtl( + bvlo << prim_to_bv(xlo), + bvhi << prim_to_bv(xhi), + bvx << bvconcat(bvlo, bvhi), + x << prim_from_bv(bvx) + ), [InTypeset(x.get_typevar(), ScalarTS)]), + Rtl( + (alo, ahi) << vsplit(xlo), + (blo, bhi) << vsplit(xhi), + a << iconcat(alo, blo), + b << iconcat(ahi, bhi), + x << vconcat(a, b), + )) diff --git a/lib/cretonne/meta/semantics/smtlib.py b/lib/cretonne/meta/semantics/smtlib.py index 53dac682e5..2bf94515ab 100644 --- a/lib/cretonne/meta/semantics/smtlib.py +++ b/lib/cretonne/meta/semantics/smtlib.py @@ -3,9 +3,10 @@ Tools to emit SMTLIB bitvector queries encoding concrete RTLs containing only primitive instructions. """ from .primitives import GROUP as PRIMITIVES, prim_from_bv, prim_to_bv, bvadd,\ - bvult, bvzeroext + bvult, bvzeroext, bvsplit, bvconcat from cdsl.ast import Var from cdsl.types import BVType +from .elaborate import elaborate try: from typing import TYPE_CHECKING, Tuple # noqa @@ -78,7 +79,32 @@ def to_smt(r): toW = df.get_typevar().singleton_type().width() exp = "(= {} ((_ zero_extend {}) {}))"\ - .format(df, toW-fromW, arg, df) + .format(df, toW-fromW, arg) + elif inst == bvsplit: + arg = d.expr.args[0] + arg_typ = arg.get_typevar().singleton_type() + width = arg_typ.width() + assert (width % 2 == 0) + + lo = d.defs[0] + hi = d.defs[1] + assert isinstance(arg, Var) + + exp = "(and " + exp += "(= {} ((_ extract {} {}) {})) "\ + .format(lo, width//2-1, 0, arg) + exp += "(= {} ((_ extract {} {}) {}))"\ + .format(hi, width-1, width//2, arg) + exp += ")" + elif inst == bvconcat: + lo = d.expr.args[0] + hi = d.expr.args[1] + assert isinstance(lo, Var) and isinstance(hi, Var) + df = d.defs[0] + + # Z3 Concat expects hi bits first, then lo bits + exp = "(= {} (concat {} {}))"\ + .format(df, hi, lo) else: assert False, "Unknown primitive instruction {}".format(inst) @@ -87,57 +113,49 @@ def to_smt(r): return (q, m) -def equivalent(r1, r2, m): - # type: (Rtl, Rtl, VarMap) -> str +def equivalent(r1, r2, inp_m, out_m): + # type: (Rtl, Rtl, VarMap, VarMap) -> str """ - Given concrete primitive Rtls r1 and r2, and a VarMap m, mapping all - non-primitive vars in r1 onto r2, return a query checking that the - two Rtls are semantically equivalent. + Given: + - concrete source Rtl r1 + - concrete dest Rtl r2 + - VarMap inp_m mapping r1's non-bitvector inputs to r2 + - VarMap out_m mapping r1's non-bitvector outputs to r2 + Build a query checking whether r1 and r2 are semantically equivalent. If the returned query is unsatisfiable, then r1 and r2 are equivalent. Otherwise, the satisfying example for the query gives us values for which the two Rtls disagree. """ - # Rename the vars in r1 and r2 to avoid conflicts + # Rename the vars in r1 and r2 with unique suffixes to avoid conflicts src_m = {v: Var(v.name + ".a", v.get_typevar()) for v in r1.vars()} dst_m = {v: Var(v.name + ".b", v.get_typevar()) for v in r2.vars()} - m = {src_m[k]: dst_m[v] for (k, v) in m.items()} - r1 = r1.copy(src_m) r2 = r2.copy(dst_m) - r1_nonprim_vars = set( - [v for v in r1.vars() - if not isinstance(v.get_typevar().singleton_type(), BVType)]) - - r2_nonprim_vars = set( - [v for v in r2.vars() - if not isinstance(v.get_typevar().singleton_type(), BVType)]) - - # Check that the map m maps all non real Cretone Vars from r1 onto r2 - assert r1_nonprim_vars == set(m.keys()) - assert r2_nonprim_vars == set(m.values()) + # Convert inp_m, out_m in terms of variables with the .a/.b suffixes + inp_m = {src_m[k]: dst_m[v] for (k, v) in inp_m.items()} + out_m = {src_m[k]: dst_m[v] for (k, v) in out_m.items()} + # Encode r1 and r2 as SMT queries (q1, m1) = to_smt(r1) (q2, m2) = to_smt(r2) - # Build an expression for the equality of real Cretone inputs + # Build an expression for the equality of real Cretone inputs of r1 and r2 args_eq_exp = "(and \n" for v in r1.free_vars(): - assert v in r1_nonprim_vars - args_eq_exp += "(= {} {})\n".format(m1[v], m2[m[v]]) + assert v in inp_m + args_eq_exp += "(= {} {})\n".format(m1[v], m2[inp_m[v]]) args_eq_exp += ")" - # Build an expression for the equality of real Cretone defs + # Build an expression for the equality of real Cretone outputs of r1 and r2 results_eq_exp = "(and \n" - for v in r1.definitions(): - if (v not in r1_nonprim_vars): - continue - - results_eq_exp += "(= {} {})\n".format(m1[v], m2[m[v]]) + for (v1, v2) in out_m.items(): + results_eq_exp += "(= {} {})\n".format(m1[v1], m2[v2]) results_eq_exp += ")" + # Put the whole query toghether q = '; Rtl 1 declarations and assertions\n' + q1 q += '; Rtl 2 declarations and assertions\n' + q2 @@ -148,3 +166,35 @@ def equivalent(r1, r2, m): '(assert (not {}))\n'.format(results_eq_exp) return q + + +def xform_correct(x, typing): + # type: (XForm, VarTyping) -> str + """ + Given an XForm x and a concrete variable typing for x typing, build the + smtlib query asserting that x is correct for the given typing. + """ + assert x.ti.permits(typing) + + # Create copies of the x.src and x.dst with the concrete types in typing. + src_m = {v: Var(v.name, typing[v]) for v in x.src.vars()} + src = x.src.copy(src_m) + dst = x.apply(src) + dst_m = x.dst.substitution(dst, {}) + + # Build maps for the inputs/outputs for src->dst + inp_m = {} + out_m = {} + + for v in x.src.vars(): + if v.is_input(): + inp_m[src_m[v]] = dst_m[v] + elif v.is_output(): + out_m[src_m[v]] = dst_m[v] + else: + assert False, "Haven't decided what to do with intermediates yet" + + # Get the primitive semantic Rtls for src and dst + prim_src = elaborate(src) + prim_dst = elaborate(dst) + return equivalent(prim_src, prim_dst, inp_m, out_m) From a324d60cccabea7c0e6dea1a47ec2be9cfeafd4f Mon Sep 17 00:00:00 2001 From: Dimo Date: Thu, 27 Jul 2017 18:05:38 -0700 Subject: [PATCH 933/968] Cleanup, typechecking and documentation nits --- lib/cretonne/meta/cdsl/xform.py | 22 ++++++++++------- lib/cretonne/meta/semantics/elaborate.py | 13 +++++++--- lib/cretonne/meta/semantics/smtlib.py | 31 ++++++++++++++++-------- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index ecbb144e16..991e429b18 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -5,7 +5,6 @@ from __future__ import absolute_import from .ast import Def, Var, Apply from .ti import ti_xform, TypeEnv, get_type_env from functools import reduce -from .typevar import TypeVar try: from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa @@ -13,6 +12,7 @@ try: from .ast import Expr, VarMap # noqa from .isa import TargetISA # noqa from .ti import TypeConstraint # noqa + from .typevar import TypeVar # noqa DefApply = Union[Def, Apply] except ImportError: pass @@ -70,12 +70,17 @@ class Rtl(object): def free_vars(self): # type: () -> Set[Var] - """ Return the set of free Vars used in self""" + """Return the set of free Vars corresp. to SSA vals used in self""" def flow_f(s, d): # type: (Set[Var], Def) -> Set[Var] """Compute the change in the set of free vars across a Def""" s = s.difference(set(d.defs)) - return s.union(set(a for a in d.expr.args if isinstance(a, Var))) + uses = set(d.expr.args[i] for i in d.expr.inst.value_opnums) + for v in uses: + assert isinstance(v, Var) + s.add(v) + + return s return reduce(flow_f, reversed(self.rtl), set([])) @@ -107,8 +112,9 @@ class Rtl(object): # type: (Rtl) -> None """ Given that there is only 1 possible concrete typing T for self, assign - a singleton TV with the single type t=T[v] for each Var v \in self. - Its an error to call this on an Rtl with more than 1 possible typing. + a singleton TV with type t=T[v] for each Var v \in self. Its an error + to call this on an Rtl with more than 1 possible typing. This modifies + the Rtl in-place. """ from .ti import ti_rtl, TypeEnv # 1) Infer the types of all vars in res @@ -123,10 +129,8 @@ class Rtl(object): # 3) Assign the only possible type to each variable. for v in typenv.vars: - if v.get_typevar().singleton_type() is not None: - continue - - v.set_typevar(TypeVar.singleton(typing[v].singleton_type())) + assert typing[v].singleton_type() is not None + v.set_typevar(typing[v]) class XForm(object): diff --git a/lib/cretonne/meta/semantics/elaborate.py b/lib/cretonne/meta/semantics/elaborate.py index 335c4ebb86..fc0ca98cc4 100644 --- a/lib/cretonne/meta/semantics/elaborate.py +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -44,15 +44,20 @@ def find_matching_xform(d): def cleanup_semantics(r, outputs): # type: (Rtl, Set[Var]) -> Rtl """ - The elaboration process creates a lot of redundant instruction pairs of the - shape: + The elaboration process creates a lot of redundant prim_to_bv conversions. + Cleanup the following cases: + 1) prim_to_bv/prim_from_bv pair: a.0 << prim_from_bv(bva.0) ... - bva.1 << prim_to_bv(a.0) + bva.1 << prim_to_bv(a.0) <-- redundant, replace by bva.0 ... - Contract these to ease manual inspection. + 2) prim_to_bv/prim_to-bv pair: + bva.0 << prim_to_bv(a) + ... + bva.1 << prim_to_bv(a) <-- redundant, replace by bva.0 + ... """ new_defs = [] # type: List[Def] subst_m = {v: v for v in r.vars()} # type: VarMap diff --git a/lib/cretonne/meta/semantics/smtlib.py b/lib/cretonne/meta/semantics/smtlib.py index 2bf94515ab..f84176dc3c 100644 --- a/lib/cretonne/meta/semantics/smtlib.py +++ b/lib/cretonne/meta/semantics/smtlib.py @@ -10,8 +10,9 @@ from .elaborate import elaborate try: from typing import TYPE_CHECKING, Tuple # noqa - from cdsl.xform import Rtl # noqa + from cdsl.xform import Rtl, XForm # noqa from cdsl.ast import VarMap # noqa + from cdsl.ti import VarTyping # noqa except ImportError: TYPE_CHECKING = False @@ -34,10 +35,12 @@ def to_smt(r): assert r.is_concrete() # Should contain only primitives primitives = set(PRIMITIVES.instructions) - assert all(d.expr.inst in primitives for d in r.rtl) + assert set(d.expr.inst for d in r.rtl).issubset(primitives) q = "" m = {} # type: VarMap + + # Build declarations for any bitvector Vars for v in r.vars(): typ = v.get_typevar().singleton_type() if not isinstance(typ, BVType): @@ -45,9 +48,11 @@ def to_smt(r): q += "(declare-fun {} () {})\n".format(v.name, bvtype_to_sort(typ)) + # Encode each instruction as a equality assertion for d in r.rtl: inst = d.expr.inst + # For prim_to_bv/prim_from_bv just update var_m. No assertion needed if inst == prim_to_bv: assert isinstance(d.expr.args[0], Var) m[d.expr.args[0]] = d.defs[0] @@ -82,13 +87,13 @@ def to_smt(r): .format(df, toW-fromW, arg) elif inst == bvsplit: arg = d.expr.args[0] + assert isinstance(arg, Var) arg_typ = arg.get_typevar().singleton_type() width = arg_typ.width() assert (width % 2 == 0) lo = d.defs[0] hi = d.defs[1] - assert isinstance(arg, Var) exp = "(and " exp += "(= {} ((_ extract {} {}) {})) "\ @@ -97,9 +102,10 @@ def to_smt(r): .format(hi, width-1, width//2, arg) exp += ")" elif inst == bvconcat: + assert isinstance(d.expr.args[0], Var) and \ + isinstance(d.expr.args[1], Var) lo = d.expr.args[0] hi = d.expr.args[1] - assert isinstance(lo, Var) and isinstance(hi, Var) df = d.defs[0] # Z3 Concat expects hi bits first, then lo bits @@ -127,6 +133,14 @@ def equivalent(r1, r2, inp_m, out_m): Otherwise, the satisfying example for the query gives us values for which the two Rtls disagree. """ + # Sanity - inp_m is a bijection from the set of inputs of r1 to the set of + # inputs of r2 + assert set(r1.free_vars()) == set(inp_m.keys()) + assert set(r2.free_vars()) == set(inp_m.values()) + + # Note that the same rule is not expected to hold for out_m due to + # temporaries/intermediates. + # Rename the vars in r1 and r2 with unique suffixes to avoid conflicts src_m = {v: Var(v.name + ".a", v.get_typevar()) for v in r1.vars()} dst_m = {v: Var(v.name + ".b", v.get_typevar()) for v in r2.vars()} @@ -145,7 +159,6 @@ def equivalent(r1, r2, inp_m, out_m): args_eq_exp = "(and \n" for v in r1.free_vars(): - assert v in inp_m args_eq_exp += "(= {} {})\n".format(m1[v], m2[inp_m[v]]) args_eq_exp += ")" @@ -171,12 +184,12 @@ def equivalent(r1, r2, inp_m, out_m): def xform_correct(x, typing): # type: (XForm, VarTyping) -> str """ - Given an XForm x and a concrete variable typing for x typing, build the - smtlib query asserting that x is correct for the given typing. + Given an XForm x and a concrete variable typing for x build the smtlib + query asserting that x is correct for the given typing. """ assert x.ti.permits(typing) - # Create copies of the x.src and x.dst with the concrete types in typing. + # Create copies of the x.src and x.dst with their concrete types src_m = {v: Var(v.name, typing[v]) for v in x.src.vars()} src = x.src.copy(src_m) dst = x.apply(src) @@ -191,8 +204,6 @@ def xform_correct(x, typing): inp_m[src_m[v]] = dst_m[v] elif v.is_output(): out_m[src_m[v]] = dst_m[v] - else: - assert False, "Haven't decided what to do with intermediates yet" # Get the primitive semantic Rtls for src and dst prim_src = elaborate(src) From 222ccae4b24998afff229bc84a02908747d6282f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 08:57:32 -0700 Subject: [PATCH 934/968] Support constant integers in AST expressions. Make it possible to write AST nodes: iconst.i32(imm64(0)). --- lib/cretonne/meta/cdsl/ast.py | 36 ++++++++++++++++++++++++++++-- lib/cretonne/meta/cdsl/operands.py | 16 ++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 4207253684..0a9b5eece4 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -350,8 +350,8 @@ class Apply(Expr): on this instruction. Immediate operands in a source pattern can be either free variables or - constants like `Enumerator`. We don't currently support constraints on - free variables, but we may in the future. + constants like `ConstantInt` and `Enumerator`. We don't currently + support constraints on free variables, but we may in the future. """ pred = None # type: PredNode iform = self.inst.format @@ -415,6 +415,12 @@ class Apply(Expr): else: if (s[self_a] != other_a): return None + elif isinstance(self_a, ConstantInt): + if not isinstance(other_a, ConstantInt): + return None + assert self_a.kind == other_a.kind + if (self_a.value != other_a.value): + return None else: assert isinstance(self_a, Enumerator) @@ -430,6 +436,32 @@ class Apply(Expr): return s +class ConstantInt(Expr): + """ + A value of an integer immediate operand. + + Immediate operands like `imm64` or `offset32` can be specified in AST + expressions using the call syntax: `imm64(5)` which greates a `ConstantInt` + node. + """ + + def __init__(self, kind, value): + # type: (ImmediateKind, int) -> None + self.kind = kind + self.value = value + + def __str__(self): + # type: () -> str + """ + Get the Rust expression form of this constant. + """ + return str(self.value) + + def __repr__(self): + # type: () -> str + return '{}({})'.format(self.kind, self.value) + + class Enumerator(Expr): """ A value of an enumerated immediate operand. diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py index 44bae5d500..abf409a8c4 100644 --- a/lib/cretonne/meta/cdsl/operands.py +++ b/lib/cretonne/meta/cdsl/operands.py @@ -8,7 +8,7 @@ try: from typing import Union, Dict, TYPE_CHECKING # noqa OperandSpec = Union['OperandKind', ValueType, TypeVar] if TYPE_CHECKING: - from .ast import Enumerator # noqa + from .ast import Enumerator, ConstantInt # noqa except ImportError: pass @@ -107,6 +107,20 @@ class ImmediateKind(OperandKind): n=self.name, a=value)) return Enumerator(self, value) + def __call__(self, value): + # type: (int) -> ConstantInt + """ + Create an AST node representing a constant integer: + + iconst(imm64(0)) + """ + from .ast import ConstantInt # noqa + if self.values: + raise AssertionError( + "{}({}): Can't make a constant numeric value for an enum" + .format(self.name, value)) + return ConstantInt(self, value) + def rust_enumerator(self, value): # type: (str) -> str """ From dd5bbc298ecc920c31986794595c93b26fb0a298 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 11:06:09 -0700 Subject: [PATCH 935/968] Allow for multiple legalization patterns for the same opcode. Each input pattern can have a predicate in addition to an opcode being matched. When an opcode has multiple patterns, execute the first pattern with a true predicate. The predicates can be type checks or instruction predicates checking immediate fields. --- lib/cretonne/meta/gen_legalizer.py | 112 +++++++++++++----------- lib/cretonne/meta/test_gen_legalizer.py | 32 +++---- 2 files changed, 73 insertions(+), 71 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 10c5bcf3ac..bee51a928a 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -9,6 +9,7 @@ the input instruction. """ from __future__ import absolute_import from srcgen import Formatter +from collections import defaultdict from base import instructions from cdsl.ast import Var from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\ @@ -18,7 +19,7 @@ from gen_instr import gen_typesets_table from cdsl.typevar import TypeVar try: - from typing import Sequence, List, Dict, Set # noqa + from typing import Sequence, List, Dict, Set, DefaultDict # noqa from cdsl.isa import TargetISA # noqa from cdsl.ast import Def # noqa from cdsl.xform import XForm, XFormGroup # noqa @@ -78,6 +79,11 @@ def emit_runtime_typecheck(check, fmt, type_sets): # type: (TypeConstraint, Formatter, UniqueTable) -> None """ Emit rust code for the given check. + + The emitted code is a statement redefining the `predicate` variable like + this: + + let predicate = predicate && ... """ def build_derived_expr(tv): # type: (TypeVar) -> str @@ -116,33 +122,26 @@ def emit_runtime_typecheck(check, fmt, type_sets): if check.ts not in type_sets.index: type_sets.add(check.ts) ts = type_sets.index[check.ts] - fmt.comment("{} must belong to {}".format(tv, check.ts)) - with fmt.indented('if !TYPE_SETS[{}].contains({}) {{'.format(ts, tv), - '};'): - fmt.line('return false;') + fmt.format( + 'let predicate = predicate && TYPE_SETS[{}].contains({});', + ts, tv) elif (isinstance(check, TypesEqual)): - with fmt.indented('{', '};'): - fmt.line('let a = {};'.format(build_derived_expr(check.tv1))) - fmt.line('let b = {};'.format(build_derived_expr(check.tv2))) - - fmt.comment('On overflow constraint doesn\'t appply') - with fmt.indented('if a.is_none() || b.is_none() {', '};'): - fmt.line('return false;') - - with fmt.indented('if a != b {', '};'): - fmt.line('return false;') + with fmt.indented( + 'let predicate = predicate && match ({}, {}) {{' + .format(build_derived_expr(check.tv1), + build_derived_expr(check.tv2)), '};'): + fmt.line('(Some(a), Some(b)) => a == b,') + fmt.comment('On overflow, constraint doesn\'t appply') + fmt.line('_ => false,') elif (isinstance(check, WiderOrEq)): - with fmt.indented('{', '};'): - fmt.line('let a = {};'.format(build_derived_expr(check.tv1))) - fmt.line('let b = {};'.format(build_derived_expr(check.tv2))) - - fmt.comment('On overflow constraint doesn\'t appply') - with fmt.indented('if a.is_none() || b.is_none() {', '};'): - fmt.line('return false;') - - with fmt.indented('if !a.wider_or_equal(b) {', '};'): - fmt.line('return false;') + with fmt.indented( + 'let predicate = predicate && match ({}, {}) {{' + .format(build_derived_expr(check.tv1), + build_derived_expr(check.tv2)), '};'): + fmt.line('(Some(a), Some(b)) => a.wider_or_equal(b),') + fmt.comment('On overflow, constraint doesn\'t appply') + fmt.line('_ => false,') else: assert False, "Unknown check {}".format(check) @@ -216,14 +215,12 @@ def unwrap_inst(iref, node, fmt): replace_inst = True else: # Boring case: Detach the result values, capture them in locals. - fmt.comment('Detaching results.') for d in node.defs: fmt.line('let {};'.format(d)) with fmt.indented('{', '}'): fmt.line('let r = dfg.inst_results(inst);') for i in range(len(node.defs)): fmt.line('{} = r[{}];'.format(node.defs[i], i)) - fmt.line('dfg.clear_results(inst);') for d in node.defs: if d.has_free_typevar(): fmt.line( @@ -312,7 +309,7 @@ def emit_dst_inst(node, fmt): def gen_xform(xform, fmt, type_sets): # type: (XForm, Formatter, UniqueTable) -> None """ - Emit code for `xform`, assuming the the opcode of xform's root instruction + Emit code for `xform`, assuming that the opcode of xform's root instruction has already been matched. `inst: Inst` is the variable to be replaced. It is pointed to by `pos: @@ -323,24 +320,33 @@ def gen_xform(xform, fmt, type_sets): # variables. replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt) - # We could support instruction predicates, but not yet. Should we just - # return false if it fails? What about multiple patterns with different - # predicates for the same opcode? + # Check instruction predicate and emit type checks. instp = xform.src.rtl[0].expr.inst_predicate() - assert instp is None, "Instruction predicates not supported in legalizer" + # TODO: The instruction predicate should be evaluated with all the inst + # immediate fields available. Probably by unwrap_inst(). + fmt.format('let predicate = {};', + instp.rust_predicate(0) if instp else 'true') # Emit any runtime checks. for check in get_runtime_typechecks(xform): emit_runtime_typecheck(check, fmt, type_sets) - # Emit the destination pattern. - for dst in xform.dst.rtl: - emit_dst_inst(dst, fmt) + # Guard the actual expansion by `predicate`. + with fmt.indented('if predicate {', '}'): + # If we're going to delete `inst`, we need to detach its results first + # so they can be reattached during pattern expansion. + if not replace_inst: + fmt.line('dfg.clear_results(inst);') - # Delete the original instruction if we didn't have an opportunity to - # replace it. - if not replace_inst: - fmt.line('assert_eq!(pos.remove_inst(), inst);') + # Emit the destination pattern. + for dst in xform.dst.rtl: + emit_dst_inst(dst, fmt) + + # Delete the original instruction if we didn't have an opportunity to + # replace it. + if not replace_inst: + fmt.line('assert_eq!(pos.remove_inst(), inst);') + fmt.line('return true;') def gen_xform_group(xgrp, fmt, type_sets): @@ -358,19 +364,27 @@ def gen_xform_group(xgrp, fmt, type_sets): # pointing at an instruction. fmt.line('let inst = pos.current_inst().expect("need instruction");') + # Group the xforms by opcode so we can generate a big switch. + # Preserve ordering. + xforms = defaultdict(list) # type: DefaultDict[str, List[XForm]] + for xform in xgrp.xforms: + inst = xform.src.rtl[0].expr.inst + xforms[inst.camel_name].append(xform) + with fmt.indented('match dfg[inst].opcode() {', '}'): - for xform in xgrp.xforms: - inst = xform.src.rtl[0].expr.inst + for camel_name in sorted(xforms.keys()): with fmt.indented( - 'ir::Opcode::{} => {{'.format(inst.camel_name), '}'): - gen_xform(xform, fmt, type_sets) + 'ir::Opcode::{} => {{'.format(camel_name), '}'): + for xform in xforms[camel_name]: + gen_xform(xform, fmt, type_sets) # We'll assume there are uncovered opcodes. - if xgrp.chain: - fmt.format('_ => return {}(dfg, cfg, pos),', - xgrp.chain.rust_name()) - else: - fmt.line('_ => return false,') - fmt.line('true') + fmt.line('_ => {},') + + # If we fall through, nothing was expanded. Call the chain if any. + if xgrp.chain: + fmt.format('{}(dfg, cfg, pos)', xgrp.chain.rust_name()) + else: + fmt.line('false') def gen_isa(isa, fmt, shared_groups): diff --git a/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py index 6a317d887b..793555a42c 100644 --- a/lib/cretonne/meta/test_gen_legalizer.py +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -49,39 +49,27 @@ def typeset_check(v, ts): # type: (Var, TypeSet) -> CheckProducer return lambda typesets: format_check( typesets, - 'if !TYPE_SETS[{}].contains(typeof_{}) ' + - '{{\n return false;\n}};\n', ts, v) + 'let predicate = predicate && TYPE_SETS[{}].contains(typeof_{});\n', + ts, v) def equiv_check(tv1, tv2): - # type: (TypeVar, TypeVar) -> CheckProducer + # type: (str, str) -> CheckProducer return lambda typesets: format_check( typesets, - '{{\n' + - ' let a = {};\n' + - ' let b = {};\n' + - ' if a.is_none() || b.is_none() {{\n' + - ' return false;\n' + - ' }};\n' + - ' if a != b {{\n' + - ' return false;\n' + - ' }};\n' + + 'let predicate = predicate && match ({}, {}) {{\n' + ' (Some(a), Some(b)) => a == b,\n' + ' _ => false,\n' '}};\n', tv1, tv2) def wider_check(tv1, tv2): - # type: (TypeVar, TypeVar) -> CheckProducer + # type: (str, str) -> CheckProducer return lambda typesets: format_check( typesets, - '{{\n' + - ' let a = {};\n' + - ' let b = {};\n' + - ' if a.is_none() || b.is_none() {{\n' + - ' return false;\n' + - ' }};\n' + - ' if !a.wider_or_equal(b) {{\n' + - ' return false;\n' + - ' }};\n' + + 'let predicate = predicate && match ({}, {}) {{\n' + ' (Some(a), Some(b)) => a.wider_or_equal(b),\n' + ' _ => false,\n' '}};\n', tv1, tv2) From 9df0b0930137dc55c4338e7257de887406e79366 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 14:45:56 -0700 Subject: [PATCH 936/968] Add an inst.all_typevars() method. Get all type variables controlling an instruction, whether it is polymorphic or not. --- lib/cretonne/meta/cdsl/instructions.py | 10 ++++++++++ lib/cretonne/meta/cdsl/ti.py | 6 +----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py index 6dec0b7dff..30c27c6649 100644 --- a/lib/cretonne/meta/cdsl/instructions.py +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -267,6 +267,16 @@ class Instruction(object): return other_tvs + def all_typevars(self): + # type: () -> List[TypeVar] + """ + Get a list of all type variables in the instruction. + """ + if self.is_polymorphic: + return [self.ctrl_typevar] + self.other_typevars + else: + return [] + @staticmethod def _to_operand_tuple(x): # type: (Union[Sequence[Operand], Operand]) -> Tuple[Operand, ...] diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py index 028579857d..b2673366be 100644 --- a/lib/cretonne/meta/cdsl/ti.py +++ b/lib/cretonne/meta/cdsl/ti.py @@ -796,11 +796,7 @@ def ti_def(definition, typ): # Create a dict m mapping each free typevar in the signature of definition # to a fresh copy of itself. - if inst.is_polymorphic: - free_formal_tvs = [inst.ctrl_typevar] + inst.other_typevars - else: - free_formal_tvs = [] - + free_formal_tvs = inst.all_typevars() m = {tv: tv.get_fresh_copy(str(typ.get_uid())) for tv in free_formal_tvs} # Update m with any explicitly bound type vars From e2bf4f898160203a071aaed30d890e927cd89b67 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 14:52:24 -0700 Subject: [PATCH 937/968] Include bound typevars in the instruction predicate. We already do this for the encoding tables, but the instruction predicates computed by Apply.inst_predicate() did not include them. Make sure we don't duplicate the type check in the Encoding constructor when passed an Apply AST node. --- lib/cretonne/meta/cdsl/ast.py | 9 ++++++++- lib/cretonne/meta/cdsl/isa.py | 19 ++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 0a9b5eece4..aa53e7b9b5 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -7,7 +7,7 @@ for patern matching an rewriting of cretonne instructions. from __future__ import absolute_import from . import instructions from .typevar import TypeVar -from .predicates import IsEqual, And +from .predicates import IsEqual, And, TypePredicate try: from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa @@ -367,6 +367,13 @@ class Apply(Expr): pred = And.combine(pred, IsEqual(ffield, arg)) + # Add checks for any bound type variables. + for bound_ty, tv in zip(self.typevars, self.inst.all_typevars()): + if bound_ty is None: + continue + type_chk = TypePredicate.typevar_check(self.inst, tv, bound_ty) + pred = And.combine(pred, type_chk) + return pred def copy(self, m): diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 3e903408ef..3e971e7a5a 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -420,6 +420,16 @@ class Encoding(object): else: self.inst, self.typevars = inst.fully_bound() + # Add secondary type variables to the instruction predicate. + # This is already included by Apply.inst_predicate() above. + if len(self.typevars) > 1: + for tv, vt in zip(self.inst.other_typevars, self.typevars[1:]): + # A None tv is an 'any' wild card: `ishl.i32.any`. + if vt is None: + continue + typred = TypePredicate.typevar_check(self.inst, tv, vt) + instp = And.combine(instp, typred) + self.cpumode = cpumode assert self.inst.format == recipe.format, ( "Format {} must match recipe: {}".format( @@ -433,15 +443,6 @@ class Encoding(object): self.recipe = recipe self.encbits = encbits - # Add secondary type variables to the instruction predicate. - if len(self.typevars) > 1: - for tv, vt in zip(self.inst.other_typevars, self.typevars[1:]): - # A None tv is an 'any' wild card: `ishl.i32.any`. - if vt is None: - continue - typred = TypePredicate.typevar_check(self.inst, tv, vt) - instp = And.combine(instp, typred) - # Record specific predicates. Note that the recipe also has predicates. self.instp = self.cpumode.isa.unique_pred(instp) self.isap = self.cpumode.isa.unique_pred(isap) From 1968ebad58cdd5a8ebe875df32166a66c39d20f8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 15:21:10 -0700 Subject: [PATCH 938/968] Evaluate instruction predicates during legalization. The generated legalization code needs to evaluate any instruction patterns on the input pattern being matched. Emit predicate checking code inside the InstructionFormat pattern match where all the instruction's immediate fields are available to the predicate code. Also make sure an `args` array is available for any type predicates to evaluate correctly. --- lib/cretonne/meta/gen_legalizer.py | 37 ++++++++++++++---------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index bee51a928a..0a75fca8af 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -154,6 +154,9 @@ def unwrap_inst(iref, node, fmt): Create local variables named after the `Var` instances in `node`. + Also create a local variable named `predicate` with the value of the + evaluated instruction predicate, or `true` if the node has no predicate. + :param iref: Name of the `Inst` reference to unwrap. :param node: `Def` node providing variable names. :returns: True if the instruction arguments were not detached, expecting a @@ -166,7 +169,7 @@ def unwrap_inst(iref, node, fmt): # The tuple of locals we're extracting is `expr.args`. with fmt.indented( - 'let ({}) = if let ir::InstructionData::{} {{' + 'let ({}, predicate) = if let ir::InstructionData::{} {{' .format(', '.join(map(str, expr.args)), iform.name), '};'): # Fields are encoded directly. for f in iform.imm_fields: @@ -179,20 +182,20 @@ def unwrap_inst(iref, node, fmt): fmt.outdented_line('} = dfg[inst] {') if iform.has_value_list: fmt.line('let args = args.as_slice(&dfg.value_lists);') + elif nvops == 1: + fmt.line('let args = [arg];') # Generate the values for the tuple. - outs = list() - for opnum, op in enumerate(expr.inst.ins): - if op.is_immediate(): - n = expr.inst.imm_opnums.index(opnum) - outs.append(iform.imm_fields[n].member) - elif op.is_value(): - if nvops == 1: - arg = 'arg' - else: + with fmt.indented('(', ')'): + for opnum, op in enumerate(expr.inst.ins): + if op.is_immediate(): + n = expr.inst.imm_opnums.index(opnum) + fmt.format('{},', iform.imm_fields[n].member) + elif op.is_value(): n = expr.inst.value_opnums.index(opnum) - arg = 'args[{}]'.format(n) - outs.append('dfg.resolve_aliases({})'.format(arg)) - fmt.line('({})'.format(', '.join(outs))) + fmt.format('dfg.resolve_aliases(args[{}]),', n) + # Evaluate the instruction predicate, if any. + instp = expr.inst_predicate() + fmt.line(instp.rust_predicate(0) if instp else 'true') fmt.outdented_line('} else {') fmt.line('unreachable!("bad instruction format")') @@ -320,14 +323,8 @@ def gen_xform(xform, fmt, type_sets): # variables. replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt) - # Check instruction predicate and emit type checks. - instp = xform.src.rtl[0].expr.inst_predicate() - # TODO: The instruction predicate should be evaluated with all the inst - # immediate fields available. Probably by unwrap_inst(). - fmt.format('let predicate = {};', - instp.rust_predicate(0) if instp else 'true') - # Emit any runtime checks. + # These will rebind `predicate` emitted by unwrap_inst(). for check in get_runtime_typechecks(xform): emit_runtime_typecheck(check, fmt, type_sets) From 6609d7baf4e8f19e4dfb5099b3a42ebe3d83b2c9 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 16:13:51 -0700 Subject: [PATCH 939/968] Try to depend only on the `ir` module being in scope. Generated code should used qualified names assuming that `ir` is in scope, not everything else. --- lib/cretonne/meta/cdsl/types.py | 2 +- lib/cretonne/meta/gen_encoding.py | 6 +++--- lib/cretonne/meta/gen_instr.py | 4 ++-- lib/cretonne/src/ir/instructions.rs | 1 + lib/cretonne/src/isa/arm32/enc_tables.rs | 2 +- lib/cretonne/src/isa/arm64/enc_tables.rs | 2 +- lib/cretonne/src/isa/intel/enc_tables.rs | 2 +- lib/cretonne/src/isa/riscv/enc_tables.rs | 2 +- lib/cretonne/src/legalizer/mod.rs | 1 - 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/cretonne/meta/cdsl/types.py b/lib/cretonne/meta/cdsl/types.py index 53a1dd79ad..8ae66ee359 100644 --- a/lib/cretonne/meta/cdsl/types.py +++ b/lib/cretonne/meta/cdsl/types.py @@ -39,7 +39,7 @@ class ValueType(object): def rust_name(self): # type: () -> str - return 'types::' + self.name.upper() + return 'ir::types::' + self.name.upper() @staticmethod def by_name(name): diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index c3b3ddb87c..0516b5c4c8 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -653,7 +653,7 @@ def emit_level2_hashtables(level2_hashtables, offt, level2_doc, fmt): if entry: fmt.line( 'Level2Entry ' + - '{{ opcode: Some(Opcode::{}), offset: {:#08x} }},' + '{{ opcode: Some(ir::Opcode::{}), offset: {:#08x} }},' .format(entry.inst.camel_name, entry.offset)) else: fmt.line( @@ -678,7 +678,7 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): # Empty hash table entry. Include the default legalization action. if not level2: fmt.format( - 'Level1Entry {{ ty: types::VOID, log2len: !0, ' + 'Level1Entry {{ ty: ir::types::VOID, log2len: !0, ' 'offset: 0, legalize: {} }},', level1.legalize_code) continue @@ -686,7 +686,7 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): if level2.ty is not None: tyname = level2.ty.rust_name() else: - tyname = 'types::VOID' + tyname = 'ir::types::VOID' lcode = cpumode.isa.legalize_code(level2.legalize) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index b0c9943806..ade66e643c 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -350,10 +350,10 @@ def gen_typesets_table(fmt, type_sets): fmt.comment('Table of value type sets.') assert len(type_sets.table) <= typeset_limit, "Too many type sets" with fmt.indented( - 'const TYPE_SETS : [ValueTypeSet; {}] = [' + 'const TYPE_SETS : [ir::instructions::ValueTypeSet; {}] = [' .format(len(type_sets.table)), '];'): for ts in type_sets.table: - with fmt.indented('ValueTypeSet {', '},'): + with fmt.indented('ir::instructions::ValueTypeSet {', '},'): ts.emit_fields(fmt) diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 279a7a37a9..900dd56e88 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -10,6 +10,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; +use ir; use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::*; diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index eeb8466669..f71dd33f87 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -1,6 +1,6 @@ //! Encoding tables for ARM32 ISA. -use ir::types; +use ir; use isa; use isa::constraints::*; use isa::enc_tables::*; diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index c2c087e039..6007450cb5 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -1,6 +1,6 @@ //! Encoding tables for ARM64 ISA. -use ir::types; +use ir; use isa; use isa::constraints::*; use isa::enc_tables::*; diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 62ef7b3cd6..ad66cb1f7f 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,6 +1,6 @@ //! Encoding tables for Intel ISAs. -use ir::{self, types, Opcode}; +use ir; use isa; use isa::constraints::*; use isa::enc_tables::*; diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 0d11d006c5..f095e67243 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -1,7 +1,7 @@ //! Encoding tables for RISC-V. use ir::condcodes::IntCC; -use ir::{self, types, Opcode}; +use ir; use isa; use isa::constraints::*; use isa::enc_tables::*; diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index c5178a97ee..c6a844c3c1 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -19,7 +19,6 @@ use ir::{self, Function, Cursor}; use ir::condcodes::IntCC; use isa::TargetIsa; use bitset::BitSet; -use ir::instructions::ValueTypeSet; mod boundary; mod split; From be8331d0a066f04a27fa8b3a416277cbd33a2a7e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 28 Jul 2017 08:46:45 -0700 Subject: [PATCH 940/968] Add Intel legalization for division and multiplication. These operations need custom legalization in order to use Intel's div and idiv instructions. --- filetests/wasm/i32-arith.cton | 26 +++++++-- filetests/wasm/i64-arith.cton | 26 +++++++-- lib/cretonne/meta/base/legalize.py | 16 ++++++ lib/cretonne/meta/isa/intel/encodings.py | 7 +-- lib/cretonne/meta/isa/intel/instructions.py | 14 ++--- lib/cretonne/meta/isa/intel/legalize.py | 58 +++++++++++++++++++++ lib/cretonne/src/isa/intel/enc_tables.rs | 3 +- 7 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 lib/cretonne/meta/isa/intel/legalize.py diff --git a/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton index b345847eb9..fe9cf19883 100644 --- a/filetests/wasm/i32-arith.cton +++ b/filetests/wasm/i32-arith.cton @@ -55,9 +55,29 @@ ebb0(v0: i32, v1: i32): return v2 } -; function %i32_div(i32, i32) -> i32 -; function %i32_rem_s(i32, i32) -> i32 -; function %i32_rem_u(i32, i32) -> i32 +function %i32_div_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = sdiv v0, v1 + return v2 +} + +function %i32_div_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = udiv v0, v1 + return v2 +} + +function %i32_rem_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = srem v0, v1 + return v2 +} + +function %i32_rem_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = urem v0, v1 + return v2 +} function %i32_and(i32, i32) -> i32 { ebb0(v0: i32, v1: i32): diff --git a/filetests/wasm/i64-arith.cton b/filetests/wasm/i64-arith.cton index a7cce18d3c..4e8cdc06df 100644 --- a/filetests/wasm/i64-arith.cton +++ b/filetests/wasm/i64-arith.cton @@ -52,9 +52,29 @@ ebb0(v0: i64, v1: i64): return v2 } -; function %i64_div(i64, i64) -> i64 -; function %i64_rem_s(i64, i64) -> i64 -; function %i64_rem_u(i64, i64) -> i64 +function %i32_div_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = sdiv v0, v1 + return v2 +} + +function %i32_div_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = udiv v0, v1 + return v2 +} + +function %i32_rem_s(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = srem v0, v1 + return v2 +} + +function %i32_rem_u(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = urem v0, v1 + return v2 +} function %i64_and(i64, i64) -> i64 { ebb0(v0: i64, v1: i64): diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index 1afefcda89..c250d53ce2 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -14,6 +14,8 @@ from .instructions import band, bor, bxor, isplit, iconcat from .instructions import bnot, band_not, bor_not, bxor_not from .instructions import icmp, icmp_imm from .instructions import iconst, bint +from .instructions import ishl, ishl_imm, sshr, sshr_imm, ushr, ushr_imm +from .instructions import rotl, rotl_imm, rotr, rotr_imm from cdsl.ast import Var from cdsl.xform import Rtl, XFormGroup @@ -153,6 +155,20 @@ expand.legalize( a << iadd(x, a1) )) +# Rotates and shifts. +for inst_imm, inst in [ + (rotl_imm, rotl), + (rotr_imm, rotr), + (ishl_imm, ishl), + (sshr_imm, sshr), + (ushr_imm, ushr)]: + expand.legalize( + a << inst_imm(x, y), + Rtl( + a1 << iconst.i32(y), + a << inst(x, a1) + )) + expand.legalize( a << icmp_imm(cc, x, y), Rtl( diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 44f8c16677..448252f43a 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -9,6 +9,7 @@ from .defs import I32, I64 from . import recipes as r from . import settings as cfg from . import instructions as x86 +from .legalize import intel_expand from base.legalize import narrow, expand try: @@ -21,14 +22,14 @@ except ImportError: I32.legalize_type( default=narrow, - i32=expand, + i32=intel_expand, f32=expand, f64=expand) I64.legalize_type( default=narrow, - i32=expand, - i64=expand, + i32=intel_expand, + i64=intel_expand, f32=expand, f64=expand) diff --git a/lib/cretonne/meta/isa/intel/instructions.py b/lib/cretonne/meta/isa/intel/instructions.py index c9d87c43f3..7921b2f431 100644 --- a/lib/cretonne/meta/isa/intel/instructions.py +++ b/lib/cretonne/meta/isa/intel/instructions.py @@ -6,17 +6,19 @@ target ISA. """ from cdsl.operands import Operand +from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup -from base.instructions import iB GROUP = InstructionGroup("x86", "Intel-specific instruction set") -nlo = Operand('nlo', iB, doc='Low part of numerator') -nhi = Operand('nhi', iB, doc='High part of numerator') -d = Operand('d', iB, doc='Denominator') -q = Operand('q', iB, doc='Quotient') -r = Operand('r', iB, doc='Remainder') +iWord = TypeVar('iWord', 'A scalar integer machine word', ints=(32, 64)) + +nlo = Operand('nlo', iWord, doc='Low part of numerator') +nhi = Operand('nhi', iWord, doc='High part of numerator') +d = Operand('d', iWord, doc='Denominator') +q = Operand('q', iWord, doc='Quotient') +r = Operand('r', iWord, doc='Remainder') udivmodx = Instruction( 'x86_udivmodx', r""" diff --git a/lib/cretonne/meta/isa/intel/legalize.py b/lib/cretonne/meta/isa/intel/legalize.py new file mode 100644 index 0000000000..cc46846d81 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/legalize.py @@ -0,0 +1,58 @@ +""" +Custom legalization patterns for Intel. +""" +from __future__ import absolute_import +from cdsl.ast import Var +from cdsl.xform import Rtl, XFormGroup +from base.immediates import imm64 +from base.types import i32, i64 +from base import legalize as shared +from base import instructions as insts +from . import instructions as x86 +from .defs import ISA + +intel_expand = XFormGroup( + 'intel_expand', + """ + Legalize instructions by expansion. + + Use Intel-specific instructions if needed. + """, + isa=ISA, chain=shared.expand) + +a = Var('a') +dead = Var('dead') +x = Var('x') +xhi = Var('xhi') +y = Var('y') + +# +# Division and remainder. +# +intel_expand.legalize( + a << insts.udiv(x, y), + Rtl( + xhi << insts.iconst(imm64(0)), + (a, dead) << x86.udivmodx(x, xhi, y) + )) + +intel_expand.legalize( + a << insts.urem(x, y), + Rtl( + xhi << insts.iconst(imm64(0)), + (dead, a) << x86.udivmodx(x, xhi, y) + )) + +for ty in [i32, i64]: + intel_expand.legalize( + a << insts.sdiv.bind(ty)(x, y), + Rtl( + xhi << insts.sshr_imm(x, imm64(ty.lane_bits() - 1)), + (a, dead) << x86.sdivmodx(x, xhi, y) + )) + intel_expand.legalize( + a << insts.srem.bind(ty)(x, y), + Rtl( + xhi << insts.sshr_imm(x, imm64(ty.lane_bits() - 1)), + (dead, a) << x86.sdivmodx(x, xhi, y) + )) diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index ad66cb1f7f..35ca9a9d76 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,10 +1,11 @@ //! Encoding tables for Intel ISAs. +use bitset::BitSet; use ir; -use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; +use isa; use predicates; use super::registers::*; From b74723cb6848d8f7a4038088c52c4904118182c9 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Mon, 31 Jul 2017 14:52:39 -0700 Subject: [PATCH 941/968] Added Intel x86-64 encodings for 64bit loads and store instructions (#127) * Added Intel x86-64 encodings for 64bit loads and store instructions * Using GPR registers instead of ABCD for istore8 with REX prefix Fixed testing of 64bit intel encoding * Emit REX and REX-less encodings for optional REX prefix Value renumbering in binary64.cton --- filetests/isa/intel/binary64.cton | 376 ++++++++++++++++++----- lib/cretonne/meta/base/instructions.py | 4 +- lib/cretonne/meta/isa/intel/encodings.py | 86 ++++-- lib/cretonne/src/isa/intel/binemit.rs | 9 + 4 files changed, 373 insertions(+), 102 deletions(-) diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index 3d93ba86ec..bc19e039d3 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -145,52 +145,199 @@ ebb0: ; asm: movq %rcx, %r10 [-,%r10] v112 = copy v1 ; bin: 49 89 ca + ; Load/Store instructions. + + ; Register indirect addressing with no displacement. + + ; asm: movq %rcx, (%rsi) + store v1, v2 ; bin: 48 89 0e + ; asm: movq %rsi, (%rcx) + store v2, v1 ; bin: 48 89 31 + ; asm: movl %ecx, (%rsi) + istore32 v1, v2 ; bin: 40 89 0e + ; asm: movl %esi, (%rcx) + istore32 v2, v1 ; bin: 40 89 31 + ; asm: movw %cx, (%rsi) + istore16 v1, v2 ; bin: 66 40 89 0e + ; asm: movw %si, (%rcx) + istore16 v2, v1 ; bin: 66 40 89 31 + ; asm: movb %cl, (%rsi) + istore8 v1, v2 ; bin: 40 88 0e + ; asm: movb %sil, (%rcx) + istore8 v2, v1 ; bin: 40 88 31 + + ; asm: movq (%rcx), %rdi + [-,%rdi] v120 = load.i64 v1 ; bin: 48 8b 39 + ; asm: movq (%rsi), %rdx + [-,%rdx] v121 = load.i64 v2 ; bin: 48 8b 16 + ; asm: movl (%rcx), %edi + [-,%rdi] v122 = uload32.i64 v1 ; bin: 40 8b 39 + ; asm: movl (%rsi), %edx + [-,%rdx] v123 = uload32.i64 v2 ; bin: 40 8b 16 + ; asm: movslq (%rcx), %rdi + [-,%rdi] v124 = sload32.i64 v1 ; bin: 48 63 39 + ; asm: movslq (%rsi), %rdx + [-,%rdx] v125 = sload32.i64 v2 ; bin: 48 63 16 + ; asm: movzwq (%rcx), %rdi + [-,%rdi] v126 = uload16.i64 v1 ; bin: 48 0f b7 39 + ; asm: movzwq (%rsi), %rdx + [-,%rdx] v127 = uload16.i64 v2 ; bin: 48 0f b7 16 + ; asm: movswq (%rcx), %rdi + [-,%rdi] v128 = sload16.i64 v1 ; bin: 48 0f bf 39 + ; asm: movswq (%rsi), %rdx + [-,%rdx] v129 = sload16.i64 v2 ; bin: 48 0f bf 16 + ; asm: movzbq (%rcx), %rdi + [-,%rdi] v130 = uload8.i64 v1 ; bin: 48 0f b6 39 + ; asm: movzbq (%rsi), %rdx + [-,%rdx] v131 = uload8.i64 v2 ; bin: 48 0f b6 16 + ; asm: movsbq (%rcx), %rdi + [-,%rdi] v132 = sload8.i64 v1 ; bin: 48 0f be 39 + ; asm: movsbq (%rsi), %rdx + [-,%rdx] v133 = sload8.i64 v2 ; bin: 48 0f be 16 + + ; Register-indirect with 8-bit signed displacement. + + ; asm: movq %rcx, 100(%rsi) + store v1, v2+100 ; bin: 48 89 4e 64 + ; asm: movq %rsi, -100(%rcx) + store v2, v1-100 ; bin: 48 89 71 9c + ; asm: movl %ecx, 100(%rsi) + istore32 v1, v2+100 ; bin: 40 89 4e 64 + ; asm: movl %esi, -100(%rcx) + istore32 v2, v1-100 ; bin: 40 89 71 9c + ; asm: movw %cx, 100(%rsi) + istore16 v1, v2+100 ; bin: 66 40 89 4e 64 + ; asm: movw %si, -100(%rcx) + istore16 v2, v1-100 ; bin: 66 40 89 71 9c + ; asm: movb %cl, 100(%rsi) + istore8 v1, v2+100 ; bin: 40 88 4e 64 + ; asm: movb %sil, 100(%rcx) + istore8 v2, v1+100 ; bin: 40 88 71 64 + + ; asm: movq 50(%rcx), %rdi + [-,%rdi] v140 = load.i64 v1+50 ; bin: 48 8b 79 32 + ; asm: movq -50(%rsi), %rdx + [-,%rdx] v141 = load.i64 v2-50 ; bin: 48 8b 56 ce + ; asm: movl 50(%rcx), %edi + [-,%rdi] v142 = uload32.i64 v1+50 ; bin: 40 8b 79 32 + ; asm: movl -50(%rsi), %edx + [-,%rdx] v143 = uload32.i64 v2-50 ; bin: 40 8b 56 ce + ; asm: movslq 50(%rcx), %rdi + [-,%rdi] v144 = sload32.i64 v1+50 ; bin: 48 63 79 32 + ; asm: movslq -50(%rsi), %rdx + [-,%rdx] v145 = sload32.i64 v2-50 ; bin: 48 63 56 ce + ; asm: movzwq 50(%rcx), %rdi + [-,%rdi] v146 = uload16.i64 v1+50 ; bin: 48 0f b7 79 32 + ; asm: movzwq -50(%rsi), %rdx + [-,%rdx] v147 = uload16.i64 v2-50 ; bin: 48 0f b7 56 ce + ; asm: movswq 50(%rcx), %rdi + [-,%rdi] v148 = sload16.i64 v1+50 ; bin: 48 0f bf 79 32 + ; asm: movswq -50(%rsi), %rdx + [-,%rdx] v149 = sload16.i64 v2-50 ; bin: 48 0f bf 56 ce + ; asm: movzbq 50(%rcx), %rdi + [-,%rdi] v150 = uload8.i64 v1+50 ; bin: 48 0f b6 79 32 + ; asm: movzbq -50(%rsi), %rdx + [-,%rdx] v151 = uload8.i64 v2-50 ; bin: 48 0f b6 56 ce + ; asm: movsbq 50(%rcx), %rdi + [-,%rdi] v152 = sload8.i64 v1+50 ; bin: 48 0f be 79 32 + ; asm: movsbq -50(%rsi), %rdx + [-,%rdx] v153 = sload8.i64 v2-50 ; bin: 48 0f be 56 ce + + ; Register-indirect with 32-bit signed displacement. + + ; asm: movq %rcx, 10000(%rsi) + store v1, v2+10000 ; bin: 48 89 8e 00002710 + ; asm: movq %rsi, -10000(%rcx) + store v2, v1-10000 ; bin: 48 89 b1 ffffd8f0 + ; asm: movl %ecx, 10000(%rsi) + istore32 v1, v2+10000 ; bin: 40 89 8e 00002710 + ; asm: movl %esi, -10000(%rcx) + istore32 v2, v1-10000 ; bin: 40 89 b1 ffffd8f0 + ; asm: movw %cx, 10000(%rsi) + istore16 v1, v2+10000 ; bin: 66 40 89 8e 00002710 + ; asm: movw %si, -10000(%rcx) + istore16 v2, v1-10000 ; bin: 66 40 89 b1 ffffd8f0 + ; asm: movb %cl, 10000(%rsi) + istore8 v1, v2+10000 ; bin: 40 88 8e 00002710 + ; asm: movb %sil, 10000(%rcx) + istore8 v2, v1+10000 ; bin: 40 88 b1 00002710 + + ; asm: movq 50000(%rcx), %rdi + [-,%rdi] v160 = load.i64 v1+50000 ; bin: 48 8b b9 0000c350 + ; asm: movq -50000(%rsi), %rdx + [-,%rdx] v161 = load.i64 v2-50000 ; bin: 48 8b 96 ffff3cb0 + ; asm: movl 50000(%rcx), %edi + [-,%rdi] v162 = uload32.i64 v1+50000 ; bin: 40 8b b9 0000c350 + ; asm: movl -50000(%rsi), %edx + [-,%rdx] v163 = uload32.i64 v2-50000 ; bin: 40 8b 96 ffff3cb0 + ; asm: movslq 50000(%rcx), %rdi + [-,%rdi] v164 = sload32.i64 v1+50000 ; bin: 48 63 b9 0000c350 + ; asm: movslq -50000(%rsi), %rdx + [-,%rdx] v165 = sload32.i64 v2-50000 ; bin: 48 63 96 ffff3cb0 + ; asm: movzwq 50000(%rcx), %rdi + [-,%rdi] v166 = uload16.i64 v1+50000 ; bin: 48 0f b7 b9 0000c350 + ; asm: movzwq -50000(%rsi), %rdx + [-,%rdx] v167 = uload16.i64 v2-50000 ; bin: 48 0f b7 96 ffff3cb0 + ; asm: movswq 50000(%rcx), %rdi + [-,%rdi] v168 = sload16.i64 v1+50000 ; bin: 48 0f bf b9 0000c350 + ; asm: movswq -50000(%rsi), %rdx + [-,%rdx] v169 = sload16.i64 v2-50000 ; bin: 48 0f bf 96 ffff3cb0 + ; asm: movzbq 50000(%rcx), %rdi + [-,%rdi] v170 = uload8.i64 v1+50000 ; bin: 48 0f b6 b9 0000c350 + ; asm: movzbq -50000(%rsi), %rdx + [-,%rdx] v171 = uload8.i64 v2-50000 ; bin: 48 0f b6 96 ffff3cb0 + ; asm: movsbq 50000(%rcx), %rdi + [-,%rdi] v172 = sload8.i64 v1+50000 ; bin: 48 0f be b9 0000c350 + ; asm: movsbq -50000(%rsi), %rdx + [-,%rdx] v173 = sload8.i64 v2-50000 ; bin: 48 0f be 96 ffff3cb0 + + ; More arithmetic. ; asm: imulq %rsi, %rcx - [-,%rcx] v120 = imul v1, v2 ; bin: 48 0f af ce + [-,%rcx] v180 = imul v1, v2 ; bin: 48 0f af ce ; asm: imulq %r10, %rsi - [-,%rsi] v121 = imul v2, v3 ; bin: 49 0f af f2 + [-,%rsi] v181 = imul v2, v3 ; bin: 49 0f af f2 ; asm: imulq %rcx, %r10 - [-,%r10] v122 = imul v3, v1 ; bin: 4c 0f af d1 + [-,%r10] v182 = imul v3, v1 ; bin: 4c 0f af d1 - [-,%rax] v130 = iconst.i64 1 - [-,%rdx] v131 = iconst.i64 2 + [-,%rax] v190 = iconst.i64 1 + [-,%rdx] v191 = iconst.i64 2 ; asm: idivq %rcx - [-,%rax,%rdx] v132, v133 = x86_sdivmodx v130, v131, v1 ; bin: 48 f7 f9 + [-,%rax,%rdx] v192, v193 = x86_sdivmodx v130, v131, v1 ; bin: 48 f7 f9 ; asm: idivq %rsi - [-,%rax,%rdx] v134, v135 = x86_sdivmodx v130, v131, v2 ; bin: 48 f7 fe + [-,%rax,%rdx] v194, v195 = x86_sdivmodx v130, v131, v2 ; bin: 48 f7 fe ; asm: idivq %r10 - [-,%rax,%rdx] v136, v137 = x86_sdivmodx v130, v131, v3 ; bin: 49 f7 fa + [-,%rax,%rdx] v196, v197 = x86_sdivmodx v130, v131, v3 ; bin: 49 f7 fa ; asm: divq %rcx - [-,%rax,%rdx] v138, v139 = x86_udivmodx v130, v131, v1 ; bin: 48 f7 f1 + [-,%rax,%rdx] v198, v199 = x86_udivmodx v130, v131, v1 ; bin: 48 f7 f1 ; asm: divq %rsi - [-,%rax,%rdx] v140, v141 = x86_udivmodx v130, v131, v2 ; bin: 48 f7 f6 + [-,%rax,%rdx] v200, v201 = x86_udivmodx v130, v131, v2 ; bin: 48 f7 f6 ; asm: divq %r10 - [-,%rax,%rdx] v142, v143 = x86_udivmodx v130, v131, v3 ; bin: 49 f7 f2 + [-,%rax,%rdx] v202, v203 = x86_udivmodx v130, v131, v3 ; bin: 49 f7 f2 ; Bit-counting instructions. ; asm: popcntq %rsi, %rcx - [-,%rcx] v200 = popcnt v2 ; bin: f3 48 0f b8 ce + [-,%rcx] v210 = popcnt v2 ; bin: f3 48 0f b8 ce ; asm: popcntq %r10, %rsi - [-,%rsi] v201 = popcnt v3 ; bin: f3 49 0f b8 f2 + [-,%rsi] v211 = popcnt v3 ; bin: f3 49 0f b8 f2 ; asm: popcntq %rcx, %r10 - [-,%r10] v202 = popcnt v1 ; bin: f3 4c 0f b8 d1 + [-,%r10] v212 = popcnt v1 ; bin: f3 4c 0f b8 d1 ; asm: lzcntq %rsi, %rcx - [-,%rcx] v203 = clz v2 ; bin: f3 48 0f bd ce + [-,%rcx] v213 = clz v2 ; bin: f3 48 0f bd ce ; asm: lzcntq %r10, %rsi - [-,%rsi] v204 = clz v3 ; bin: f3 49 0f bd f2 + [-,%rsi] v214 = clz v3 ; bin: f3 49 0f bd f2 ; asm: lzcntq %rcx, %r10 - [-,%r10] v205 = clz v1 ; bin: f3 4c 0f bd d1 + [-,%r10] v215 = clz v1 ; bin: f3 4c 0f bd d1 ; asm: tzcntq %rsi, %rcx - [-,%rcx] v206 = ctz v2 ; bin: f3 48 0f bc ce + [-,%rcx] v216 = ctz v2 ; bin: f3 48 0f bc ce ; asm: tzcntq %r10, %rsi - [-,%rsi] v207 = ctz v3 ; bin: f3 49 0f bc f2 + [-,%rsi] v217 = ctz v3 ; bin: f3 49 0f bc f2 ; asm: tzcntq %rcx, %r10 - [-,%r10] v208 = ctz v1 ; bin: f3 4c 0f bc d1 + [-,%r10] v218 = ctz v1 ; bin: f3 4c 0f bc d1 ; Integer comparisons. @@ -327,146 +474,217 @@ ebb0: ; asm: movl $0x88001122, %r14d [-,%r14] v5 = iconst.i32 0xffff_ffff_8800_1122 ; bin: 41 be 88001122 + ; Load/Store instructions. + + ; Register indirect addressing with no displacement. + + ; asm: movl (%rcx), %edi + [-,%rdi] v10 = load.i32 v1 ; bin: 40 8b 39 + ; asm: movl (%rsi), %edx + [-,%rdx] v11 = load.i32 v2 ; bin: 40 8b 16 + ; asm: movzwl (%rcx), %edi + [-,%rdi] v12 = uload16.i32 v1 ; bin: 40 0f b7 39 + ; asm: movzwl (%rsi), %edx + [-,%rdx] v13 = uload16.i32 v2 ; bin: 40 0f b7 16 + ; asm: movswl (%rcx), %edi + [-,%rdi] v14 = sload16.i32 v1 ; bin: 40 0f bf 39 + ; asm: movswl (%rsi), %edx + [-,%rdx] v15 = sload16.i32 v2 ; bin: 40 0f bf 16 + ; asm: movzbl (%rcx), %edi + [-,%rdi] v16 = uload8.i32 v1 ; bin: 40 0f b6 39 + ; asm: movzbl (%rsi), %edx + [-,%rdx] v17 = uload8.i32 v2 ; bin: 40 0f b6 16 + ; asm: movsbl (%rcx), %edi + [-,%rdi] v18 = sload8.i32 v1 ; bin: 40 0f be 39 + ; asm: movsbl (%rsi), %edx + [-,%rdx] v19 = sload8.i32 v2 ; bin: 40 0f be 16 + + ; Register-indirect with 8-bit signed displacement. + + ; asm: movl 50(%rcx), %edi + [-,%rdi] v20 = load.i32 v1+50 ; bin: 40 8b 79 32 + ; asm: movl -50(%rsi), %edx + [-,%rdx] v21 = load.i32 v2-50 ; bin: 40 8b 56 ce + ; asm: movzwl 50(%rcx), %edi + [-,%rdi] v22 = uload16.i32 v1+50 ; bin: 40 0f b7 79 32 + ; asm: movzwl -50(%rsi), %edx + [-,%rdx] v23 = uload16.i32 v2-50 ; bin: 40 0f b7 56 ce + ; asm: movswl 50(%rcx), %edi + [-,%rdi] v24 = sload16.i32 v1+50 ; bin: 40 0f bf 79 32 + ; asm: movswl -50(%rsi), %edx + [-,%rdx] v25 = sload16.i32 v2-50 ; bin: 40 0f bf 56 ce + ; asm: movzbl 50(%rcx), %edi + [-,%rdi] v26 = uload8.i32 v1+50 ; bin: 40 0f b6 79 32 + ; asm: movzbl -50(%rsi), %edx + [-,%rdx] v27 = uload8.i32 v2-50 ; bin: 40 0f b6 56 ce + ; asm: movsbl 50(%rcx), %edi + [-,%rdi] v28 = sload8.i32 v1+50 ; bin: 40 0f be 79 32 + ; asm: movsbl -50(%rsi), %edx + [-,%rdx] v29 = sload8.i32 v2-50 ; bin: 40 0f be 56 ce + + ; Register-indirect with 32-bit signed displacement. + + ; asm: movl 50000(%rcx), %edi + [-,%rdi] v30 = load.i32 v1+50000 ; bin: 40 8b b9 0000c350 + ; asm: movl -50000(%rsi), %edx + [-,%rdx] v31 = load.i32 v2-50000 ; bin: 40 8b 96 ffff3cb0 + ; asm: movzwl 50000(%rcx), %edi + [-,%rdi] v32 = uload16.i32 v1+50000 ; bin: 40 0f b7 b9 0000c350 + ; asm: movzwl -50000(%rsi), %edx + [-,%rdx] v33 = uload16.i32 v2-50000 ; bin: 40 0f b7 96 ffff3cb0 + ; asm: movswl 50000(%rcx), %edi + [-,%rdi] v34 = sload16.i32 v1+50000 ; bin: 40 0f bf b9 0000c350 + ; asm: movswl -50000(%rsi), %edx + [-,%rdx] v35 = sload16.i32 v2-50000 ; bin: 40 0f bf 96 ffff3cb0 + ; asm: movzbl 50000(%rcx), %edi + [-,%rdi] v36 = uload8.i32 v1+50000 ; bin: 40 0f b6 b9 0000c350 + ; asm: movzbl -50000(%rsi), %edx + [-,%rdx] v37 = uload8.i32 v2-50000 ; bin: 40 0f b6 96 ffff3cb0 + ; asm: movsbl 50000(%rcx), %edi + [-,%rdi] v38 = sload8.i32 v1+50000 ; bin: 40 0f be b9 0000c350 + ; asm: movsbl -50000(%rsi), %edx + [-,%rdx] v39 = sload8.i32 v2-50000 ; bin: 40 0f be 96 ffff3cb0 + ; Integer Register-Register Operations. ; asm: addl %esi, %ecx - [-,%rcx] v10 = iadd v1, v2 ; bin: 40 01 f1 + [-,%rcx] v40 = iadd v1, v2 ; bin: 40 01 f1 ; asm: addl %r10d, %esi - [-,%rsi] v11 = iadd v2, v3 ; bin: 44 01 d6 + [-,%rsi] v41 = iadd v2, v3 ; bin: 44 01 d6 ; asm: addl %ecx, %r10d - [-,%r10] v12 = iadd v3, v1 ; bin: 41 01 ca + [-,%r10] v42 = iadd v3, v1 ; bin: 41 01 ca ; asm: subl %esi, %ecx - [-,%rcx] v20 = isub v1, v2 ; bin: 40 29 f1 + [-,%rcx] v50 = isub v1, v2 ; bin: 40 29 f1 ; asm: subl %r10d, %esi - [-,%rsi] v21 = isub v2, v3 ; bin: 44 29 d6 + [-,%rsi] v51 = isub v2, v3 ; bin: 44 29 d6 ; asm: subl %ecx, %r10d - [-,%r10] v22 = isub v3, v1 ; bin: 41 29 ca + [-,%r10] v52 = isub v3, v1 ; bin: 41 29 ca ; asm: andl %esi, %ecx - [-,%rcx] v30 = band v1, v2 ; bin: 40 21 f1 + [-,%rcx] v60 = band v1, v2 ; bin: 40 21 f1 ; asm: andl %r10d, %esi - [-,%rsi] v31 = band v2, v3 ; bin: 44 21 d6 + [-,%rsi] v61 = band v2, v3 ; bin: 44 21 d6 ; asm: andl %ecx, %r10d - [-,%r10] v32 = band v3, v1 ; bin: 41 21 ca + [-,%r10] v62 = band v3, v1 ; bin: 41 21 ca ; asm: orl %esi, %ecx - [-,%rcx] v40 = bor v1, v2 ; bin: 40 09 f1 + [-,%rcx] v70 = bor v1, v2 ; bin: 40 09 f1 ; asm: orl %r10d, %esi - [-,%rsi] v41 = bor v2, v3 ; bin: 44 09 d6 + [-,%rsi] v71 = bor v2, v3 ; bin: 44 09 d6 ; asm: orl %ecx, %r10d - [-,%r10] v42 = bor v3, v1 ; bin: 41 09 ca + [-,%r10] v72 = bor v3, v1 ; bin: 41 09 ca ; asm: xorl %esi, %ecx - [-,%rcx] v50 = bxor v1, v2 ; bin: 40 31 f1 + [-,%rcx] v80 = bxor v1, v2 ; bin: 40 31 f1 ; asm: xorl %r10d, %esi - [-,%rsi] v51 = bxor v2, v3 ; bin: 44 31 d6 + [-,%rsi] v81 = bxor v2, v3 ; bin: 44 31 d6 ; asm: xorl %ecx, %r10d - [-,%r10] v52 = bxor v3, v1 ; bin: 41 31 ca + [-,%r10] v82 = bxor v3, v1 ; bin: 41 31 ca ; asm: shll %cl, %esi - [-,%rsi] v60 = ishl v2, v1 ; bin: 40 d3 e6 + [-,%rsi] v90 = ishl v2, v1 ; bin: 40 d3 e6 ; asm: shll %cl, %r10d - [-,%r10] v61 = ishl v3, v1 ; bin: 41 d3 e2 + [-,%r10] v91 = ishl v3, v1 ; bin: 41 d3 e2 ; asm: sarl %cl, %esi - [-,%rsi] v62 = sshr v2, v1 ; bin: 40 d3 fe + [-,%rsi] v92 = sshr v2, v1 ; bin: 40 d3 fe ; asm: sarl %cl, %r10d - [-,%r10] v63 = sshr v3, v1 ; bin: 41 d3 fa + [-,%r10] v93 = sshr v3, v1 ; bin: 41 d3 fa ; asm: shrl %cl, %esi - [-,%rsi] v64 = ushr v2, v1 ; bin: 40 d3 ee + [-,%rsi] v94 = ushr v2, v1 ; bin: 40 d3 ee ; asm: shrl %cl, %r10d - [-,%r10] v65 = ushr v3, v1 ; bin: 41 d3 ea + [-,%r10] v95 = ushr v3, v1 ; bin: 41 d3 ea ; asm: roll %cl, %esi - [-,%rsi] v66 = rotl v2, v1 ; bin: 40 d3 c6 + [-,%rsi] v96 = rotl v2, v1 ; bin: 40 d3 c6 ; asm: roll %cl, %r10d - [-,%r10] v67 = rotl v3, v1 ; bin: 41 d3 c2 + [-,%r10] v97 = rotl v3, v1 ; bin: 41 d3 c2 ; asm: rorl %cl, %esi - [-,%rsi] v68 = rotr v2, v1 ; bin: 40 d3 ce + [-,%rsi] v98 = rotr v2, v1 ; bin: 40 d3 ce ; asm: rorl %cl, %r10d - [-,%r10] v69 = rotr v3, v1 ; bin: 41 d3 ca + [-,%r10] v99 = rotr v3, v1 ; bin: 41 d3 ca ; Integer Register-Immediate Operations. ; These 64-bit ops all use a 32-bit immediate that is sign-extended to 64 bits. ; Some take 8-bit immediates that are sign-extended to 64 bits. ; asm: addl $-100000, %ecx - [-,%rcx] v70 = iadd_imm v1, -100000 ; bin: 40 81 c1 fffe7960 + [-,%rcx] v100 = iadd_imm v1, -100000 ; bin: 40 81 c1 fffe7960 ; asm: addl $100000, %esi - [-,%rsi] v71 = iadd_imm v2, 100000 ; bin: 40 81 c6 000186a0 + [-,%rsi] v101 = iadd_imm v2, 100000 ; bin: 40 81 c6 000186a0 ; asm: addl $0x7fffffff, %r10d - [-,%r10] v72 = iadd_imm v3, 0x7fff_ffff ; bin: 41 81 c2 7fffffff + [-,%r10] v102 = iadd_imm v3, 0x7fff_ffff ; bin: 41 81 c2 7fffffff ; asm: addl $100, %r8d - [-,%r8] v73 = iadd_imm v4, 100 ; bin: 41 83 c0 64 + [-,%r8] v103 = iadd_imm v4, 100 ; bin: 41 83 c0 64 ; asm: addl $-100, %r14d - [-,%r14] v74 = iadd_imm v5, -100 ; bin: 41 83 c6 9c + [-,%r14] v104 = iadd_imm v5, -100 ; bin: 41 83 c6 9c ; asm: andl $-100000, %ecx - [-,%rcx] v80 = band_imm v1, -100000 ; bin: 40 81 e1 fffe7960 + [-,%rcx] v110 = band_imm v1, -100000 ; bin: 40 81 e1 fffe7960 ; asm: andl $100000, %esi - [-,%rsi] v81 = band_imm v2, 100000 ; bin: 40 81 e6 000186a0 + [-,%rsi] v111 = band_imm v2, 100000 ; bin: 40 81 e6 000186a0 ; asm: andl $0x7fffffff, %r10d - [-,%r10] v82 = band_imm v3, 0x7fff_ffff ; bin: 41 81 e2 7fffffff + [-,%r10] v112 = band_imm v3, 0x7fff_ffff ; bin: 41 81 e2 7fffffff ; asm: andl $100, %r8d - [-,%r8] v83 = band_imm v4, 100 ; bin: 41 83 e0 64 + [-,%r8] v113 = band_imm v4, 100 ; bin: 41 83 e0 64 ; asm: andl $-100, %r14d - [-,%r14] v84 = band_imm v5, -100 ; bin: 41 83 e6 9c + [-,%r14] v114 = band_imm v5, -100 ; bin: 41 83 e6 9c ; asm: orl $-100000, %ecx - [-,%rcx] v90 = bor_imm v1, -100000 ; bin: 40 81 c9 fffe7960 + [-,%rcx] v120 = bor_imm v1, -100000 ; bin: 40 81 c9 fffe7960 ; asm: orl $100000, %esi - [-,%rsi] v91 = bor_imm v2, 100000 ; bin: 40 81 ce 000186a0 + [-,%rsi] v121 = bor_imm v2, 100000 ; bin: 40 81 ce 000186a0 ; asm: orl $0x7fffffff, %r10d - [-,%r10] v92 = bor_imm v3, 0x7fff_ffff ; bin: 41 81 ca 7fffffff + [-,%r10] v122 = bor_imm v3, 0x7fff_ffff ; bin: 41 81 ca 7fffffff ; asm: orl $100, %r8d - [-,%r8] v93 = bor_imm v4, 100 ; bin: 41 83 c8 64 + [-,%r8] v123 = bor_imm v4, 100 ; bin: 41 83 c8 64 ; asm: orl $-100, %r14d - [-,%r14] v94 = bor_imm v5, -100 ; bin: 41 83 ce 9c + [-,%r14] v124 = bor_imm v5, -100 ; bin: 41 83 ce 9c ; asm: ret ; asm: xorl $-100000, %ecx - [-,%rcx] v100 = bxor_imm v1, -100000 ; bin: 40 81 f1 fffe7960 + [-,%rcx] v130 = bxor_imm v1, -100000 ; bin: 40 81 f1 fffe7960 ; asm: xorl $100000, %esi - [-,%rsi] v101 = bxor_imm v2, 100000 ; bin: 40 81 f6 000186a0 + [-,%rsi] v131 = bxor_imm v2, 100000 ; bin: 40 81 f6 000186a0 ; asm: xorl $0x7fffffff, %r10d - [-,%r10] v102 = bxor_imm v3, 0x7fff_ffff ; bin: 41 81 f2 7fffffff + [-,%r10] v132 = bxor_imm v3, 0x7fff_ffff ; bin: 41 81 f2 7fffffff ; asm: xorl $100, %r8d - [-,%r8] v103 = bxor_imm v4, 100 ; bin: 41 83 f0 64 + [-,%r8] v133 = bxor_imm v4, 100 ; bin: 41 83 f0 64 ; asm: xorl $-100, %r14d - [-,%r14] v104 = bxor_imm v5, -100 ; bin: 41 83 f6 9c + [-,%r14] v134 = bxor_imm v5, -100 ; bin: 41 83 f6 9c ; Register copies. ; asm: movl %esi, %ecx - [-,%rcx] v110 = copy v2 ; bin: 40 89 f1 + [-,%rcx] v140 = copy v2 ; bin: 40 89 f1 ; asm: movl %r10d, %esi - [-,%rsi] v111 = copy v3 ; bin: 44 89 d6 + [-,%rsi] v141 = copy v3 ; bin: 44 89 d6 ; asm: movl %ecx, %r10d - [-,%r10] v112 = copy v1 ; bin: 41 89 ca + [-,%r10] v142 = copy v1 ; bin: 41 89 ca ; More arithmetic. ; asm: imull %esi, %ecx - [-,%rcx] v120 = imul v1, v2 ; bin: 40 0f af ce + [-,%rcx] v150 = imul v1, v2 ; bin: 40 0f af ce ; asm: imull %r10d, %esi - [-,%rsi] v121 = imul v2, v3 ; bin: 41 0f af f2 + [-,%rsi] v151 = imul v2, v3 ; bin: 41 0f af f2 ; asm: imull %ecx, %r10d - [-,%r10] v122 = imul v3, v1 ; bin: 44 0f af d1 + [-,%r10] v152 = imul v3, v1 ; bin: 44 0f af d1 - [-,%rax] v130 = iconst.i32 1 - [-,%rdx] v131 = iconst.i32 2 + [-,%rax] v160 = iconst.i32 1 + [-,%rdx] v161 = iconst.i32 2 ; asm: idivl %ecx - [-,%rax,%rdx] v132, v133 = x86_sdivmodx v130, v131, v1 ; bin: 40 f7 f9 + [-,%rax,%rdx] v162, v163 = x86_sdivmodx v130, v131, v1 ; bin: 40 f7 f9 ; asm: idivl %esi - [-,%rax,%rdx] v134, v135 = x86_sdivmodx v130, v131, v2 ; bin: 40 f7 fe + [-,%rax,%rdx] v164, v165 = x86_sdivmodx v130, v131, v2 ; bin: 40 f7 fe ; asm: idivl %r10d - [-,%rax,%rdx] v136, v137 = x86_sdivmodx v130, v131, v3 ; bin: 41 f7 fa + [-,%rax,%rdx] v166, v167 = x86_sdivmodx v130, v131, v3 ; bin: 41 f7 fa ; asm: divl %ecx - [-,%rax,%rdx] v138, v139 = x86_udivmodx v130, v131, v1 ; bin: 40 f7 f1 + [-,%rax,%rdx] v168, v169 = x86_udivmodx v130, v131, v1 ; bin: 40 f7 f1 ; asm: divl %esi - [-,%rax,%rdx] v140, v141 = x86_udivmodx v130, v131, v2 ; bin: 40 f7 f6 + [-,%rax,%rdx] v170, v171 = x86_udivmodx v130, v131, v2 ; bin: 40 f7 f6 ; asm: divl %r10d - [-,%rax,%rdx] v142, v143 = x86_udivmodx v130, v131, v3 ; bin: 41 f7 f2 + [-,%rax,%rdx] v172, v173 = x86_udivmodx v130, v131, v3 ; bin: 41 f7 f2 ; Bit-counting instructions. diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index a26d791719..3a4ce25cda 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -271,7 +271,7 @@ istore16 = Instruction( 'istore16', r""" Store the low 16 bits of ``x`` to memory at ``p + Offset``. - This is equivalent to ``ireduce.i16`` followed by ``store.i8``. + This is equivalent to ``ireduce.i16`` followed by ``store.i16``. """, ins=(Flags, x, p, Offset), can_store=True) @@ -301,7 +301,7 @@ istore32 = Instruction( 'istore32', r""" Store the low 32 bits of ``x`` to memory at ``p + Offset``. - This is equivalent to ``ireduce.i32`` followed by ``store.i8``. + This is equivalent to ``ireduce.i32`` followed by ``store.i32``. """, ins=(Flags, x, p, Offset), can_store=True) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 448252f43a..39b6812058 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -55,6 +55,28 @@ def enc_i32_i64(inst, recipe, *args, **kwargs): I64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs)) +def enc_i32_i64_ld_st(inst, w_bit, recipe, *args, **kwargs): + # type: (MaybeBoundInst, bool, r.TailRecipe, *int, **int) -> None + """ + Add encodings for `inst.i32` to I32. + Add encodings for `inst.i32` to I64 with and without REX. + Add encodings for `inst.i64` to I64 with a REX prefix, using the `w_bit` + argument to determine wheter or not to set the REX.W bit. + """ + I32.enc(inst.i32.any, *recipe(*args, **kwargs)) + + # REX-less encoding must come after REX encoding so we don't use it by + # default. Otherwise reg-alloc would never use r8 and up. + I64.enc(inst.i32.any, *recipe.rex(*args, **kwargs)) + I64.enc(inst.i32.any, *recipe(*args, **kwargs)) + + if w_bit: + I64.enc(inst.i64.any, *recipe.rex(*args, w=1, **kwargs)) + else: + I64.enc(inst.i64.any, *recipe.rex(*args, **kwargs)) + I64.enc(inst.i64.any, *recipe(*args, **kwargs)) + + def enc_flt(inst, recipe, *args, **kwargs): # type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None """ @@ -142,38 +164,60 @@ I64.enc(base.ctz.i64, *r.urm.rex(0xf3, 0x0f, 0xbc, w=1), I64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) I64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) +# # Loads and stores. -I32.enc(base.store.i32.any, *r.st(0x89)) -I32.enc(base.store.i32.any, *r.stDisp8(0x89)) -I32.enc(base.store.i32.any, *r.stDisp32(0x89)) +# +enc_i32_i64_ld_st(base.store, True, r.st, 0x89) +enc_i32_i64_ld_st(base.store, True, r.stDisp8, 0x89) +enc_i32_i64_ld_st(base.store, True, r.stDisp32, 0x89) -I32.enc(base.istore16.i32.any, *r.st(0x66, 0x89)) -I32.enc(base.istore16.i32.any, *r.stDisp8(0x66, 0x89)) -I32.enc(base.istore16.i32.any, *r.stDisp32(0x66, 0x89)) +I64.enc(base.istore32.i64.any, *r.st.rex(0x89)) +I64.enc(base.istore32.i64.any, *r.stDisp8.rex(0x89)) +I64.enc(base.istore32.i64.any, *r.stDisp32.rex(0x89)) +enc_i32_i64_ld_st(base.istore16, False, r.st, 0x66, 0x89) +enc_i32_i64_ld_st(base.istore16, False, r.stDisp8, 0x66, 0x89) +enc_i32_i64_ld_st(base.istore16, False, r.stDisp32, 0x66, 0x89) + +# Byte stores are more complicated because the registers they can address +# depends of the presence of a REX prefix I32.enc(base.istore8.i32.any, *r.st_abcd(0x88)) +I64.enc(base.istore8.i32.any, *r.st_abcd(0x88)) +I64.enc(base.istore8.i64.any, *r.st.rex(0x88)) I32.enc(base.istore8.i32.any, *r.stDisp8_abcd(0x88)) +I64.enc(base.istore8.i32.any, *r.stDisp8_abcd(0x88)) +I64.enc(base.istore8.i64.any, *r.stDisp8.rex(0x88)) I32.enc(base.istore8.i32.any, *r.stDisp32_abcd(0x88)) +I64.enc(base.istore8.i32.any, *r.stDisp32_abcd(0x88)) +I64.enc(base.istore8.i64.any, *r.stDisp32.rex(0x88)) -I32.enc(base.load.i32.any, *r.ld(0x8b)) -I32.enc(base.load.i32.any, *r.ldDisp8(0x8b)) -I32.enc(base.load.i32.any, *r.ldDisp32(0x8b)) +enc_i32_i64_ld_st(base.load, True, r.ld, 0x8b) +enc_i32_i64_ld_st(base.load, True, r.ldDisp8, 0x8b) +enc_i32_i64_ld_st(base.load, True, r.ldDisp32, 0x8b) -I32.enc(base.uload16.i32.any, *r.ld(0x0f, 0xb7)) -I32.enc(base.uload16.i32.any, *r.ldDisp8(0x0f, 0xb7)) -I32.enc(base.uload16.i32.any, *r.ldDisp32(0x0f, 0xb7)) +I64.enc(base.uload32.i64, *r.ld.rex(0x8b)) +I64.enc(base.uload32.i64, *r.ldDisp8.rex(0x8b)) +I64.enc(base.uload32.i64, *r.ldDisp32.rex(0x8b)) -I32.enc(base.sload16.i32.any, *r.ld(0x0f, 0xbf)) -I32.enc(base.sload16.i32.any, *r.ldDisp8(0x0f, 0xbf)) -I32.enc(base.sload16.i32.any, *r.ldDisp32(0x0f, 0xbf)) +I64.enc(base.sload32.i64, *r.ld.rex(0x63, w=1)) +I64.enc(base.sload32.i64, *r.ldDisp8.rex(0x63, w=1)) +I64.enc(base.sload32.i64, *r.ldDisp32.rex(0x63, w=1)) -I32.enc(base.uload8.i32.any, *r.ld(0x0f, 0xb6)) -I32.enc(base.uload8.i32.any, *r.ldDisp8(0x0f, 0xb6)) -I32.enc(base.uload8.i32.any, *r.ldDisp32(0x0f, 0xb6)) +enc_i32_i64_ld_st(base.uload16, True, r.ld, 0x0f, 0xb7) +enc_i32_i64_ld_st(base.uload16, True, r.ldDisp8, 0x0f, 0xb7) +enc_i32_i64_ld_st(base.uload16, True, r.ldDisp32, 0x0f, 0xb7) -I32.enc(base.sload8.i32.any, *r.ld(0x0f, 0xbe)) -I32.enc(base.sload8.i32.any, *r.ldDisp8(0x0f, 0xbe)) -I32.enc(base.sload8.i32.any, *r.ldDisp32(0x0f, 0xbe)) +enc_i32_i64_ld_st(base.sload16, True, r.ld, 0x0f, 0xbf) +enc_i32_i64_ld_st(base.sload16, True, r.ldDisp8, 0x0f, 0xbf) +enc_i32_i64_ld_st(base.sload16, True, r.ldDisp32, 0x0f, 0xbf) + +enc_i32_i64_ld_st(base.uload8, True, r.ld, 0x0f, 0xb6) +enc_i32_i64_ld_st(base.uload8, True, r.ldDisp8, 0x0f, 0xb6) +enc_i32_i64_ld_st(base.uload8, True, r.ldDisp32, 0x0f, 0xb6) + +enc_i32_i64_ld_st(base.sload8, True, r.ld, 0x0f, 0xbe) +enc_i32_i64_ld_st(base.sload8, True, r.ldDisp8, 0x0f, 0xbe) +enc_i32_i64_ld_st(base.sload8, True, r.ldDisp32, 0x0f, 0xbe) # # Call/return diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 0f3b1b76c6..0eb6199e66 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -114,6 +114,15 @@ fn put_rexmp2(bits: u16, rex: u8, sink: &mut CS) { sink.put1(bits as u8); } +// Emit single-byte opcode with mandatory prefix and REX. +fn put_rexmp1(bits: u16, rex: u8, sink: &mut CS) { + debug_assert_eq!(bits & 0x0c00, 0, "Invalid encoding bits for Mp1*"); + let pp = (bits >> 8) & 3; + sink.put1(PREFIX[(pp - 1) as usize]); + rex_prefix(bits, rex, sink); + sink.put1(bits as u8); +} + /// Emit a ModR/M byte for reg-reg operands. fn modrm_rr(rm: RegUnit, reg: RegUnit, sink: &mut CS) { let reg = reg as u8 & 7; From 9e3f4e9195130e66c040e78e55021367fadfde6d Mon Sep 17 00:00:00 2001 From: d1m0 Date: Mon, 31 Jul 2017 16:02:27 -0700 Subject: [PATCH 942/968] Cleanup for PR #123 (#129) * Fix bextend semantics; Change smtlib.py to use z3 python bindings for query building instead of raw strings * Forgot the mypy stubs for z3 --- lib/cretonne/meta/base/semantics.py | 4 +- lib/cretonne/meta/semantics/primitives.py | 4 + lib/cretonne/meta/semantics/smtlib.py | 114 ++++++++-------- lib/cretonne/meta/stubs/z3/__init__.pyi | 151 ++++++++++++++++++++++ lib/cretonne/meta/stubs/z3/z3core.pyi | 3 + lib/cretonne/meta/stubs/z3/z3types.pyi | 12 ++ 6 files changed, 234 insertions(+), 54 deletions(-) create mode 100644 lib/cretonne/meta/stubs/z3/__init__.pyi create mode 100644 lib/cretonne/meta/stubs/z3/z3core.pyi create mode 100644 lib/cretonne/meta/stubs/z3/z3types.pyi diff --git a/lib/cretonne/meta/base/semantics.py b/lib/cretonne/meta/base/semantics.py index 582fdc0889..edf4c5f82e 100644 --- a/lib/cretonne/meta/base/semantics.py +++ b/lib/cretonne/meta/base/semantics.py @@ -1,6 +1,6 @@ from __future__ import absolute_import from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\ - bvadd, bvult, bvzeroext + bvadd, bvult, bvzeroext, bvsignext from .instructions import vsplit, vconcat, iadd, iadd_cout, icmp, bextend, \ isplit, iconcat, iadd_cin, iadd_carry from .immediates import intcc @@ -116,7 +116,7 @@ bextend.set_semantics( a << bextend(x), (Rtl( bvx << prim_to_bv(x), - bvy << bvzeroext(bvx), + bvy << bvsignext(bvx), a << prim_from_bv(bvy) ), [InTypeset(x.get_typevar(), ScalarTS)]), Rtl( diff --git a/lib/cretonne/meta/semantics/primitives.py b/lib/cretonne/meta/semantics/primitives.py index 62d936bc31..0a727c1cf9 100644 --- a/lib/cretonne/meta/semantics/primitives.py +++ b/lib/cretonne/meta/semantics/primitives.py @@ -82,4 +82,8 @@ bvzeroext = Instruction( 'bvzeroext', r"""Unsigned bitvector extension""", ins=x, outs=x1, constraints=WiderOrEq(ToBV, BV)) +bvsignext = Instruction( + 'bvsignext', r"""Signed bitvector extension""", + ins=x, outs=x1, constraints=WiderOrEq(ToBV, BV)) + GROUP.close() diff --git a/lib/cretonne/meta/semantics/smtlib.py b/lib/cretonne/meta/semantics/smtlib.py index f84176dc3c..c1b2526832 100644 --- a/lib/cretonne/meta/semantics/smtlib.py +++ b/lib/cretonne/meta/semantics/smtlib.py @@ -3,64 +3,74 @@ Tools to emit SMTLIB bitvector queries encoding concrete RTLs containing only primitive instructions. """ from .primitives import GROUP as PRIMITIVES, prim_from_bv, prim_to_bv, bvadd,\ - bvult, bvzeroext, bvsplit, bvconcat + bvult, bvzeroext, bvsplit, bvconcat, bvsignext from cdsl.ast import Var from cdsl.types import BVType from .elaborate import elaborate +from z3 import BitVec, ZeroExt, SignExt, And, Extract, Concat, Not, Solver,\ + unsat, BoolRef, BitVecVal, If +from z3.z3core import Z3_mk_eq try: - from typing import TYPE_CHECKING, Tuple # noqa + from typing import TYPE_CHECKING, Tuple, Dict, List # noqa from cdsl.xform import Rtl, XForm # noqa from cdsl.ast import VarMap # noqa from cdsl.ti import VarTyping # noqa + if TYPE_CHECKING: + from z3 import ExprRef, BitVecRef # noqa + Z3VarMap = Dict[Var, BitVecRef] except ImportError: TYPE_CHECKING = False -def bvtype_to_sort(typ): - # type: (BVType) -> str - """Return the BitVec sort corresponding to a BVType""" - return "(_ BitVec {})".format(typ.bits) +# Use this for constructing a == b instead of == since MyPy doesn't +# accept overloading of __eq__ that doesn't return bool +def mk_eq(e1, e2): + # type: (ExprRef, ExprRef) -> ExprRef + """Return a z3 expression equivalent to e1 == e2""" + return BoolRef(Z3_mk_eq(e1.ctx_ref(), e1.as_ast(), e2.as_ast()), e1.ctx) def to_smt(r): - # type: (Rtl) -> Tuple[str, VarMap] + # type: (Rtl) -> Tuple[List[ExprRef], Z3VarMap] """ - Encode a concrete primitive Rtl r sa SMTLIB 2.0 query. + Encode a concrete primitive Rtl r sa z3 query. Returns a tuple (query, var_m) where: - - query is the resulting query. - - var_m is a map from Vars v with non-BVType to their Vars v' with - BVType s.t. v' holds the flattend bitvector value of v. + - query is a list of z3 expressions + - var_m is a map from Vars v with non-BVType to their correspodning z3 + bitvector variable. """ assert r.is_concrete() # Should contain only primitives primitives = set(PRIMITIVES.instructions) assert set(d.expr.inst for d in r.rtl).issubset(primitives) - q = "" - m = {} # type: VarMap + q = [] # type: List[ExprRef] + m = {} # type: Z3VarMap # Build declarations for any bitvector Vars + var_to_bv = {} # type: Z3VarMap for v in r.vars(): typ = v.get_typevar().singleton_type() if not isinstance(typ, BVType): continue - q += "(declare-fun {} () {})\n".format(v.name, bvtype_to_sort(typ)) + var_to_bv[v] = BitVec(v.name, typ.bits) # Encode each instruction as a equality assertion for d in r.rtl: inst = d.expr.inst + exp = None # type: ExprRef # For prim_to_bv/prim_from_bv just update var_m. No assertion needed if inst == prim_to_bv: assert isinstance(d.expr.args[0], Var) - m[d.expr.args[0]] = d.defs[0] + m[d.expr.args[0]] = var_to_bv[d.defs[0]] continue if inst == prim_from_bv: assert isinstance(d.expr.args[0], Var) - m[d.defs[0]] = d.expr.args[0] + m[d.defs[0]] = var_to_bv[d.expr.args[0]] continue if inst in [bvadd, bvult]: # Binary instructions @@ -70,12 +80,15 @@ def to_smt(r): df = d.defs[0] assert isinstance(lhs, Var) and isinstance(rhs, Var) - if inst in [bvadd]: # Normal binary - output type same as args - exp = "(= {} ({} {} {}))".format(df, inst.name, lhs, rhs) + if inst == bvadd: # Normal binary - output type same as args + exp = (var_to_bv[lhs] + var_to_bv[rhs]) else: + assert inst == bvult + exp = (var_to_bv[lhs] < var_to_bv[rhs]) # Comparison binary - need to convert bool to BitVec 1 - exp = "(= {} (ite ({} {} {}) #b1 #b0))"\ - .format(df, inst.name, lhs, rhs) + exp = If(exp, BitVecVal(1, 1), BitVecVal(0, 1)) + + exp = mk_eq(var_to_bv[df], exp) elif inst == bvzeroext: arg = d.expr.args[0] df = d.defs[0] @@ -83,8 +96,15 @@ def to_smt(r): fromW = arg.get_typevar().singleton_type().width() toW = df.get_typevar().singleton_type().width() - exp = "(= {} ((_ zero_extend {}) {}))"\ - .format(df, toW-fromW, arg) + exp = mk_eq(var_to_bv[df], ZeroExt(toW-fromW, var_to_bv[arg])) + elif inst == bvsignext: + arg = d.expr.args[0] + df = d.defs[0] + assert isinstance(arg, Var) + fromW = arg.get_typevar().singleton_type().width() + toW = df.get_typevar().singleton_type().width() + + exp = mk_eq(var_to_bv[df], SignExt(toW-fromW, var_to_bv[arg])) elif inst == bvsplit: arg = d.expr.args[0] assert isinstance(arg, Var) @@ -95,12 +115,10 @@ def to_smt(r): lo = d.defs[0] hi = d.defs[1] - exp = "(and " - exp += "(= {} ((_ extract {} {}) {})) "\ - .format(lo, width//2-1, 0, arg) - exp += "(= {} ((_ extract {} {}) {}))"\ - .format(hi, width-1, width//2, arg) - exp += ")" + exp = And(mk_eq(var_to_bv[lo], + Extract(width//2-1, 0, var_to_bv[arg])), + mk_eq(var_to_bv[hi], + Extract(width-1, width//2, var_to_bv[arg]))) elif inst == bvconcat: assert isinstance(d.expr.args[0], Var) and \ isinstance(d.expr.args[1], Var) @@ -109,18 +127,17 @@ def to_smt(r): df = d.defs[0] # Z3 Concat expects hi bits first, then lo bits - exp = "(= {} (concat {} {}))"\ - .format(df, hi, lo) + exp = mk_eq(var_to_bv[df], Concat(var_to_bv[hi], var_to_bv[lo])) else: assert False, "Unknown primitive instruction {}".format(inst) - q += "(assert {})\n".format(exp) + q.append(exp) return (q, m) def equivalent(r1, r2, inp_m, out_m): - # type: (Rtl, Rtl, VarMap, VarMap) -> str + # type: (Rtl, Rtl, VarMap, VarMap) -> List[ExprRef] """ Given: - concrete source Rtl r1 @@ -156,36 +173,25 @@ def equivalent(r1, r2, inp_m, out_m): (q2, m2) = to_smt(r2) # Build an expression for the equality of real Cretone inputs of r1 and r2 - args_eq_exp = "(and \n" + args_eq_exp = [] # type: List[ExprRef] for v in r1.free_vars(): - args_eq_exp += "(= {} {})\n".format(m1[v], m2[inp_m[v]]) - args_eq_exp += ")" + args_eq_exp.append(mk_eq(m1[v], m2[inp_m[v]])) # Build an expression for the equality of real Cretone outputs of r1 and r2 - results_eq_exp = "(and \n" + results_eq_exp = [] # type: List[ExprRef] for (v1, v2) in out_m.items(): - results_eq_exp += "(= {} {})\n".format(m1[v1], m2[v2]) - results_eq_exp += ")" + results_eq_exp.append(mk_eq(m1[v1], m2[v2])) # Put the whole query toghether - q = '; Rtl 1 declarations and assertions\n' + q1 - q += '; Rtl 2 declarations and assertions\n' + q2 - - q += '; Assert that the inputs of Rtl1 and Rtl2 are equal\n' + \ - '(assert {})\n'.format(args_eq_exp) - - q += '; Assert that the outputs of Rtl1 and Rtl2 are not equal\n' + \ - '(assert (not {}))\n'.format(results_eq_exp) - - return q + return q1 + q2 + args_eq_exp + [Not(And(*results_eq_exp))] def xform_correct(x, typing): - # type: (XForm, VarTyping) -> str + # type: (XForm, VarTyping) -> bool """ - Given an XForm x and a concrete variable typing for x build the smtlib - query asserting that x is correct for the given typing. + Given an XForm x and a concrete variable typing for x check whether x is + semantically preserving for the concrete typing. """ assert x.ti.permits(typing) @@ -208,4 +214,8 @@ def xform_correct(x, typing): # Get the primitive semantic Rtls for src and dst prim_src = elaborate(src) prim_dst = elaborate(dst) - return equivalent(prim_src, prim_dst, inp_m, out_m) + asserts = equivalent(prim_src, prim_dst, inp_m, out_m) + + s = Solver() + s.add(*asserts) + return s.check() == unsat diff --git a/lib/cretonne/meta/stubs/z3/__init__.pyi b/lib/cretonne/meta/stubs/z3/__init__.pyi new file mode 100644 index 0000000000..2fd6c8341f --- /dev/null +++ b/lib/cretonne/meta/stubs/z3/__init__.pyi @@ -0,0 +1,151 @@ +from typing import overload, Tuple, Any, List, Iterable, Union, TypeVar +from .z3types import Ast, ContextObj + +TExprRef = TypeVar("TExprRef", bound="ExprRef") + +class Context: + ... + +class Z3PPObject: + ... + +class AstRef(Z3PPObject): + @overload + def __init__(self, ast: Ast, ctx: Context) -> None: + self.ast: Ast = ... + self.ctx: Context= ... + + @overload + def __init__(self, ast: Ast) -> None: + self.ast: Ast = ... + self.ctx: Context= ... + def ctx_ref(self) -> ContextObj: ... + def as_ast(self) -> Ast: ... + def children(self) -> List[AstRef]: ... + +class SortRef(AstRef): + ... + +class FuncDeclRef(AstRef): + def arity(self) -> int: ... + def name(self) -> str: ... + +class ExprRef(AstRef): + def eq(self, other: ExprRef) -> ExprRef: ... + def sort(self) -> SortRef: ... + def decl(self) -> FuncDeclRef: ... + +class BoolSortRef(SortRef): + ... + +class BoolRef(ExprRef): + ... + + +def is_true(a: BoolRef) -> bool: ... +def is_false(a: BoolRef) -> bool: ... +def is_int_value(a: AstRef) -> bool: ... +def substitute(a: AstRef, *m: Tuple[AstRef, AstRef]) -> AstRef: ... + + +class ArithSortRef(SortRef): + ... + +class ArithRef(ExprRef): + def __neg__(self) -> ExprRef: ... + def __le__(self, other: ArithRef) -> ArithRef: ... + def __lt__(self, other: ArithRef) -> ArithRef: ... + def __ge__(self, other: ArithRef) -> ArithRef: ... + def __gt__(self, other: ArithRef) -> ArithRef: ... + def __add__(self, other: ArithRef) -> ArithRef: ... + def __sub__(self, other: ArithRef) -> ArithRef: ... + def __mul__(self, other: ArithRef) -> ArithRef: ... + def __div__(self, other: ArithRef) -> ArithRef: ... + def __mod__(self, other: ArithRef) -> ArithRef: ... + +class IntNumRef(ArithRef): + def as_long(self) -> int: ... + +class BitVecRef(ExprRef): + def __neg__(self) -> ExprRef: ... + def __le__(self, other: BitVecRef) -> ExprRef: ... + def __lt__(self, other: BitVecRef) -> ExprRef: ... + def __ge__(self, other: BitVecRef) -> ExprRef: ... + def __gt__(self, other: BitVecRef) -> ExprRef: ... + def __add__(self, other: BitVecRef) -> BitVecRef: ... + def __sub__(self, other: BitVecRef) -> BitVecRef: ... + def __mul__(self, other: BitVecRef) -> BitVecRef: ... + def __div__(self, other: BitVecRef) -> BitVecRef: ... + def __mod__(self, other: BitVecRef) -> BitVecRef: ... + +class BitVecNumRef(BitVecRef): + def as_long(self) -> int: ... + +class CheckSatResult: ... + +class ModelRef(Z3PPObject): + def __getitem__(self, k: FuncDeclRef) -> IntNumRef: ... + def decls(self) -> Iterable[FuncDeclRef]: ... + +class Solver(Z3PPObject): + @overload + def __init__(self) -> None: + self.ctx: Context = ... + @overload + def __init__(self, ctx:Context) -> None: + self.ctx: Context = ... + + def add(self, e:ExprRef) -> None: ... + def to_smt2(self) -> str: ... + def check(self) -> CheckSatResult: ... + def push(self) -> None: ... + def pop(self) -> None: ... + def model(self) -> ModelRef: ... + +sat: CheckSatResult = ... +unsat: CheckSatResult = ... + +@overload +def Int(name: str) -> ArithRef: ... +@overload +def Int(name: str, ctx: Context) -> ArithRef: ... + +@overload +def Bool(name: str) -> BoolRef: ... +@overload +def Bool(name: str, ctx: Context) -> BoolRef: ... + +def BitVec(name: str, width: int) -> BitVecRef: ... + +@overload +def parse_smt2_string(s: str) -> ExprRef: ... +@overload +def parse_smt2_string(s: str, ctx: Context) -> ExprRef: ... + +# Can't give more precise types here since func signature is +# a vararg list of ExprRef optionally followed by a Context +def Or(*args: Union[ExprRef, Context]) -> ExprRef: ... +def And(*args: Union[ExprRef, Context]) -> ExprRef: ... +@overload +def Not(p: ExprRef) -> ExprRef: ... +@overload +def Not(p: ExprRef, ctx: Context) -> ExprRef: ... +def Implies(a: ExprRef, b: ExprRef, ctx:Context) -> ExprRef: ... +def If(a: ExprRef, b:TExprRef, c:TExprRef) -> TExprRef: ... + +def ZeroExt(width: int, expr: BitVecRef) -> BitVecRef: ... +def SignExt(width: int, expr: BitVecRef) -> BitVecRef: ... +def Extract(hi: int, lo: int, expr: BitVecRef) -> BitVecRef: ... +def Concat(expr1: BitVecRef, expr2: BitVecRef) -> BitVecRef: ... + +def Function(name: str, *sig: Tuple[SortRef,...]) -> FuncDeclRef: ... + +def IntVal(val: int, ctx: Context) -> IntNumRef: ... +@overload +def BoolVal(val: bool, ctx: Context) -> BoolRef: ... +@overload +def BoolVal(val: bool) -> BoolRef: ... +@overload +def BitVecVal(val: int, bits: int, ctx: Context) -> BitVecNumRef: ... +@overload +def BitVecVal(val: int, bits: int) -> BitVecNumRef: ... diff --git a/lib/cretonne/meta/stubs/z3/z3core.pyi b/lib/cretonne/meta/stubs/z3/z3core.pyi new file mode 100644 index 0000000000..36f1f88792 --- /dev/null +++ b/lib/cretonne/meta/stubs/z3/z3core.pyi @@ -0,0 +1,3 @@ +from .z3types import Ast, ContextObj +def Z3_mk_eq(ctx: ContextObj, a: Ast, b: Ast) -> Ast: ... +def Z3_mk_div(ctx: ContextObj, a: Ast, b: Ast) -> Ast: ... diff --git a/lib/cretonne/meta/stubs/z3/z3types.pyi b/lib/cretonne/meta/stubs/z3/z3types.pyi new file mode 100644 index 0000000000..fa8fc446d1 --- /dev/null +++ b/lib/cretonne/meta/stubs/z3/z3types.pyi @@ -0,0 +1,12 @@ +from typing import Any + +class Z3Exception(Exception): + def __init__(self, a: Any) -> None: + self.value = a + ... + +class ContextObj: + ... + +class Ast: + ... From c50a836a56ef8f14428bc6155d01acdd7b0237df Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 31 Jul 2017 12:58:20 -0700 Subject: [PATCH 943/968] Keep track of OutgoingArg stack slots. Stack slots for outgoing arguments can be reused between function calls. Add a list of outgoing argument stack slots allocated so far, and provide a `get_outgoing_arg()` method which will reuse any outgoing stack slots with matching size and offset. --- lib/cretonne/src/ir/stackslot.rs | 63 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index c6fd64ce5c..56dd037dc5 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -75,6 +75,9 @@ pub struct StackSlotData { /// On Intel ISAs, the base address is the stack pointer *before* the return address was /// pushed. On RISC ISAs, the base address is the value of the stack pointer on entry to the /// function. + /// + /// For `OutgoingArg` stack slots, the offset is relative to the current function's stack + /// pointer immediately before the call. pub offset: i32, } @@ -106,19 +109,27 @@ impl PrimaryEntityData for StackSlotData {} /// Keep track of all the stack slots used by a function. #[derive(Clone, Debug)] pub struct StackSlots { + /// All allocated stack slots. slots: EntityMap, + + /// All the outgoing stack slots, ordered by offset. + outgoing: Vec, } /// Stack slot manager functions that behave mostly like an entity map. impl StackSlots { /// Create an empty stack slot manager. pub fn new() -> StackSlots { - StackSlots { slots: EntityMap::new() } + StackSlots { + slots: EntityMap::new(), + outgoing: Vec::new(), + } } /// Clear out everything. pub fn clear(&mut self) { self.slots.clear(); + self.outgoing.clear(); } /// Allocate a new stack slot. @@ -161,6 +172,33 @@ impl StackSlots { data.offset = offset; self.push(data) } + + /// Get a stack slot representing an outgoing argument. + /// + /// This may create a new stack slot, or reuse an existing outgoing stack slot with the + /// requested offset and size. + /// + /// The requested offset is relative to this function's stack pointer immediately before making + /// the call. + pub fn get_outgoing_arg(&mut self, ty: Type, offset: i32) -> StackSlot { + let size = ty.bytes(); + + // Look for an existing outgoing stack slot with the same offset and size. + let inspos = match self.outgoing + .binary_search_by_key(&(offset, size), + |&ss| (self[ss].offset, self[ss].size)) { + Ok(idx) => return self.outgoing[idx], + Err(idx) => idx, + }; + + // No existing slot found. Make one and insert it into `outgoing`. + let mut data = StackSlotData::new(StackSlotKind::OutgoingArg, size); + assert!(offset <= i32::max_value() - size as i32); + data.offset = offset; + let ss = self.slots.push(data); + self.outgoing.insert(inspos, ss); + ss + } } impl Index for StackSlots { @@ -174,6 +212,7 @@ impl Index for StackSlots { #[cfg(test)] mod tests { use ir::Function; + use ir::types; use super::*; #[test] @@ -193,4 +232,26 @@ mod tests { assert_eq!(func.stack_slots[ss0].to_string(), "incoming_arg 4"); assert_eq!(func.stack_slots[ss1].to_string(), "spill_slot 8"); } + + #[test] + fn outgoing() { + let mut sss = StackSlots::new(); + + let ss0 = sss.get_outgoing_arg(types::I32, 8); + let ss1 = sss.get_outgoing_arg(types::I32, 4); + let ss2 = sss.get_outgoing_arg(types::I64, 8); + + assert_eq!(sss[ss0].offset, 8); + assert_eq!(sss[ss0].size, 4); + + assert_eq!(sss[ss1].offset, 4); + assert_eq!(sss[ss1].size, 4); + + assert_eq!(sss[ss2].offset, 8); + assert_eq!(sss[ss2].size, 8); + + assert_eq!(sss.get_outgoing_arg(types::I32, 8), ss0); + assert_eq!(sss.get_outgoing_arg(types::I32, 4), ss1); + assert_eq!(sss.get_outgoing_arg(types::I64, 8), ss2); + } } From d53688872563de831c39289b73576894178bb04a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 31 Jul 2017 15:04:41 -0700 Subject: [PATCH 944/968] Verify the location of outgoing call arguments. Once a signature has been legalized, the arguments to any call using that signature must be assigned to the proper stack locations. Outgoing arguments that are passed on the stack must be assigned to matching OutgoingArg stack slot locations. Outgoing arguments that are passed in registers don't need to appear in the correct registers until after register allocation. --- lib/cretonne/src/verifier/mod.rs | 62 +++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index a1e04fbb79..751f6d5f6f 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -57,7 +57,7 @@ use flowgraph::ControlFlowGraph; use ir::entities::AnyEntity; use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, - Value, Type, Opcode}; + StackSlotKind, Value, Type, Opcode, ValueLoc, ArgumentLoc}; use isa::TargetIsa; use std::error as std_error; use std::fmt::{self, Display, Formatter}; @@ -552,6 +552,7 @@ impl<'a> Verifier<'a> { .iter() .map(|a| a.value_type); self.typecheck_variable_args_iterator(inst, arg_types)?; + self.check_outgoing_args(inst, sig_ref)?; } CallInfo::Indirect(sig_ref, _) => { let arg_types = self.func.dfg.signatures[sig_ref] @@ -559,6 +560,7 @@ impl<'a> Verifier<'a> { .iter() .map(|a| a.value_type); self.typecheck_variable_args_iterator(inst, arg_types)?; + self.check_outgoing_args(inst, sig_ref)?; } CallInfo::NotACall => {} } @@ -599,6 +601,64 @@ impl<'a> Verifier<'a> { Ok(()) } + /// Check the locations assigned to outgoing call arguments. + /// + /// When a signature has been legalized, all values passed as outgoing arguments on the stack + /// must be assigned to a matching `OutgoingArg` stack slot. + fn check_outgoing_args(&self, inst: Inst, sig_ref: SigRef) -> Result { + let sig = &self.func.dfg.signatures[sig_ref]; + + // Before legalization, there's nothing to check. + if sig.argument_bytes.is_none() { + return Ok(()); + } + + let args = self.func.dfg.inst_variable_args(inst); + let expected_args = &sig.argument_types[..]; + + for (&arg, &abi) in args.iter().zip(expected_args) { + // Value types have already been checked by `typecheck_variable_args_iterator()`. + if let ArgumentLoc::Stack(offset) = abi.location { + let arg_loc = self.func.locations.get_or_default(arg); + if let ValueLoc::Stack(ss) = arg_loc { + // Argument value is assigned to a stack slot as expected. + self.verify_stack_slot(inst, ss)?; + let slot = &self.func.stack_slots[ss]; + if slot.kind != StackSlotKind::OutgoingArg { + return err!(inst, + "Outgoing stack argument {} in wrong stack slot: {} = {}", + arg, + ss, + slot); + } + if slot.offset != offset { + return err!(inst, + "Outgoing stack argument {} should have offset {}: {} = {}", + arg, + offset, + ss, + slot); + } + if slot.size != abi.value_type.bytes() { + return err!(inst, + "Outgoing stack argument {} wrong size for {}: {} = {}", + arg, + abi.value_type, + ss, + slot); + } + } else { + let reginfo = self.isa.map(|i| i.register_info()); + return err!(inst, + "Outgoing stack argument {} in wrong location: {}", + arg, + arg_loc.display(reginfo.as_ref())); + } + } + } + Ok(()) + } + fn typecheck_return(&self, inst: Inst) -> Result { if self.func.dfg[inst].opcode().is_return() { let args = self.func.dfg.inst_variable_args(inst); From 6dc5b3e6083a09446f9c8d992a1f676da51472b8 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 1 Aug 2017 13:32:30 -0700 Subject: [PATCH 945/968] Assign call arguments to stack slots. When making an outgoing call, some arguments may have to be passed on the stack. Allocate OutgoingArg stack slots for these arguments and write them immediately before the outgoing call instruction. Do the same for incoming function arguments on the stack, but use IncomingArg stack slots instead. This was previously done in the spiller, but we move it to the legalizer so it is done at the same time as outgoing stack arguments. These stack slot assignments are done in the legalizer before live range analysis because the outgoing arguments usually are in different SSSA values with their own short live ranges. --- filetests/isa/riscv/legalize-abi.cton | 14 ++++ lib/cretonne/src/ir/extfunc.rs | 2 +- lib/cretonne/src/legalizer/boundary.rs | 99 +++++++++++++++++++++++++- lib/cretonne/src/legalizer/mod.rs | 7 +- lib/cretonne/src/regalloc/spilling.rs | 29 +------- 5 files changed, 118 insertions(+), 33 deletions(-) diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index 4d6f4e6e7f..cb94836ab4 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -3,6 +3,8 @@ test legalizer isa riscv ; regex: V=v\d+ +; regex: SS=ss\d+ +; regex: WS=\s+ function %int_split_args(i64) -> i64 { ebb0(v0: i64): @@ -118,3 +120,15 @@ ebb0(v0: i32, v1: f32x2): ; check: call_indirect $sig1, $v0($V, $V) return } + +; Call a function that takes arguments on the stack. +function %stack_args(i32) { + ; check: $(ss0=$SS) = outgoing_arg 4 + fn1 = function %foo(i64, i64, i64, i64, i32) +ebb0(v0: i32): + v1 = iconst.i64 1 + call fn1(v1, v1, v1, v1, v0) + ; check: [GPsp#48,$ss0]$WS $(v0s=$V) = spill $v0 + ; check: call $fn1($(=.*), $v0s) + return +} diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index e46ef39a54..4cf5b9d61f 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -51,7 +51,7 @@ impl Signature { let bytes = self.argument_types .iter() .filter_map(|arg| match arg.location { - ArgumentLoc::Stack(offset) if offset > 0 => { + ArgumentLoc::Stack(offset) if offset >= 0 => { Some(offset as u32 + arg.value_type.bytes()) } _ => None, diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 3f22694eb8..caed486f3a 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -20,7 +20,8 @@ use abi::{legalize_abi_value, ValueConversion}; use flowgraph::ControlFlowGraph; use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef, - ArgumentType, ArgumentPurpose}; + ArgumentType, ArgumentPurpose, ArgumentLoc, ValueLoc, ValueLocations, StackSlots, + StackSlotKind}; use ir::instructions::CallInfo; use isa::TargetIsa; use legalizer::split::{isplit, vsplit}; @@ -32,12 +33,15 @@ use legalizer::split::{isplit, vsplit}; /// in a state with type discrepancies. pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { isa.legalize_signature(&mut func.signature, true); + func.signature.compute_argument_bytes(); for sig in func.dfg.signatures.keys() { isa.legalize_signature(&mut func.dfg.signatures[sig], false); + func.dfg.signatures[sig].compute_argument_bytes(); } if let Some(entry) = func.layout.entry_block() { legalize_entry_arguments(func, entry); + spill_entry_arguments(func, entry); } } @@ -448,13 +452,18 @@ fn legalize_inst_arguments(dfg: &mut DataFlowGraph, /// original return values. The call's result values will be adapted to match the new signature. /// /// Returns `true` if any instructions were inserted. -pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mut Cursor) -> bool { +pub fn handle_call_abi(dfg: &mut DataFlowGraph, + locations: &mut ValueLocations, + stack_slots: &mut StackSlots, + cfg: &ControlFlowGraph, + pos: &mut Cursor) + -> bool { let mut inst = pos.current_inst() .expect("Cursor must point to a call instruction"); // Start by checking if the argument types already match the signature. let sig_ref = match check_call_signature(dfg, inst) { - Ok(_) => return false, + Ok(_) => return spill_call_arguments(dfg, locations, stack_slots, pos), Err(s) => s, }; @@ -478,6 +487,10 @@ pub fn handle_call_abi(dfg: &mut DataFlowGraph, cfg: &ControlFlowGraph, pos: &mu sig_ref, dfg.signatures[sig_ref]); + // Go back and insert spills for any stack arguments. + pos.goto_inst(inst); + spill_call_arguments(dfg, locations, stack_slots, pos); + // Yes, we changed stuff. true } @@ -556,3 +569,83 @@ pub fn handle_return_abi(dfg: &mut DataFlowGraph, // Yes, we changed stuff. true } + +/// Assign stack slots to incoming function arguments on the stack. +/// +/// Values that are passed into the function on the stack must be assigned to an `IncomingArg` +/// stack slot already during legalization. +fn spill_entry_arguments(func: &mut Function, entry: Ebb) { + for (abi, &arg) in func.signature + .argument_types + .iter() + .zip(func.dfg.ebb_args(entry)) { + if let ArgumentLoc::Stack(offset) = abi.location { + let ss = func.stack_slots.make_incoming_arg(abi.value_type, offset); + *func.locations.ensure(arg) = ValueLoc::Stack(ss); + } + } +} + +/// Assign stack slots to outgoing function arguments on the stack. +/// +/// Values that are passed to a called function on the stack must be assigned to a matching +/// `OutgoingArg` stack slot. The assignment must happen immediately before the call. +/// +/// TODO: The outgoing stack slots can be written a bit earlier, as long as there are no branches +/// or calls between writing the stack slots and the call instruction. Writing the slots earlier +/// could help reduce register pressure before the call. +fn spill_call_arguments(dfg: &mut DataFlowGraph, + locations: &mut ValueLocations, + stack_slots: &mut StackSlots, + pos: &mut Cursor) + -> bool { + let inst = pos.current_inst() + .expect("Cursor must point to a call instruction"); + let sig_ref = dfg.call_signature(inst) + .expect("Call instruction expected."); + + // Start by building a list of stack slots and arguments to be replaced. + // This requires borrowing `dfg`, so we can't change anything. + let arglist = dfg.inst_variable_args(inst) + .iter() + .zip(&dfg.signatures[sig_ref].argument_types) + .enumerate() + .filter_map(|(idx, (&arg, abi))| { + match abi.location { + ArgumentLoc::Stack(offset) => { + // Is `arg` already in the right kind of stack slot? + match locations.get(arg) { + Some(&ValueLoc::Stack(ss)) => { + // We won't reassign `arg` to a different stack slot. Assert out of + // the stack slot is wrong. + assert_eq!(stack_slots[ss].kind, StackSlotKind::OutgoingArg); + assert_eq!(stack_slots[ss].offset, offset); + assert_eq!(stack_slots[ss].size, abi.value_type.bytes()); + None + } + _ => { + // Assign `arg` to a new stack slot. + let ss = stack_slots.get_outgoing_arg(abi.value_type, offset); + Some((idx, arg, ss)) + } + } + } + _ => None, + } + }) + .collect::>(); + + if arglist.is_empty() { + return false; + } + + // Insert the spill instructions and rewrite call arguments. + for (idx, arg, ss) in arglist { + let stack_val = dfg.ins(pos).spill(arg); + *locations.ensure(stack_val) = ValueLoc::Stack(ss); + dfg.inst_variable_args_mut(inst)[idx] = stack_val; + } + + // We changed stuff. + true +} diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index c6a844c3c1..4494eb60e9 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -51,7 +51,12 @@ pub fn legalize_function(func: &mut Function, let opcode = func.dfg[inst].opcode(); // Check for ABI boundaries that need to be converted to the legalized signature. - if opcode.is_call() && boundary::handle_call_abi(&mut func.dfg, cfg, &mut pos) { + if opcode.is_call() && + boundary::handle_call_abi(&mut func.dfg, + &mut func.locations, + &mut func.stack_slots, + cfg, + &mut pos) { // Go back and legalize the inserted argument conversion instructions. pos.set_position(prev_pos); continue; diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 49f6d67d09..8b3e8b7837 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -17,7 +17,7 @@ use dominator_tree::DominatorTree; use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; -use ir::{Function, Ebb, Inst, Value, ValueLoc, ArgumentLoc, Signature, SigRef}; +use ir::{Function, Ebb, Inst, Value, ValueLoc, SigRef}; use ir::{InstEncodings, StackSlots, ValueLocations}; use isa::registers::{RegClassMask, RegClassIndex}; use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; @@ -46,7 +46,6 @@ struct Context<'a> { encodings: &'a mut InstEncodings, stack_slots: &'a mut StackSlots, locations: &'a mut ValueLocations, - func_signature: &'a Signature, // References to contextual data structures we need. domtree: &'a DominatorTree, @@ -94,7 +93,6 @@ impl Spilling { encodings: &mut func.encodings, stack_slots: &mut func.stack_slots, locations: &mut func.locations, - func_signature: &func.signature, domtree, liveness, virtregs, @@ -112,37 +110,12 @@ impl<'a> Context<'a> { layout: &mut Layout, dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker) { - if let Some(entry) = layout.entry_block() { - self.spill_entry_arguments(entry, dfg); - } - self.topo.reset(layout.ebbs()); while let Some(ebb) = self.topo.next(layout, self.domtree) { self.visit_ebb(ebb, layout, dfg, tracker); } } - /// Assign stack slots to incoming function arguments on the stack. - fn spill_entry_arguments(&mut self, entry: Ebb, dfg: &DataFlowGraph) { - for (abi, &arg) in self.func_signature - .argument_types - .iter() - .zip(dfg.ebb_args(entry)) { - if let ArgumentLoc::Stack(offset) = abi.location { - // Function arguments passed on the stack can't be part of a virtual register. We - // would need to write other values to the stack slot, but it belongs to the - // caller. (Not that the caller would care, nobody depends on stack arguments being - // preserved across calls). - assert_eq!(self.virtregs.get(arg), - None, - "Stack argument {} can't be part of a virtual register", - arg); - let ss = self.stack_slots.make_incoming_arg(abi.value_type, offset); - *self.locations.ensure(arg) = ValueLoc::Stack(ss); - } - } - } - fn visit_ebb(&mut self, ebb: Ebb, layout: &mut Layout, From 39488630f6335ee011d7922b5b8fddaa7a36cb51 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 2 Aug 2017 11:05:49 -0700 Subject: [PATCH 946/968] Avoid floating-point types in Ieee32::new and Ieee64::new. (#130) * Avoid floating-point types in Ieee32::new and Ieee64::new. This eliminates the need for unsafe code in code that uses Cretonne, a few instances of unsafe code in Cretonne itself, and eliminates the only instance of floating point in Cretonne. * Rename new to with_bits, and new_from_float to with_float. --- lib/cretonne/src/ir/immediates.rs | 83 +++++++++++++++++++------------ lib/frontend/src/ssa.rs | 4 +- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index fa6185cb8b..c525305a19 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -7,9 +7,11 @@ use std::fmt::{self, Display, Formatter}; use std::{i32, u32}; -use std::mem; use std::str::FromStr; +#[cfg(test)] +use std::mem; + /// 64-bit immediate integer operand. /// /// An `Imm64` operand can also be used to represent immediate values of smaller integer types by @@ -531,8 +533,14 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { } impl Ieee32 { + /// Create a new `Ieee32` containing the bits of `x`. + pub fn with_bits(x: u32) -> Ieee32 { + Ieee32(x) + } + /// Create a new `Ieee32` representing the number `x`. - pub fn new(x: f32) -> Ieee32 { + #[cfg(test)] + pub fn with_float(x: f32) -> Ieee32 { Ieee32(unsafe { mem::transmute(x) }) } } @@ -556,8 +564,14 @@ impl FromStr for Ieee32 { } impl Ieee64 { + /// Create a new `Ieee64` containing the bits of `x`. + pub fn with_bits(x: u64) -> Ieee64 { + Ieee64(x) + } + /// Create a new `Ieee64` representing the number `x`. - pub fn new(x: f64) -> Ieee64 { + #[cfg(test)] + pub fn with_float(x: f64) -> Ieee64 { Ieee64(unsafe { mem::transmute(x) }) } } @@ -714,26 +728,27 @@ mod tests { #[test] fn format_ieee32() { - assert_eq!(Ieee32::new(0.0).to_string(), "0.0"); - assert_eq!(Ieee32::new(-0.0).to_string(), "-0.0"); - assert_eq!(Ieee32::new(1.0).to_string(), "0x1.000000p0"); - assert_eq!(Ieee32::new(1.5).to_string(), "0x1.800000p0"); - assert_eq!(Ieee32::new(0.5).to_string(), "0x1.000000p-1"); - assert_eq!(Ieee32::new(f32::EPSILON).to_string(), "0x1.000000p-23"); - assert_eq!(Ieee32::new(f32::MIN).to_string(), "-0x1.fffffep127"); - assert_eq!(Ieee32::new(f32::MAX).to_string(), "0x1.fffffep127"); + assert_eq!(Ieee32::with_float(0.0).to_string(), "0.0"); + assert_eq!(Ieee32::with_float(-0.0).to_string(), "-0.0"); + assert_eq!(Ieee32::with_float(1.0).to_string(), "0x1.000000p0"); + assert_eq!(Ieee32::with_float(1.5).to_string(), "0x1.800000p0"); + assert_eq!(Ieee32::with_float(0.5).to_string(), "0x1.000000p-1"); + assert_eq!(Ieee32::with_float(f32::EPSILON).to_string(), + "0x1.000000p-23"); + assert_eq!(Ieee32::with_float(f32::MIN).to_string(), "-0x1.fffffep127"); + assert_eq!(Ieee32::with_float(f32::MAX).to_string(), "0x1.fffffep127"); // Smallest positive normal number. - assert_eq!(Ieee32::new(f32::MIN_POSITIVE).to_string(), + assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE).to_string(), "0x1.000000p-126"); // Subnormals. - assert_eq!(Ieee32::new(f32::MIN_POSITIVE / 2.0).to_string(), + assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE / 2.0).to_string(), "0x0.800000p-126"); - assert_eq!(Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON).to_string(), + assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE * f32::EPSILON).to_string(), "0x0.000002p-126"); - assert_eq!(Ieee32::new(f32::INFINITY).to_string(), "+Inf"); - assert_eq!(Ieee32::new(f32::NEG_INFINITY).to_string(), "-Inf"); - assert_eq!(Ieee32::new(f32::NAN).to_string(), "+NaN"); - assert_eq!(Ieee32::new(-f32::NAN).to_string(), "-NaN"); + assert_eq!(Ieee32::with_float(f32::INFINITY).to_string(), "+Inf"); + assert_eq!(Ieee32::with_float(f32::NEG_INFINITY).to_string(), "-Inf"); + assert_eq!(Ieee32::with_float(f32::NAN).to_string(), "+NaN"); + assert_eq!(Ieee32::with_float(-f32::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1"); assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001"); @@ -815,27 +830,29 @@ mod tests { #[test] fn format_ieee64() { - assert_eq!(Ieee64::new(0.0).to_string(), "0.0"); - assert_eq!(Ieee64::new(-0.0).to_string(), "-0.0"); - assert_eq!(Ieee64::new(1.0).to_string(), "0x1.0000000000000p0"); - assert_eq!(Ieee64::new(1.5).to_string(), "0x1.8000000000000p0"); - assert_eq!(Ieee64::new(0.5).to_string(), "0x1.0000000000000p-1"); - assert_eq!(Ieee64::new(f64::EPSILON).to_string(), + assert_eq!(Ieee64::with_float(0.0).to_string(), "0.0"); + assert_eq!(Ieee64::with_float(-0.0).to_string(), "-0.0"); + assert_eq!(Ieee64::with_float(1.0).to_string(), "0x1.0000000000000p0"); + assert_eq!(Ieee64::with_float(1.5).to_string(), "0x1.8000000000000p0"); + assert_eq!(Ieee64::with_float(0.5).to_string(), "0x1.0000000000000p-1"); + assert_eq!(Ieee64::with_float(f64::EPSILON).to_string(), "0x1.0000000000000p-52"); - assert_eq!(Ieee64::new(f64::MIN).to_string(), "-0x1.fffffffffffffp1023"); - assert_eq!(Ieee64::new(f64::MAX).to_string(), "0x1.fffffffffffffp1023"); + assert_eq!(Ieee64::with_float(f64::MIN).to_string(), + "-0x1.fffffffffffffp1023"); + assert_eq!(Ieee64::with_float(f64::MAX).to_string(), + "0x1.fffffffffffffp1023"); // Smallest positive normal number. - assert_eq!(Ieee64::new(f64::MIN_POSITIVE).to_string(), + assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE).to_string(), "0x1.0000000000000p-1022"); // Subnormals. - assert_eq!(Ieee64::new(f64::MIN_POSITIVE / 2.0).to_string(), + assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE / 2.0).to_string(), "0x0.8000000000000p-1022"); - assert_eq!(Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON).to_string(), + assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE * f64::EPSILON).to_string(), "0x0.0000000000001p-1022"); - assert_eq!(Ieee64::new(f64::INFINITY).to_string(), "+Inf"); - assert_eq!(Ieee64::new(f64::NEG_INFINITY).to_string(), "-Inf"); - assert_eq!(Ieee64::new(f64::NAN).to_string(), "+NaN"); - assert_eq!(Ieee64::new(-f64::NAN).to_string(), "-NaN"); + assert_eq!(Ieee64::with_float(f64::INFINITY).to_string(), "+Inf"); + assert_eq!(Ieee64::with_float(f64::NEG_INFINITY).to_string(), "-Inf"); + assert_eq!(Ieee64::with_float(f64::NAN).to_string(), "+NaN"); + assert_eq!(Ieee64::with_float(-f64::NAN).to_string(), "-NaN"); // Construct some qNaNs with payloads. assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1"); assert_eq!(Ieee64(0x7ffc000000000001).to_string(), diff --git a/lib/frontend/src/ssa.rs b/lib/frontend/src/ssa.rs index 46b44d5251..3794d332b1 100644 --- a/lib/frontend/src/ssa.rs +++ b/lib/frontend/src/ssa.rs @@ -490,9 +490,9 @@ impl SSABuilder let val = if ty.is_int() { dfg.ins(&mut cur).iconst(ty, 0) } else if ty == F32 { - dfg.ins(&mut cur).f32const(Ieee32::new(0.0)) + dfg.ins(&mut cur).f32const(Ieee32::with_bits(0)) } else if ty == F64 { - dfg.ins(&mut cur).f64const(Ieee64::new(0.0)) + dfg.ins(&mut cur).f64const(Ieee64::with_bits(0)) } else { panic!("value used but never declared and initialization not supported") }; From f19ddb49a33bf7254e1d03b46287aac5814a9e9d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 2 Aug 2017 11:19:16 -0700 Subject: [PATCH 947/968] Don't use documentation-style comments inside a function body. (#131) --- lib/cretonne/src/regalloc/coalescing.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 964644fad3..29051bf426 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -383,8 +383,8 @@ impl<'a> Context<'a> { succ_val: Value, preds: &[BasicBlock]) -> Option { - /// Initialize the value list with the split values. These are guaranteed to be - /// interference free, and anything that interferes with them must be split away. + // Initialize the value list with the split values. These are guaranteed to be + // interference free, and anything that interferes with them must be split away. self.reset_values(); dbg!("Trying {} with split values: {:?}", succ_val, self.values); From 534d1dd10d0bd6f8c1b3454eb5cb51ca0c3a044b Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Aug 2017 12:58:53 -0700 Subject: [PATCH 948/968] Compute the stack frame layout. Add a StackSlots::layout() method which computes the total stack frame size and assigns offsets to all spill slots and local variables so they don't interfere with each other or with incoming or outgoing function arguments. Stack slots are given an ad hoc alignment that is the natural alignment for power-of-two sized spill slots, up to the stack pointer alignment. It is possible we need explicit stack slot alignment in the future, but at least for spill slots, this scheme is likely to work for most ISAs. --- lib/cretonne/src/ir/stackslot.rs | 242 +++++++++++++++++++++++++++++-- 1 file changed, 230 insertions(+), 12 deletions(-) diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index 56dd037dc5..ea28eeae1a 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -5,10 +5,23 @@ use entity_map::{EntityMap, PrimaryEntityData, Keys}; use ir::{Type, StackSlot}; +use std::cmp::{min, max}; use std::fmt; use std::ops::Index; use std::str::FromStr; +/// The size of an object on the stack, or the size of a stack frame. +/// +/// We don't use `usize` to represent object sizes on the target platform because Cretonne supports +/// cross-compilation, and `usize` is a type that depends on the host platform, not the target +/// platform. +type StackSize = u32; + +/// A stack offset. +/// +/// The location of a stack offset relative to a stack pointer or frame pointer. +type StackOffset = i32; + /// The kind of a stack slot. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StackSlotKind { @@ -68,7 +81,7 @@ pub struct StackSlotData { pub kind: StackSlotKind, /// Size of stack slot in bytes. - pub size: u32, + pub size: StackSize, /// Offset of stack slot relative to the stack pointer in the caller. /// @@ -78,18 +91,28 @@ pub struct StackSlotData { /// /// For `OutgoingArg` stack slots, the offset is relative to the current function's stack /// pointer immediately before the call. - pub offset: i32, + pub offset: StackOffset, } impl StackSlotData { /// Create a stack slot with the specified byte size. - pub fn new(kind: StackSlotKind, size: u32) -> StackSlotData { + pub fn new(kind: StackSlotKind, size: StackSize) -> StackSlotData { StackSlotData { kind, size, offset: 0, } } + + /// Get the alignment in bytes of this stack slot given the stack pointer alignment. + pub fn alignment(&self, max_align: StackSize) -> StackSize { + debug_assert!(max_align.is_power_of_two()); + // We want to find the largest power of two that divides both `self.size` and `max_align`. + // That is the same as isolating the rightmost bit in `x`. + let x = self.size | max_align; + // C.f. Hacker's delight. + x & x.wrapping_neg() + } } impl fmt::Display for StackSlotData { @@ -114,6 +137,15 @@ pub struct StackSlots { /// All the outgoing stack slots, ordered by offset. outgoing: Vec, + + /// The total size of the stack frame. + /// + /// This is the distance from the stack pointer in the current function to the stack pointer in + /// the calling function, so it includes a pushed return address as well as space for outgoing + /// call arguments. + /// + /// This is computed by the `layout()` method. + pub frame_size: Option, } /// Stack slot manager functions that behave mostly like an entity map. @@ -123,6 +155,7 @@ impl StackSlots { StackSlots { slots: EntityMap::new(), outgoing: Vec::new(), + frame_size: None, } } @@ -130,6 +163,7 @@ impl StackSlots { pub fn clear(&mut self) { self.slots.clear(); self.outgoing.clear(); + self.frame_size = None; } /// Allocate a new stack slot. @@ -158,6 +192,14 @@ impl StackSlots { } } +impl Index for StackSlots { + type Output = StackSlotData; + + fn index(&self, ss: StackSlot) -> &StackSlotData { + &self.slots[ss] + } +} + /// Higher-level stack frame manipulation functions. impl StackSlots { /// Create a new spill slot for spilling values of type `ty`. @@ -166,9 +208,9 @@ impl StackSlots { } /// Create a stack slot representing an incoming function argument. - pub fn make_incoming_arg(&mut self, ty: Type, offset: i32) -> StackSlot { + pub fn make_incoming_arg(&mut self, ty: Type, offset: StackOffset) -> StackSlot { let mut data = StackSlotData::new(StackSlotKind::IncomingArg, ty.bytes()); - assert!(offset <= i32::max_value() - data.size as i32); + assert!(offset <= StackOffset::max_value() - data.size as StackOffset); data.offset = offset; self.push(data) } @@ -180,7 +222,7 @@ impl StackSlots { /// /// The requested offset is relative to this function's stack pointer immediately before making /// the call. - pub fn get_outgoing_arg(&mut self, ty: Type, offset: i32) -> StackSlot { + pub fn get_outgoing_arg(&mut self, ty: Type, offset: StackOffset) -> StackSlot { let size = ty.bytes(); // Look for an existing outgoing stack slot with the same offset and size. @@ -193,19 +235,108 @@ impl StackSlots { // No existing slot found. Make one and insert it into `outgoing`. let mut data = StackSlotData::new(StackSlotKind::OutgoingArg, size); - assert!(offset <= i32::max_value() - size as i32); + assert!(offset <= StackOffset::max_value() - size as StackOffset); data.offset = offset; let ss = self.slots.push(data); self.outgoing.insert(inspos, ss); ss } -} -impl Index for StackSlots { - type Output = StackSlotData; + /// Compute the stack frame layout. + /// + /// Determine the total size of this function's stack frame and assign offsets to all `Spill` + /// and `Local` stack slots. + /// + /// The total frame size will be a multiple of `alignment` which must be a power of two. + /// + /// Returns the total stack frame size which is also saved in `self.frame_size`. + pub fn layout(&mut self, alignment: StackSize) -> StackSize { + assert!(alignment.is_power_of_two() && alignment <= StackOffset::max_value() as StackSize, + "Invalid stack alignment {}", + alignment); - fn index(&self, ss: StackSlot) -> &StackSlotData { - &self.slots[ss] + // We assume a stack that grows toward lower addresses as implemented by modern ISAs. The + // stack layout from high to low addresses will be: + // + // 1. incoming arguments. + // 2. spills + locals. + // 3. outgoing arguments. + // + // The incoming arguments can have both positive and negative offsets. A negative offset + // incoming arguments is usually the x86 return address pushed by the call instruction, but + // it can also be fixed stack slots pushed by an externally generated prologue. + // + // Both incoming and outgoing argument slots have fixed offsets that are treated as + // reserved zones by the layout algorithm. + + let mut incoming_min = 0; + let mut outgoing_max = 0; + let mut min_align = alignment; + + for ss in self.keys() { + let slot = &self[ss]; + assert!(slot.size <= StackOffset::max_value() as StackSize); + match slot.kind { + StackSlotKind::IncomingArg => { + incoming_min = min(incoming_min, slot.offset); + } + StackSlotKind::OutgoingArg => { + let offset = slot.offset + .checked_add(slot.size as StackOffset) + .expect("Outgoing call argument overflows stack"); + outgoing_max = max(outgoing_max, offset); + } + StackSlotKind::SpillSlot | StackSlotKind::Local => { + // Determine the smallest alignment of any local or spill slot. + min_align = slot.alignment(min_align); + } + } + } + + // Lay out spill slots and locals below the incoming arguments. + // The offset is negative, growing downwards. + // Start with the smallest alignments for better packing. + let mut offset = incoming_min; + assert!(min_align.is_power_of_two()); + while min_align <= alignment { + for ss in self.keys() { + let slot = &mut self.slots[ss]; + + // Pick out locals and spill slots with exact alignment `min_align`. + match slot.kind { + StackSlotKind::SpillSlot | StackSlotKind::Local => { + if slot.alignment(alignment) != min_align { + continue; + } + } + _ => continue, + } + + // These limits should never be exceeded by spill slots, but locals can be + // arbitrarily large. + assert!(slot.size <= StackOffset::max_value() as StackSize); + offset = offset + .checked_sub(slot.size as StackOffset) + .expect("Stack frame larger than 2 GB"); + + // Aligning the negative offset can never cause overflow. We're only clearing bits. + offset &= -(min_align as StackOffset); + slot.offset = offset; + } + + // Move on to the next higher alignment. + min_align *= 2; + } + + // Finally, make room for the outgoing arguments. + offset = offset + .checked_sub(outgoing_max) + .expect("Stack frame larger than 2 GB"); + offset &= -(alignment as StackOffset); + + let frame_size = (offset as StackSize).wrapping_neg(); + self.frame_size = Some(frame_size); + frame_size } } @@ -254,4 +385,91 @@ mod tests { assert_eq!(sss.get_outgoing_arg(types::I32, 4), ss1); assert_eq!(sss.get_outgoing_arg(types::I64, 8), ss2); } + + #[test] + fn alignment() { + let slot = StackSlotData::new(StackSlotKind::SpillSlot, 8); + + assert_eq!(slot.alignment(4), 4); + assert_eq!(slot.alignment(8), 8); + assert_eq!(slot.alignment(16), 8); + + let slot2 = StackSlotData::new(StackSlotKind::Local, 24); + + assert_eq!(slot2.alignment(4), 4); + assert_eq!(slot2.alignment(8), 8); + assert_eq!(slot2.alignment(16), 8); + assert_eq!(slot2.alignment(32), 8); + } + + #[test] + fn layout() { + let mut sss = StackSlots::new(); + + // An empty layout should have 0-sized stack frame. + assert_eq!(sss.layout(1), 0); + assert_eq!(sss.layout(16), 0); + + // Same for incoming arguments with non-negative offsets. + let in0 = sss.make_incoming_arg(types::I64, 0); + let in1 = sss.make_incoming_arg(types::I64, 8); + + assert_eq!(sss.layout(1), 0); + assert_eq!(sss.layout(16), 0); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + + // Add some spill slots. + let ss0 = sss.make_spill_slot(types::I64); + let ss1 = sss.make_spill_slot(types::I32); + + assert_eq!(sss.layout(1), 12); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[ss0].offset, -8); + assert_eq!(sss[ss1].offset, -12); + + assert_eq!(sss.layout(16), 16); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[ss0].offset, -16); + assert_eq!(sss[ss1].offset, -4); + + // An incoming argument with negative offset counts towards the total frame size, but it + // should still pack nicely with the spill slots. + let in2 = sss.make_incoming_arg(types::I32, -4); + + assert_eq!(sss.layout(1), 16); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -12); + assert_eq!(sss[ss1].offset, -16); + + assert_eq!(sss.layout(16), 16); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -16); + assert_eq!(sss[ss1].offset, -8); + + // Finally, make sure there is room for the outgoing args. + let out0 = sss.get_outgoing_arg(types::I32, 0); + + assert_eq!(sss.layout(1), 20); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -12); + assert_eq!(sss[ss1].offset, -16); + assert_eq!(sss[out0].offset, 0); + + assert_eq!(sss.layout(16), 32); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -16); + assert_eq!(sss[ss1].offset, -8); + assert_eq!(sss[out0].offset, 0); + } } From bf5edd5c7121af9a1df5ee97989c55907342f610 Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Sat, 29 Jul 2017 16:21:41 +0500 Subject: [PATCH 949/968] Implement conditional compilation configuration in build.rs --- lib/cretonne/build.rs | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/lib/cretonne/build.rs b/lib/cretonne/build.rs index 4766a032e0..2653bf54a0 100644 --- a/lib/cretonne/build.rs +++ b/lib/cretonne/build.rs @@ -8,6 +8,13 @@ // OUT_DIR // Directory where generated files should be placed. // +// TARGET +// Target triple provided by Cargo. +// +// CRETONNE_TARGETS (Optional) +// A setting for conditional compilation of isa targets. Possible values can be "native" or +// known isa targets separated by ','. +// // The build script expects to be run from the directory where this build.rs file lives. The // current directory is used to find the sources. @@ -17,6 +24,22 @@ use std::process; fn main() { let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); + let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); + let cretonne_targets = env::var("CRETONNE_TARGETS").ok(); + let cretonne_targets = cretonne_targets.as_ref().map(|s| s.as_ref()); + + // Configure isa targets cfg. + match isa_targets(cretonne_targets, &target_triple) { + Ok(isa_targets) => { + for isa in &isa_targets { + println!("cargo:rustc-cfg=build_{}", isa.name()); + } + } + Err(err) => { + eprintln!("Error: {}", err); + process::exit(1); + } + } println!("Build script generating files in {}", out_dir); @@ -45,3 +68,84 @@ fn main() { process::exit(status.code().unwrap()); } } + +/// Represents known ISA target. +#[derive(Copy, Clone)] +enum Isa { + Riscv, + Intel, + Arm32, + Arm64, +} + +impl Isa { + /// Creates isa target using name. + fn new(name: &str) -> Option { + Isa::all() + .iter() + .cloned() + .filter(|isa| isa.name() == name) + .next() + } + + /// Creates isa target from arch. + fn from_arch(arch: &str) -> Option { + Isa::all() + .iter() + .cloned() + .filter(|isa| isa.is_arch_applicable(arch)) + .next() + } + + /// Returns all supported isa targets. + fn all() -> [Isa; 4] { + [Isa::Riscv, Isa::Intel, Isa::Arm32, Isa::Arm64] + } + + /// Returns name of the isa target. + fn name(&self) -> &'static str { + match *self { + Isa::Riscv => "riscv", + Isa::Intel => "intel", + Isa::Arm32 => "arm32", + Isa::Arm64 => "arm64", + } + } + + /// Checks if arch is applicable for the isa target. + fn is_arch_applicable(&self, arch: &str) -> bool { + match *self { + Isa::Riscv => arch == "riscv", + Isa::Intel => ["x86_64", "i386", "i586", "i686"].contains(&arch), + Isa::Arm32 => arch.starts_with("arm") || arch.starts_with("thumb"), + Isa::Arm64 => arch == "aarch64", + } + } +} + +/// Returns isa targets to configure conditional compilation. +fn isa_targets(cretonne_targets: Option<&str>, target_triple: &str) -> Result, String> { + match cretonne_targets { + Some("native") => { + Isa::from_arch(target_triple.split('-').next().unwrap()) + .map(|isa| vec![isa]) + .ok_or_else(|| { + format!("no supported isa found for target triple `{}`", + target_triple) + }) + } + Some(targets) => { + let unknown_isa_targets = targets + .split(',') + .filter(|target| Isa::new(target).is_none()) + .collect::>(); + let isa_targets = targets.split(',').flat_map(Isa::new).collect::>(); + match (unknown_isa_targets.is_empty(), isa_targets.is_empty()) { + (true, true) => Ok(Isa::all().to_vec()), + (true, _) => Ok(isa_targets), + (_, _) => Err(format!("unknown isa targets: `{}`", unknown_isa_targets.join(", "))), + } + } + None => Ok(Isa::all().to_vec()), + } +} From bf1820587c5fb1f0130bea8ce9f33c97d1b88f06 Mon Sep 17 00:00:00 2001 From: Aleksey Kuznetsov Date: Sat, 29 Jul 2017 20:05:45 +0500 Subject: [PATCH 950/968] Apply conditional compilation of isa targets --- lib/cretonne/src/isa/mod.rs | 63 +++++++++++++++++---------- lib/cretonne/src/regalloc/pressure.rs | 3 +- lib/cretonne/src/regalloc/solver.rs | 3 +- lib/reader/src/parser.rs | 14 ++++-- 4 files changed, 55 insertions(+), 28 deletions(-) diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index b808b88746..7caac93a67 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -27,10 +27,10 @@ //! let shared_flags = settings::Flags::new(&shared_builder); //! //! match isa::lookup("riscv") { -//! None => { +//! Err(_) => { //! // The RISC-V target ISA is not available. //! } -//! Some(mut isa_builder) => { +//! Ok(mut isa_builder) => { //! isa_builder.set("supports_m", "on"); //! let isa = isa_builder.finish(shared_flags); //! } @@ -51,42 +51,61 @@ use ir; use regalloc; use isa::enc_tables::Encodings; +#[cfg(build_riscv)] pub mod riscv; + +#[cfg(build_intel)] pub mod intel; + +#[cfg(build_arm32)] pub mod arm32; + +#[cfg(build_arm64)] pub mod arm64; + pub mod registers; mod encoding; mod enc_tables; mod constraints; +/// Returns a builder that can create a corresponding `TargetIsa` +/// or `Err(LookupError::Unsupported)` if not enabled. +macro_rules! isa_builder { + ($module:ident, $name:ident) => { + { + #[cfg($name)] + fn $name() -> Result { + Ok($module::isa_builder()) + }; + #[cfg(not($name))] + fn $name() -> Result { + Err(LookupError::Unsupported) + } + $name() + } + }; +} + /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. -pub fn lookup(name: &str) -> Option { +pub fn lookup(name: &str) -> Result { match name { - "riscv" => riscv_builder(), - "intel" => intel_builder(), - "arm32" => arm32_builder(), - "arm64" => arm64_builder(), - _ => None, + "riscv" => isa_builder!(riscv, build_riscv), + "intel" => isa_builder!(intel, build_intel), + "arm32" => isa_builder!(arm32, build_arm32), + "arm64" => isa_builder!(arm64, build_arm64), + _ => Err(LookupError::Unknown), } } -// Make a builder for RISC-V. -fn riscv_builder() -> Option { - Some(riscv::isa_builder()) -} +/// Describes reason for target lookup failure +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LookupError { + /// Unknown Target + Unknown, -fn intel_builder() -> Option { - Some(intel::isa_builder()) -} - -fn arm32_builder() -> Option { - Some(arm32::isa_builder()) -} - -fn arm64_builder() -> Option { - Some(arm64::isa_builder()) + /// Target known but not built and thus not supported + Unsupported, } /// Builder for a `TargetIsa`. diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index fd9cf7c363..a9ab6bbbcb 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -247,6 +247,7 @@ impl fmt::Display for Pressure { } #[cfg(test)] +#[cfg(build_arm32)] mod tests { use isa::{TargetIsa, RegClass}; use regalloc::AllocatableSet; @@ -261,7 +262,7 @@ mod tests { let shared_builder = settings::builder(); let shared_flags = settings::Flags::new(&shared_builder); - isa::lookup("arm32").map(|b| b.finish(shared_flags)) + isa::lookup("arm32").ok().map(|b| b.finish(shared_flags)) } // Get a register class by name. diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index b135f051f3..ea028f551b 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -700,6 +700,7 @@ impl Solver { } #[cfg(test)] +#[cfg(build_arm32)] mod tests { use entity_ref::EntityRef; use ir::Value; @@ -716,7 +717,7 @@ mod tests { let shared_builder = settings::builder(); let shared_flags = settings::Flags::new(&shared_builder); - isa::lookup("arm32").map(|b| b.finish(shared_flags)) + isa::lookup("arm32").ok().map(|b| b.finish(shared_flags)) } // Get a register class by name. diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index fb659a6b8e..865cdcbd0d 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -621,8 +621,6 @@ impl<'a> Parser<'a> { &self.loc)?; } "isa" => { - last_set_loc = None; - seen_isa = true; let loc = self.loc; // Grab the whole line so the lexer won't go looking for tokens on the // following lines. @@ -633,9 +631,16 @@ impl<'a> Parser<'a> { Some(w) => w, }; let mut isa_builder = match isa::lookup(isa_name) { - None => return err!(loc, "unknown ISA '{}'", isa_name), - Some(b) => b, + Err(isa::LookupError::Unknown) => { + return err!(loc, "unknown ISA '{}'", isa_name) + } + Err(isa::LookupError::Unsupported) => { + continue; + } + Ok(b) => b, }; + last_set_loc = None; + seen_isa = true; // Apply the ISA-specific settings to `isa_builder`. isaspec::parse_options(words, &mut isa_builder, &self.loc)?; @@ -1939,6 +1944,7 @@ mod tests { } #[test] + #[cfg(build_riscv)] fn isa_spec() { assert!(parse_test("isa function %foo() {}") From 7f3b8075972f90f43ce00b57034e46d887516031 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Wed, 2 Aug 2017 16:40:35 -0700 Subject: [PATCH 951/968] Add a calling convention to all function signatures. A CallConv enum on every function signature makes it possible to generate calls to functions with different calling conventions within the same ISA / within a single function. The calling conventions also serve as a way of customizing Cretonne's behavior when embedded inside a VM. As an example, the SpiderWASM calling convention is used to compile WebAssembly functions that run inside the SpiderMonkey virtual machine. All function signatures must have a calling convention at the end, so this changes the textual IL syntax. Before: sig1 = signature(i32, f64) -> f64 After sig1 = (i32, f64) -> f64 native sig2 = (i32) spiderwasm When printing functions, the signature goes after the return types: function %r1() -> i32, f32 spiderwasm { ebb1: ... } In the parser, this calling convention is optional and defaults to "native". This is mostly to avoid updating all the existing test cases under filetests/. When printing a function, the calling convention is always included, including for "native" functions. --- docs/example.cton | 2 +- docs/langref.rst | 7 -- filetests/isa/intel/abi64.cton | 12 ++-- filetests/isa/intel/binary32.cton | 2 +- filetests/isa/intel/binary64.cton | 4 +- filetests/isa/riscv/abi-e.cton | 4 +- filetests/isa/riscv/abi.cton | 24 +++---- filetests/isa/riscv/binary32.cton | 2 +- filetests/isa/riscv/legalize-abi.cton | 4 +- filetests/isa/riscv/parse-encoding.cton | 30 ++++----- filetests/licm/basic.cton | 2 +- filetests/licm/complex.cton | 2 +- filetests/parser/branch.cton | 12 ++-- filetests/parser/call.cton | 26 ++++---- filetests/parser/instruction_encoding.cton | 2 +- filetests/parser/keywords.cton | 2 +- filetests/parser/rewrite.cton | 4 +- filetests/parser/tiny.cton | 24 +++---- filetests/regalloc/spill.cton | 4 +- lib/cretonne/src/ir/extfunc.rs | 70 +++++++++++++++++--- lib/cretonne/src/ir/function.rs | 6 +- lib/cretonne/src/ir/mod.rs | 3 +- lib/cretonne/src/write.rs | 15 +++-- lib/frontend/src/frontend.rs | 4 +- lib/frontend/src/lib.rs | 4 +- lib/reader/src/parser.rs | 74 ++++++++++++++-------- tests/cfg_traversal.rs | 10 +-- 27 files changed, 211 insertions(+), 144 deletions(-) diff --git a/docs/example.cton b/docs/example.cton index 493794da50..0cbd77a6cd 100644 --- a/docs/example.cton +++ b/docs/example.cton @@ -1,6 +1,6 @@ test verifier -function %average(i32, i32) -> f32 { +function %average(i32, i32) -> f32 native { ss1 = local 8 ; Stack slot for ``sum``. ebb1(v1: i32, v2: i32): diff --git a/docs/langref.rst b/docs/langref.rst index e7b0cad3d7..f20b5b6e84 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -410,13 +410,6 @@ This simple example illustrates direct function calls and signatures:: Indirect function calls use a signature declared in the preamble. -.. inst:: SIG = signature signature - - Declare a function signature for use with indirect calls. - - :arg signature: Function signature. See :token:`signature`. - :result SIG: A signature identifier. - .. autoinst:: call_indirect .. todo:: Define safe indirect function calls. diff --git a/filetests/isa/intel/abi64.cton b/filetests/isa/intel/abi64.cton index ecd3d29fd6..59f3107560 100644 --- a/filetests/isa/intel/abi64.cton +++ b/filetests/isa/intel/abi64.cton @@ -6,14 +6,14 @@ isa intel ; regex: V=v\d+ function %f() { - sig0 = signature(i32) -> i32 - ; check: sig0 = signature(i32 [%rdi]) -> i32 [%rax] + sig0 = (i32) -> i32 native + ; check: sig0 = (i32 [%rdi]) -> i32 [%rax] native - sig1 = signature(i64) -> b1 - ; check: sig1 = signature(i64 [%rdi]) -> b1 [%rax] + sig1 = (i64) -> b1 native + ; check: sig1 = (i64 [%rdi]) -> b1 [%rax] native - sig2 = signature(f32, i64) -> f64 - ; check: sig2 = signature(f32 [%xmm0], i64 [%rdi]) -> f64 [%xmm0] + sig2 = (f32, i64) -> f64 native + ; check: sig2 = (f32 [%xmm0], i64 [%rdi]) -> f64 [%xmm0] native ebb0: return diff --git a/filetests/isa/intel/binary32.cton b/filetests/isa/intel/binary32.cton index 456623a235..af1bf73a9b 100644 --- a/filetests/isa/intel/binary32.cton +++ b/filetests/isa/intel/binary32.cton @@ -9,7 +9,7 @@ isa intel haswell function %I32() { fn0 = function %foo() - sig0 = signature() + sig0 = () ebb0: ; asm: movl $1, %ecx diff --git a/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton index bc19e039d3..fb6f62d928 100644 --- a/filetests/isa/intel/binary64.cton +++ b/filetests/isa/intel/binary64.cton @@ -11,7 +11,7 @@ isa intel haswell ; Tests for i64 instructions. function %I64() { fn0 = function %foo() - sig0 = signature() + sig0 = () ebb0: @@ -457,7 +457,7 @@ ebb2: ; be done by an instruction shrinking pass. function %I32() { fn0 = function %foo() - sig0 = signature() + sig0 = () ebb0: diff --git a/filetests/isa/riscv/abi-e.cton b/filetests/isa/riscv/abi-e.cton index 543b55079f..df06402283 100644 --- a/filetests/isa/riscv/abi-e.cton +++ b/filetests/isa/riscv/abi-e.cton @@ -7,8 +7,8 @@ isa riscv enable_e function %f() { ; Spilling into the stack args after %x15 since %16 and up are not ; available in RV32E. - sig0 = signature(i64, i64, i64, i64) -> i64 - ; check: sig0 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [0], i32 [4]) -> i32 [%x10], i32 [%x11] + sig0 = (i64, i64, i64, i64) -> i64 native + ; check: sig0 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [0], i32 [4]) -> i32 [%x10], i32 [%x11] native ebb0: return } diff --git a/filetests/isa/riscv/abi.cton b/filetests/isa/riscv/abi.cton index f26c7686cf..c57c09fd97 100644 --- a/filetests/isa/riscv/abi.cton +++ b/filetests/isa/riscv/abi.cton @@ -5,27 +5,27 @@ isa riscv ; regex: V=v\d+ function %f() { - sig0 = signature(i32) -> i32 - ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + sig0 = (i32) -> i32 native + ; check: sig0 = (i32 [%x10]) -> i32 [%x10] native - sig1 = signature(i64) -> b1 - ; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + sig1 = (i64) -> b1 native + ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native ; The i64 argument must go in an even-odd register pair. - sig2 = signature(f32, i64) -> f64 - ; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + sig2 = (f32, i64) -> f64 native + ; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] native ; Spilling into the stack args. - sig3 = signature(f64, f64, f64, f64, f64, f64, f64, i64) -> f64 - ; check: sig3 = signature(f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] + sig3 = (f64, f64, f64, f64, f64, f64, f64, i64) -> f64 native + ; check: sig3 = (f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] native ; Splitting vectors. - sig4 = signature(i32x4) - ; check: sig4 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) + sig4 = (i32x4) native + ; check: sig4 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) native ; Splitting vectors, then splitting ints. - sig5 = signature(i64x4) - ; check: sig5 = signature(i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) + sig5 = (i64x4) native + ; check: sig5 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) native ebb0: return diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton index 6575609267..1ed2fcabea 100644 --- a/filetests/isa/riscv/binary32.cton +++ b/filetests/isa/riscv/binary32.cton @@ -4,7 +4,7 @@ isa riscv function %RV32I(i32 link [%x1]) -> i32 link [%x1] { fn0 = function %foo() - sig0 = signature() + sig0 = () ebb0(v9999: i32): [-,%x10] v1 = iconst.i32 1 diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton index cb94836ab4..f80494cc1a 100644 --- a/filetests/isa/riscv/legalize-abi.cton +++ b/filetests/isa/riscv/legalize-abi.cton @@ -106,7 +106,7 @@ ebb0(v0: i64x4): } function %indirect(i32) { - sig1 = signature() + sig1 = () native ebb0(v0: i32): call_indirect sig1, v0() return @@ -114,7 +114,7 @@ ebb0(v0: i32): ; The first argument to call_indirect doesn't get altered. function %indirect_arg(i32, f32x2) { - sig1 = signature(f32x2) + sig1 = (f32x2) native ebb0(v0: i32, v1: f32x2): call_indirect sig1, v0(v1) ; check: call_indirect $sig1, $v0($V, $V) diff --git a/filetests/isa/riscv/parse-encoding.cton b/filetests/isa/riscv/parse-encoding.cton index cd02a3ca47..3fdb8f62d6 100644 --- a/filetests/isa/riscv/parse-encoding.cton +++ b/filetests/isa/riscv/parse-encoding.cton @@ -3,32 +3,32 @@ test legalizer isa riscv function %parse_encoding(i32 [%x5]) -> i32 [%x10] { - ; check: function %parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] { + ; check: function %parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] native { - sig0 = signature(i32 [%x10]) -> i32 [%x10] - ; check: sig0 = signature(i32 [%x10]) -> i32 [%x10] + sig0 = (i32 [%x10]) -> i32 [%x10] native + ; check: sig0 = (i32 [%x10]) -> i32 [%x10] native - sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] - ; check: sig1 = signature(i32 [%x10], i32 [%x11]) -> b1 [%x10] + sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native + ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native - sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] - ; check: sig2 = signature(f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] + sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] native + ; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] native ; Arguments on stack where not necessary - sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] - ; check: sig3 = signature(f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] + sig3 = (f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] native + ; check: sig3 = (f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] native ; Stack argument before register argument - sig4 = signature(f32 [72], i32 [%x10]) - ; check: sig4 = signature(f32 [72], i32 [%x10]) + sig4 = (f32 [72], i32 [%x10]) native + ; check: sig4 = (f32 [72], i32 [%x10]) native ; Return value on stack - sig5 = signature() -> f32 [0] - ; check: sig5 = signature() -> f32 [0] + sig5 = () -> f32 [0] native + ; check: sig5 = () -> f32 [0] native ; function + signature - fn15 = function %bar(i32 [%x10]) -> b1 [%x10] - ; check: sig6 = signature(i32 [%x10]) -> b1 [%x10] + fn15 = function %bar(i32 [%x10]) -> b1 [%x10] native + ; check: sig6 = (i32 [%x10]) -> b1 [%x10] native ; nextln: fn0 = sig6 %bar ebb0(v0: i32): diff --git a/filetests/licm/basic.cton b/filetests/licm/basic.cton index 36b7864cfe..37dda60d2a 100644 --- a/filetests/licm/basic.cton +++ b/filetests/licm/basic.cton @@ -14,7 +14,7 @@ ebb2(v5: i32): return v5 } -; sameln: function %simple_loop(i32) -> i32 { +; sameln: function %simple_loop ; nextln: ebb2(v6: i32): ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 2 diff --git a/filetests/licm/complex.cton b/filetests/licm/complex.cton index b5063d807f..07efb9ff5f 100644 --- a/filetests/licm/complex.cton +++ b/filetests/licm/complex.cton @@ -39,7 +39,7 @@ ebb5(v16: i32): return v17 } -; sameln: function %complex(i32) -> i32 { +; sameln: function %complex ; nextln: ebb6(v20: i32): ; nextln: v1 = iconst.i32 1 ; nextln: v2 = iconst.i32 4 diff --git a/filetests/parser/branch.cton b/filetests/parser/branch.cton index 4adb4b5d27..283dd9a03a 100644 --- a/filetests/parser/branch.cton +++ b/filetests/parser/branch.cton @@ -9,7 +9,7 @@ ebb0: ebb1: jump ebb0() } -; sameln: function %minimal() { +; sameln: function %minimal() native { ; nextln: ebb0: ; nextln: jump ebb1 ; nextln: @@ -25,7 +25,7 @@ ebb0(v90: i32): ebb1(v91: i32): jump ebb0(v91) } -; sameln: function %onearg(i32) { +; sameln: function %onearg(i32) native { ; nextln: ebb0($v90: i32): ; nextln: jump ebb1($v90) ; nextln: @@ -41,7 +41,7 @@ ebb0(v90: i32, v91: f32): ebb1(v92: i32, v93: f32): jump ebb0(v92, v93) } -; sameln: function %twoargs(i32, f32) { +; sameln: function %twoargs(i32, f32) native { ; nextln: ebb0($v90: i32, $v91: f32): ; nextln: jump ebb1($v90, $v91) ; nextln: @@ -57,7 +57,7 @@ ebb0(v90: i32): ebb1: brnz v90, ebb1() } -; sameln: function %minimal(i32) { +; sameln: function %minimal(i32) native { ; nextln: ebb0($v90: i32): ; nextln: brz $v90, ebb1 ; nextln: @@ -72,7 +72,7 @@ ebb0(v90: i32, v91: f32): ebb1(v92: i32, v93: f32): brnz v90, ebb0(v92, v93) } -; sameln: function %twoargs(i32, f32) { +; sameln: function %twoargs(i32, f32) native { ; nextln: ebb0($v90: i32, $v91: f32): ; nextln: brz $v90, ebb1($v90, $v91) ; nextln: @@ -94,7 +94,7 @@ ebb30: ebb40: trap } -; sameln: function %jumptable(i32) { +; sameln: function %jumptable(i32) native { ; nextln: jt0 = jump_table 0 ; nextln: jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 ; nextln: diff --git a/filetests/parser/call.cton b/filetests/parser/call.cton index 9d7c2c3d16..662925db7f 100644 --- a/filetests/parser/call.cton +++ b/filetests/parser/call.cton @@ -5,18 +5,18 @@ function %mini() { ebb1: return } -; sameln: function %mini() { +; sameln: function %mini() native { ; nextln: ebb0: ; nextln: return ; nextln: } -function %r1() -> i32, f32 { +function %r1() -> i32, f32 spiderwasm { ebb1: v1 = iconst.i32 3 v2 = f32const 0.0 return v1, v2 } -; sameln: function %r1() -> i32, f32 { +; sameln: function %r1() -> i32, f32 spiderwasm { ; nextln: ebb0: ; nextln: $v1 = iconst.i32 3 ; nextln: $v2 = f32const 0.0 @@ -24,15 +24,15 @@ ebb1: ; nextln: } function %signatures() { - sig10 = signature() - sig11 = signature(i32, f64) -> i32, b1 + sig10 = () + sig11 = (i32, f64) -> i32, b1 spiderwasm fn5 = sig11 %foo fn8 = function %bar(i32) -> b1 } -; sameln: function %signatures() { -; nextln: $sig10 = signature() -; nextln: $sig11 = signature(i32, f64) -> i32, b1 -; nextln: sig2 = signature(i32) -> b1 +; sameln: function %signatures() native { +; nextln: $sig10 = () native +; nextln: $sig11 = (i32, f64) -> i32, b1 spiderwasm +; nextln: sig2 = (i32) -> b1 native ; nextln: $fn5 = $sig11 %foo ; nextln: $fn8 = sig2 %bar ; nextln: } @@ -54,9 +54,9 @@ ebb0: ; check: return function %indirect(i64) { - sig0 = signature(i64) - sig1 = signature() -> i32 - sig2 = signature() -> i32, f32 + sig0 = (i64) + sig1 = () -> i32 + sig2 = () -> i32, f32 ebb0(v0: i64): v1 = call_indirect sig1, v0() @@ -74,7 +74,7 @@ function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 ebb0(v1: i32, v2: i32, v3: i32, v4: i32): return v4, v2, v3, v1 } -; check: function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret { +; check: function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret native { ; check: ebb0($v1: i32, $v2: i32, $v3: i32, $v4: i32): ; check: return $v4, $v2, $v3, $v1 ; check: } diff --git a/filetests/parser/instruction_encoding.cton b/filetests/parser/instruction_encoding.cton index bc2e1dd239..c808892701 100644 --- a/filetests/parser/instruction_encoding.cton +++ b/filetests/parser/instruction_encoding.cton @@ -13,7 +13,7 @@ ebb1(v0: i32, v1: i32): v9 = iadd v8, v7 [Iret#5] return v0, v8 } -; sameln: function %foo(i32, i32) { +; sameln: function %foo(i32, i32) native { ; nextln: $ebb1($v0: i32, $v1: i32): ; nextln: [-,-]$WS $v2 = iadd $v0, $v1 ; nextln: [-]$WS trap diff --git a/filetests/parser/keywords.cton b/filetests/parser/keywords.cton index 37d0390a58..a4b894574e 100644 --- a/filetests/parser/keywords.cton +++ b/filetests/parser/keywords.cton @@ -2,4 +2,4 @@ test cat ; 'function' is not a keyword, and can be used as the name of a function too. function %function() {} -; check: function %function() +; check: function %function() native diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton index f7ebfa3876..c6a04a9e9c 100644 --- a/filetests/parser/rewrite.cton +++ b/filetests/parser/rewrite.cton @@ -15,7 +15,7 @@ ebb100(v20: i32): v9200 = f64const 0x4.0p0 trap } -; sameln: function %defs() { +; sameln: function %defs() native { ; nextln: $ebb100($v20: i32): ; nextln: $v1000 = iconst.i32x8 5 ; nextln: $v9200 = f64const 0x1.0000000000000p2 @@ -29,7 +29,7 @@ ebb100(v20: i32): v200 = iadd v20, v1000 jump ebb100(v1000) } -; sameln: function %use_value() { +; sameln: function %use_value() native { ; nextln: ebb0($v20: i32): ; nextln: $v1000 = iadd_imm $v20, 5 ; nextln: $v200 = iadd $v20, $v1000 diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index fff7ccd4c1..ecd2525ba2 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -5,7 +5,7 @@ function %minimal() { ebb0: trap } -; sameln: function %minimal() { +; sameln: function %minimal() native { ; nextln: ebb0: ; nextln: trap ; nextln: } @@ -18,7 +18,7 @@ ebb0: v1 = iconst.i8 6 v2 = ishl v0, v1 } -; sameln: function %ivalues() { +; sameln: function %ivalues() native { ; nextln: ebb0: ; nextln: $v0 = iconst.i32 2 ; nextln: $v1 = iconst.i8 6 @@ -34,7 +34,7 @@ ebb0: v2 = bextend.b32 v1 v3 = bxor v0, v2 } -; sameln: function %bvalues() { +; sameln: function %bvalues() native { ; nextln: ebb0: ; nextln: $v0 = bconst.b32 true ; nextln: $v1 = bconst.b8 false @@ -47,7 +47,7 @@ function %select() { ebb0(v90: i32, v91: i32, v92: b1): v0 = select v92, v90, v91 } -; sameln: function %select() { +; sameln: function %select() native { ; nextln: ebb0($v90: i32, $v91: i32, $v92: b1): ; nextln: $v0 = select $v92, $v90, $v91 ; nextln: } @@ -59,7 +59,7 @@ ebb0: v1 = extractlane v0, 3 v2 = insertlane v0, 1, v1 } -; sameln: function %lanes() { +; sameln: function %lanes() native { ; nextln: ebb0: ; nextln: $v0 = iconst.i32x4 2 ; nextln: $v1 = extractlane $v0, 3 @@ -75,7 +75,7 @@ ebb0(v90: i32, v91: i32): v3 = irsub_imm v91, 45 br_icmp eq v90, v91, ebb0(v91, v90) } -; sameln: function %icmp(i32, i32) { +; sameln: function %icmp(i32, i32) native { ; nextln: ebb0($v90: i32, $v91: i32): ; nextln: $v0 = icmp eq $v90, $v91 ; nextln: $v1 = icmp ult $v90, $v91 @@ -91,7 +91,7 @@ ebb0(v90: f32, v91: f32): v1 = fcmp uno v90, v91 v2 = fcmp lt v90, v91 } -; sameln: function %fcmp(f32, f32) { +; sameln: function %fcmp(f32, f32) native { ; nextln: ebb0($v90: f32, $v91: f32): ; nextln: $v0 = fcmp eq $v90, $v91 ; nextln: $v1 = fcmp uno $v90, $v91 @@ -105,7 +105,7 @@ ebb0(v90: i32, v91: f32): v0 = bitcast.i8x4 v90 v1 = bitcast.i32 v91 } -; sameln: function %bitcast(i32, f32) { +; sameln: function %bitcast(i32, f32) native { ; nextln: ebb0($v90: i32, $v91: f32): ; nextln: $v0 = bitcast.i8x4 $v90 ; nextln: $v1 = bitcast.i32 $v91 @@ -124,7 +124,7 @@ ebb0: stack_store v1, ss10+2 stack_store v2, ss2 } -; sameln: function %stack() { +; sameln: function %stack() native { ; nextln: $ss10 = spill_slot 8 ; nextln: $ss2 = local 4 ; nextln: $ss3 = incoming_arg 4, offset 8 @@ -144,7 +144,7 @@ ebb0(v1: i32): v3 = heap_load.f32 v1+12 heap_store v3, v1 } -; sameln: function %heap(i32) { +; sameln: function %heap(i32) native { ; nextln: ebb0($v1: i32): ; nextln: $v2 = heap_load.f32 $v1 ; nextln: $v3 = heap_load.f32 $v1+12 @@ -164,7 +164,7 @@ ebb0(v1: i32): store aligned v3, v1+12 store notrap aligned v3, v1-12 } -; sameln: function %memory(i32) { +; sameln: function %memory(i32) native { ; nextln: ebb0($v1: i32): ; nextln: $v2 = load.i64 $v1 ; nextln: $v3 = load.i64 aligned $v1 @@ -185,7 +185,7 @@ ebb0(v1: i32): regmove v1, %20 -> %10 return } -; sameln: function %diversion(i32) { +; sameln: function %diversion(i32) native { ; nextln: ebb0($v1: i32): ; nextln: regmove $v1, %10 -> %20 ; nextln: regmove $v1, %20 -> %10 diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton index 4c470a73e6..901509a8d4 100644 --- a/filetests/regalloc/spill.cton +++ b/filetests/regalloc/spill.cton @@ -93,7 +93,7 @@ ebb0(v0: i32): ; The same value used as indirect callee and argument. function %doubleuse_icall1(i32) { - sig0 = signature(i32) + sig0 = (i32) native ebb0(v0: i32): ; not:copy call_indirect sig0, v0(v0) @@ -102,7 +102,7 @@ ebb0(v0: i32): ; The same value used as indirect callee and two arguments. function %doubleuse_icall2(i32) { - sig0 = signature(i32, i32) + sig0 = (i32, i32) native ebb0(v0: i32): ; check: $(c=$V) = copy $v0 call_indirect sig0, v0(v0, v0) diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 4cf5b9d61f..16211a4d83 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -25,6 +25,9 @@ pub struct Signature { /// Types returned from the function. pub return_types: Vec, + /// Calling convention. + pub call_conv: CallConv, + /// When the signature has been legalized to a specific ISA, this holds the size of the /// argument array on the stack. Before legalization, this is `None`. /// @@ -35,10 +38,11 @@ pub struct Signature { impl Signature { /// Create a new blank signature. - pub fn new() -> Signature { + pub fn new(call_conv: CallConv) -> Signature { Signature { argument_types: Vec::new(), return_types: Vec::new(), + call_conv, argument_bytes: None, } } @@ -94,7 +98,7 @@ impl<'a> fmt::Display for DisplaySignature<'a> { write!(f, " -> ")?; write_list(f, &self.0.return_types, self.1)?; } - Ok(()) + write!(f, " {}", self.0.call_conv) } } @@ -278,6 +282,46 @@ impl fmt::Display for ExtFuncData { } } +/// A Calling convention. +/// +/// A function's calling convention determines exactly how arguments and return values are passed, +/// and how stack frames are managed. Since all of these details depend on both the instruction set +/// architecture and possibly the operating system, a function's calling convention is only fully +/// determined by a `(TargetIsa, CallConv)` tuple. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CallConv { + /// The C calling convention. + /// + /// This is the native calling convention that a C compiler would use on the platform. + Native, + + /// A JIT-compiled WebAssembly function in the SpiderMonkey VM. + SpiderWASM, +} + +impl fmt::Display for CallConv { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::CallConv::*; + f.write_str(match *self { + Native => "native", + SpiderWASM => "spiderwasm", + }) + } +} + +impl FromStr for CallConv { + type Err = (); + + fn from_str(s: &str) -> Result { + use self::CallConv::*; + match s { + "native" => Ok(Native), + "spiderwasm" => Ok(SpiderWASM), + _ => Err(()), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -306,19 +350,26 @@ mod tests { } } + #[test] + fn call_conv() { + for &cc in &[CallConv::Native, CallConv::SpiderWASM] { + assert_eq!(Ok(cc), cc.to_string().parse()) + } + } + #[test] fn signatures() { - let mut sig = Signature::new(); - assert_eq!(sig.to_string(), "()"); + let mut sig = Signature::new(CallConv::SpiderWASM); + assert_eq!(sig.to_string(), "() spiderwasm"); sig.argument_types.push(ArgumentType::new(I32)); - assert_eq!(sig.to_string(), "(i32)"); + assert_eq!(sig.to_string(), "(i32) spiderwasm"); sig.return_types.push(ArgumentType::new(F32)); - assert_eq!(sig.to_string(), "(i32) -> f32"); + assert_eq!(sig.to_string(), "(i32) -> f32 spiderwasm"); sig.argument_types .push(ArgumentType::new(I32.by(4).unwrap())); - assert_eq!(sig.to_string(), "(i32, i32x4) -> f32"); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32 spiderwasm"); sig.return_types.push(ArgumentType::new(B8)); - assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8"); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8 spiderwasm"); // Test the offset computation algorithm. assert_eq!(sig.argument_bytes, None); @@ -332,6 +383,7 @@ mod tests { assert_eq!(sig.argument_bytes, Some(28)); // Writing ABI-annotated signatures. - assert_eq!(sig.to_string(), "(i32 [24], i32x4 [8]) -> f32, b8"); + assert_eq!(sig.to_string(), + "(i32 [24], i32x4 [8]) -> f32, b8 spiderwasm"); } } diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 59213aff13..c2b280dc6c 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -4,7 +4,7 @@ //! instructions. use entity_map::{EntityMap, PrimaryEntityData}; -use ir::{FunctionName, Signature, JumpTableData, DataFlowGraph, Layout}; +use ir::{FunctionName, CallConv, Signature, JumpTableData, DataFlowGraph, Layout}; use ir::{JumpTables, InstEncodings, ValueLocations, StackSlots, EbbOffsets}; use isa::TargetIsa; use std::fmt; @@ -67,9 +67,9 @@ impl Function { } } - /// Create a new empty, anonymous function. + /// Create a new empty, anonymous function with a native calling convention. pub fn new() -> Function { - Self::with_name_signature(FunctionName::default(), Signature::new()) + Self::with_name_signature(FunctionName::default(), Signature::new(CallConv::Native)) } /// Return an object that can display this function with correct ISA-specific annotations. diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index ebe39a3fcb..76f37efa3c 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -18,7 +18,8 @@ mod progpoint; mod valueloc; pub use ir::funcname::FunctionName; -pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ArgumentPurpose, ExtFuncData}; +pub use ir::extfunc::{Signature, CallConv, ArgumentType, ArgumentExtension, ArgumentPurpose, + ExtFuncData}; pub use ir::types::Type; pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 56a06a0c04..c342c4f273 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -54,7 +54,7 @@ fn write_preamble(w: &mut Write, for sig in func.dfg.signatures.keys() { any = true; writeln!(w, - " {} = signature{}", + " {} = {}", sig, func.dfg.signatures[sig].display(regs))?; } @@ -366,26 +366,27 @@ mod tests { #[test] fn basic() { let mut f = Function::new(); - assert_eq!(f.to_string(), "function %() {\n}\n"); + assert_eq!(f.to_string(), "function %() native {\n}\n"); f.name = FunctionName::new("foo"); - assert_eq!(f.to_string(), "function %foo() {\n}\n"); + assert_eq!(f.to_string(), "function %foo() native {\n}\n"); f.stack_slots .push(StackSlotData::new(StackSlotKind::Local, 4)); - assert_eq!(f.to_string(), "function %foo() {\n ss0 = local 4\n}\n"); + assert_eq!(f.to_string(), + "function %foo() native {\n ss0 = local 4\n}\n"); let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); assert_eq!(f.to_string(), - "function %foo() {\n ss0 = local 4\n\nebb0:\n}\n"); + "function %foo() native {\n ss0 = local 4\n\nebb0:\n}\n"); f.dfg.append_ebb_arg(ebb, types::I8); assert_eq!(f.to_string(), - "function %foo() {\n ss0 = local 4\n\nebb0(v0: i8):\n}\n"); + "function %foo() native {\n ss0 = local 4\n\nebb0(v0: i8):\n}\n"); f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap()); assert_eq!(f.to_string(), - "function %foo() {\n ss0 = local 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); + "function %foo() native {\n ss0 = local 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); } } diff --git a/lib/frontend/src/frontend.rs b/lib/frontend/src/frontend.rs index 7f9fb9edb6..d81cee4bc7 100644 --- a/lib/frontend/src/frontend.rs +++ b/lib/frontend/src/frontend.rs @@ -572,7 +572,7 @@ impl<'a, Variable> FunctionBuilder<'a, Variable> mod tests { use cretonne::entity_ref::EntityRef; - use cretonne::ir::{FunctionName, Function, Signature, ArgumentType, InstBuilder}; + use cretonne::ir::{FunctionName, Function, CallConv, Signature, ArgumentType, InstBuilder}; use cretonne::ir::types::*; use frontend::{ILBuilder, FunctionBuilder}; use cretonne::verifier::verify_function; @@ -600,7 +600,7 @@ mod tests { #[test] fn sample_function() { - let mut sig = Signature::new(); + let mut sig = Signature::new(CallConv::Native); sig.return_types.push(ArgumentType::new(I32)); sig.argument_types.push(ArgumentType::new(I32)); diff --git a/lib/frontend/src/lib.rs b/lib/frontend/src/lib.rs index 2cbbc30459..10a06b23cc 100644 --- a/lib/frontend/src/lib.rs +++ b/lib/frontend/src/lib.rs @@ -36,7 +36,7 @@ //! extern crate cton_frontend; //! //! use cretonne::entity_ref::EntityRef; -//! use cretonne::ir::{FunctionName, Function, Signature, ArgumentType, InstBuilder}; +//! use cretonne::ir::{FunctionName, CallConv, Function, Signature, ArgumentType, InstBuilder}; //! use cretonne::ir::types::*; //! use cton_frontend::{ILBuilder, FunctionBuilder}; //! use cretonne::verifier::verify_function; @@ -62,7 +62,7 @@ //! } //! //! fn main() { -//! let mut sig = Signature::new(); +//! let mut sig = Signature::new(CallConv::Native); //! sig.return_types.push(ArgumentType::new(I32)); //! sig.argument_types.push(ArgumentType::new(I32)); //! let mut il_builder = ILBuilder::::new(); diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 865cdcbd0d..fe2aec3e38 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -9,9 +9,9 @@ use std::collections::HashMap; use std::str::FromStr; use std::{u16, u32}; use std::mem; -use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, - JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, - FuncRef, StackSlot, ValueLoc, ArgumentLoc, MemFlags}; +use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, CallConv, StackSlotData, + JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, + ExtFuncData, SigRef, FuncRef, StackSlot, ValueLoc, ArgumentLoc, MemFlags}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; @@ -767,13 +767,14 @@ impl<'a> Parser<'a> { // Parse a function signature. // - // signature ::= * "(" [arglist] ")" ["->" retlist] [call_conv] + // signature ::= * "(" [arglist] ")" ["->" retlist] [callconv] // fn parse_signature(&mut self, unique_isa: Option<&TargetIsa>) -> Result { - let mut sig = Signature::new(); + // Calling convention defaults to `native`, but can be changed. + let mut sig = Signature::new(CallConv::Native); self.match_token(Token::LPar, "expected function signature: ( args... )")?; - // signature ::= "(" * [arglist] ")" ["->" retlist] [call_conv] + // signature ::= "(" * [arglist] ")" ["->" retlist] [callconv] if self.token() != Some(Token::RPar) { sig.argument_types = self.parse_argument_list(unique_isa)?; } @@ -782,12 +783,21 @@ impl<'a> Parser<'a> { sig.return_types = self.parse_argument_list(unique_isa)?; } + // The calling convention is optional. + if let Some(Token::Identifier(text)) = self.token() { + match text.parse() { + Ok(cc) => { + self.consume(); + sig.call_conv = cc; + } + _ => return err!(self.loc, "unknown calling convention: {}", text), + } + } + if sig.argument_types.iter().all(|a| a.location.is_assigned()) { sig.compute_argument_bytes(); } - // TBD: calling convention. - Ok(sig) } @@ -951,12 +961,11 @@ impl<'a> Parser<'a> { // Parse a signature decl. // - // signature-decl ::= SigRef(sigref) "=" "signature" signature + // signature-decl ::= SigRef(sigref) "=" signature // fn parse_signature_decl(&mut self, unique_isa: Option<&TargetIsa>) -> Result<(u32, Signature)> { let number = self.match_sig("expected signature number: sig«n»")?; self.match_token(Token::Equal, "expected '=' in signature decl")?; - self.match_identifier("signature", "expected 'signature'")?; let data = self.parse_signature(unique_isa)?; Ok((number, data)) } @@ -1755,7 +1764,7 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::ir::{ArgumentExtension, ArgumentPurpose}; + use cretonne::ir::{CallConv, ArgumentExtension, ArgumentPurpose}; use cretonne::ir::types; use cretonne::ir::StackSlotKind; use cretonne::ir::entities::AnyEntity; @@ -1777,7 +1786,7 @@ mod tests { #[test] fn aliases() { - let (func, details) = Parser::new("function %qux() { + let (func, details) = Parser::new("function %qux() native { ebb0: v4 = iconst.i8 6 v3 -> v4 @@ -1801,15 +1810,26 @@ mod tests { #[test] fn signature() { - let sig = Parser::new("()").parse_signature(None).unwrap(); + let sig = Parser::new("()native").parse_signature(None).unwrap(); assert_eq!(sig.argument_types.len(), 0); assert_eq!(sig.return_types.len(), 0); + assert_eq!(sig.call_conv, CallConv::Native); - let sig2 = Parser::new("(i8 uext, f32, f64, i32 sret) -> i32 sext, f64") + let sig2 = Parser::new("(i8 uext, f32, f64, i32 sret) -> i32 sext, f64 spiderwasm") .parse_signature(None) .unwrap(); assert_eq!(sig2.to_string(), - "(i8 uext, f32, f64, i32 sret) -> i32 sext, f64"); + "(i8 uext, f32, f64, i32 sret) -> i32 sext, f64 spiderwasm"); + assert_eq!(sig2.call_conv, CallConv::SpiderWASM); + + // Old-style signature without a calling convention. + assert_eq!(Parser::new("()").parse_signature(None).unwrap().to_string(), + "() native"); + assert_eq!(Parser::new("() notacc") + .parse_signature(None) + .unwrap_err() + .to_string(), + "1: unknown calling convention: notacc"); // `void` is not recognized as a type by the lexer. It should not appear in files. assert_eq!(Parser::new("() -> void") @@ -1831,7 +1851,7 @@ mod tests { #[test] fn stack_slot_decl() { - let (func, _) = Parser::new("function %foo() { + let (func, _) = Parser::new("function %foo() native { ss3 = incoming_arg 13 ss1 = spill_slot 1 }") @@ -1850,7 +1870,7 @@ mod tests { assert_eq!(iter.next(), None); // Catch duplicate definitions. - assert_eq!(Parser::new("function %bar() { + assert_eq!(Parser::new("function %bar() native { ss1 = spill_slot 13 ss1 = spill_slot 1 }") @@ -1862,7 +1882,7 @@ mod tests { #[test] fn ebb_header() { - let (func, _) = Parser::new("function %ebbs() { + let (func, _) = Parser::new("function %ebbs() native { ebb0: ebb4(v3: i32): }") @@ -1884,7 +1904,7 @@ mod tests { #[test] fn comments() { let (func, Details { comments, .. }) = Parser::new("; before - function %comment() { ; decl + function %comment() native { ; decl ss10 = outgoing_arg 13 ; stackslot. ; Still stackslot. jt10 = jump_table ebb0 @@ -1924,7 +1944,7 @@ mod tests { test verify set enable_float=false ; still preamble - function %comment() {}") + function %comment() native {}") .unwrap(); assert_eq!(tf.commands.len(), 2); assert_eq!(tf.commands[0].command, "cfg"); @@ -1947,17 +1967,17 @@ mod tests { #[cfg(build_riscv)] fn isa_spec() { assert!(parse_test("isa - function %foo() {}") + function %foo() native {}") .is_err()); assert!(parse_test("isa riscv set enable_float=false - function %foo() {}") + function %foo() native {}") .is_err()); match parse_test("set enable_float=false isa riscv - function %foo() {}") + function %foo() native {}") .unwrap() .isa_spec { IsaSpec::None(_) => panic!("Expected some ISA"), @@ -1971,7 +1991,7 @@ mod tests { #[test] fn binary_function_name() { // Valid characters in the name. - let func = Parser::new("function #1234567890AbCdEf() { + let func = Parser::new("function #1234567890AbCdEf() native { ebb0: trap }") @@ -1981,21 +2001,21 @@ mod tests { assert_eq!(func.name.to_string(), "#1234567890abcdef"); // Invalid characters in the name. - let mut parser = Parser::new("function #12ww() { + let mut parser = Parser::new("function #12ww() native { ebb0: trap }"); assert!(parser.parse_function(None).is_err()); // The length of binary function name should be multiple of two. - let mut parser = Parser::new("function #1() { + let mut parser = Parser::new("function #1() native { ebb0: trap }"); assert!(parser.parse_function(None).is_err()); // Empty binary function name should be valid. - let func = Parser::new("function #() { + let func = Parser::new("function #() native { ebb0: trap }") diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs index a0c9a64d90..37d568e0e4 100644 --- a/tests/cfg_traversal.rs +++ b/tests/cfg_traversal.rs @@ -27,7 +27,7 @@ fn test_reverse_postorder_traversal(function_source: &str, ebb_order: Vec) #[test] fn simple_traversal() { test_reverse_postorder_traversal(" - function %test(i32) { + function %test(i32) native { ebb0(v0: i32): brz v0, ebb1 jump ebb2 @@ -56,7 +56,7 @@ fn simple_traversal() { #[test] fn loops_one() { test_reverse_postorder_traversal(" - function %test(i32) { + function %test(i32) native { ebb0(v0: i32): jump ebb1 ebb1: @@ -74,7 +74,7 @@ fn loops_one() { #[test] fn loops_two() { test_reverse_postorder_traversal(" - function %test(i32) { + function %test(i32) native { ebb0(v0: i32): brz v0, ebb1 jump ebb2 @@ -99,7 +99,7 @@ fn loops_two() { #[test] fn loops_three() { test_reverse_postorder_traversal(" - function %test(i32) { + function %test(i32) native { ebb0(v0: i32): brz v0, ebb1 jump ebb2 @@ -129,7 +129,7 @@ fn loops_three() { #[test] fn back_edge_one() { test_reverse_postorder_traversal(" - function %test(i32) { + function %test(i32) native { ebb0(v0: i32): brz v0, ebb1 jump ebb2 From f82004e3c084079ff4527e1542ab14b95165a68d Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Aug 2017 13:31:58 -0700 Subject: [PATCH 952/968] Move the stack layout computation into its own module. This is trying to keep algorithms out if the ir module which deals with the intermediate representation. Also give the layout_stack() function a Result return value so it can report a soft error when the stack frame is too large instead of asserting. Since local variables can be arbitrarily large, it is easy enough to overflow the stack with even a small function. --- lib/cretonne/src/ir/stackslot.rs | 178 ++--------------------------- lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/result.rs | 2 +- lib/cretonne/src/stack_layout.rs | 185 +++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 172 deletions(-) create mode 100644 lib/cretonne/src/stack_layout.rs diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index ea28eeae1a..6f3de96cd2 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -5,7 +5,6 @@ use entity_map::{EntityMap, PrimaryEntityData, Keys}; use ir::{Type, StackSlot}; -use std::cmp::{min, max}; use std::fmt; use std::ops::Index; use std::str::FromStr; @@ -15,12 +14,12 @@ use std::str::FromStr; /// We don't use `usize` to represent object sizes on the target platform because Cretonne supports /// cross-compilation, and `usize` is a type that depends on the host platform, not the target /// platform. -type StackSize = u32; +pub type StackSize = u32; /// A stack offset. /// /// The location of a stack offset relative to a stack pointer or frame pointer. -type StackOffset = i32; +pub type StackOffset = i32; /// The kind of a stack slot. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -179,6 +178,11 @@ impl StackSlots { self.slots.is_valid(ss) } + /// Set the offset of a stack slot. + pub fn set_offset(&mut self, ss: StackSlot, offset: StackOffset) { + self.slots[ss].offset = offset; + } + /// Get an iterator over all the stack slot keys. pub fn keys(&self) -> Keys { self.slots.keys() @@ -241,103 +245,6 @@ impl StackSlots { self.outgoing.insert(inspos, ss); ss } - - /// Compute the stack frame layout. - /// - /// Determine the total size of this function's stack frame and assign offsets to all `Spill` - /// and `Local` stack slots. - /// - /// The total frame size will be a multiple of `alignment` which must be a power of two. - /// - /// Returns the total stack frame size which is also saved in `self.frame_size`. - pub fn layout(&mut self, alignment: StackSize) -> StackSize { - assert!(alignment.is_power_of_two() && alignment <= StackOffset::max_value() as StackSize, - "Invalid stack alignment {}", - alignment); - - // We assume a stack that grows toward lower addresses as implemented by modern ISAs. The - // stack layout from high to low addresses will be: - // - // 1. incoming arguments. - // 2. spills + locals. - // 3. outgoing arguments. - // - // The incoming arguments can have both positive and negative offsets. A negative offset - // incoming arguments is usually the x86 return address pushed by the call instruction, but - // it can also be fixed stack slots pushed by an externally generated prologue. - // - // Both incoming and outgoing argument slots have fixed offsets that are treated as - // reserved zones by the layout algorithm. - - let mut incoming_min = 0; - let mut outgoing_max = 0; - let mut min_align = alignment; - - for ss in self.keys() { - let slot = &self[ss]; - assert!(slot.size <= StackOffset::max_value() as StackSize); - match slot.kind { - StackSlotKind::IncomingArg => { - incoming_min = min(incoming_min, slot.offset); - } - StackSlotKind::OutgoingArg => { - let offset = slot.offset - .checked_add(slot.size as StackOffset) - .expect("Outgoing call argument overflows stack"); - outgoing_max = max(outgoing_max, offset); - } - StackSlotKind::SpillSlot | StackSlotKind::Local => { - // Determine the smallest alignment of any local or spill slot. - min_align = slot.alignment(min_align); - } - } - } - - // Lay out spill slots and locals below the incoming arguments. - // The offset is negative, growing downwards. - // Start with the smallest alignments for better packing. - let mut offset = incoming_min; - assert!(min_align.is_power_of_two()); - while min_align <= alignment { - for ss in self.keys() { - let slot = &mut self.slots[ss]; - - // Pick out locals and spill slots with exact alignment `min_align`. - match slot.kind { - StackSlotKind::SpillSlot | StackSlotKind::Local => { - if slot.alignment(alignment) != min_align { - continue; - } - } - _ => continue, - } - - // These limits should never be exceeded by spill slots, but locals can be - // arbitrarily large. - assert!(slot.size <= StackOffset::max_value() as StackSize); - offset = offset - .checked_sub(slot.size as StackOffset) - .expect("Stack frame larger than 2 GB"); - - // Aligning the negative offset can never cause overflow. We're only clearing bits. - offset &= -(min_align as StackOffset); - slot.offset = offset; - } - - // Move on to the next higher alignment. - min_align *= 2; - } - - // Finally, make room for the outgoing arguments. - offset = offset - .checked_sub(outgoing_max) - .expect("Stack frame larger than 2 GB"); - offset &= -(alignment as StackOffset); - - let frame_size = (offset as StackSize).wrapping_neg(); - self.frame_size = Some(frame_size); - frame_size - } } #[cfg(test)] @@ -401,75 +308,4 @@ mod tests { assert_eq!(slot2.alignment(16), 8); assert_eq!(slot2.alignment(32), 8); } - - #[test] - fn layout() { - let mut sss = StackSlots::new(); - - // An empty layout should have 0-sized stack frame. - assert_eq!(sss.layout(1), 0); - assert_eq!(sss.layout(16), 0); - - // Same for incoming arguments with non-negative offsets. - let in0 = sss.make_incoming_arg(types::I64, 0); - let in1 = sss.make_incoming_arg(types::I64, 8); - - assert_eq!(sss.layout(1), 0); - assert_eq!(sss.layout(16), 0); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - - // Add some spill slots. - let ss0 = sss.make_spill_slot(types::I64); - let ss1 = sss.make_spill_slot(types::I32); - - assert_eq!(sss.layout(1), 12); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - assert_eq!(sss[ss0].offset, -8); - assert_eq!(sss[ss1].offset, -12); - - assert_eq!(sss.layout(16), 16); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - assert_eq!(sss[ss0].offset, -16); - assert_eq!(sss[ss1].offset, -4); - - // An incoming argument with negative offset counts towards the total frame size, but it - // should still pack nicely with the spill slots. - let in2 = sss.make_incoming_arg(types::I32, -4); - - assert_eq!(sss.layout(1), 16); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - assert_eq!(sss[in2].offset, -4); - assert_eq!(sss[ss0].offset, -12); - assert_eq!(sss[ss1].offset, -16); - - assert_eq!(sss.layout(16), 16); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - assert_eq!(sss[in2].offset, -4); - assert_eq!(sss[ss0].offset, -16); - assert_eq!(sss[ss1].offset, -8); - - // Finally, make sure there is room for the outgoing args. - let out0 = sss.get_outgoing_arg(types::I32, 0); - - assert_eq!(sss.layout(1), 20); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - assert_eq!(sss[in2].offset, -4); - assert_eq!(sss[ss0].offset, -12); - assert_eq!(sss[ss1].offset, -16); - assert_eq!(sss[out0].offset, 0); - - assert_eq!(sss.layout(16), 32); - assert_eq!(sss[in0].offset, 0); - assert_eq!(sss[in1].offset, 8); - assert_eq!(sss[in2].offset, -4); - assert_eq!(sss[ss0].offset, -16); - assert_eq!(sss[ss1].offset, -8); - assert_eq!(sss[out0].offset, 0); - } } diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 0b09a8f10d..44f469ab4e 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -41,5 +41,6 @@ mod partition_slice; mod predicates; mod ref_slice; mod simple_gvn; +mod stack_layout; mod topo_order; mod write; diff --git a/lib/cretonne/src/result.rs b/lib/cretonne/src/result.rs index 960bd021d4..f4d7316fb5 100644 --- a/lib/cretonne/src/result.rs +++ b/lib/cretonne/src/result.rs @@ -7,7 +7,7 @@ use std::fmt; /// A compilation error. /// /// When Cretonne fails to compile a function, it will return one of these error codes. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum CtonError { /// An IL verifier error. /// diff --git a/lib/cretonne/src/stack_layout.rs b/lib/cretonne/src/stack_layout.rs new file mode 100644 index 0000000000..f968e56a07 --- /dev/null +++ b/lib/cretonne/src/stack_layout.rs @@ -0,0 +1,185 @@ +//! Computing stack layout. + +use ir::StackSlots; +use ir::stackslot::{StackSize, StackOffset, StackSlotKind}; +use result::CtonError; +use std::cmp::{min, max}; + +/// Compute the stack frame layout. +/// +/// Determine the total size of this stack frame and assign offsets to all `Spill` and `Local` +/// stack slots. +/// +/// The total frame size will be a multiple of `alignment` which must be a power of two. +/// +/// Returns the total stack frame size which is also saved in `frame.frame_size`. +/// +/// If the stack frame is too big, returns an `ImplLimitExceeded` error. +pub fn layout_stack(frame: &mut StackSlots, alignment: StackSize) -> Result { + // Each object and the whole stack frame must fit in 2 GB such that any relative offset within + // the frame fits in a `StackOffset`. + let max_size = StackOffset::max_value() as StackSize; + assert!(alignment.is_power_of_two() && alignment <= max_size); + + // We assume a stack that grows toward lower addresses as implemented by modern ISAs. The + // stack layout from high to low addresses will be: + // + // 1. incoming arguments. + // 2. spills + locals. + // 3. outgoing arguments. + // + // The incoming arguments can have both positive and negative offsets. A negative offset + // incoming arguments is usually the x86 return address pushed by the call instruction, but + // it can also be fixed stack slots pushed by an externally generated prologue. + // + // Both incoming and outgoing argument slots have fixed offsets that are treated as + // reserved zones by the layout algorithm. + + let mut incoming_min = 0; + let mut outgoing_max = 0; + let mut min_align = alignment; + + for ss in frame.keys() { + let slot = &frame[ss]; + + if slot.size > max_size { + return Err(CtonError::ImplLimitExceeded); + } + + match slot.kind { + StackSlotKind::IncomingArg => { + incoming_min = min(incoming_min, slot.offset); + } + StackSlotKind::OutgoingArg => { + let offset = slot.offset + .checked_add(slot.size as StackOffset) + .ok_or(CtonError::ImplLimitExceeded)?; + outgoing_max = max(outgoing_max, offset); + } + StackSlotKind::SpillSlot | StackSlotKind::Local => { + // Determine the smallest alignment of any local or spill slot. + min_align = slot.alignment(min_align); + } + } + } + + // Lay out spill slots and locals below the incoming arguments. + // The offset is negative, growing downwards. + // Start with the smallest alignments for better packing. + let mut offset = incoming_min; + assert!(min_align.is_power_of_two()); + while min_align <= alignment { + for ss in frame.keys() { + let slot = frame[ss].clone(); + + // Pick out locals and spill slots with exact alignment `min_align`. + match slot.kind { + StackSlotKind::SpillSlot | StackSlotKind::Local => { + if slot.alignment(alignment) != min_align { + continue; + } + } + _ => continue, + } + + offset = offset + .checked_sub(slot.size as StackOffset) + .ok_or(CtonError::ImplLimitExceeded)?; + + // Aligning the negative offset can never cause overflow. We're only clearing bits. + offset &= -(min_align as StackOffset); + frame.set_offset(ss, offset); + } + + // Move on to the next higher alignment. + min_align *= 2; + } + + // Finally, make room for the outgoing arguments. + offset = offset + .checked_sub(outgoing_max) + .ok_or(CtonError::ImplLimitExceeded)?; + offset &= -(alignment as StackOffset); + + let frame_size = (offset as StackSize).wrapping_neg(); + frame.frame_size = Some(frame_size); + Ok(frame_size) +} + +#[cfg(test)] +mod tests { + use ir::StackSlots; + use ir::types; + use super::layout_stack; + + #[test] + fn layout() { + let sss = &mut StackSlots::new(); + + // An empty layout should have 0-sized stack frame. + assert_eq!(layout_stack(sss, 1), Ok(0)); + assert_eq!(layout_stack(sss, 16), Ok(0)); + + // Same for incoming arguments with non-negative offsets. + let in0 = sss.make_incoming_arg(types::I64, 0); + let in1 = sss.make_incoming_arg(types::I64, 8); + + assert_eq!(layout_stack(sss, 1), Ok(0)); + assert_eq!(layout_stack(sss, 16), Ok(0)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + + // Add some spill slots. + let ss0 = sss.make_spill_slot(types::I64); + let ss1 = sss.make_spill_slot(types::I32); + + assert_eq!(layout_stack(sss, 1), Ok(12)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[ss0].offset, -8); + assert_eq!(sss[ss1].offset, -12); + + assert_eq!(layout_stack(sss, 16), Ok(16)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[ss0].offset, -16); + assert_eq!(sss[ss1].offset, -4); + + // An incoming argument with negative offset counts towards the total frame size, but it + // should still pack nicely with the spill slots. + let in2 = sss.make_incoming_arg(types::I32, -4); + + assert_eq!(layout_stack(sss, 1), Ok(16)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -12); + assert_eq!(sss[ss1].offset, -16); + + assert_eq!(layout_stack(sss, 16), Ok(16)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -16); + assert_eq!(sss[ss1].offset, -8); + + // Finally, make sure there is room for the outgoing args. + let out0 = sss.get_outgoing_arg(types::I32, 0); + + assert_eq!(layout_stack(sss, 1), Ok(20)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -12); + assert_eq!(sss[ss1].offset, -16); + assert_eq!(sss[out0].offset, 0); + + assert_eq!(layout_stack(sss, 16), Ok(32)); + assert_eq!(sss[in0].offset, 0); + assert_eq!(sss[in1].offset, 8); + assert_eq!(sss[in2].offset, -4); + assert_eq!(sss[ss0].offset, -16); + assert_eq!(sss[ss1].offset, -8); + assert_eq!(sss[out0].offset, 0); + } +} From d591b38b37746b4471eb01d5f458dd38964b9f27 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Aug 2017 13:48:30 -0700 Subject: [PATCH 953/968] Add a prologue_epilogue() hook to TargetIsa. This will compute the stack frame layout as appropriate for the function's calling convention and insert prologue and epilogue code. The default implementation is not useful, each target ISA will need to override this function. --- lib/cretonne/src/context.rs | 7 +++++++ lib/cretonne/src/isa/mod.rs | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index d0288789f6..9967f1e11e 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -68,6 +68,7 @@ impl Context { self.legalize(isa)?; self.regalloc(isa)?; + self.prologue_epilogue(isa)?; self.relax_branches(isa) } @@ -135,6 +136,12 @@ impl Context { .run(isa, &mut self.func, &self.cfg, &self.domtree) } + /// Insert prologue and epilogues after computing the stack frame layout. + pub fn prologue_epilogue(&mut self, isa: &TargetIsa) -> CtonResult { + isa.prologue_epilogue(&mut self.func)?; + self.verify_if(isa) + } + /// Run the branch relaxation pass and return the final code size. pub fn relax_branches(&mut self, isa: &TargetIsa) -> Result { let code_size = relax_branches(&mut self.func, isa)?; diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 7caac93a67..08c45dd8b8 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -49,6 +49,7 @@ use flowgraph; use settings; use ir; use regalloc; +use result; use isa::enc_tables::Encodings; #[cfg(build_riscv)] @@ -227,6 +228,18 @@ pub trait TargetIsa { /// registers. fn allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet; + /// Compute the stack layout and insert prologue and epilogue code into `func`. + /// + /// Return an error if the stack frame is too large. + fn prologue_epilogue(&self, func: &mut ir::Function) -> result::CtonResult { + // This default implementation is unlikely to be good enough. + use stack_layout::layout_stack; + + let align = if self.flags().is_64bit() { 8 } else { 4 }; + layout_stack(&mut func.stack_slots, align)?; + Ok(()) + } + /// Emit binary machine code for a single instruction into the `sink` trait object. /// /// Note that this will call `put*` methods on the trait object via its vtable which is not the From e0fd5252d5c9d0fb0eaae5f33a2d55b197c33ad4 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Thu, 3 Aug 2017 18:26:41 -0700 Subject: [PATCH 954/968] Added partial recompute of dominator tree in case of Ebb splitting (#135) * Partial recompute for dominator tree in case of ebb splitting Implemented via striding in RPO numbers --- lib/cretonne/src/dominator_tree.rs | 138 ++++++++++++++++++++++++++++- lib/cretonne/src/verifier/mod.rs | 30 +++++++ 2 files changed, 164 insertions(+), 4 deletions(-) diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 14f08e4263..6e61eeaa9d 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -7,10 +7,16 @@ use packed_option::PackedOption; use std::cmp::Ordering; +// RPO numbers are not first assigned in a contiguous way but as multiples of STRIDE, to leave +// room for modifications of the dominator tree. +const STRIDE: u32 = 4; + // Dominator tree node. We keep one of these per EBB. #[derive(Clone, Default)] struct DomNode { // Number of this node in a reverse post-order traversal of the CFG, starting from 1. + // This number is monotonic in the reverse postorder but not contiguous, since we leave + // holes for later localized modifications of the dominator tree. // Unreachable nodes get number 0, all others are positive. rpo_number: u32, @@ -137,7 +143,6 @@ impl DominatorTree { ebb_b = layout.inst_ebb(idom).expect("Dominator got removed."); inst_b = Some(idom); } - if a == ebb_b { inst_b } else { None } } @@ -266,10 +271,11 @@ impl DominatorTree { debug_assert_eq!(Some(entry_block), func.layout.entry_block()); // Do a first pass where we assign RPO numbers to all reachable nodes. - self.nodes[entry_block].rpo_number = 2; + self.nodes[entry_block].rpo_number = 2 * STRIDE; for (rpo_idx, &ebb) in postorder.iter().rev().enumerate() { // Update the current node and give it an RPO number. - // The entry block got 2, the rest start at 3. + // The entry block got 2, the rest start at 3 by multiples of STRIDE to leave + // room for future dominator tree modifications. // // Since `compute_idom` will only look at nodes with an assigned RPO number, the // function will never see an uninitialized predecessor. @@ -278,7 +284,7 @@ impl DominatorTree { // least one predecessor that has previously been visited during this RPO. self.nodes[ebb] = DomNode { idom: self.compute_idom(ebb, cfg, &func.layout).into(), - rpo_number: rpo_idx as u32 + 3, + rpo_number: (rpo_idx as u32 + 3) * STRIDE, } } @@ -323,11 +329,69 @@ impl DominatorTree { } } +impl DominatorTree { + /// When splitting an `Ebb` using `Layout::split_ebb`, you can use this method to update + /// the dominator tree locally rather than recomputing it. + /// + /// `old_ebb` is the `Ebb` before splitting, and `new_ebb` is the `Ebb` which now contains + /// the second half of `old_ebb`. `split_jump_inst` is the terminator jump instruction of + /// `old_ebb` that points to `new_ebb`. + pub fn recompute_split_ebb(&mut self, old_ebb: Ebb, new_ebb: Ebb, split_jump_inst: Inst) { + if !self.is_reachable(old_ebb) { + // old_ebb is unreachable, it stays so and new_ebb is unreachable too + *self.nodes.ensure(new_ebb) = Default::default(); + return; + } + // We use the RPO comparison on the postorder list so we invert the operands of the + // comparison + let old_ebb_postorder_index = + self.postorder + .as_slice() + .binary_search_by(|probe| self.rpo_cmp_ebb(old_ebb, *probe)) + .expect("the old ebb is not declared to the dominator tree"); + let new_ebb_rpo = self.insert_after_rpo(old_ebb, old_ebb_postorder_index, new_ebb); + *self.nodes.ensure(new_ebb) = DomNode { + rpo_number: new_ebb_rpo, + idom: Some(split_jump_inst).into(), + }; + + } + + // Insert new_ebb just after ebb in the RPO. This function checks + // if there is a gap in rpo numbers; if yes it returns the number in the gap and if + // not it renumbers. + fn insert_after_rpo(&mut self, ebb: Ebb, ebb_postorder_index: usize, new_ebb: Ebb) -> u32 { + let ebb_rpo_number = self.nodes[ebb].rpo_number; + let inserted_rpo_number = ebb_rpo_number + 1; + // If there is no gaps in RPo numbers to insert this new number, we iterate + // forward in RPO numbers and backwards in the postorder list of EBBs, renumbering the Ebbs + // until we find a gap + for (¤t_ebb, current_rpo) in + self.postorder[0..ebb_postorder_index] + .iter() + .rev() + .zip(inserted_rpo_number + 1..) { + if self.nodes[current_ebb].rpo_number < current_rpo { + // There is no gap, we renumber + self.nodes[current_ebb].rpo_number = current_rpo; + } else { + // There is a gap, we stop the renumbering and exit + break; + } + } + // TODO: insert in constant time? + self.postorder.insert(ebb_postorder_index, new_ebb); + inserted_rpo_number + } +} + #[cfg(test)] mod test { use flowgraph::ControlFlowGraph; use ir::{Function, InstBuilder, Cursor, types}; use super::*; + use ir::types::*; + use verifier::verify_context; #[test] fn empty() { @@ -465,4 +529,70 @@ mod test { assert!(!dt.dominates(jmp21, ebb2, &func.layout)); assert!(dt.dominates(jmp21, jmp21, &func.layout)); } + + #[test] + fn renumbering() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb100 = func.dfg.make_ebb(); + + let inst2; + let inst3; + let inst4; + let inst5; + { + let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); + + cur.insert_ebb(ebb0); + let cond = dfg.ins(cur).iconst(I32, 0); + inst2 = dfg.ins(cur).brz(cond, ebb0, &[]); + inst3 = dfg.ins(cur).brz(cond, ebb0, &[]); + inst4 = dfg.ins(cur).brz(cond, ebb0, &[]); + inst5 = dfg.ins(cur).brz(cond, ebb0, &[]); + dfg.ins(cur).jump(ebb100, &[]); + cur.insert_ebb(ebb100); + dfg.ins(cur).return_(&[]); + } + let mut cfg = ControlFlowGraph::with_function(&func); + let mut dt = DominatorTree::with_function(&func, &cfg); + + let ebb1 = func.dfg.make_ebb(); + func.layout.split_ebb(ebb1, inst2); + let middle_jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb0); + func.dfg.ins(cur).jump(ebb1, &[]) + }; + dt.recompute_split_ebb(ebb0, ebb1, middle_jump_inst); + + let ebb2 = func.dfg.make_ebb(); + func.layout.split_ebb(ebb2, inst3); + let middle_jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb1); + func.dfg.ins(cur).jump(ebb2, &[]) + }; + dt.recompute_split_ebb(ebb1, ebb2, middle_jump_inst); + + let ebb3 = func.dfg.make_ebb(); + func.layout.split_ebb(ebb3, inst4); + let middle_jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb2); + func.dfg.ins(cur).jump(ebb3, &[]) + }; + dt.recompute_split_ebb(ebb2, ebb3, middle_jump_inst); + + let ebb4 = func.dfg.make_ebb(); + func.layout.split_ebb(ebb4, inst5); + let middle_jump_inst = { + let cur = &mut Cursor::new(&mut func.layout); + cur.goto_bottom(ebb3); + func.dfg.ins(cur).jump(ebb4, &[]) + }; + dt.recompute_split_ebb(ebb3, ebb4, middle_jump_inst); + cfg.compute(&func); + verify_context(&func, &cfg, &dt, None).unwrap(); + } } diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index 751f6d5f6f..d3f05ef4c8 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -63,6 +63,8 @@ use std::error as std_error; use std::fmt::{self, Display, Formatter}; use std::result; use std::collections::BTreeSet; +use std::cmp::Ordering; +use iterators::IteratorExtras; pub use self::liveness::verify_liveness; pub use self::cssa::verify_cssa; @@ -409,6 +411,34 @@ impl<'a> Verifier<'a> { got); } } + // We also verify if the postorder defined by `DominatorTree` is sane + if self.domtree.cfg_postorder().len() != domtree.cfg_postorder().len() { + return err!(AnyEntity::Function, + "incorrect number of Ebbs in postorder traversal"); + } + for (index, (&true_ebb, &test_ebb)) in + self.domtree + .cfg_postorder() + .iter() + .zip(domtree.cfg_postorder().iter()) + .enumerate() { + if true_ebb != test_ebb { + return err!(test_ebb, + "invalid domtree, postorder ebb number {} should be {}, got {}", + index, + true_ebb, + test_ebb); + } + } + // We verify rpo_cmp on pairs of adjacent ebbs in the postorder + for (&prev_ebb, &next_ebb) in self.domtree.cfg_postorder().iter().adjacent_pairs() { + if domtree.rpo_cmp(prev_ebb, next_ebb, &self.func.layout) != Ordering::Greater { + return err!(next_ebb, + "invalid domtree, rpo_cmp does not says {} is greater than {}", + prev_ebb, + next_ebb); + } + } Ok(()) } From 81ff9bac726d7e805b375d5934cf424caa5753d0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Aug 2017 08:44:17 -0700 Subject: [PATCH 955/968] Avoid evaluating dbg!() arguments in a closure. The dbg!() macro should behave like a function call in how it evaluates its arguments, and captures by Rust closures are not fully compatible with path-specific borrowing. Specifically: let borrow = &mut obj.foo; dbg!("{}", obj.bar); would fail because the closure inside dbg!() would borrow all of obj instead of just obj.foo. Fix this by using the format_args!() macro to evaluate the dbg!() arguments and produce an fmt::Arguments object which can be safely passed to the thread-local closure for printing. The arguments are still evaluated inside an if { .. } which constant-folds away in release builds. --- lib/cretonne/src/dbg.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/cretonne/src/dbg.rs b/lib/cretonne/src/dbg.rs index dc7793f3d9..bf1b92ef75 100644 --- a/lib/cretonne/src/dbg.rs +++ b/lib/cretonne/src/dbg.rs @@ -15,7 +15,7 @@ use std::env; use std::ffi::OsStr; use std::fmt; use std::fs::File; -use std::io::{Write, BufWriter}; +use std::io::{self, Write}; use std::sync::atomic; use std::thread; @@ -56,20 +56,18 @@ fn initialize() -> bool { } thread_local! { - static WRITER : RefCell> = RefCell::new(open_file()); + static WRITER : RefCell> = RefCell::new(open_file()); } -/// Execute a closure with mutable access to the tracing file writer. +/// Write a line with the given format arguments. /// /// This is for use by the `dbg!` macro. -pub fn with_writer(f: F) -> R - where F: FnOnce(&mut Write) -> R -{ - WRITER.with(|rc| f(&mut *rc.borrow_mut())) +pub fn writeln_with_format_args(args: fmt::Arguments) -> io::Result<()> { + WRITER.with(|rc| writeln!(*rc.borrow_mut(), "{}", args)) } /// Open the tracing file for the current thread. -fn open_file() -> BufWriter { +fn open_file() -> io::BufWriter { let file = match thread::current().name() { None => File::create("cretonne.dbg"), Some(name) => { @@ -83,7 +81,7 @@ fn open_file() -> BufWriter { } } .expect("Can't open tracing file"); - BufWriter::new(file) + io::BufWriter::new(file) } /// Write a line to the debug trace file if tracing is enabled. @@ -95,7 +93,7 @@ macro_rules! dbg { if $crate::dbg::enabled() { // Drop the error result so we don't get compiler errors for ignoring it. // What are you going to do, log the error? - $crate::dbg::with_writer(|w| { writeln!(w, $($arg)+).ok(); }) + $crate::dbg::writeln_with_format_args(format_args!($($arg)+)).ok(); } } } From 78db5b371549c7d2ff2fb55b18b6eb676b644c3f Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Aug 2017 16:43:47 -0700 Subject: [PATCH 956/968] Move most Cursor methods into a CursorBase trait. The Cursor navigation methods all just depend on the cursor's position and layout reference. Make a CursorBase trait that provides access to this information with methods and implement the navigation methods on top of that. This makes it possible to have multiple types implement the cursor interface. --- lib/cretonne/meta/gen_legalizer.py | 2 +- lib/cretonne/src/binemit/relaxation.rs | 2 +- lib/cretonne/src/dominator_tree.rs | 2 +- lib/cretonne/src/flowgraph.rs | 2 +- lib/cretonne/src/ir/builder.rs | 4 +- lib/cretonne/src/ir/dfg.rs | 2 +- lib/cretonne/src/ir/layout.rs | 197 +++++++++++++----------- lib/cretonne/src/ir/mod.rs | 2 +- lib/cretonne/src/legalizer/boundary.rs | 6 +- lib/cretonne/src/legalizer/mod.rs | 2 +- lib/cretonne/src/legalizer/split.rs | 4 +- lib/cretonne/src/licm.rs | 2 +- lib/cretonne/src/loop_analysis.rs | 2 +- lib/cretonne/src/regalloc/coalescing.rs | 2 +- lib/cretonne/src/regalloc/coloring.rs | 2 +- lib/cretonne/src/regalloc/reload.rs | 2 +- lib/cretonne/src/regalloc/spilling.rs | 2 +- lib/cretonne/src/simple_gvn.rs | 2 +- lib/cretonne/src/topo_order.rs | 2 +- lib/frontend/src/ssa.rs | 5 +- 20 files changed, 136 insertions(+), 110 deletions(-) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 0a75fca8af..8a76d15e98 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -355,7 +355,7 @@ def gen_xform_group(xgrp, fmt, type_sets): 'cfg: &mut ::flowgraph::ControlFlowGraph, ' 'pos: &mut ir::Cursor) -> ' 'bool {{'.format(xgrp.name), '}'): - fmt.line('use ir::InstBuilder;') + fmt.line('use ir::{InstBuilder, CursorBase};') # Gen the instruction to be legalized. The cursor we're passed must be # pointing at an instruction. diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index 3420134d1b..0d0c07df35 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -28,7 +28,7 @@ //! ``` use binemit::CodeOffset; -use ir::{Function, DataFlowGraph, Cursor, InstructionData, Opcode, InstEncodings}; +use ir::{Function, DataFlowGraph, Cursor, CursorBase, InstructionData, Opcode, InstEncodings}; use isa::{TargetIsa, EncInfo}; use iterators::IteratorExtras; use result::CtonError; diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 6e61eeaa9d..9d47cc4bd7 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -388,7 +388,7 @@ impl DominatorTree { #[cfg(test)] mod test { use flowgraph::ControlFlowGraph; - use ir::{Function, InstBuilder, Cursor, types}; + use ir::{Function, InstBuilder, Cursor, CursorBase, types}; use super::*; use ir::types::*; use verifier::verify_context; diff --git a/lib/cretonne/src/flowgraph.rs b/lib/cretonne/src/flowgraph.rs index 45596d41ec..f7cb0c9729 100644 --- a/lib/cretonne/src/flowgraph.rs +++ b/lib/cretonne/src/flowgraph.rs @@ -136,7 +136,7 @@ impl ControlFlowGraph { #[cfg(test)] mod tests { use super::*; - use ir::{Function, InstBuilder, Cursor, types}; + use ir::{Function, InstBuilder, Cursor, CursorBase, types}; #[test] fn empty() { diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 332ce43b03..59de1f68b5 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -4,7 +4,7 @@ //! function. Many of its methods are generated from the meta language instruction definitions. use ir::types; -use ir::{InstructionData, DataFlowGraph, Cursor}; +use ir::{InstructionData, DataFlowGraph, Cursor, CursorBase}; use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; @@ -185,7 +185,7 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { #[cfg(test)] mod tests { - use ir::{Function, Cursor, InstBuilder, ValueDef}; + use ir::{Function, Cursor, CursorBase, InstBuilder, ValueDef}; use ir::types::*; use ir::condcodes::*; diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index ad67f7722b..49d4d13d6a 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -865,7 +865,7 @@ impl<'a> fmt::Display for DisplayInst<'a> { mod tests { use super::*; use ir::types; - use ir::{Function, Cursor, Opcode, InstructionData}; + use ir::{Function, Cursor, CursorBase, Opcode, InstructionData}; #[test] fn make_inst() { diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 0fa48edc6f..b1e068ff5c 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -658,40 +658,34 @@ pub enum CursorPosition { After(Ebb), } -impl<'f> Cursor<'f> { - /// Create a new `Cursor` for `layout`. - /// The cursor holds a mutable reference to `layout` for its entire lifetime. - pub fn new(layout: &'f mut Layout) -> Cursor { - Cursor { - layout, - pos: CursorPosition::Nowhere, - } - } +/// All cursor types implement the `CursorBase` which provides common navigation operations. +pub trait CursorBase { + /// Get the current cursor position. + fn position(&self) -> CursorPosition; - /// Get the current position. - pub fn position(&self) -> CursorPosition { - self.pos - } + /// Set the current position. + fn set_position(&mut self, pos: CursorPosition); - /// Move the cursor to a new position. - pub fn set_position(&mut self, pos: CursorPosition) { - self.pos = pos; - } + /// Borrow a reference to the function layout that this cursor is navigating. + fn layout(&self) -> &Layout; + + /// Borrow a mutable reference to the function layout that this cursor is navigating. + fn layout_mut(&mut self) -> &mut Layout; /// Get the EBB corresponding to the current position. - pub fn current_ebb(&self) -> Option { + fn current_ebb(&self) -> Option { use self::CursorPosition::*; - match self.pos { + match self.position() { Nowhere => None, - At(inst) => self.layout.inst_ebb(inst), + At(inst) => self.layout().inst_ebb(inst), Before(ebb) | After(ebb) => Some(ebb), } } /// Get the instruction corresponding to the current position, if any. - pub fn current_inst(&self) -> Option { + fn current_inst(&self) -> Option { use self::CursorPosition::*; - match self.pos { + match self.position() { At(inst) => Some(inst), _ => None, } @@ -699,24 +693,24 @@ impl<'f> Cursor<'f> { /// Go to a specific instruction which must be inserted in the layout. /// New instructions will be inserted before `inst`. - pub fn goto_inst(&mut self, inst: Inst) { - assert!(self.layout.inst_ebb(inst).is_some()); - self.pos = CursorPosition::At(inst); + fn goto_inst(&mut self, inst: Inst) { + assert!(self.layout().inst_ebb(inst).is_some()); + self.set_position(CursorPosition::At(inst)); } /// Go to the top of `ebb` which must be inserted into the layout. /// At this position, instructions cannot be inserted, but `next_inst()` will move to the first /// instruction in `ebb`. - pub fn goto_top(&mut self, ebb: Ebb) { - assert!(self.layout.is_ebb_inserted(ebb)); - self.pos = CursorPosition::Before(ebb); + fn goto_top(&mut self, ebb: Ebb) { + assert!(self.layout().is_ebb_inserted(ebb)); + self.set_position(CursorPosition::Before(ebb)); } /// Go to the bottom of `ebb` which must be inserted into the layout. /// At this position, inserted instructions will be appended to `ebb`. - pub fn goto_bottom(&mut self, ebb: Ebb) { - assert!(self.layout.is_ebb_inserted(ebb)); - self.pos = CursorPosition::After(ebb); + fn goto_bottom(&mut self, ebb: Ebb) { + assert!(self.layout().is_ebb_inserted(ebb)); + self.set_position(CursorPosition::After(ebb)); } /// Go to the top of the next EBB in layout order and return it. @@ -731,7 +725,7 @@ impl<'f> Cursor<'f> { /// /// ``` /// # use cretonne::ir::{Function, Ebb}; - /// # use cretonne::ir::layout::Cursor; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; /// fn edit_func(func: &mut Function) { /// let mut cursor = Cursor::new(&mut func.layout); /// while let Some(ebb) = cursor.next_ebb() { @@ -739,16 +733,16 @@ impl<'f> Cursor<'f> { /// } /// } /// ``` - pub fn next_ebb(&mut self) -> Option { + fn next_ebb(&mut self) -> Option { let next = if let Some(ebb) = self.current_ebb() { - self.layout.ebbs[ebb].next.expand() + self.layout().ebbs[ebb].next.expand() } else { - self.layout.first_ebb - }; - self.pos = match next { - Some(ebb) => CursorPosition::Before(ebb), - None => CursorPosition::Nowhere, + self.layout().first_ebb }; + self.set_position(match next { + Some(ebb) => CursorPosition::Before(ebb), + None => CursorPosition::Nowhere, + }); next } @@ -764,7 +758,7 @@ impl<'f> Cursor<'f> { /// /// ``` /// # use cretonne::ir::{Function, Ebb}; - /// # use cretonne::ir::layout::Cursor; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; /// fn edit_func(func: &mut Function) { /// let mut cursor = Cursor::new(&mut func.layout); /// while let Some(ebb) = cursor.prev_ebb() { @@ -772,16 +766,16 @@ impl<'f> Cursor<'f> { /// } /// } /// ``` - pub fn prev_ebb(&mut self) -> Option { + fn prev_ebb(&mut self) -> Option { let prev = if let Some(ebb) = self.current_ebb() { - self.layout.ebbs[ebb].prev.expand() + self.layout().ebbs[ebb].prev.expand() } else { - self.layout.last_ebb - }; - self.pos = match prev { - Some(ebb) => CursorPosition::After(ebb), - None => CursorPosition::Nowhere, + self.layout().last_ebb }; + self.set_position(match prev { + Some(ebb) => CursorPosition::After(ebb), + None => CursorPosition::Nowhere, + }); prev } @@ -801,7 +795,7 @@ impl<'f> Cursor<'f> { /// /// ``` /// # use cretonne::ir::{Function, Ebb}; - /// # use cretonne::ir::layout::Cursor; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; /// fn edit_ebb(func: &mut Function, ebb: Ebb) { /// let mut cursor = Cursor::new(&mut func.layout); /// cursor.goto_top(ebb); @@ -816,7 +810,7 @@ impl<'f> Cursor<'f> { /// /// ``` /// # use cretonne::ir::{Function, Ebb}; - /// # use cretonne::ir::layout::Cursor; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; /// fn edit_func(func: &mut Function) { /// let mut cursor = Cursor::new(&mut func.layout); /// while let Some(ebb) = cursor.next_ebb() { @@ -826,27 +820,28 @@ impl<'f> Cursor<'f> { /// } /// } /// ``` - pub fn next_inst(&mut self) -> Option { + fn next_inst(&mut self) -> Option { use self::CursorPosition::*; - match self.pos { + match self.position() { Nowhere | After(..) => None, At(inst) => { - if let Some(next) = self.layout.insts[inst].next.expand() { - self.pos = At(next); + if let Some(next) = self.layout().insts[inst].next.expand() { + self.set_position(At(next)); Some(next) } else { - self.pos = After(self.layout - .inst_ebb(inst) - .expect("current instruction removed?")); + let pos = After(self.layout() + .inst_ebb(inst) + .expect("current instruction removed?")); + self.set_position(pos); None } } Before(ebb) => { - if let Some(next) = self.layout.ebbs[ebb].first_inst.expand() { - self.pos = At(next); + if let Some(next) = self.layout().ebbs[ebb].first_inst.expand() { + self.set_position(At(next)); Some(next) } else { - self.pos = After(ebb); + self.set_position(After(ebb)); None } } @@ -869,7 +864,7 @@ impl<'f> Cursor<'f> { /// /// ``` /// # use cretonne::ir::{Function, Ebb}; - /// # use cretonne::ir::layout::Cursor; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; /// fn edit_ebb(func: &mut Function, ebb: Ebb) { /// let mut cursor = Cursor::new(&mut func.layout); /// cursor.goto_bottom(ebb); @@ -878,27 +873,28 @@ impl<'f> Cursor<'f> { /// } /// } /// ``` - pub fn prev_inst(&mut self) -> Option { + fn prev_inst(&mut self) -> Option { use self::CursorPosition::*; - match self.pos { + match self.position() { Nowhere | Before(..) => None, At(inst) => { - if let Some(prev) = self.layout.insts[inst].prev.expand() { - self.pos = At(prev); + if let Some(prev) = self.layout().insts[inst].prev.expand() { + self.set_position(At(prev)); Some(prev) } else { - self.pos = Before(self.layout - .inst_ebb(inst) - .expect("current instruction removed?")); + let pos = Before(self.layout() + .inst_ebb(inst) + .expect("current instruction removed?")); + self.set_position(pos); None } } After(ebb) => { - if let Some(prev) = self.layout.ebbs[ebb].last_inst.expand() { - self.pos = At(prev); + if let Some(prev) = self.layout().ebbs[ebb].last_inst.expand() { + self.set_position(At(prev)); Some(prev) } else { - self.pos = Before(ebb); + self.set_position(Before(ebb)); None } } @@ -914,12 +910,12 @@ impl<'f> Cursor<'f> { /// /// In either case, the cursor is not moved, such that repeated calls to `insert_inst()` causes /// instructions to appear in insertion order in the EBB. - pub fn insert_inst(&mut self, inst: Inst) { + fn insert_inst(&mut self, inst: Inst) { use self::CursorPosition::*; - match self.pos { + match self.position() { Nowhere | Before(..) => panic!("Invalid insert_inst position"), - At(cur) => self.layout.insert_inst(inst, cur), - After(ebb) => self.layout.append_inst(inst, ebb), + At(cur) => self.layout_mut().insert_inst(inst, cur), + After(ebb) => self.layout_mut().append_inst(inst, ebb), } } @@ -928,10 +924,10 @@ impl<'f> Cursor<'f> { /// The cursor is left pointing at the position following the current instruction. /// /// Return the instruction that was removed. - pub fn remove_inst(&mut self) -> Inst { + fn remove_inst(&mut self) -> Inst { let inst = self.current_inst().expect("No instruction to remove"); self.next_inst(); - self.layout.remove_inst(inst); + self.layout_mut().remove_inst(inst); inst } @@ -940,10 +936,10 @@ impl<'f> Cursor<'f> { /// The cursor is left pointing at the position preceding the current instruction. /// /// Return the instruction that was removed. - pub fn remove_inst_and_step_back(&mut self) -> Inst { + fn remove_inst_and_step_back(&mut self) -> Inst { let inst = self.current_inst().expect("No instruction to remove"); self.prev_inst(); - self.layout.remove_inst(inst); + self.layout_mut().remove_inst(inst); inst } @@ -961,27 +957,56 @@ impl<'f> Cursor<'f> { /// /// This means that is is always valid to call this method, and it always leaves the cursor in /// a state that will insert instructions into the new EBB. - pub fn insert_ebb(&mut self, new_ebb: Ebb) { + fn insert_ebb(&mut self, new_ebb: Ebb) { use self::CursorPosition::*; - match self.pos { + match self.position() { At(inst) => { - self.layout.split_ebb(new_ebb, inst); + self.layout_mut().split_ebb(new_ebb, inst); // All other cases move to `After(ebb)`, but in this case we we'll stay `At(inst)`. return; } - Nowhere => self.layout.append_ebb(new_ebb), - Before(ebb) => self.layout.insert_ebb(new_ebb, ebb), - After(ebb) => self.layout.insert_ebb_after(new_ebb, ebb), + Nowhere => self.layout_mut().append_ebb(new_ebb), + Before(ebb) => self.layout_mut().insert_ebb(new_ebb, ebb), + After(ebb) => self.layout_mut().insert_ebb_after(new_ebb, ebb), } // For everything but `At(inst)` we end up appending to the new EBB. - self.pos = After(new_ebb); + self.set_position(After(new_ebb)); + } +} + +impl<'f> CursorBase for Cursor<'f> { + fn position(&self) -> CursorPosition { + self.pos + } + + fn set_position(&mut self, pos: CursorPosition) { + self.pos = pos; + } + + fn layout(&self) -> &Layout { + self.layout + } + + fn layout_mut(&mut self) -> &mut Layout { + self.layout + } +} + +impl<'f> Cursor<'f> { + /// Create a new `Cursor` for `layout`. + /// The cursor holds a mutable reference to `layout` for its entire lifetime. + pub fn new(layout: &'f mut Layout) -> Cursor { + Cursor { + layout, + pos: CursorPosition::Nowhere, + } } } #[cfg(test)] mod tests { - use super::{Layout, Cursor, CursorPosition}; + use super::{Layout, Cursor, CursorBase, CursorPosition}; use entity_ref::EntityRef; use ir::{Ebb, Inst, ProgramOrder}; use std::cmp::Ordering; diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 76f37efa3c..dfea1048b5 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -27,7 +27,7 @@ pub use ir::stackslot::{StackSlots, StackSlotKind, StackSlotData}; pub use ir::jumptable::JumpTableData; pub use ir::valueloc::{ValueLoc, ArgumentLoc}; pub use ir::dfg::{DataFlowGraph, ValueDef}; -pub use ir::layout::{Layout, Cursor}; +pub use ir::layout::{Layout, CursorBase, Cursor}; pub use ir::function::Function; pub use ir::builder::InstBuilder; pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index caed486f3a..7d248c0cfa 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -19,9 +19,9 @@ use abi::{legalize_abi_value, ValueConversion}; use flowgraph::ControlFlowGraph; -use ir::{Function, Cursor, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef, - ArgumentType, ArgumentPurpose, ArgumentLoc, ValueLoc, ValueLocations, StackSlots, - StackSlotKind}; +use ir::{Function, Cursor, CursorBase, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, + Signature, SigRef, ArgumentType, ArgumentPurpose, ArgumentLoc, ValueLoc, ValueLocations, + StackSlots, StackSlotKind}; use ir::instructions::CallInfo; use isa::TargetIsa; use legalizer::split::{isplit, vsplit}; diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 4494eb60e9..9cc37dd896 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -15,7 +15,7 @@ use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; -use ir::{self, Function, Cursor}; +use ir::{self, Function, Cursor, CursorBase}; use ir::condcodes::IntCC; use isa::TargetIsa; use bitset::BitSet; diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index 3e3a36f619..da61303e05 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -65,8 +65,8 @@ //! instructions. These loops will remain in the program. use flowgraph::ControlFlowGraph; -use ir::{DataFlowGraph, Ebb, Inst, Cursor, Value, Type, Opcode, ValueDef, InstructionData, - InstBuilder}; +use ir::{DataFlowGraph, Ebb, Inst, Cursor, CursorBase, Value, Type, Opcode, ValueDef, + InstructionData, InstBuilder}; use std::iter; /// Split `value` into two values using the `isplit` semantics. Do this by reusing existing values diff --git a/lib/cretonne/src/licm.rs b/lib/cretonne/src/licm.rs index 10e0558999..8ed88d47dd 100644 --- a/lib/cretonne/src/licm.rs +++ b/lib/cretonne/src/licm.rs @@ -1,6 +1,6 @@ //! A Loop Invariant Code Motion optimization pass -use ir::{Function, Ebb, Inst, Value, Cursor, Type, InstBuilder, Layout}; +use ir::{Function, Ebb, Inst, Value, Cursor, CursorBase, Type, InstBuilder, Layout}; use flowgraph::ControlFlowGraph; use std::collections::HashSet; use dominator_tree::DominatorTree; diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs index d0a9d505bd..5a4607ba79 100644 --- a/lib/cretonne/src/loop_analysis.rs +++ b/lib/cretonne/src/loop_analysis.rs @@ -199,7 +199,7 @@ impl LoopAnalysis { #[cfg(test)] mod test { - use ir::{Function, InstBuilder, Cursor, types}; + use ir::{Function, InstBuilder, Cursor, CursorBase, types}; use loop_analysis::{Loop, LoopAnalysis}; use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 29051bf426..4b4ffff34e 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -8,7 +8,7 @@ use dbg::DisplayList; use dominator_tree::DominatorTree; use flowgraph::{ControlFlowGraph, BasicBlock}; -use ir::{DataFlowGraph, Layout, Cursor, InstBuilder, ValueDef}; +use ir::{DataFlowGraph, Layout, Cursor, CursorBase, InstBuilder, ValueDef}; use ir::{Function, Ebb, Inst, Value, ExpandedProgramPoint}; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index 21a4453bb2..02e4d47f81 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -43,7 +43,7 @@ //! The exception is the entry block whose arguments are colored from the ABI requirements. use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Cursor, ValueLoc, DataFlowGraph, Layout}; +use ir::{Ebb, Inst, Value, Function, Cursor, CursorBase, ValueLoc, DataFlowGraph, Layout}; use ir::{InstEncodings, ValueLocations}; use ir::{InstBuilder, Signature, ArgumentType, ArgumentLoc}; use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index e22978938b..18e834179b 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -11,7 +11,7 @@ use dominator_tree::DominatorTree; use ir::{Ebb, Inst, Value, Function, Signature, DataFlowGraph, InstEncodings}; -use ir::layout::{Cursor, CursorPosition}; +use ir::layout::{Cursor, CursorBase, CursorPosition}; use ir::{InstBuilder, Opcode, ArgumentType, ArgumentLoc}; use isa::RegClass; use isa::{TargetIsa, Encoding, EncInfo, RecipeConstraints, ConstraintKind}; diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 8b3e8b7837..e1e610c3f0 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -16,7 +16,7 @@ //! operands. use dominator_tree::DominatorTree; -use ir::{DataFlowGraph, Layout, Cursor, InstBuilder}; +use ir::{DataFlowGraph, Layout, Cursor, CursorBase, InstBuilder}; use ir::{Function, Ebb, Inst, Value, ValueLoc, SigRef}; use ir::{InstEncodings, StackSlots, ValueLocations}; use isa::registers::{RegClassMask, RegClassIndex}; diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs index bb339fbd4d..078bca2857 100644 --- a/lib/cretonne/src/simple_gvn.rs +++ b/lib/cretonne/src/simple_gvn.rs @@ -2,7 +2,7 @@ use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; -use ir::{Cursor, InstructionData, Function, Inst, Opcode}; +use ir::{Cursor, CursorBase, InstructionData, Function, Inst, Opcode}; use std::collections::HashMap; /// Test whether the given opcode is unsafe to even consider for GVN. diff --git a/lib/cretonne/src/topo_order.rs b/lib/cretonne/src/topo_order.rs index 323df99ef0..938e4a876a 100644 --- a/lib/cretonne/src/topo_order.rs +++ b/lib/cretonne/src/topo_order.rs @@ -81,7 +81,7 @@ impl TopoOrder { mod test { use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; - use ir::{Function, InstBuilder, Cursor}; + use ir::{Function, InstBuilder, Cursor, CursorBase}; use std::iter; use super::*; diff --git a/lib/frontend/src/ssa.rs b/lib/frontend/src/ssa.rs index 3794d332b1..fc5c85f1dd 100644 --- a/lib/frontend/src/ssa.rs +++ b/lib/frontend/src/ssa.rs @@ -5,7 +5,8 @@ //! In: Jhala R., De Bosschere K. (eds) Compiler Construction. CC 2013. //! Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg -use cretonne::ir::{Ebb, Value, Inst, Type, DataFlowGraph, JumpTables, Layout, Cursor, InstBuilder}; +use cretonne::ir::{Ebb, Value, Inst, Type, DataFlowGraph, JumpTables, Layout, Cursor, CursorBase, + InstBuilder}; use cretonne::ir::instructions::BranchInfo; use std::hash::Hash; use cretonne::entity_map::{EntityMap, PrimaryEntityData}; @@ -607,7 +608,7 @@ impl SSABuilder #[cfg(test)] mod tests { use cretonne::entity_ref::EntityRef; - use cretonne::ir::{Function, InstBuilder, Cursor, Inst, JumpTableData}; + use cretonne::ir::{Function, InstBuilder, Cursor, CursorBase, Inst, JumpTableData}; use cretonne::ir::types::*; use cretonne::verify_function; use cretonne::ir::instructions::BranchInfo; From 019e5dd8940fbc65c999f624b96cdb9e31398e2a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Aug 2017 18:03:58 -0700 Subject: [PATCH 957/968] Add CursorBase builder methods. The CursorBase::at_* are convenience methods for building a cursor that points to a specific instruction. --- lib/cretonne/src/ir/layout.rs | 41 +++++++++++++++++++++++++ lib/cretonne/src/regalloc/coalescing.rs | 7 ++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index b1e068ff5c..081a289053 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -672,6 +672,47 @@ pub trait CursorBase { /// Borrow a mutable reference to the function layout that this cursor is navigating. fn layout_mut(&mut self) -> &mut Layout; + /// Rebuild this cursor positioned at `inst`. + /// + /// This is intended to be used as a builder method: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb, Inst}; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; + /// fn edit_func(func: &mut Function, inst: Inst) { + /// let mut pos = Cursor::new(&mut func.layout).at_inst(inst); + /// + /// // Use `pos`... + /// } + /// ``` + fn at_inst(mut self, inst: Inst) -> Self + where Self: Sized + { + self.goto_inst(inst); + self + } + + /// Rebuild this cursor positioned at the first instruction in `ebb`. + /// + /// This is intended to be used as a builder method: + /// + /// ``` + /// # use cretonne::ir::{Function, Ebb, Inst}; + /// # use cretonne::ir::layout::{Cursor, CursorBase}; + /// fn edit_func(func: &mut Function, ebb: Ebb) { + /// let mut pos = Cursor::new(&mut func.layout).at_first_inst(ebb); + /// + /// // Use `pos`... + /// } + /// ``` + fn at_first_inst(mut self, ebb: Ebb) -> Self + where Self: Sized + { + let inst = self.layout().ebbs[ebb].first_inst.expect("Empty EBB"); + self.goto_inst(inst); + self + } + /// Get the EBB corresponding to the current position. fn current_ebb(&self) -> Option { use self::CursorPosition::*; diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 4b4ffff34e..b21175ad87 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -466,8 +466,7 @@ impl<'a> Context<'a> { -> Value { let copy; { - let mut pos = Cursor::new(&mut self.func.layout); - pos.goto_inst(pred_inst); + let mut pos = Cursor::new(&mut self.func.layout).at_inst(pred_inst); copy = self.func.dfg.ins(&mut pos).copy(pred_val); } let inst = self.func.dfg.value_def(copy).unwrap_inst(); @@ -507,9 +506,7 @@ impl<'a> Context<'a> { // Insert a copy instruction at the top of ebb. { - let mut pos = Cursor::new(&mut self.func.layout); - pos.goto_top(ebb); - pos.next_inst(); + let mut pos = Cursor::new(&mut self.func.layout).at_first_inst(ebb); self.func .dfg .ins(&mut pos) From ff39458f9668c5c1552952172fd4db145d0fe322 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Aug 2017 11:23:37 -0700 Subject: [PATCH 958/968] Reorganize the instruction builder traits. Leave the primary InstBuilderBase trait alone, but add an alternative InstInserterBase trait that can be implemented instead by builders that always allocate new instructions with dfg.make_inst(). Any implementation of InstInserterBase can be used as an instruction builder by wrapping it in an InsertBuilder. The InsertBuilder type adds additional functionality via the with_results() method which makes it possible to override the result values on the instruction that is built. The motivation for this shuffle is that the with_result() functionality can now be reused by different kinds of instruction builders, as long as they insert new instructions. So ReplaceBuilder doesn't get with_results(). --- lib/cretonne/src/ir/builder.rs | 109 +++++++++++++++++++++------------ lib/cretonne/src/ir/dfg.rs | 6 +- lib/cretonne/src/ir/layout.rs | 35 ++++++++++- lib/cretonne/src/ir/mod.rs | 23 ++++--- 4 files changed, 117 insertions(+), 56 deletions(-) diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 59de1f68b5..697acfebd5 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -4,7 +4,7 @@ //! function. Many of its methods are generated from the meta language instruction definitions. use ir::types; -use ir::{InstructionData, DataFlowGraph, Cursor, CursorBase}; +use ir::{InstructionData, DataFlowGraph}; use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; @@ -43,23 +43,44 @@ include!(concat!(env!("OUT_DIR"), "/builder.rs")); /// Any type implementing `InstBuilderBase` gets all the `InstBuilder` methods for free. impl<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {} -/// Builder that inserts an instruction at the current cursor position. +/// Base trait for instruction inserters. /// -/// An `InsertBuilder` holds mutable references to a data flow graph and a layout cursor. It -/// provides convenience methods for creating and inserting instructions at the current cursor -/// position. -pub struct InsertBuilder<'c, 'fc: 'c, 'fd> { - pos: &'c mut Cursor<'fc>, - dfg: &'fd mut DataFlowGraph, +/// This is an alternative base trait for an instruction builder to implement. +/// +/// An instruction inserter can be adapted into an instruction builder by wrapping it in an +/// `InsertBuilder`. This provides some common functionality for instruction builders that insert +/// new instructions, as opposed to the `ReplaceBuilder` which overwrites existing instructions. +pub trait InstInserterBase<'f>: Sized { + /// Get an immutable reference to the data flow graph. + fn data_flow_graph(&self) -> &DataFlowGraph; + + /// Get a mutable reference to the data flow graph. + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph; + + /// Insert a new instruction which belongs to the DFG. + fn insert_built_inst(self, inst: Inst, ctrl_typevar: Type) -> &'f mut DataFlowGraph; } -impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { +use std::marker::PhantomData; + +/// Builder that inserts an instruction at the current position. +/// +/// An `InsertBuilder` is a wrapper for an `InstInserterBase` that turns it into an instruction +/// builder with some additional facilities for creating instructions that reuse existing values as +/// their results. +pub struct InsertBuilder<'f, IIB: InstInserterBase<'f>> { + inserter: IIB, + unused: PhantomData<&'f u32>, +} + +impl<'f, IIB: InstInserterBase<'f>> InsertBuilder<'f, IIB> { /// Create a new builder which inserts instructions at `pos`. /// The `dfg` and `pos.layout` references should be from the same `Function`. - pub fn new(dfg: &'fd mut DataFlowGraph, - pos: &'c mut Cursor<'fc>) - -> InsertBuilder<'c, 'fc, 'fd> { - InsertBuilder { dfg, pos } + pub fn new(inserter: IIB) -> InsertBuilder<'f, IIB> { + InsertBuilder { + inserter, + unused: PhantomData, + } } /// Reuse result values in `reuse`. @@ -69,13 +90,13 @@ impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { /// missing result values will be allocated as normal. /// /// The `reuse` argument is expected to be an array of `Option`. - pub fn with_results(self, reuse: Array) -> InsertReuseBuilder<'c, 'fc, 'fd, Array> + pub fn with_results(self, reuse: Array) -> InsertReuseBuilder<'f, IIB, Array> where Array: AsRef<[Option]> { InsertReuseBuilder { - dfg: self.dfg, - pos: self.pos, + inserter: self.inserter, reuse, + unused: PhantomData, } } @@ -86,57 +107,65 @@ impl<'c, 'fc, 'fd> InsertBuilder<'c, 'fc, 'fd> { /// /// This method should only be used when building an instruction with exactly one result. Use /// `with_results()` for the more general case. - pub fn with_result(self, v: Value) -> InsertReuseBuilder<'c, 'fc, 'fd, [Option; 1]> { + pub fn with_result(self, v: Value) -> InsertReuseBuilder<'f, IIB, [Option; 1]> { // TODO: Specialize this to return a different builder that just attaches `v` instead of // calling `make_inst_results_reusing()`. self.with_results([Some(v)]) } } -impl<'c, 'fc, 'fd> InstBuilderBase<'fd> for InsertBuilder<'c, 'fc, 'fd> { +impl<'f, IIB: InstInserterBase<'f>> InstBuilderBase<'f> for InsertBuilder<'f, IIB> { fn data_flow_graph(&self) -> &DataFlowGraph { - self.dfg + self.inserter.data_flow_graph() } fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - self.dfg + self.inserter.data_flow_graph_mut() } - fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'fd mut DataFlowGraph) { - let inst = self.dfg.make_inst(data); - self.dfg.make_inst_results(inst, ctrl_typevar); - self.pos.insert_inst(inst); - (inst, self.dfg) + fn build(mut self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) { + let inst; + { + let dfg = self.inserter.data_flow_graph_mut(); + inst = dfg.make_inst(data); + dfg.make_inst_results(inst, ctrl_typevar); + } + (inst, self.inserter.insert_built_inst(inst, ctrl_typevar)) } } /// Builder that inserts a new instruction like `InsertBuilder`, but reusing result values. -pub struct InsertReuseBuilder<'c, 'fc: 'c, 'fd, Array> - where Array: AsRef<[Option]> +pub struct InsertReuseBuilder<'f, IIB, Array> + where IIB: InstInserterBase<'f>, + Array: AsRef<[Option]> { - pos: &'c mut Cursor<'fc>, - dfg: &'fd mut DataFlowGraph, + inserter: IIB, reuse: Array, + unused: PhantomData<&'f u32>, } -impl<'c, 'fc, 'fd, Array> InstBuilderBase<'fd> for InsertReuseBuilder<'c, 'fc, 'fd, Array> - where Array: AsRef<[Option]> +impl<'f, IIB, Array> InstBuilderBase<'f> for InsertReuseBuilder<'f, IIB, Array> + where IIB: InstInserterBase<'f>, + Array: AsRef<[Option]> { fn data_flow_graph(&self) -> &DataFlowGraph { - self.dfg + self.inserter.data_flow_graph() } fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - self.dfg + self.inserter.data_flow_graph_mut() } - fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'fd mut DataFlowGraph) { - let inst = self.dfg.make_inst(data); - // Make an `Interator>`. - let ru = self.reuse.as_ref().iter().cloned(); - self.dfg.make_inst_results_reusing(inst, ctrl_typevar, ru); - self.pos.insert_inst(inst); - (inst, self.dfg) + fn build(mut self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) { + let inst; + { + let dfg = self.inserter.data_flow_graph_mut(); + inst = dfg.make_inst(data); + // Make an `Interator>`. + let ru = self.reuse.as_ref().iter().cloned(); + dfg.make_inst_results_reusing(inst, ctrl_typevar, ru); + } + (inst, self.inserter.insert_built_inst(inst, ctrl_typevar)) } } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 49d4d13d6a..959b29c8e8 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -5,7 +5,7 @@ use isa::TargetIsa; use ir::builder::{InsertBuilder, ReplaceBuilder}; use ir::extfunc::ExtFuncData; use ir::instructions::{Opcode, InstructionData, CallInfo}; -use ir::layout::Cursor; +use ir::layout::{Cursor, LayoutCursorInserter}; use ir::types; use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueListPool}; use write::write_operands; @@ -486,8 +486,8 @@ impl DataFlowGraph { /// Create an `InsertBuilder` that will insert an instruction at the cursor's current position. pub fn ins<'c, 'fc: 'c, 'fd>(&'fd mut self, at: &'c mut Cursor<'fc>) - -> InsertBuilder<'c, 'fc, 'fd> { - InsertBuilder::new(self, at) + -> InsertBuilder<'fd, LayoutCursorInserter<'c, 'fc, 'fd>> { + InsertBuilder::new(LayoutCursorInserter::new(at, self)) } /// Create a `ReplaceBuilder` that will replace `inst` with a new instruction in place. diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 081a289053..b219a13bb2 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -7,7 +7,8 @@ use std::cmp; use std::iter::{Iterator, IntoIterator}; use entity_map::EntityMap; use packed_option::PackedOption; -use ir::entities::{Ebb, Inst}; +use ir::{Ebb, Inst, Type, DataFlowGraph}; +use ir::builder::InstInserterBase; use ir::progpoint::{ProgramOrder, ExpandedProgramPoint}; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not @@ -1044,6 +1045,38 @@ impl<'f> Cursor<'f> { } } +/// An instruction inserter which can be used to build and insert instructions at a cursor +/// position. +/// +/// This is used by `dfg.ins()`. +pub struct LayoutCursorInserter<'c, 'fc: 'c, 'fd> { + pos: &'c mut Cursor<'fc>, + dfg: &'fd mut DataFlowGraph, +} + +impl<'c, 'fc: 'c, 'fd> LayoutCursorInserter<'c, 'fc, 'fd> { + /// Create a new inserter. Don't use this, use `dfg.ins(pos)`. + pub fn new(pos: &'c mut Cursor<'fc>, + dfg: &'fd mut DataFlowGraph) + -> LayoutCursorInserter<'c, 'fc, 'fd> { + LayoutCursorInserter { pos, dfg } + } +} + +impl<'c, 'fc: 'c, 'fd> InstInserterBase<'fd> for LayoutCursorInserter<'c, 'fc, 'fd> { + fn data_flow_graph(&self) -> &DataFlowGraph { + self.dfg + } + + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + self.dfg + } + + fn insert_built_inst(self, inst: Inst, _ctrl_typevar: Type) -> &'fd mut DataFlowGraph { + self.pos.insert_inst(inst); + self.dfg + } +} #[cfg(test)] mod tests { diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index dfea1048b5..443d70bd1c 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -17,22 +17,21 @@ mod memflags; mod progpoint; mod valueloc; -pub use ir::funcname::FunctionName; +pub use ir::builder::{InstBuilder, InstBuilderBase, InstInserterBase, InsertBuilder}; +pub use ir::dfg::{DataFlowGraph, ValueDef}; +pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; pub use ir::extfunc::{Signature, CallConv, ArgumentType, ArgumentExtension, ArgumentPurpose, ExtFuncData}; -pub use ir::types::Type; -pub use ir::entities::{Ebb, Inst, Value, StackSlot, JumpTable, FuncRef, SigRef}; -pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; -pub use ir::stackslot::{StackSlots, StackSlotKind, StackSlotData}; -pub use ir::jumptable::JumpTableData; -pub use ir::valueloc::{ValueLoc, ArgumentLoc}; -pub use ir::dfg::{DataFlowGraph, ValueDef}; -pub use ir::layout::{Layout, CursorBase, Cursor}; +pub use ir::funcname::FunctionName; pub use ir::function::Function; -pub use ir::builder::InstBuilder; -pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; +pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; +pub use ir::jumptable::JumpTableData; +pub use ir::layout::{Layout, CursorBase, Cursor}; pub use ir::memflags::MemFlags; -pub use ir::builder::InstBuilderBase; +pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; +pub use ir::stackslot::{StackSlots, StackSlotKind, StackSlotData}; +pub use ir::types::Type; +pub use ir::valueloc::{ValueLoc, ArgumentLoc}; use binemit; use entity_map::EntityMap; From 0ab1976231228da46f66030062a67e3739a29c37 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 3 Aug 2017 21:08:36 -0700 Subject: [PATCH 959/968] Cursor library. Add a new cursor module and define an EncCursor data type in it. An EncCursor is a cursor that inserts instructions with a valid encoding for the ISA. This is useful for passes generating code after legalization. Implement a builder interface via the new InstInserterBase trait such that the EncCursor builders support with_result(). Use EncCursor in coalescing.rs instead of the layout cursor as a proof of concept. --- lib/cretonne/src/cursor.rs | 105 ++++++++++++++++++++++++ lib/cretonne/src/lib.rs | 1 + lib/cretonne/src/regalloc/coalescing.rs | 55 ++++--------- 3 files changed, 122 insertions(+), 39 deletions(-) create mode 100644 lib/cretonne/src/cursor.rs diff --git a/lib/cretonne/src/cursor.rs b/lib/cretonne/src/cursor.rs new file mode 100644 index 0000000000..b706531eed --- /dev/null +++ b/lib/cretonne/src/cursor.rs @@ -0,0 +1,105 @@ +//! Cursor library. +//! +//! This module defines cursor data types that can be used for inserting instructions. + +use ir; +use isa::TargetIsa; + +// Re-export these types, anticipating their being moved here. +pub use ir::layout::CursorBase as Cursor; +pub use ir::layout::CursorPosition; +pub use ir::layout::Cursor as LayoutCursor; + +/// Encoding cursor. +/// +/// An `EncCursor` can be used to insert instructions that are immediately assigned an encoding. +/// The cursor holds a mutable reference to the whole function which can be re-borrowed from the +/// public `pos.func` member. +pub struct EncCursor<'f> { + pos: CursorPosition, + built_inst: Option, + pub func: &'f mut ir::Function, + pub isa: &'f TargetIsa, +} + +impl<'f> EncCursor<'f> { + /// Create a new `EncCursor` pointing nowhere. + pub fn new(func: &'f mut ir::Function, isa: &'f TargetIsa) -> EncCursor<'f> { + EncCursor { + pos: CursorPosition::Nowhere, + built_inst: None, + func, + isa, + } + } + + /// Create an instruction builder that will insert an encoded instruction at the current + /// position. + /// + /// The builder will panic if it is used to insert an instruction that can't be encoded for + /// `self.isa`. + pub fn ins(&mut self) -> ir::InsertBuilder<&mut EncCursor<'f>> { + ir::InsertBuilder::new(self) + } + + /// Get the last built instruction. + /// + /// This returns the last instruction that was built using the `ins()` method on this cursor. + /// Panics if no instruction was built. + pub fn built_inst(&self) -> ir::Inst { + self.built_inst.expect("No instruction was inserted") + } + + /// Return an object that can display `inst`. + /// + /// This is a convenience wrapper for the DFG equivalent. + pub fn display_inst(&self, inst: ir::Inst) -> ir::dfg::DisplayInst { + self.func.dfg.display_inst(inst, self.isa) + } +} + +impl<'f> Cursor for EncCursor<'f> { + fn position(&self) -> CursorPosition { + self.pos + } + + fn set_position(&mut self, pos: CursorPosition) { + self.pos = pos + } + + fn layout(&self) -> &ir::Layout { + &self.func.layout + } + + fn layout_mut(&mut self) -> &mut ir::Layout { + &mut self.func.layout + } +} + +impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut EncCursor<'f> { + fn data_flow_graph(&self) -> &ir::DataFlowGraph { + &self.func.dfg + } + + fn data_flow_graph_mut(&mut self) -> &mut ir::DataFlowGraph { + &mut self.func.dfg + } + + fn insert_built_inst(self, + inst: ir::Inst, + ctrl_typevar: ir::Type) + -> &'c mut ir::DataFlowGraph { + // Insert the instruction and remember the reference. + self.insert_inst(inst); + self.built_inst = Some(inst); + + // Assign an encoding. + match self.isa + .encode(&self.func.dfg, &self.func.dfg[inst], ctrl_typevar) { + Ok(e) => *self.func.encodings.ensure(inst) = e, + Err(_) => panic!("can't encode {}", self.display_inst(inst)), + } + + &mut self.func.dfg + } +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 44f469ab4e..b021bf55b2 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -34,6 +34,7 @@ pub mod verifier; mod abi; mod constant_hash; mod context; +mod cursor; mod iterators; mod legalizer; mod licm; diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index b21175ad87..ac11df3498 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -5,10 +5,11 @@ //! and inserting copies where necessary such that all values passed to an EBB argument will belong //! to the same virtual register as the EBB argument value itself. +use cursor::{Cursor, EncCursor}; use dbg::DisplayList; use dominator_tree::DominatorTree; use flowgraph::{ControlFlowGraph, BasicBlock}; -use ir::{DataFlowGraph, Layout, Cursor, CursorBase, InstBuilder, ValueDef}; +use ir::{DataFlowGraph, Layout, InstBuilder, ValueDef}; use ir::{Function, Ebb, Inst, Value, ExpandedProgramPoint}; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; @@ -464,37 +465,26 @@ impl<'a> Context<'a> { argnum: usize, pred_val: Value) -> Value { - let copy; - { - let mut pos = Cursor::new(&mut self.func.layout).at_inst(pred_inst); - copy = self.func.dfg.ins(&mut pos).copy(pred_val); - } - let inst = self.func.dfg.value_def(copy).unwrap_inst(); - let ty = self.func.dfg.value_type(copy); + let mut pos = EncCursor::new(self.func, self.isa).at_inst(pred_inst); + let copy = pos.ins().copy(pred_val); + let inst = pos.built_inst(); dbg!("Inserted {}, before {}: {}", - self.func.dfg.display_inst(inst, self.isa), + pos.display_inst(inst), pred_ebb, - self.func.dfg.display_inst(pred_inst, self.isa)); - - // Give it an encoding. - let encoding = match self.isa.encode(&self.func.dfg, &self.func.dfg[inst], ty) { - Ok(e) => e, - Err(_) => panic!("Can't encode copy.{}", ty), - }; - *self.func.encodings.ensure(inst) = encoding; + pos.display_inst(pred_inst)); // Create a live range for the new value. let affinity = Affinity::new(&self.encinfo - .operand_constraints(encoding) + .operand_constraints(pos.func.encodings[inst]) .expect("Bad copy encoding") .outs [0]); self.liveness.create_dead(copy, inst, affinity); self.liveness - .extend_locally(copy, pred_ebb, pred_inst, &self.func.layout); + .extend_locally(copy, pred_ebb, pred_inst, &pos.func.layout); - self.func.dfg.inst_variable_args_mut(pred_inst)[argnum] = copy; + pos.func.dfg.inst_variable_args_mut(pred_inst)[argnum] = copy; self.split_values.push(copy); copy } @@ -505,39 +495,26 @@ impl<'a> Context<'a> { let new_val = self.func.dfg.replace_ebb_arg(succ_val, ty); // Insert a copy instruction at the top of ebb. - { - let mut pos = Cursor::new(&mut self.func.layout).at_first_inst(ebb); - self.func - .dfg - .ins(&mut pos) - .with_result(succ_val) - .copy(new_val); - } - let inst = self.func.dfg.value_def(succ_val).unwrap_inst(); + let mut pos = EncCursor::new(self.func, self.isa).at_first_inst(ebb); + pos.ins().with_result(succ_val).copy(new_val); + let inst = pos.built_inst(); self.liveness.move_def_locally(succ_val, inst); dbg!("Inserted {}, following {}({}: {})", - self.func.dfg.display_inst(inst, self.isa), + pos.display_inst(inst), ebb, new_val, ty); - // Give it an encoding. - let encoding = match self.isa.encode(&self.func.dfg, &self.func.dfg[inst], ty) { - Ok(e) => e, - Err(_) => panic!("Can't encode copy.{}", ty), - }; - *self.func.encodings.ensure(inst) = encoding; - // Create a live range for the new value. let affinity = Affinity::new(&self.encinfo - .operand_constraints(encoding) + .operand_constraints(pos.func.encodings[inst]) .expect("Bad copy encoding") .outs [0]); self.liveness.create_dead(new_val, ebb, affinity); self.liveness - .extend_locally(new_val, ebb, inst, &self.func.layout); + .extend_locally(new_val, ebb, inst, &pos.func.layout); self.split_values.push(new_val); new_val From 3eb80fde152a8d3176c7524c9c5a33c728e3586e Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Aug 2017 14:13:56 -0700 Subject: [PATCH 960/968] Use EncCursor in regalloc/spilling.rs Use an EncCursor instead of a layout cursor to keep track of the current position in the function. Since the EncCursor holds a reference to the whole IR function insteadof just the layout, we can rework how IR borrowing works. The Context data structure that's live during the spilling pass now owns an EncCursor which in turn holds references to the function and ISA. This means that we no longer need to pass around references to parts of the ir::Function. We can no longer borrow any part of the IR function across a context method call, but that turns out to be not necessary. --- lib/cretonne/src/regalloc/spilling.rs | 194 +++++++++++--------------- 1 file changed, 81 insertions(+), 113 deletions(-) diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index e1e610c3f0..12a938c81c 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -15,10 +15,9 @@ //! be compatible. Otherwise, the value must be copied into a new register for some of the //! operands. +use cursor::{Cursor, EncCursor}; use dominator_tree::DominatorTree; -use ir::{DataFlowGraph, Layout, Cursor, CursorBase, InstBuilder}; -use ir::{Function, Ebb, Inst, Value, ValueLoc, SigRef}; -use ir::{InstEncodings, StackSlots, ValueLocations}; +use ir::{InstBuilder, Function, Ebb, Inst, Value, ValueLoc, SigRef}; use isa::registers::{RegClassMask, RegClassIndex}; use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; @@ -37,16 +36,13 @@ pub struct Spilling { /// Context data structure that gets instantiated once per pass. struct Context<'a> { - isa: &'a TargetIsa, + // Current instruction as well as reference to function and ISA. + cur: EncCursor<'a>, + // Cached ISA information. reginfo: RegInfo, encinfo: EncInfo, - // References to parts of the current function. - encodings: &'a mut InstEncodings, - stack_slots: &'a mut StackSlots, - locations: &'a mut ValueLocations, - // References to contextual data structures we need. domtree: &'a DominatorTree, liveness: &'a mut Liveness, @@ -87,12 +83,9 @@ impl Spilling { let reginfo = isa.register_info(); let usable_regs = isa.allocatable_registers(func); let mut ctx = Context { - isa, + cur: EncCursor::new(func, isa), reginfo: isa.register_info(), encinfo: isa.encoding_info(), - encodings: &mut func.encodings, - stack_slots: &mut func.stack_slots, - locations: &mut func.locations, domtree, liveness, virtregs, @@ -101,35 +94,29 @@ impl Spilling { spills: &mut self.spills, reg_uses: &mut self.reg_uses, }; - ctx.run(&mut func.layout, &mut func.dfg, tracker) + ctx.run(tracker) } } impl<'a> Context<'a> { - fn run(&mut self, - layout: &mut Layout, - dfg: &mut DataFlowGraph, - tracker: &mut LiveValueTracker) { - self.topo.reset(layout.ebbs()); - while let Some(ebb) = self.topo.next(layout, self.domtree) { - self.visit_ebb(ebb, layout, dfg, tracker); + fn run(&mut self, tracker: &mut LiveValueTracker) { + self.topo.reset(self.cur.func.layout.ebbs()); + while let Some(ebb) = self.topo.next(&self.cur.func.layout, self.domtree) { + self.visit_ebb(ebb, tracker); } } - fn visit_ebb(&mut self, - ebb: Ebb, - layout: &mut Layout, - dfg: &mut DataFlowGraph, - tracker: &mut LiveValueTracker) { + fn visit_ebb(&mut self, ebb: Ebb, tracker: &mut LiveValueTracker) { dbg!("Spilling {}:", ebb); - self.visit_ebb_header(ebb, layout, dfg, tracker); + self.cur.goto_top(ebb); + self.visit_ebb_header(ebb, tracker); tracker.drop_dead_args(); - let mut pos = Cursor::new(layout); - pos.goto_top(ebb); - while let Some(inst) = pos.next_inst() { - if let Some(constraints) = self.encinfo.operand_constraints(self.encodings[inst]) { - self.visit_inst(inst, ebb, constraints, &mut pos, dfg, tracker); + while let Some(inst) = self.cur.next_inst() { + if let Some(constraints) = + self.encinfo + .operand_constraints(self.cur.func.encodings[inst]) { + self.visit_inst(inst, ebb, constraints, tracker); } else { let (_throughs, kills) = tracker.process_ghost(inst); self.free_regs(kills); @@ -162,12 +149,12 @@ impl<'a> Context<'a> { } } - fn visit_ebb_header(&mut self, - ebb: Ebb, - layout: &mut Layout, - dfg: &mut DataFlowGraph, - tracker: &mut LiveValueTracker) { - let (liveins, args) = tracker.ebb_top(ebb, dfg, self.liveness, layout, self.domtree); + fn visit_ebb_header(&mut self, ebb: Ebb, tracker: &mut LiveValueTracker) { + let (liveins, args) = tracker.ebb_top(ebb, + &self.cur.func.dfg, + self.liveness, + &self.cur.func.layout, + self.domtree); // Count the live-in registers. These should already fit in registers; they did at the // dominator. @@ -184,13 +171,13 @@ impl<'a> Context<'a> { rc, lv.value, liveins.len()); - match self.spill_candidate(mask, liveins, dfg, layout) { + match self.spill_candidate(mask, liveins) { Some(cand) => { dbg!("Spilling live-in {} to make room for {} EBB argument {}", cand, rc, lv.value); - self.spill_reg(cand, dfg); + self.spill_reg(cand); } None => { // We can't spill any of the live-in registers, so we have to spill an @@ -200,7 +187,7 @@ impl<'a> Context<'a> { // Since `spill_reg` will free a register, add the current one here. self.pressure.take(rc); - self.spill_reg(lv.value, dfg); + self.spill_reg(lv.value); break 'try_take; } } @@ -216,29 +203,27 @@ impl<'a> Context<'a> { inst: Inst, ebb: Ebb, constraints: &RecipeConstraints, - pos: &mut Cursor, - dfg: &mut DataFlowGraph, tracker: &mut LiveValueTracker) { - dbg!("Inst {}, {}", - dfg.display_inst(inst, self.isa), - self.pressure); + dbg!("Inst {}, {}", self.cur.display_inst(inst), self.pressure); + debug_assert_eq!(self.cur.current_inst(), Some(inst)); + debug_assert_eq!(self.cur.current_ebb(), Some(ebb)); // We may need to resolve register constraints if there are any noteworthy uses. assert!(self.reg_uses.is_empty()); - self.collect_reg_uses(inst, ebb, constraints, dfg, pos.layout); + self.collect_reg_uses(inst, ebb, constraints); // Calls usually have fixed register uses. - let call_sig = dfg.call_signature(inst); + let call_sig = self.cur.func.dfg.call_signature(inst); if let Some(sig) = call_sig { - self.collect_abi_reg_uses(inst, sig, dfg); + self.collect_abi_reg_uses(inst, sig); } if !self.reg_uses.is_empty() { - self.process_reg_uses(inst, pos, dfg, tracker); + self.process_reg_uses(inst, tracker); } // Update the live value tracker with this instruction. - let (throughs, kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + let (throughs, kills, defs) = tracker.process_inst(inst, &self.cur.func.dfg, self.liveness); // Remove kills from the pressure tracker. self.free_regs(kills); @@ -249,7 +234,7 @@ impl<'a> Context<'a> { if call_sig.is_some() { for lv in throughs { if lv.affinity.is_reg() && !self.spills.contains(&lv.value) { - self.spill_reg(lv.value, dfg); + self.spill_reg(lv.value); } } } @@ -262,12 +247,12 @@ impl<'a> Context<'a> { // Add register def to pressure, spill if needed. while let Err(mask) = self.pressure.take_transient(op.regclass) { dbg!("Need {} reg from {} throughs", op.regclass, throughs.len()); - match self.spill_candidate(mask, throughs, dfg, pos.layout) { - Some(cand) => self.spill_reg(cand, dfg), + match self.spill_candidate(mask, throughs) { + Some(cand) => self.spill_reg(cand), None => { panic!("Ran out of {} registers for {}", op.regclass, - dfg.display_inst(inst, self.isa)) + self.cur.display_inst(inst)) } } } @@ -290,13 +275,8 @@ impl<'a> Context<'a> { // We are assuming here that if a value is used both by a fixed register operand and a register // class operand, they two are compatible. We are also assuming that two register class // operands are always compatible. - fn collect_reg_uses(&mut self, - inst: Inst, - ebb: Ebb, - constraints: &RecipeConstraints, - dfg: &DataFlowGraph, - layout: &Layout) { - let args = dfg.inst_args(inst); + fn collect_reg_uses(&mut self, inst: Inst, ebb: Ebb, constraints: &RecipeConstraints) { + let args = self.cur.func.dfg.inst_args(inst); for (idx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() { let mut reguse = RegUse::new(arg, idx, op.regclass.into()); let lr = &self.liveness[arg]; @@ -305,7 +285,7 @@ impl<'a> Context<'a> { ConstraintKind::FixedReg(_) => reguse.fixed = true, ConstraintKind::Tied(_) => { // A tied operand must kill the used value. - reguse.tied = !lr.killed_at(inst, ebb, layout); + reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout); } ConstraintKind::Reg => {} } @@ -322,11 +302,14 @@ impl<'a> Context<'a> { } // Collect register uses from the ABI input constraints. - fn collect_abi_reg_uses(&mut self, inst: Inst, sig: SigRef, dfg: &DataFlowGraph) { - let fixed_args = dfg[inst].opcode().constraints().fixed_value_arguments(); - let args = dfg.inst_variable_args(inst); + fn collect_abi_reg_uses(&mut self, inst: Inst, sig: SigRef) { + let fixed_args = self.cur.func.dfg[inst] + .opcode() + .constraints() + .fixed_value_arguments(); + let args = self.cur.func.dfg.inst_variable_args(inst); for (idx, (abi, &arg)) in - dfg.signatures[sig] + self.cur.func.dfg.signatures[sig] .argument_types .iter() .zip(args) @@ -335,7 +318,7 @@ impl<'a> Context<'a> { let (rci, spilled) = match self.liveness[arg].affinity { Affinity::Reg(rci) => (rci, false), Affinity::Stack => { - (self.isa.regclass_for_abi_type(abi.value_type).into(), true) + (self.cur.isa.regclass_for_abi_type(abi.value_type).into(), true) } Affinity::None => panic!("Missing affinity for {}", arg), }; @@ -353,11 +336,7 @@ impl<'a> Context<'a> { // Trigger spilling if any of the temporaries cause the register pressure to become too high. // // Leave `self.reg_uses` empty. - fn process_reg_uses(&mut self, - inst: Inst, - pos: &mut Cursor, - dfg: &mut DataFlowGraph, - tracker: &LiveValueTracker) { + fn process_reg_uses(&mut self, inst: Inst, tracker: &LiveValueTracker) { // We're looking for multiple uses of the same value, so start by sorting by value. The // secondary `opidx` key makes it possible to use an unstable sort once that is available // outside nightly Rust. @@ -381,8 +360,8 @@ impl<'a> Context<'a> { }; if need_copy { - let copy = self.insert_copy(ru.value, ru.rci, pos, dfg); - dfg.inst_args_mut(inst)[ru.opidx as usize] = copy; + let copy = self.insert_copy(ru.value, ru.rci); + self.cur.func.dfg.inst_args_mut(inst)[ru.opidx as usize] = copy; } // Even if we don't insert a copy, we may need to account for register pressure for the @@ -393,18 +372,18 @@ impl<'a> Context<'a> { dbg!("Copy of {} reg causes spill", rc); // Spill a live register that is *not* used by the current instruction. // Spilling a use wouldn't help. - let args = dfg.inst_args(inst); - match self.spill_candidate(mask, - tracker.live().iter().filter(|lv| { - !args.contains(&lv.value) - }), - dfg, - &pos.layout) { - Some(cand) => self.spill_reg(cand, dfg), + match { + let args = self.cur.func.dfg.inst_args(inst); + self.spill_candidate(mask, + tracker.live().iter().filter(|lv| { + !args.contains(&lv.value) + })) + } { + Some(cand) => self.spill_reg(cand), None => { panic!("Ran out of {} registers when inserting copy before {}", rc, - dfg.display_inst(inst, self.isa)) + self.cur.display_inst(inst)) } } } @@ -415,12 +394,7 @@ impl<'a> Context<'a> { } // Find a spill candidate from `candidates` whose top-level register class is in `mask`. - fn spill_candidate<'ii, II>(&self, - mask: RegClassMask, - candidates: II, - dfg: &DataFlowGraph, - layout: &Layout) - -> Option + fn spill_candidate<'ii, II>(&self, mask: RegClassMask, candidates: II) -> Option where II: IntoIterator { // Find the best viable spill candidate. @@ -448,7 +422,9 @@ impl<'a> Context<'a> { .min_by(|&a, &b| { // Find the minimum candidate according to the RPO of their defs. self.domtree - .rpo_cmp(dfg.value_def(a), dfg.value_def(b), layout) + .rpo_cmp(self.cur.func.dfg.value_def(a), + self.cur.func.dfg.value_def(b), + &self.cur.func.layout) }) } @@ -460,7 +436,7 @@ impl<'a> Context<'a> { /// /// Note that this does not update the cached affinity in the live value tracker. Call /// `process_spills` to do that. - fn spill_reg(&mut self, value: Value, dfg: &DataFlowGraph) { + fn spill_reg(&mut self, value: Value) { if let Affinity::Reg(rci) = self.liveness.spill(value) { let rc = self.reginfo.rc(rci); self.pressure.free(rc); @@ -471,10 +447,13 @@ impl<'a> Context<'a> { } // Assign a spill slot for the whole virtual register. - let ss = self.stack_slots.make_spill_slot(dfg.value_type(value)); + let ss = self.cur + .func + .stack_slots + .make_spill_slot(self.cur.func.dfg.value_type(value)); for &v in self.virtregs.congruence_class(&value) { self.liveness.spill(v); - *self.locations.ensure(v) = ValueLoc::Stack(ss); + *self.cur.func.locations.ensure(v) = ValueLoc::Stack(ss); } } @@ -492,32 +471,21 @@ impl<'a> Context<'a> { } } - /// Insert a `copy value` before `pos` and give it a live range extending to `pos`. + /// Insert a `copy value` before the current instruction and give it a live range extending to + /// the current instruction. /// /// Returns the new local value created. - fn insert_copy(&mut self, - value: Value, - rci: RegClassIndex, - pos: &mut Cursor, - dfg: &mut DataFlowGraph) - -> Value { - let copy = dfg.ins(pos).copy(value); - let inst = dfg.value_def(copy).unwrap_inst(); - let ty = dfg.value_type(copy); - - // Give it an encoding. - match self.isa.encode(dfg, &dfg[inst], ty) { - Ok(e) => *self.encodings.ensure(inst) = e, - Err(_) => panic!("Can't encode {}", dfg.display_inst(inst, self.isa)), - } + fn insert_copy(&mut self, value: Value, rci: RegClassIndex) -> Value { + let copy = self.cur.ins().copy(value); + let inst = self.cur.built_inst(); // Update live ranges. self.liveness.create_dead(copy, inst, Affinity::Reg(rci)); self.liveness .extend_locally(copy, - pos.layout.pp_ebb(inst), - pos.current_inst().expect("must be at an instruction"), - pos.layout); + self.cur.func.layout.pp_ebb(inst), + self.cur.current_inst().expect("must be at an instruction"), + &self.cur.func.layout); copy } From 8b2f5c418b65d46c9ba42cf113252f09a4665589 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Aug 2017 15:02:46 -0700 Subject: [PATCH 961/968] Use EncCursor for reload.rs. Same deal as for spilling. Place an EncCursor in the context and use that to reference into the IR function when necessary. --- lib/cretonne/src/ir/layout.rs | 9 +- lib/cretonne/src/regalloc/reload.rs | 222 ++++++++++++---------------- 2 files changed, 98 insertions(+), 133 deletions(-) diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index b219a13bb2..3f31d34f23 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -709,8 +709,7 @@ pub trait CursorBase { fn at_first_inst(mut self, ebb: Ebb) -> Self where Self: Sized { - let inst = self.layout().ebbs[ebb].first_inst.expect("Empty EBB"); - self.goto_inst(inst); + self.goto_first_inst(ebb); self } @@ -740,6 +739,12 @@ pub trait CursorBase { self.set_position(CursorPosition::At(inst)); } + /// Go to the first instruction in `ebb`. + fn goto_first_inst(&mut self, ebb: Ebb) { + let inst = self.layout().ebbs[ebb].first_inst.expect("Empty EBB"); + self.set_position(CursorPosition::At(inst)); + } + /// Go to the top of `ebb` which must be inserted into the layout. /// At this position, instructions cannot be inserted, but `next_inst()` will move to the first /// instruction in `ebb`. diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 18e834179b..837dbd40d0 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -9,10 +9,10 @@ //! possible to minimize the number of `fill` instructions needed. This must not cause the register //! pressure limits to be exceeded. +use cursor::{Cursor, EncCursor}; use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Signature, DataFlowGraph, InstEncodings}; -use ir::layout::{Cursor, CursorBase, CursorPosition}; -use ir::{InstBuilder, Opcode, ArgumentType, ArgumentLoc}; +use ir::{Ebb, Inst, Value, Function}; +use ir::{InstBuilder, ArgumentType, ArgumentLoc}; use isa::RegClass; use isa::{TargetIsa, Encoding, EncInfo, RecipeConstraints, ConstraintKind}; use regalloc::affinity::Affinity; @@ -29,7 +29,8 @@ pub struct Reload { /// Context data structure that gets instantiated once per pass. struct Context<'a> { - isa: &'a TargetIsa, + cur: EncCursor<'a>, + // Cached ISA information. // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. encinfo: EncInfo, @@ -62,7 +63,7 @@ impl Reload { tracker: &mut LiveValueTracker) { dbg!("Reload for:\n{}", func.display(isa)); let mut ctx = Context { - isa, + cur: EncCursor::new(func, isa), encinfo: isa.encoding_info(), domtree, liveness, @@ -70,7 +71,7 @@ impl Reload { candidates: &mut self.candidates, reloads: &mut self.reloads, }; - ctx.run(func, tracker) + ctx.run(tracker) } } @@ -98,82 +99,63 @@ impl SparseMapValue for ReloadedValue { } impl<'a> Context<'a> { - fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { - self.topo.reset(func.layout.ebbs()); - while let Some(ebb) = self.topo.next(&func.layout, self.domtree) { - self.visit_ebb(ebb, func, tracker); + fn run(&mut self, tracker: &mut LiveValueTracker) { + self.topo.reset(self.cur.func.layout.ebbs()); + while let Some(ebb) = self.topo.next(&self.cur.func.layout, self.domtree) { + self.visit_ebb(ebb, tracker); } } - fn visit_ebb(&mut self, ebb: Ebb, func: &mut Function, tracker: &mut LiveValueTracker) { + fn visit_ebb(&mut self, ebb: Ebb, tracker: &mut LiveValueTracker) { dbg!("Reloading {}:", ebb); - let start_from = self.visit_ebb_header(ebb, func, tracker); + self.visit_ebb_header(ebb, tracker); tracker.drop_dead_args(); - let mut pos = Cursor::new(&mut func.layout); - pos.set_position(start_from); - while let Some(inst) = pos.current_inst() { - let encoding = func.encodings[inst]; + // visit_ebb_header() places us at the first interesting instruction in the EBB. + while let Some(inst) = self.cur.current_inst() { + let encoding = self.cur.func.encodings[inst]; if encoding.is_legal() { - self.visit_inst(ebb, - inst, - encoding, - &mut pos, - &mut func.dfg, - &mut func.encodings, - &func.signature, - tracker); + self.visit_inst(ebb, inst, encoding, tracker); tracker.drop_dead(inst); } else { - pos.next_inst(); + self.cur.next_inst(); } } } - /// Process the EBB parameters. Return the next instruction in the EBB to be processed - fn visit_ebb_header(&mut self, - ebb: Ebb, - func: &mut Function, - tracker: &mut LiveValueTracker) - -> CursorPosition { - let (liveins, args) = - tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + /// Process the EBB parameters. Move to the next instruction in the EBB to be processed + fn visit_ebb_header(&mut self, ebb: Ebb, tracker: &mut LiveValueTracker) { + let (liveins, args) = tracker.ebb_top(ebb, + &self.cur.func.dfg, + self.liveness, + &self.cur.func.layout, + self.domtree); - if func.layout.entry_block() == Some(ebb) { + if self.cur.func.layout.entry_block() == Some(ebb) { assert_eq!(liveins.len(), 0); - self.visit_entry_args(ebb, func, args) + self.visit_entry_args(ebb, args); } else { - self.visit_ebb_args(ebb, func, args) + self.visit_ebb_args(ebb, args); } } /// Visit the arguments to the entry block. /// These values have ABI constraints from the function signature. - fn visit_entry_args(&mut self, - ebb: Ebb, - func: &mut Function, - args: &[LiveValue]) - -> CursorPosition { - assert_eq!(func.signature.argument_types.len(), args.len()); - let mut pos = Cursor::new(&mut func.layout); - pos.goto_top(ebb); - pos.next_inst(); + fn visit_entry_args(&mut self, ebb: Ebb, args: &[LiveValue]) { + assert_eq!(self.cur.func.signature.argument_types.len(), args.len()); + self.cur.goto_first_inst(ebb); - for (abi, arg) in func.signature.argument_types.iter().zip(args) { + for (arg_idx, arg) in args.iter().enumerate() { + let abi = self.cur.func.signature.argument_types[arg_idx]; match abi.location { ArgumentLoc::Reg(_) => { if arg.affinity.is_stack() { // An incoming register parameter was spilled. Replace the parameter value // with a temporary register value that is immediately spilled. - let reg = func.dfg.replace_ebb_arg(arg.value, abi.value_type); - let affinity = Affinity::abi(abi, self.isa); + let reg = self.cur.func.dfg.replace_ebb_arg(arg.value, abi.value_type); + let affinity = Affinity::abi(&abi, self.cur.isa); self.liveness.create_dead(reg, ebb, affinity); - self.insert_spill(ebb, - arg.value, - reg, - &mut pos, - &mut func.encodings, - &mut func.dfg); + self.insert_spill(ebb, arg.value, reg); } } ArgumentLoc::Stack(_) => { @@ -182,14 +164,10 @@ impl<'a> Context<'a> { ArgumentLoc::Unassigned => panic!("Unexpected ABI location"), } } - pos.position() } - fn visit_ebb_args(&self, ebb: Ebb, func: &mut Function, _args: &[LiveValue]) -> CursorPosition { - let mut pos = Cursor::new(&mut func.layout); - pos.goto_top(ebb); - pos.next_inst(); - pos.position() + fn visit_ebb_args(&mut self, ebb: Ebb, _args: &[LiveValue]) { + self.cur.goto_first_inst(ebb); } /// Process the instruction pointed to by `pos`, and advance the cursor to the next instruction @@ -198,10 +176,6 @@ impl<'a> Context<'a> { ebb: Ebb, inst: Inst, encoding: Encoding, - pos: &mut Cursor, - dfg: &mut DataFlowGraph, - encodings: &mut InstEncodings, - func_signature: &Signature, tracker: &mut LiveValueTracker) { // Get the operand constraints for `inst` that we are trying to satisfy. let constraints = self.encinfo @@ -210,7 +184,7 @@ impl<'a> Context<'a> { // Identify reload candidates. assert!(self.candidates.is_empty()); - self.find_candidates(inst, constraints, func_signature, dfg); + self.find_candidates(inst, constraints); // Insert fill instructions before `inst`. while let Some(cand) = self.candidates.pop() { @@ -218,12 +192,8 @@ impl<'a> Context<'a> { continue; } - let reg = dfg.ins(pos).fill(cand.value); - let fill = dfg.value_def(reg).unwrap_inst(); - match self.isa.encode(dfg, &dfg[fill], dfg.value_type(reg)) { - Ok(e) => *encodings.ensure(fill) = e, - Err(_) => panic!("Can't encode fill {}", cand.value), - } + let reg = self.cur.ins().fill(cand.value); + let fill = self.cur.built_inst(); self.reloads .insert(ReloadedValue { @@ -233,12 +203,13 @@ impl<'a> Context<'a> { // Create a live range for the new reload. let affinity = Affinity::Reg(cand.regclass.into()); - self.liveness.create_dead(reg, dfg.value_def(reg), affinity); - self.liveness.extend_locally(reg, ebb, inst, pos.layout); + self.liveness.create_dead(reg, fill, affinity); + self.liveness + .extend_locally(reg, ebb, inst, &self.cur.func.layout); } // Rewrite arguments. - for arg in dfg.inst_args_mut(inst) { + for arg in self.cur.func.dfg.inst_args_mut(inst) { if let Some(reload) = self.reloads.get(*arg) { *arg = reload.reg; } @@ -247,10 +218,11 @@ impl<'a> Context<'a> { // TODO: Reuse reloads for future instructions. self.reloads.clear(); - let (_throughs, _kills, defs) = tracker.process_inst(inst, dfg, self.liveness); + let (_throughs, _kills, defs) = tracker + .process_inst(inst, &self.cur.func.dfg, self.liveness); // Advance to the next instruction so we can insert any spills after the instruction. - pos.next_inst(); + self.cur.next_inst(); // Rewrite register defs that need to be spilled. // @@ -266,10 +238,10 @@ impl<'a> Context<'a> { // That way, we don't need to rewrite all future uses of v2. for (lv, op) in defs.iter().zip(constraints.outs) { if lv.affinity.is_stack() && op.kind != ConstraintKind::Stack { - let value_type = dfg.value_type(lv.value); - let reg = dfg.replace_result(lv.value, value_type); + let value_type = self.cur.func.dfg.value_type(lv.value); + let reg = self.cur.func.dfg.replace_result(lv.value, value_type); self.liveness.create_dead(reg, inst, Affinity::new(op)); - self.insert_spill(ebb, lv.value, reg, pos, encodings, dfg); + self.insert_spill(ebb, lv.value, reg); } } } @@ -277,12 +249,8 @@ impl<'a> Context<'a> { // Find reload candidates for `inst` and add them to `self.condidates`. // // These are uses of spilled values where the operand constraint requires a register. - fn find_candidates(&mut self, - inst: Inst, - constraints: &RecipeConstraints, - func_signature: &Signature, - dfg: &DataFlowGraph) { - let args = dfg.inst_args(inst); + fn find_candidates(&mut self, inst: Inst, constraints: &RecipeConstraints) { + let args = self.cur.func.dfg.inst_args(inst); for (op, &arg) in constraints.ins.iter().zip(args) { if op.kind != ConstraintKind::Stack { @@ -303,30 +271,18 @@ impl<'a> Context<'a> { let var_args = &args[constraints.ins.len()..]; // Handle ABI arguments. - if let Some(sig) = dfg.call_signature(inst) { - self.handle_abi_args(&dfg.signatures[sig].argument_types, var_args); - } else if dfg[inst].opcode().is_return() { - self.handle_abi_args(&func_signature.return_types, var_args); - } - } - - /// Find reload candidates in the instruction's ABI variable arguments. This handles both - /// return values and call arguments. - fn handle_abi_args(&mut self, abi_types: &[ArgumentType], var_args: &[Value]) { - assert_eq!(abi_types.len(), var_args.len()); - for (abi, &arg) in abi_types.iter().zip(var_args) { - if abi.location.is_reg() { - let lv = self.liveness - .get(arg) - .expect("Missing live range for ABI arg"); - if lv.affinity.is_stack() { - self.candidates - .push(ReloadCandidate { - value: arg, - regclass: self.isa.regclass_for_abi_type(abi.value_type), - }); - } - } + if let Some(sig) = self.cur.func.dfg.call_signature(inst) { + handle_abi_args(self.candidates, + &self.cur.func.dfg.signatures[sig].argument_types, + var_args, + self.cur.isa, + self.liveness); + } else if self.cur.func.dfg[inst].opcode().is_return() { + handle_abi_args(self.candidates, + &self.cur.func.signature.return_types, + var_args, + self.cur.isa, + self.liveness); } } @@ -335,30 +291,34 @@ impl<'a> Context<'a> { /// - Insert `stack = spill reg` at `pos`, and assign an encoding. /// - Move the `stack` live range starting point to the new instruction. /// - Extend the `reg` live range to reach the new instruction. - fn insert_spill(&mut self, - ebb: Ebb, - stack: Value, - reg: Value, - pos: &mut Cursor, - encodings: &mut InstEncodings, - dfg: &mut DataFlowGraph) { - let ty = dfg.value_type(reg); - - // Insert spill instruction. Use the low-level `Unary` constructor because it returns an - // instruction reference directly rather than a result value (which we know is equal to - // `stack`). - let (inst, _) = dfg.ins(pos) - .with_result(stack) - .Unary(Opcode::Spill, ty, reg); - - // Give it an encoding. - match self.isa.encode(dfg, &dfg[inst], ty) { - Ok(e) => *encodings.ensure(inst) = e, - Err(_) => panic!("Can't encode spill.{}", ty), - } + fn insert_spill(&mut self, ebb: Ebb, stack: Value, reg: Value) { + self.cur.ins().with_result(stack).spill(reg); + let inst = self.cur.built_inst(); // Update live ranges. self.liveness.move_def_locally(stack, inst); - self.liveness.extend_locally(reg, ebb, inst, pos.layout); + self.liveness + .extend_locally(reg, ebb, inst, &self.cur.func.layout); + } +} + +/// Find reload candidates in the instruction's ABI variable arguments. This handles both +/// return values and call arguments. +fn handle_abi_args(candidates: &mut Vec, + abi_types: &[ArgumentType], + var_args: &[Value], + isa: &TargetIsa, + liveness: &Liveness) { + assert_eq!(abi_types.len(), var_args.len()); + for (abi, &arg) in abi_types.iter().zip(var_args) { + if abi.location.is_reg() { + let lv = liveness.get(arg).expect("Missing live range for ABI arg"); + if lv.affinity.is_stack() { + candidates.push(ReloadCandidate { + value: arg, + regclass: isa.regclass_for_abi_type(abi.value_type), + }); + } + } } } From 6f024267c6f528241581dbd68b28ae849223e621 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Aug 2017 15:31:08 -0700 Subject: [PATCH 962/968] Add a FuncCursor type to the cursor library. A FuncCursor works a like a layout cursor, but it holds a reference to the entire function and lets you re-borrow the function reference. Rewrite the dominator tree unit tests with a FuncCursor instead of a layout cursor to demonstrate the difference. It avoids the constrained lifetimes of the layout cursor in the tests. --- lib/cretonne/src/cursor.rs | 61 ++++++++ lib/cretonne/src/dominator_tree.rs | 228 +++++++++++++---------------- 2 files changed, 161 insertions(+), 128 deletions(-) diff --git a/lib/cretonne/src/cursor.rs b/lib/cretonne/src/cursor.rs index b706531eed..3719aa1d34 100644 --- a/lib/cretonne/src/cursor.rs +++ b/lib/cretonne/src/cursor.rs @@ -10,6 +10,67 @@ pub use ir::layout::CursorBase as Cursor; pub use ir::layout::CursorPosition; pub use ir::layout::Cursor as LayoutCursor; +/// Function cursor. +/// +/// A `FuncCursor` holds a mutable reference to a whole `ir::Function` while keeping a position +/// too. The function can be re-borrowed by accessing the public `cur.func` member. +/// +/// This cursor is for use before legalization. The inserted instructions are not given an +/// encoding. +pub struct FuncCursor<'f> { + pos: CursorPosition, + pub func: &'f mut ir::Function, +} + +impl<'f> FuncCursor<'f> { + /// Create a new `FuncCursor` pointing nowhere. + pub fn new(func: &'f mut ir::Function) -> FuncCursor<'f> { + FuncCursor { + pos: CursorPosition::Nowhere, + func, + } + } + + /// Create an instruction builder that inserts an instruction at the current position. + pub fn ins(&mut self) -> ir::InsertBuilder<&mut FuncCursor<'f>> { + ir::InsertBuilder::new(self) + } +} + +impl<'f> Cursor for FuncCursor<'f> { + fn position(&self) -> CursorPosition { + self.pos + } + + fn set_position(&mut self, pos: CursorPosition) { + self.pos = pos + } + + fn layout(&self) -> &ir::Layout { + &self.func.layout + } + + fn layout_mut(&mut self) -> &mut ir::Layout { + &mut self.func.layout + } +} + +impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> { + fn data_flow_graph(&self) -> &ir::DataFlowGraph { + &self.func.dfg + } + + fn data_flow_graph_mut(&mut self) -> &mut ir::DataFlowGraph { + &mut self.func.dfg + } + + fn insert_built_inst(self, inst: ir::Inst, _: ir::Type) -> &'c mut ir::DataFlowGraph { + self.insert_inst(inst); + &mut self.func.dfg + } +} + + /// Encoding cursor. /// /// An `EncCursor` can be used to insert instructions that are immediately assigned an encoding. diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 9d47cc4bd7..d88adbd4a0 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -387,8 +387,9 @@ impl DominatorTree { #[cfg(test)] mod test { + use cursor::{Cursor, FuncCursor}; use flowgraph::ControlFlowGraph; - use ir::{Function, InstBuilder, Cursor, CursorBase, types}; + use ir::{Function, InstBuilder, types}; use super::*; use ir::types::*; use verifier::verify_context; @@ -411,45 +412,38 @@ mod test { let ebb2 = func.dfg.make_ebb(); let ebb0 = func.dfg.make_ebb(); - let jmp_ebb3_ebb1; - let br_ebb1_ebb0; - let jmp_ebb1_ebb2; + let mut cur = FuncCursor::new(&mut func); - { - let dfg = &mut func.dfg; - let cur = &mut Cursor::new(&mut func.layout); + cur.insert_ebb(ebb3); + let jmp_ebb3_ebb1 = cur.ins().jump(ebb1, &[]); - cur.insert_ebb(ebb3); - jmp_ebb3_ebb1 = dfg.ins(cur).jump(ebb1, &[]); + cur.insert_ebb(ebb1); + let br_ebb1_ebb0 = cur.ins().brnz(cond, ebb0, &[]); + let jmp_ebb1_ebb2 = cur.ins().jump(ebb2, &[]); - cur.insert_ebb(ebb1); - br_ebb1_ebb0 = dfg.ins(cur).brnz(cond, ebb0, &[]); - jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, &[]); + cur.insert_ebb(ebb2); + cur.ins().jump(ebb0, &[]); - cur.insert_ebb(ebb2); - dfg.ins(cur).jump(ebb0, &[]); + cur.insert_ebb(ebb0); - cur.insert_ebb(ebb0); - } + let cfg = ControlFlowGraph::with_function(cur.func); + let dt = DominatorTree::with_function(cur.func, &cfg); - let cfg = ControlFlowGraph::with_function(&func); - let dt = DominatorTree::with_function(&func, &cfg); - - assert_eq!(func.layout.entry_block().unwrap(), ebb3); + assert_eq!(cur.func.layout.entry_block().unwrap(), ebb3); assert_eq!(dt.idom(ebb3), None); assert_eq!(dt.idom(ebb1).unwrap(), jmp_ebb3_ebb1); assert_eq!(dt.idom(ebb2).unwrap(), jmp_ebb1_ebb2); assert_eq!(dt.idom(ebb0).unwrap(), br_ebb1_ebb0); - assert!(dt.dominates(br_ebb1_ebb0, br_ebb1_ebb0, &func.layout)); - assert!(!dt.dominates(br_ebb1_ebb0, jmp_ebb3_ebb1, &func.layout)); - assert!(dt.dominates(jmp_ebb3_ebb1, br_ebb1_ebb0, &func.layout)); + assert!(dt.dominates(br_ebb1_ebb0, br_ebb1_ebb0, &cur.func.layout)); + assert!(!dt.dominates(br_ebb1_ebb0, jmp_ebb3_ebb1, &cur.func.layout)); + assert!(dt.dominates(jmp_ebb3_ebb1, br_ebb1_ebb0, &cur.func.layout)); - assert_eq!(dt.rpo_cmp(ebb3, ebb3, &func.layout), Ordering::Equal); - assert_eq!(dt.rpo_cmp(ebb3, ebb1, &func.layout), Ordering::Less); - assert_eq!(dt.rpo_cmp(ebb3, jmp_ebb3_ebb1, &func.layout), + assert_eq!(dt.rpo_cmp(ebb3, ebb3, &cur.func.layout), Ordering::Equal); + assert_eq!(dt.rpo_cmp(ebb3, ebb1, &cur.func.layout), Ordering::Less); + assert_eq!(dt.rpo_cmp(ebb3, jmp_ebb3_ebb1, &cur.func.layout), Ordering::Less); - assert_eq!(dt.rpo_cmp(jmp_ebb3_ebb1, jmp_ebb1_ebb2, &func.layout), + assert_eq!(dt.rpo_cmp(jmp_ebb3_ebb1, jmp_ebb1_ebb2, &cur.func.layout), Ordering::Less); assert_eq!(dt.cfg_postorder(), &[ebb2, ebb0, ebb1, ebb3]); @@ -462,72 +456,66 @@ mod test { let ebb1 = func.dfg.make_ebb(); let ebb2 = func.dfg.make_ebb(); - let jmp02; - let jmp21; - let trap; - { - let dfg = &mut func.dfg; - let cur = &mut Cursor::new(&mut func.layout); + let mut cur = FuncCursor::new(&mut func); - cur.insert_ebb(ebb0); - jmp02 = dfg.ins(cur).jump(ebb2, &[]); + cur.insert_ebb(ebb0); + let jmp02 = cur.ins().jump(ebb2, &[]); - cur.insert_ebb(ebb1); - trap = dfg.ins(cur).trap(); + cur.insert_ebb(ebb1); + let trap = cur.ins().trap(); - cur.insert_ebb(ebb2); - jmp21 = dfg.ins(cur).jump(ebb1, &[]); - } + cur.insert_ebb(ebb2); + let jmp21 = cur.ins().jump(ebb1, &[]); - let cfg = ControlFlowGraph::with_function(&func); - let dt = DominatorTree::with_function(&func, &cfg); + let cfg = ControlFlowGraph::with_function(cur.func); + let dt = DominatorTree::with_function(cur.func, &cfg); - assert_eq!(func.layout.entry_block(), Some(ebb0)); + assert_eq!(cur.func.layout.entry_block(), Some(ebb0)); assert_eq!(dt.idom(ebb0), None); assert_eq!(dt.idom(ebb1), Some(jmp21)); assert_eq!(dt.idom(ebb2), Some(jmp02)); - assert!(dt.dominates(ebb0, ebb0, &func.layout)); - assert!(dt.dominates(ebb0, jmp02, &func.layout)); - assert!(dt.dominates(ebb0, ebb1, &func.layout)); - assert!(dt.dominates(ebb0, trap, &func.layout)); - assert!(dt.dominates(ebb0, ebb2, &func.layout)); - assert!(dt.dominates(ebb0, jmp21, &func.layout)); + assert!(dt.dominates(ebb0, ebb0, &cur.func.layout)); + assert!(dt.dominates(ebb0, jmp02, &cur.func.layout)); + assert!(dt.dominates(ebb0, ebb1, &cur.func.layout)); + assert!(dt.dominates(ebb0, trap, &cur.func.layout)); + assert!(dt.dominates(ebb0, ebb2, &cur.func.layout)); + assert!(dt.dominates(ebb0, jmp21, &cur.func.layout)); - assert!(!dt.dominates(jmp02, ebb0, &func.layout)); - assert!(dt.dominates(jmp02, jmp02, &func.layout)); - assert!(dt.dominates(jmp02, ebb1, &func.layout)); - assert!(dt.dominates(jmp02, trap, &func.layout)); - assert!(dt.dominates(jmp02, ebb2, &func.layout)); - assert!(dt.dominates(jmp02, jmp21, &func.layout)); + assert!(!dt.dominates(jmp02, ebb0, &cur.func.layout)); + assert!(dt.dominates(jmp02, jmp02, &cur.func.layout)); + assert!(dt.dominates(jmp02, ebb1, &cur.func.layout)); + assert!(dt.dominates(jmp02, trap, &cur.func.layout)); + assert!(dt.dominates(jmp02, ebb2, &cur.func.layout)); + assert!(dt.dominates(jmp02, jmp21, &cur.func.layout)); - assert!(!dt.dominates(ebb1, ebb0, &func.layout)); - assert!(!dt.dominates(ebb1, jmp02, &func.layout)); - assert!(dt.dominates(ebb1, ebb1, &func.layout)); - assert!(dt.dominates(ebb1, trap, &func.layout)); - assert!(!dt.dominates(ebb1, ebb2, &func.layout)); - assert!(!dt.dominates(ebb1, jmp21, &func.layout)); + assert!(!dt.dominates(ebb1, ebb0, &cur.func.layout)); + assert!(!dt.dominates(ebb1, jmp02, &cur.func.layout)); + assert!(dt.dominates(ebb1, ebb1, &cur.func.layout)); + assert!(dt.dominates(ebb1, trap, &cur.func.layout)); + assert!(!dt.dominates(ebb1, ebb2, &cur.func.layout)); + assert!(!dt.dominates(ebb1, jmp21, &cur.func.layout)); - assert!(!dt.dominates(trap, ebb0, &func.layout)); - assert!(!dt.dominates(trap, jmp02, &func.layout)); - assert!(!dt.dominates(trap, ebb1, &func.layout)); - assert!(dt.dominates(trap, trap, &func.layout)); - assert!(!dt.dominates(trap, ebb2, &func.layout)); - assert!(!dt.dominates(trap, jmp21, &func.layout)); + assert!(!dt.dominates(trap, ebb0, &cur.func.layout)); + assert!(!dt.dominates(trap, jmp02, &cur.func.layout)); + assert!(!dt.dominates(trap, ebb1, &cur.func.layout)); + assert!(dt.dominates(trap, trap, &cur.func.layout)); + assert!(!dt.dominates(trap, ebb2, &cur.func.layout)); + assert!(!dt.dominates(trap, jmp21, &cur.func.layout)); - assert!(!dt.dominates(ebb2, ebb0, &func.layout)); - assert!(!dt.dominates(ebb2, jmp02, &func.layout)); - assert!(dt.dominates(ebb2, ebb1, &func.layout)); - assert!(dt.dominates(ebb2, trap, &func.layout)); - assert!(dt.dominates(ebb2, ebb2, &func.layout)); - assert!(dt.dominates(ebb2, jmp21, &func.layout)); + assert!(!dt.dominates(ebb2, ebb0, &cur.func.layout)); + assert!(!dt.dominates(ebb2, jmp02, &cur.func.layout)); + assert!(dt.dominates(ebb2, ebb1, &cur.func.layout)); + assert!(dt.dominates(ebb2, trap, &cur.func.layout)); + assert!(dt.dominates(ebb2, ebb2, &cur.func.layout)); + assert!(dt.dominates(ebb2, jmp21, &cur.func.layout)); - assert!(!dt.dominates(jmp21, ebb0, &func.layout)); - assert!(!dt.dominates(jmp21, jmp02, &func.layout)); - assert!(dt.dominates(jmp21, ebb1, &func.layout)); - assert!(dt.dominates(jmp21, trap, &func.layout)); - assert!(!dt.dominates(jmp21, ebb2, &func.layout)); - assert!(dt.dominates(jmp21, jmp21, &func.layout)); + assert!(!dt.dominates(jmp21, ebb0, &cur.func.layout)); + assert!(!dt.dominates(jmp21, jmp02, &cur.func.layout)); + assert!(dt.dominates(jmp21, ebb1, &cur.func.layout)); + assert!(dt.dominates(jmp21, trap, &cur.func.layout)); + assert!(!dt.dominates(jmp21, ebb2, &cur.func.layout)); + assert!(dt.dominates(jmp21, jmp21, &cur.func.layout)); } #[test] @@ -536,63 +524,47 @@ mod test { let ebb0 = func.dfg.make_ebb(); let ebb100 = func.dfg.make_ebb(); - let inst2; - let inst3; - let inst4; - let inst5; - { - let dfg = &mut func.dfg; - let cur = &mut Cursor::new(&mut func.layout); + let mut cur = FuncCursor::new(&mut func); - cur.insert_ebb(ebb0); - let cond = dfg.ins(cur).iconst(I32, 0); - inst2 = dfg.ins(cur).brz(cond, ebb0, &[]); - inst3 = dfg.ins(cur).brz(cond, ebb0, &[]); - inst4 = dfg.ins(cur).brz(cond, ebb0, &[]); - inst5 = dfg.ins(cur).brz(cond, ebb0, &[]); - dfg.ins(cur).jump(ebb100, &[]); - cur.insert_ebb(ebb100); - dfg.ins(cur).return_(&[]); - } - let mut cfg = ControlFlowGraph::with_function(&func); - let mut dt = DominatorTree::with_function(&func, &cfg); + cur.insert_ebb(ebb0); + let cond = cur.ins().iconst(I32, 0); + let inst2 = cur.ins().brz(cond, ebb0, &[]); + let inst3 = cur.ins().brz(cond, ebb0, &[]); + let inst4 = cur.ins().brz(cond, ebb0, &[]); + let inst5 = cur.ins().brz(cond, ebb0, &[]); + cur.ins().jump(ebb100, &[]); + cur.insert_ebb(ebb100); + cur.ins().return_(&[]); + + let mut cfg = ControlFlowGraph::with_function(cur.func); + let mut dt = DominatorTree::with_function(cur.func, &cfg); + + let ebb1 = cur.func.dfg.make_ebb(); + cur.func.layout.split_ebb(ebb1, inst2); + cur.goto_bottom(ebb0); + let middle_jump_inst = cur.ins().jump(ebb1, &[]); - let ebb1 = func.dfg.make_ebb(); - func.layout.split_ebb(ebb1, inst2); - let middle_jump_inst = { - let cur = &mut Cursor::new(&mut func.layout); - cur.goto_bottom(ebb0); - func.dfg.ins(cur).jump(ebb1, &[]) - }; dt.recompute_split_ebb(ebb0, ebb1, middle_jump_inst); - let ebb2 = func.dfg.make_ebb(); - func.layout.split_ebb(ebb2, inst3); - let middle_jump_inst = { - let cur = &mut Cursor::new(&mut func.layout); - cur.goto_bottom(ebb1); - func.dfg.ins(cur).jump(ebb2, &[]) - }; + let ebb2 = cur.func.dfg.make_ebb(); + cur.func.layout.split_ebb(ebb2, inst3); + cur.goto_bottom(ebb1); + let middle_jump_inst = cur.ins().jump(ebb2, &[]); dt.recompute_split_ebb(ebb1, ebb2, middle_jump_inst); - let ebb3 = func.dfg.make_ebb(); - func.layout.split_ebb(ebb3, inst4); - let middle_jump_inst = { - let cur = &mut Cursor::new(&mut func.layout); - cur.goto_bottom(ebb2); - func.dfg.ins(cur).jump(ebb3, &[]) - }; + let ebb3 = cur.func.dfg.make_ebb(); + cur.func.layout.split_ebb(ebb3, inst4); + cur.goto_bottom(ebb2); + let middle_jump_inst = cur.ins().jump(ebb3, &[]); dt.recompute_split_ebb(ebb2, ebb3, middle_jump_inst); - let ebb4 = func.dfg.make_ebb(); - func.layout.split_ebb(ebb4, inst5); - let middle_jump_inst = { - let cur = &mut Cursor::new(&mut func.layout); - cur.goto_bottom(ebb3); - func.dfg.ins(cur).jump(ebb4, &[]) - }; + let ebb4 = cur.func.dfg.make_ebb(); + cur.func.layout.split_ebb(ebb4, inst5); + cur.goto_bottom(ebb3); + let middle_jump_inst = cur.ins().jump(ebb4, &[]); dt.recompute_split_ebb(ebb3, ebb4, middle_jump_inst); - cfg.compute(&func); - verify_context(&func, &cfg, &dt, None).unwrap(); + + cfg.compute(cur.func); + verify_context(cur.func, &cfg, &dt, None).unwrap(); } } From 378e7cfe6b6ab4d14c68de8ee7477700d003f7da Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 4 Aug 2017 16:00:48 -0700 Subject: [PATCH 963/968] Switch branch relaxation to a FuncCursor. --- lib/cretonne/src/binemit/relaxation.rs | 44 +++++++++++--------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index 0d0c07df35..e49b534624 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -28,7 +28,8 @@ //! ``` use binemit::CodeOffset; -use ir::{Function, DataFlowGraph, Cursor, CursorBase, InstructionData, Opcode, InstEncodings}; +use cursor::{Cursor, FuncCursor}; +use ir::{Function, InstructionData, Opcode}; use isa::{TargetIsa, EncInfo}; use iterators::IteratorExtras; use result::CtonError; @@ -55,34 +56,29 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> Result CodeOffset { - let inst = pos.current_inst().unwrap(); + let inst = cur.current_inst().unwrap(); dbg!("Relaxing [{}] {} for {:#x}-{:#x} range", - encinfo.display(encodings[inst]), - dfg.display_inst(inst, None), + encinfo.display(cur.func.encodings[inst]), + cur.func.dfg.display_inst(inst, None), offset, dest_offset); unimplemented!(); From 234e72a5b317a8c77ce21fea28b238644ff20868 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Thu, 10 Aug 2017 16:05:04 -0700 Subject: [PATCH 964/968] Dumped code from the wasm2cretonne repo --- Cargo.toml | 3 +- lib/wasm2cretonne-util/Cargo.toml | 22 + lib/wasm2cretonne-util/README.md | 62 + lib/wasm2cretonne-util/filetests/arith.wasm | Bin 0 -> 57 bytes lib/wasm2cretonne-util/filetests/arith.wast | 13 + lib/wasm2cretonne-util/filetests/call.wasm | Bin 0 -> 46 bytes lib/wasm2cretonne-util/filetests/call.wast | 10 + lib/wasm2cretonne-util/filetests/globals.wasm | Bin 0 -> 49 bytes lib/wasm2cretonne-util/filetests/globals.wast | 8 + lib/wasm2cretonne-util/filetests/memory.wasm | Bin 0 -> 76 bytes lib/wasm2cretonne-util/filetests/memory.wast | 11 + lib/wasm2cretonne-util/filetests/sample.wasm | Bin 0 -> 45 bytes lib/wasm2cretonne-util/src/main.rs | 440 ++++++ .../testsuite/address.wast.0.wasm | Bin 0 -> 205 bytes .../testsuite/binary.wast.0.wasm | Bin 0 -> 8 bytes .../testsuite/binary.wast.1.wasm | Bin 0 -> 8 bytes .../testsuite/binary.wast.2.wasm | Bin 0 -> 8 bytes .../testsuite/binary.wast.3.wasm | Bin 0 -> 8 bytes .../testsuite/block.wast.0.wasm | Bin 0 -> 709 bytes .../testsuite/br.wast.0.wasm | Bin 0 -> 2093 bytes .../testsuite/br_if.wast.0.wasm | Bin 0 -> 746 bytes .../testsuite/br_table.wast.0.wasm | Bin 0 -> 27334 bytes .../testsuite/break-drop.wast.0.wasm | Bin 0 -> 79 bytes .../testsuite/call.wast.0.wasm | Bin 0 -> 692 bytes .../testsuite/call_indirect.wast.0.wasm | Bin 0 -> 873 bytes .../testsuite/comments.wast.0.wasm | Bin 0 -> 8 bytes .../testsuite/comments.wast.1.wasm | Bin 0 -> 8 bytes .../testsuite/comments.wast.2.wasm | Bin 0 -> 8 bytes .../testsuite/comments.wast.3.wasm | Bin 0 -> 8 bytes .../testsuite/conversions.wast.0.wasm | Bin 0 -> 737 bytes .../testsuite/custom_section.wast.0.wasm | Bin 0 -> 8 bytes .../testsuite/custom_section.wast.1.wasm | Bin 0 -> 8 bytes .../testsuite/custom_section.wast.2.wasm | Bin 0 -> 44 bytes .../testsuite/endianness.wast.0.wasm | Bin 0 -> 682 bytes .../testsuite/exports.wast.0.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.1.wasm | Bin 0 -> 35 bytes .../testsuite/exports.wast.10.wasm | Bin 0 -> 8 bytes .../testsuite/exports.wast.11.wasm | Bin 0 -> 8 bytes .../testsuite/exports.wast.18.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.19.wasm | Bin 0 -> 27 bytes .../testsuite/exports.wast.2.wasm | Bin 0 -> 39 bytes .../testsuite/exports.wast.20.wasm | Bin 0 -> 32 bytes .../testsuite/exports.wast.21.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.22.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.23.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.24.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.25.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.26.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.27.wasm | Bin 0 -> 23 bytes .../testsuite/exports.wast.28.wasm | Bin 0 -> 8 bytes .../testsuite/exports.wast.29.wasm | Bin 0 -> 8 bytes .../testsuite/exports.wast.3.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.36.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.37.wasm | Bin 0 -> 25 bytes .../testsuite/exports.wast.38.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.39.wasm | Bin 0 -> 22 bytes .../testsuite/exports.wast.4.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.40.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.41.wasm | Bin 0 -> 22 bytes .../testsuite/exports.wast.42.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.43.wasm | Bin 0 -> 22 bytes .../testsuite/exports.wast.44.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.45.wasm | Bin 0 -> 22 bytes .../testsuite/exports.wast.46.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.47.wasm | Bin 0 -> 22 bytes .../testsuite/exports.wast.48.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.49.wasm | Bin 0 -> 22 bytes .../testsuite/exports.wast.5.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.55.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.56.wasm | Bin 0 -> 24 bytes .../testsuite/exports.wast.57.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.58.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.59.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.6.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.60.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.61.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.62.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.63.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.64.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.65.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.66.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.67.wasm | Bin 0 -> 20 bytes .../testsuite/exports.wast.68.wasm | Bin 0 -> 21 bytes .../testsuite/exports.wast.7.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.8.wasm | Bin 0 -> 31 bytes .../testsuite/exports.wast.9.wasm | Bin 0 -> 39 bytes .../testsuite/f32.wast.0.wasm | Bin 0 -> 196 bytes .../testsuite/f32_bitwise.wast.0.wasm | Bin 0 -> 77 bytes .../testsuite/f32_cmp.wast.0.wasm | Bin 0 -> 110 bytes .../testsuite/f64.wast.0.wasm | Bin 0 -> 196 bytes .../testsuite/f64_bitwise.wast.0.wasm | Bin 0 -> 77 bytes .../testsuite/f64_cmp.wast.0.wasm | Bin 0 -> 110 bytes .../testsuite/fac.wast.0.wasm | Bin 0 -> 290 bytes .../testsuite/float_exprs.wast.0.wasm | Bin 0 -> 60 bytes .../testsuite/float_exprs.wast.1.wasm | Bin 0 -> 84 bytes .../testsuite/float_exprs.wast.10.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.11.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.12.wasm | Bin 0 -> 92 bytes .../testsuite/float_exprs.wast.13.wasm | Bin 0 -> 92 bytes .../testsuite/float_exprs.wast.14.wasm | Bin 0 -> 94 bytes .../testsuite/float_exprs.wast.15.wasm | Bin 0 -> 94 bytes .../testsuite/float_exprs.wast.16.wasm | Bin 0 -> 98 bytes .../testsuite/float_exprs.wast.17.wasm | Bin 0 -> 96 bytes .../testsuite/float_exprs.wast.18.wasm | Bin 0 -> 98 bytes .../testsuite/float_exprs.wast.19.wasm | Bin 0 -> 108 bytes .../testsuite/float_exprs.wast.2.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.20.wasm | Bin 0 -> 108 bytes .../testsuite/float_exprs.wast.21.wasm | Bin 0 -> 116 bytes .../testsuite/float_exprs.wast.22.wasm | Bin 0 -> 116 bytes .../testsuite/float_exprs.wast.23.wasm | Bin 0 -> 98 bytes .../testsuite/float_exprs.wast.24.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.25.wasm | Bin 0 -> 110 bytes .../testsuite/float_exprs.wast.26.wasm | Bin 0 -> 69 bytes .../testsuite/float_exprs.wast.27.wasm | Bin 0 -> 127 bytes .../testsuite/float_exprs.wast.28.wasm | Bin 0 -> 75 bytes .../testsuite/float_exprs.wast.29.wasm | Bin 0 -> 142 bytes .../testsuite/float_exprs.wast.3.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.30.wasm | Bin 0 -> 100 bytes .../testsuite/float_exprs.wast.31.wasm | Bin 0 -> 100 bytes .../testsuite/float_exprs.wast.32.wasm | Bin 0 -> 100 bytes .../testsuite/float_exprs.wast.33.wasm | Bin 0 -> 100 bytes .../testsuite/float_exprs.wast.34.wasm | Bin 0 -> 122 bytes .../testsuite/float_exprs.wast.35.wasm | Bin 0 -> 58 bytes .../testsuite/float_exprs.wast.36.wasm | Bin 0 -> 58 bytes .../testsuite/float_exprs.wast.37.wasm | Bin 0 -> 107 bytes .../testsuite/float_exprs.wast.38.wasm | Bin 0 -> 59 bytes .../testsuite/float_exprs.wast.39.wasm | Bin 0 -> 735 bytes .../testsuite/float_exprs.wast.4.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.40.wasm | Bin 0 -> 116 bytes .../testsuite/float_exprs.wast.41.wasm | Bin 0 -> 116 bytes .../testsuite/float_exprs.wast.42.wasm | Bin 0 -> 192 bytes .../testsuite/float_exprs.wast.43.wasm | Bin 0 -> 337 bytes .../testsuite/float_exprs.wast.44.wasm | Bin 0 -> 330 bytes .../testsuite/float_exprs.wast.45.wasm | Bin 0 -> 440 bytes .../testsuite/float_exprs.wast.46.wasm | Bin 0 -> 432 bytes .../testsuite/float_exprs.wast.47.wasm | Bin 0 -> 152 bytes .../testsuite/float_exprs.wast.48.wasm | Bin 0 -> 110 bytes .../testsuite/float_exprs.wast.49.wasm | Bin 0 -> 122 bytes .../testsuite/float_exprs.wast.5.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.50.wasm | Bin 0 -> 58 bytes .../testsuite/float_exprs.wast.51.wasm | Bin 0 -> 60 bytes .../testsuite/float_exprs.wast.52.wasm | Bin 0 -> 64 bytes .../testsuite/float_exprs.wast.53.wasm | Bin 0 -> 114 bytes .../testsuite/float_exprs.wast.54.wasm | Bin 0 -> 47 bytes .../testsuite/float_exprs.wast.55.wasm | Bin 0 -> 98 bytes .../testsuite/float_exprs.wast.56.wasm | Bin 0 -> 126 bytes .../testsuite/float_exprs.wast.57.wasm | Bin 0 -> 120 bytes .../testsuite/float_exprs.wast.58.wasm | Bin 0 -> 120 bytes .../testsuite/float_exprs.wast.59.wasm | Bin 0 -> 235 bytes .../testsuite/float_exprs.wast.6.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.60.wasm | Bin 0 -> 283 bytes .../testsuite/float_exprs.wast.61.wasm | Bin 0 -> 1197 bytes .../testsuite/float_exprs.wast.62.wasm | Bin 0 -> 2221 bytes .../testsuite/float_exprs.wast.63.wasm | Bin 0 -> 96 bytes .../testsuite/float_exprs.wast.64.wasm | Bin 0 -> 96 bytes .../testsuite/float_exprs.wast.65.wasm | Bin 0 -> 106 bytes .../testsuite/float_exprs.wast.66.wasm | Bin 0 -> 94 bytes .../testsuite/float_exprs.wast.67.wasm | Bin 0 -> 122 bytes .../testsuite/float_exprs.wast.68.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.69.wasm | Bin 0 -> 112 bytes .../testsuite/float_exprs.wast.7.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.70.wasm | Bin 0 -> 110 bytes .../testsuite/float_exprs.wast.71.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.72.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.73.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.74.wasm | Bin 0 -> 112 bytes .../testsuite/float_exprs.wast.75.wasm | Bin 0 -> 134 bytes .../testsuite/float_exprs.wast.76.wasm | Bin 0 -> 246 bytes .../testsuite/float_exprs.wast.77.wasm | Bin 0 -> 122 bytes .../testsuite/float_exprs.wast.78.wasm | Bin 0 -> 132 bytes .../testsuite/float_exprs.wast.79.wasm | Bin 0 -> 388 bytes .../testsuite/float_exprs.wast.8.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.80.wasm | Bin 0 -> 231 bytes .../testsuite/float_exprs.wast.81.wasm | Bin 0 -> 126 bytes .../testsuite/float_exprs.wast.82.wasm | Bin 0 -> 231 bytes .../testsuite/float_exprs.wast.83.wasm | Bin 0 -> 216 bytes .../testsuite/float_exprs.wast.84.wasm | Bin 0 -> 142 bytes .../testsuite/float_exprs.wast.85.wasm | Bin 0 -> 160 bytes .../testsuite/float_exprs.wast.86.wasm | Bin 0 -> 337 bytes .../testsuite/float_exprs.wast.87.wasm | Bin 0 -> 964 bytes .../testsuite/float_exprs.wast.88.wasm | Bin 0 -> 135 bytes .../testsuite/float_exprs.wast.89.wasm | Bin 0 -> 102 bytes .../testsuite/float_exprs.wast.9.wasm | Bin 0 -> 104 bytes .../testsuite/float_exprs.wast.90.wasm | Bin 0 -> 120 bytes .../testsuite/float_exprs.wast.91.wasm | Bin 0 -> 98 bytes .../testsuite/float_exprs.wast.92.wasm | Bin 0 -> 126 bytes .../testsuite/float_exprs.wast.93.wasm | Bin 0 -> 58 bytes .../testsuite/float_exprs.wast.94.wasm | Bin 0 -> 235 bytes .../testsuite/float_exprs.wast.95.wasm | Bin 0 -> 146 bytes .../testsuite/float_literals.wast.0.wasm | Bin 0 -> 1930 bytes .../testsuite/float_memory.wast.0.wasm | Bin 0 -> 161 bytes .../testsuite/float_memory.wast.1.wasm | Bin 0 -> 174 bytes .../testsuite/float_memory.wast.2.wasm | Bin 0 -> 162 bytes .../testsuite/float_memory.wast.3.wasm | Bin 0 -> 175 bytes .../testsuite/float_memory.wast.4.wasm | Bin 0 -> 161 bytes .../testsuite/float_memory.wast.5.wasm | Bin 0 -> 174 bytes .../testsuite/float_misc.wast.0.wasm | Bin 0 -> 578 bytes .../testsuite/forward.wast.0.wasm | Bin 0 -> 82 bytes .../testsuite/func.wast.0.wasm | Bin 0 -> 1951 bytes .../testsuite/func_ptrs.wast.0.wasm | Bin 0 -> 133 bytes .../testsuite/func_ptrs.wast.8.wasm | Bin 0 -> 119 bytes .../testsuite/func_ptrs.wast.9.wasm | Bin 0 -> 75 bytes .../testsuite/get_local.wast.0.wasm | Bin 0 -> 398 bytes .../testsuite/globals.wast.0.wasm | Bin 0 -> 291 bytes .../testsuite/globals.wast.16.wasm | Bin 0 -> 30 bytes .../testsuite/globals.wast.19.wasm | Bin 0 -> 16 bytes .../testsuite/i32.wast.0.wasm | Bin 0 -> 482 bytes .../testsuite/i64.wast.0.wasm | Bin 0 -> 493 bytes .../testsuite/if.wast.0.wasm | Bin 0 -> 706 bytes .../testsuite/imports.wast.0.wasm | Bin 0 -> 257 bytes .../testsuite/imports.wast.1.wasm | Bin 0 -> 346 bytes .../testsuite/imports.wast.10.wasm | Bin 0 -> 36 bytes .../testsuite/imports.wast.11.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.12.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.13.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.14.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.15.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.16.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.17.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.18.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.19.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.2.wasm | Bin 0 -> 29 bytes .../testsuite/imports.wast.20.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.21.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.22.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.23.wasm | Bin 0 -> 36 bytes .../testsuite/imports.wast.24.wasm | Bin 0 -> 38 bytes .../testsuite/imports.wast.25.wasm | Bin 0 -> 39 bytes .../testsuite/imports.wast.26.wasm | Bin 0 -> 39 bytes .../testsuite/imports.wast.27.wasm | Bin 0 -> 36 bytes .../testsuite/imports.wast.28.wasm | Bin 0 -> 37 bytes .../testsuite/imports.wast.29.wasm | Bin 0 -> 37 bytes .../testsuite/imports.wast.3.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.30.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.31.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.32.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.33.wasm | Bin 0 -> 197 bytes .../testsuite/imports.wast.34.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.35.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.36.wasm | Bin 0 -> 27 bytes .../testsuite/imports.wast.37.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.38.wasm | Bin 0 -> 24 bytes .../testsuite/imports.wast.39.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.4.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.40.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.41.wasm | Bin 0 -> 29 bytes .../testsuite/imports.wast.42.wasm | Bin 0 -> 29 bytes .../testsuite/imports.wast.43.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.44.wasm | Bin 0 -> 90 bytes .../testsuite/imports.wast.45.wasm | Bin 0 -> 90 bytes .../testsuite/imports.wast.49.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.5.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.50.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.51.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.52.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.53.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.54.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.55.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.56.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.57.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.58.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.59.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.6.wasm | Bin 0 -> 35 bytes .../testsuite/imports.wast.60.wasm | Bin 0 -> 28 bytes .../testsuite/imports.wast.61.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.62.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.63.wasm | Bin 0 -> 34 bytes .../testsuite/imports.wast.64.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.65.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.66.wasm | Bin 0 -> 25 bytes .../testsuite/imports.wast.67.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.68.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.69.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.7.wasm | Bin 0 -> 40 bytes .../testsuite/imports.wast.70.wasm | Bin 0 -> 73 bytes .../testsuite/imports.wast.71.wasm | Bin 0 -> 73 bytes .../testsuite/imports.wast.75.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.76.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.77.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.78.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.79.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.8.wasm | Bin 0 -> 40 bytes .../testsuite/imports.wast.80.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.81.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.82.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.83.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.84.wasm | Bin 0 -> 27 bytes .../testsuite/imports.wast.85.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.86.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.87.wasm | Bin 0 -> 33 bytes .../testsuite/imports.wast.88.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.89.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.9.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.90.wasm | Bin 0 -> 28 bytes .../testsuite/imports.wast.91.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.92.wasm | Bin 0 -> 32 bytes .../testsuite/imports.wast.93.wasm | Bin 0 -> 29 bytes .../testsuite/imports.wast.94.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.95.wasm | Bin 0 -> 29 bytes .../testsuite/imports.wast.96.wasm | Bin 0 -> 30 bytes .../testsuite/imports.wast.97.wasm | Bin 0 -> 31 bytes .../testsuite/imports.wast.98.wasm | Bin 0 -> 63 bytes .../testsuite/int_exprs.wast.0.wasm | Bin 0 -> 200 bytes .../testsuite/int_exprs.wast.1.wasm | Bin 0 -> 61 bytes .../testsuite/int_exprs.wast.10.wasm | Bin 0 -> 92 bytes .../testsuite/int_exprs.wast.11.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.12.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.13.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.14.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.15.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.16.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.17.wasm | Bin 0 -> 122 bytes .../testsuite/int_exprs.wast.18.wasm | Bin 0 -> 94 bytes .../testsuite/int_exprs.wast.2.wasm | Bin 0 -> 61 bytes .../testsuite/int_exprs.wast.3.wasm | Bin 0 -> 174 bytes .../testsuite/int_exprs.wast.4.wasm | Bin 0 -> 174 bytes .../testsuite/int_exprs.wast.5.wasm | Bin 0 -> 174 bytes .../testsuite/int_exprs.wast.6.wasm | Bin 0 -> 166 bytes .../testsuite/int_exprs.wast.7.wasm | Bin 0 -> 166 bytes .../testsuite/int_exprs.wast.8.wasm | Bin 0 -> 174 bytes .../testsuite/int_exprs.wast.9.wasm | Bin 0 -> 92 bytes .../testsuite/int_literals.wast.0.wasm | Bin 0 -> 585 bytes .../testsuite/labels.wast.0.wasm | Bin 0 -> 948 bytes .../testsuite/left-to-right.wast.0.wasm | Bin 0 -> 3015 bytes .../testsuite/linking.wast.0.wasm | Bin 0 -> 43 bytes .../testsuite/linking.wast.1.wasm | Bin 0 -> 87 bytes .../testsuite/linking.wast.15.wasm | Bin 0 -> 71 bytes .../testsuite/linking.wast.16.wasm | Bin 0 -> 83 bytes .../testsuite/linking.wast.17.wasm | Bin 0 -> 70 bytes .../testsuite/linking.wast.2.wasm | Bin 0 -> 46 bytes .../testsuite/linking.wast.3.wasm | Bin 0 -> 37 bytes .../testsuite/linking.wast.4.wasm | Bin 0 -> 38 bytes .../testsuite/linking.wast.5.wasm | Bin 0 -> 51 bytes .../testsuite/linking.wast.6.wasm | Bin 0 -> 93 bytes .../testsuite/linking.wast.7.wasm | Bin 0 -> 88 bytes .../testsuite/linking.wast.8.wasm | Bin 0 -> 127 bytes .../testsuite/linking.wast.9.wasm | Bin 0 -> 82 bytes .../testsuite/loop.wast.0.wasm | Bin 0 -> 1060 bytes .../testsuite/memory.wast.0.wasm | Bin 0 -> 14 bytes .../testsuite/memory.wast.1.wasm | Bin 0 -> 14 bytes .../testsuite/memory.wast.15.wasm | Bin 0 -> 55 bytes .../testsuite/memory.wast.2.wasm | Bin 0 -> 15 bytes .../testsuite/memory.wast.3.wasm | Bin 0 -> 16 bytes .../testsuite/memory.wast.39.wasm | Bin 0 -> 33 bytes .../testsuite/memory.wast.40.wasm | Bin 0 -> 31 bytes .../testsuite/memory.wast.41.wasm | Bin 0 -> 35 bytes .../testsuite/memory.wast.49.wasm | Bin 0 -> 35 bytes .../testsuite/memory.wast.50.wasm | Bin 0 -> 35 bytes .../testsuite/memory.wast.51.wasm | Bin 0 -> 35 bytes .../testsuite/memory.wast.52.wasm | Bin 0 -> 35 bytes .../testsuite/memory.wast.6.wasm | Bin 0 -> 23 bytes .../testsuite/memory.wast.62.wasm | Bin 0 -> 685 bytes .../testsuite/memory.wast.8.wasm | Bin 0 -> 35 bytes .../testsuite/memory_redundancy.wast.0.wasm | Bin 0 -> 319 bytes .../testsuite/memory_trap.wast.0.wasm | Bin 0 -> 113 bytes .../testsuite/memory_trap.wast.1.wasm | Bin 0 -> 629 bytes .../testsuite/names.wast.0.wasm | Bin 0 -> 36 bytes .../testsuite/names.wast.1.wasm | Bin 0 -> 36 bytes .../testsuite/names.wast.2.wasm | Bin 0 -> 7488 bytes .../testsuite/names.wast.3.wasm | Bin 0 -> 88 bytes .../testsuite/nop.wast.0.wasm | Bin 0 -> 1496 bytes lib/wasm2cretonne-util/testsuite/reloc.wasm | Bin 0 -> 563 bytes .../testsuite/resizing.wast.0.wasm | Bin 0 -> 181 bytes .../testsuite/resizing.wast.1.wasm | Bin 0 -> 45 bytes .../testsuite/resizing.wast.2.wasm | Bin 0 -> 46 bytes .../testsuite/return.wast.0.wasm | Bin 0 -> 1653 bytes .../testsuite/select.wast.0.wasm | Bin 0 -> 249 bytes .../testsuite/set_local.wast.0.wasm | Bin 0 -> 454 bytes lib/wasm2cretonne-util/testsuite/simple.wasm | Bin 0 -> 41 bytes .../skip-stack-guard-page.wast.0.wasm | Bin 0 -> 18586 bytes .../testsuite/stack.wast.0.wasm | Bin 0 -> 341 bytes .../testsuite/start.wast.3.wasm | Bin 0 -> 94 bytes .../testsuite/start.wast.4.wasm | Bin 0 -> 94 bytes .../testsuite/start.wast.5.wasm | Bin 0 -> 55 bytes .../testsuite/start.wast.6.wasm | Bin 0 -> 55 bytes .../testsuite/start.wast.7.wasm | Bin 0 -> 37 bytes .../testsuite/start.wast.8.wasm | Bin 0 -> 28 bytes .../testsuite/switch.wast.0.wasm | Bin 0 -> 289 bytes .../testsuite/tee_local.wast.0.wasm | Bin 0 -> 594 bytes .../testsuite/traps.wast.0.wasm | Bin 0 -> 146 bytes .../testsuite/traps.wast.1.wasm | Bin 0 -> 146 bytes .../testsuite/traps.wast.2.wasm | Bin 0 -> 293 bytes .../testsuite/traps.wast.3.wasm | Bin 0 -> 458 bytes .../testsuite/unreachable.wast.0.wasm | Bin 0 -> 1647 bytes .../testsuite/unwind.wast.0.wasm | Bin 0 -> 1713 bytes lib/wasm2cretonne/.gitignore | 3 + lib/wasm2cretonne/Cargo.toml | 10 + lib/wasm2cretonne/src/code_translator.rs | 1375 +++++++++++++++++ lib/wasm2cretonne/src/lib.rs | 27 + lib/wasm2cretonne/src/module_translator.rs | 288 ++++ lib/wasm2cretonne/src/runtime/dummy.rs | 93 ++ lib/wasm2cretonne/src/runtime/mod.rs | 5 + lib/wasm2cretonne/src/runtime/spec.rs | 61 + lib/wasm2cretonne/src/sections_translator.rs | 367 +++++ lib/wasm2cretonne/src/translation_utils.rs | 138 ++ lib/wasmstandalone/.gitignore | 3 + lib/wasmstandalone/Cargo.toml | 11 + lib/wasmstandalone/src/execution.rs | 256 +++ lib/wasmstandalone/src/lib.rs | 15 + lib/wasmstandalone/src/standalone.rs | 332 ++++ 400 files changed, 3552 insertions(+), 1 deletion(-) create mode 100644 lib/wasm2cretonne-util/Cargo.toml create mode 100644 lib/wasm2cretonne-util/README.md create mode 100644 lib/wasm2cretonne-util/filetests/arith.wasm create mode 100644 lib/wasm2cretonne-util/filetests/arith.wast create mode 100644 lib/wasm2cretonne-util/filetests/call.wasm create mode 100644 lib/wasm2cretonne-util/filetests/call.wast create mode 100644 lib/wasm2cretonne-util/filetests/globals.wasm create mode 100644 lib/wasm2cretonne-util/filetests/globals.wast create mode 100644 lib/wasm2cretonne-util/filetests/memory.wasm create mode 100644 lib/wasm2cretonne-util/filetests/memory.wast create mode 100644 lib/wasm2cretonne-util/filetests/sample.wasm create mode 100644 lib/wasm2cretonne-util/src/main.rs create mode 100644 lib/wasm2cretonne-util/testsuite/address.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/binary.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/binary.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/binary.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/binary.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/block.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/br.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/br_if.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/br_table.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/break-drop.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/call.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/call_indirect.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/comments.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/comments.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/comments.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/comments.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/conversions.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/custom_section.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/custom_section.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/custom_section.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/endianness.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.10.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.11.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.18.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.19.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.20.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.21.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.22.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.23.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.24.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.25.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.26.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.27.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.28.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.29.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.36.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.37.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.38.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.39.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.40.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.41.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.42.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.43.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.44.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.45.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.46.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.47.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.48.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.49.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.55.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.56.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.57.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.58.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.59.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.60.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.61.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.62.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.63.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.64.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.65.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.66.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.67.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.68.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.7.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/exports.wast.9.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/f32.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/f32_bitwise.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/f32_cmp.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/f64.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/f64_bitwise.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/f64_cmp.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/fac.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.10.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.11.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.12.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.13.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.14.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.15.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.16.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.17.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.18.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.19.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.20.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.21.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.22.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.23.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.24.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.25.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.26.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.27.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.28.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.29.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.30.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.31.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.32.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.33.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.34.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.35.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.36.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.37.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.38.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.39.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.40.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.41.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.42.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.43.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.44.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.45.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.46.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.47.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.48.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.49.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.50.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.51.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.52.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.53.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.54.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.55.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.56.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.57.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.58.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.59.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.60.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.61.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.62.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.63.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.64.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.65.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.66.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.67.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.68.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.69.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.7.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.70.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.71.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.72.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.73.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.74.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.75.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.76.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.77.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.78.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.79.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.80.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.81.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.82.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.83.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.84.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.85.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.86.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.87.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.88.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.89.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.9.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.90.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.91.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.92.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.93.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.94.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_exprs.wast.95.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_literals.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_memory.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_memory.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_memory.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_memory.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_memory.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_memory.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/float_misc.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/forward.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/func.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/func_ptrs.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/func_ptrs.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/func_ptrs.wast.9.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/get_local.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/globals.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/globals.wast.16.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/globals.wast.19.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/i32.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/i64.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/if.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.10.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.11.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.12.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.13.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.14.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.15.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.16.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.17.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.18.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.19.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.20.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.21.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.22.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.23.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.24.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.25.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.26.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.27.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.28.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.29.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.30.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.31.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.32.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.33.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.34.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.35.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.36.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.37.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.38.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.39.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.40.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.41.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.42.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.43.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.44.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.45.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.49.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.50.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.51.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.52.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.53.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.54.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.55.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.56.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.57.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.58.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.59.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.60.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.61.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.62.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.63.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.64.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.65.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.66.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.67.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.68.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.69.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.7.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.70.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.71.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.75.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.76.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.77.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.78.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.79.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.80.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.81.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.82.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.83.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.84.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.85.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.86.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.87.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.88.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.89.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.9.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.90.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.91.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.92.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.93.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.94.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.95.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.96.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.97.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/imports.wast.98.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.10.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.11.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.12.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.13.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.14.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.15.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.16.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.17.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.18.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.7.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_exprs.wast.9.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/int_literals.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/labels.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/left-to-right.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.15.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.16.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.17.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.7.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/linking.wast.9.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/loop.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.15.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.39.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.40.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.41.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.49.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.50.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.51.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.52.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.62.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory_redundancy.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory_trap.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/memory_trap.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/names.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/names.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/names.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/names.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/nop.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/reloc.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/resizing.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/resizing.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/resizing.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/return.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/select.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/set_local.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/simple.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/skip-stack-guard-page.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/stack.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/start.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/start.wast.4.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/start.wast.5.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/start.wast.6.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/start.wast.7.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/start.wast.8.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/switch.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/tee_local.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/traps.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/traps.wast.1.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/traps.wast.2.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/traps.wast.3.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/unreachable.wast.0.wasm create mode 100644 lib/wasm2cretonne-util/testsuite/unwind.wast.0.wasm create mode 100644 lib/wasm2cretonne/.gitignore create mode 100644 lib/wasm2cretonne/Cargo.toml create mode 100644 lib/wasm2cretonne/src/code_translator.rs create mode 100644 lib/wasm2cretonne/src/lib.rs create mode 100644 lib/wasm2cretonne/src/module_translator.rs create mode 100644 lib/wasm2cretonne/src/runtime/dummy.rs create mode 100644 lib/wasm2cretonne/src/runtime/mod.rs create mode 100644 lib/wasm2cretonne/src/runtime/spec.rs create mode 100644 lib/wasm2cretonne/src/sections_translator.rs create mode 100644 lib/wasm2cretonne/src/translation_utils.rs create mode 100644 lib/wasmstandalone/.gitignore create mode 100644 lib/wasmstandalone/Cargo.toml create mode 100644 lib/wasmstandalone/src/execution.rs create mode 100644 lib/wasmstandalone/src/lib.rs create mode 100644 lib/wasmstandalone/src/standalone.rs diff --git a/Cargo.toml b/Cargo.toml index 6df15bd86c..7fdbee1e07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ path = "src/cton-util.rs" [dependencies] cretonne = { path = "lib/cretonne" } cretonne-reader = { path = "lib/reader" } -cretonne-frontend = { path ="lib/frontend" } +cretonne-frontend = { path = "lib/frontend" } +wasm2cretonne-util = { path = "lib/wasm2cretonne-util" } filecheck = { path = "lib/filecheck" } docopt = "0.8.0" serde = "1.0.8" diff --git a/lib/wasm2cretonne-util/Cargo.toml b/lib/wasm2cretonne-util/Cargo.toml new file mode 100644 index 0000000000..a4f3eb94a6 --- /dev/null +++ b/lib/wasm2cretonne-util/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "wasm2cretonne-util" +version = "0.0.0" +authors = ["The Cretonne Project Developers"] +publish = false + +[[bin]] +name = "wasm2cretonne-util" +path = "src/main.rs" + +[dependencies] +wasm2cretonne = { path = "../wasm2cretonne" } +wasmstandalone = { path = "../wasmstandalone" } +wasmparser = "0.6.1" +cretonne = { path = "../cretonne" } +cretonne-frontend = { path = "../frontend" } +wasmtext = { git = "https://github.com/yurydelendik/wasmtext" } +docopt = "0.8.0" +serde = "1.0.8" +serde_derive = "1.0.8" +term = "*" +tempdir="*" diff --git a/lib/wasm2cretonne-util/README.md b/lib/wasm2cretonne-util/README.md new file mode 100644 index 0000000000..a3f63c05c6 --- /dev/null +++ b/lib/wasm2cretonne-util/README.md @@ -0,0 +1,62 @@ +# wasm2cretonne + +[Cretonne](https://github.com/stoklund/cretonne) frontend for WebAssembly. Reads wasm binary modules and translate the functions it contains into Cretonne IL functions. + +The translation needs some info about the runtime in order to handle the wasm instructions `get_global`, `set_global`, and `call_indirect`. These informations are included in structs implementing the `WasmRuntime` trait like `DummyRuntime` or `StandaloneRuntime`. + + +The `StandaloneRuntime` is a setup for in-memory execution of the module just after translation to Cretonne IL. It allocates memory for the wasm linear memories, the globals and the tables and embeds the addresses of these memories inside the generated Cretonne IL functions. Then it runs Cretonne's compilation, emits the code to memory and executes the `start` function of the module. + +## API + +Use the functions defined in the crates `wasm2cretonne` and `wasmruntime`. + +### Example + +```rust +use wasm2cretonne::translate_module; +use wasmruntime::{StandaloneRuntime, compile_module, execute}; +use std::path::{Path, PathBuf}; + +fn read_wasm_file(path: PathBuf) -> Result, io::Error> { + let mut buf: Vec = Vec::new(); + let file = File::open(path)?; + let mut buf_reader = BufReader::new(file); + buf_reader.read_to_end(&mut buf)?; + Ok(buf) +} + +let path = Path::new("filetests/arith.wasm"); +let data = match read_wasm_file(path.to_path_buf()) { + Ok(data) => data, + Err(err) => { + panic!("Error: {}", err); + } +}; +let mut runtime = StandaloneRuntime::new(); +let translation = match translate_module(&data, &mut runtime) { + Ok(x) => x, + Err(string) => { + panic!(string); + } +}; +let exec = compile_module(&translation, "intel"); +execute(exec); +println!("Memory after execution: {:?}", runtime.inspect_memory(0,0,4)); +``` + +## CLI tool + +The binary created by the root crate of this repo is an utility to parse, translate, compile and execute wasm binaries using Cretonne. Usage: + +``` +wasm2cretonne-util + -v, --verbose displays info on the different steps + -p, --print displays the module and translated functions + -c, --check checks the corectness of the translated functions + -o, --optimize runs optimization passes on the translated functions + -e, --execute enable the standalone runtime and executes the start function of the module + -m, --memory interactive memory inspector after execution +``` + +The tool reads `.wasm` files but also `.wast` as long as the [WebAssembly binary toolkit](https://github.com/WebAssembly/wabt)'s `wast2wasm` executable is accessible in your `PATH`. For now, only the 64 bits Intel architecture is supported for execution. diff --git a/lib/wasm2cretonne-util/filetests/arith.wasm b/lib/wasm2cretonne-util/filetests/arith.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a393264a2aac2aad3e262823b3d9549905c3c226 GIT binary patch literal 57 zcmV~$+YNvq5Jkar1rp*$d!UrK{GtF`cZND%0gBonOdHxUPF CcmwJH literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/filetests/globals.wast b/lib/wasm2cretonne-util/filetests/globals.wast new file mode 100644 index 0000000000..646e5f0f45 --- /dev/null +++ b/lib/wasm2cretonne-util/filetests/globals.wast @@ -0,0 +1,8 @@ +(module + (global $x (mut i32) (i32.const 4)) + (memory 1) + (func $main (local i32) + (i32.store (i32.const 0) (get_global $x)) + ) + (start $main) +) diff --git a/lib/wasm2cretonne-util/filetests/memory.wasm b/lib/wasm2cretonne-util/filetests/memory.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0d3074bb7e10f30983be06f9012938252ac944db GIT binary patch literal 76 zcmWNGu?>JQ5X5}RM?(265CtU_HQ6K#K*a#8&Rt>4I_~!opwNU`M5{uJ=$lsQ8w}23 S?1i{o-PP>0GqcNI#^L<|0tUwb literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/filetests/memory.wast b/lib/wasm2cretonne-util/filetests/memory.wast new file mode 100644 index 0000000000..0c81bad174 --- /dev/null +++ b/lib/wasm2cretonne-util/filetests/memory.wast @@ -0,0 +1,11 @@ +(module + (memory 1) + (func $main (local i32) + (i32.store (i32.const 0) (i32.const 0x0)) + (if (i32.load (i32.const 0)) + (then (i32.store (i32.const 0) (i32.const 0xa))) + (else (i32.store (i32.const 0) (i32.const 0xb)))) + ) + (start $main) + (data (i32.const 0) "0000") +) diff --git a/lib/wasm2cretonne-util/filetests/sample.wasm b/lib/wasm2cretonne-util/filetests/sample.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0126305329e95ec56ce7952d525b25ea7acb1045 GIT binary patch literal 45 ycmZQbEY4+QU|?WmV@zPIXRK#tVr1YFXB1^nU~pv2W~o;IGO`?5a#$T1xVZs}Z3XWD literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/src/main.rs b/lib/wasm2cretonne-util/src/main.rs new file mode 100644 index 0000000000..8a1c7c95d3 --- /dev/null +++ b/lib/wasm2cretonne-util/src/main.rs @@ -0,0 +1,440 @@ +//! CLI tool to use the functions provided by crates [wasm2cretonne](../wasm2cretonne/index.html) +//! and [wasmstandalone](../wasmstandalone/index.html). +//! +//! Reads Wasm binary files (one Wasm module per file), translates the functions' code to Cretonne +//! IL. Can also executes the `start` function of the module by laying out the memories, globals +//! and tables, then emitting the translated code with hardcoded addresses to memory. + +extern crate wasm2cretonne; +extern crate wasmstandalone; +extern crate wasmparser; +extern crate cretonne; +extern crate wasmtext; +extern crate docopt; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate term; +extern crate tempdir; + +use wasm2cretonne::{translate_module, TranslationResult, FunctionTranslation, DummyRuntime, + WasmRuntime}; +use wasmstandalone::{StandaloneRuntime, compile_module, execute}; +use std::path::PathBuf; +use wasmparser::{Parser, ParserState, WasmDecoder, SectionCode}; +use wasmtext::Writer; +use cretonne::loop_analysis::LoopAnalysis; +use cretonne::flowgraph::ControlFlowGraph; +use cretonne::dominator_tree::DominatorTree; +use cretonne::Context; +use cretonne::result::CtonError; +use cretonne::ir; +use cretonne::ir::entities::AnyEntity; +use cretonne::isa::TargetIsa; +use cretonne::verifier; +use std::fs::File; +use std::error::Error; +use std::io; +use std::io::{BufReader, stdout}; +use std::io::prelude::*; +use docopt::Docopt; +use std::path::Path; +use std::process::Command; +use tempdir::TempDir; + +macro_rules! vprintln { + ($x: expr, $($tts:tt)*) => { + if $x { + println!($($tts)*); + } + } +} + +macro_rules! vprint { + ($x: expr, $($tts:tt)*) => { + if $x { + print!($($tts)*); + } + } +} + +const USAGE: &str = " +Wasm to Cretonne IL translation utility. +Takes a binary WebAssembly module and returns its functions in Cretonne IL format. +The translation is dependent on the runtime chosen. +The default is a dummy runtime that produces placeholder values. + +Usage: + wasm2cretonne-util [-vcop] ... + wasm2cretonne-util -e [-mvcop] ... + wasm2cretonne-util --help | --version + +Options: + -v, --verbose displays info on the different steps + -p, --print displays the module and translated functions + -c, --check checks the corectness of the translated functions + -o, --optimize runs optimization passes on the translated functions + -e, --execute enable the standalone runtime and executes the start function of the module + -m, --memory interactive memory inspector after execution + -h, --help print this help message + --version print the Cretonne version +"; + +#[derive(Deserialize, Debug, Clone)] +struct Args { + arg_file: Vec, + flag_verbose: bool, + flag_execute: bool, + flag_memory: bool, + flag_check: bool, + flag_optimize: bool, + flag_print: bool, +} + +fn read_wasm_file(path: PathBuf) -> Result, io::Error> { + let mut buf: Vec = Vec::new(); + let file = File::open(path)?; + let mut buf_reader = BufReader::new(file); + buf_reader.read_to_end(&mut buf)?; + Ok(buf) +} + + +fn main() { + let args: Args = Docopt::new(USAGE) + .and_then(|d| d.help(true).version(Some(format!("0.0.0"))).deserialize()) + .unwrap_or_else(|e| e.exit()); + let mut terminal = term::stdout().unwrap(); + for filename in args.arg_file.iter() { + let path = Path::new(&filename); + let name = String::from(path.as_os_str().to_string_lossy()); + match handle_module(&args, path.to_path_buf(), name) { + Ok(()) => {} + Err(message) => { + terminal.fg(term::color::RED).unwrap(); + vprintln!(args.flag_verbose, "error"); + terminal.reset().unwrap(); + vprintln!(args.flag_verbose, "{}", message) + } + } + } +} + +fn handle_module(args: &Args, path: PathBuf, name: String) -> Result<(), String> { + let mut terminal = term::stdout().unwrap(); + terminal.fg(term::color::YELLOW).unwrap(); + vprint!(args.flag_verbose, "Handling: "); + terminal.reset().unwrap(); + vprintln!(args.flag_verbose, "\"{}\"", name); + terminal.fg(term::color::MAGENTA).unwrap(); + vprint!(args.flag_verbose, "Translating..."); + terminal.reset().unwrap(); + let data = match path.extension() { + None => { + return Err(String::from("the file extension is not wasm or wast")); + } + Some(ext) => { + match ext.to_str() { + Some("wasm") => { + match read_wasm_file(path.clone()) { + Ok(data) => data, + Err(err) => { + return Err(String::from(err.description())); + } + } + } + Some("wast") => { + let tmp_dir = TempDir::new("wasm2cretonne").unwrap(); + let file_path = tmp_dir.path().join("module.wasm"); + File::create(file_path.clone()).unwrap(); + Command::new("wast2wasm") + .arg(path.clone()) + .arg("-o") + .arg(file_path.to_str().unwrap()) + .output() + .or_else(|e| if let io::ErrorKind::NotFound = e.kind() { + return Err(String::from("wast2wasm not found")); + } else { + return Err(String::from(e.description())); + }) + .unwrap(); + match read_wasm_file(file_path) { + Ok(data) => data, + Err(err) => { + return Err(String::from(err.description())); + } + } + } + None | Some(&_) => { + return Err(String::from("the file extension is not wasm or wast")); + } + } + } + }; + let mut dummy_runtime = DummyRuntime::new(); + let mut standalone_runtime = StandaloneRuntime::new(); + let translation = { + let mut runtime: &mut WasmRuntime = if args.flag_execute { + &mut standalone_runtime + } else { + &mut dummy_runtime + }; + match translate_module(&data, runtime) { + Ok(x) => x, + Err(string) => { + return Err(string); + } + } + }; + terminal.fg(term::color::GREEN).unwrap(); + vprintln!(args.flag_verbose, " ok"); + terminal.reset().unwrap(); + if args.flag_check { + terminal.fg(term::color::MAGENTA).unwrap(); + vprint!(args.flag_verbose, "Checking... "); + terminal.reset().unwrap(); + for func in translation.functions.iter() { + let il = match func { + &FunctionTranslation::Import() => continue, + &FunctionTranslation::Code { ref il, .. } => il.clone(), + }; + match verifier::verify_function(&il, None) { + Ok(()) => (), + Err(err) => return Err(pretty_verifier_error(&il, None, err)), + } + } + terminal.fg(term::color::GREEN).unwrap(); + vprintln!(args.flag_verbose, " ok"); + terminal.reset().unwrap(); + } + if args.flag_print { + let mut writer1 = stdout(); + let mut writer2 = stdout(); + match pretty_print_translation(&name, &data, &translation, &mut writer1, &mut writer2) { + Err(error) => return Err(String::from(error.description())), + Ok(()) => (), + } + } + if args.flag_optimize { + terminal.fg(term::color::MAGENTA).unwrap(); + vprint!(args.flag_verbose, "Optimizing... "); + terminal.reset().unwrap(); + for func in translation.functions.iter() { + let mut il = match func { + &FunctionTranslation::Import() => continue, + &FunctionTranslation::Code { ref il, .. } => il.clone(), + }; + let mut loop_analysis = LoopAnalysis::new(); + let mut cfg = ControlFlowGraph::new(); + cfg.compute(&il); + let mut domtree = DominatorTree::new(); + domtree.compute(&mut il, &cfg); + loop_analysis.compute(&mut il, &mut cfg, &mut domtree); + let mut context = Context::new(); + context.func = il; + context.cfg = cfg; + context.domtree = domtree; + context.loop_analysis = loop_analysis; + match verifier::verify_context(&context.func, &context.cfg, &context.domtree, None) { + Ok(()) => (), + Err(err) => { + return Err(pretty_verifier_error(&context.func, None, err)); + } + }; + match context.licm() { + Ok(())=> (), + Err(error) => { + match error { + CtonError::Verifier(err) => { + return Err(pretty_verifier_error(&context.func, None, err)); + } + CtonError::ImplLimitExceeded | + CtonError::CodeTooLarge => return Err(String::from(error.description())), + } + } + }; + match verifier::verify_context(&context.func, &context.cfg, &context.domtree, None) { + Ok(()) => (), + Err(err) => return Err(pretty_verifier_error(&context.func, None, err)), + } + } + terminal.fg(term::color::GREEN).unwrap(); + vprintln!(args.flag_verbose, " ok"); + terminal.reset().unwrap(); + } + if args.flag_execute { + terminal.fg(term::color::MAGENTA).unwrap(); + vprint!(args.flag_verbose, "Compiling... "); + terminal.reset().unwrap(); + match compile_module(&translation) { + Ok(exec) => { + terminal.fg(term::color::GREEN).unwrap(); + vprintln!(args.flag_verbose, "ok"); + terminal.reset().unwrap(); + terminal.fg(term::color::MAGENTA).unwrap(); + vprint!(args.flag_verbose, "Executing... "); + terminal.reset().unwrap(); + match execute(exec) { + Ok(()) => { + terminal.fg(term::color::GREEN).unwrap(); + vprintln!(args.flag_verbose, "ok"); + terminal.reset().unwrap(); + } + Err(s) => { + return Err(s); + } + } + } + Err(s) => { + return Err(s); + } + }; + if args.flag_memory { + let mut input = String::new(); + terminal.fg(term::color::YELLOW).unwrap(); + println!("Inspecting memory"); + terminal.fg(term::color::MAGENTA).unwrap(); + println!("Type 'quit' to exit."); + terminal.reset().unwrap(); + loop { + input.clear(); + terminal.fg(term::color::YELLOW).unwrap(); + print!("Memory index, offset, length (e.g. 0,0,4): "); + terminal.reset().unwrap(); + let _ = stdout().flush(); + match io::stdin().read_line(&mut input) { + Ok(_) => { + input.pop(); + if input == "quit" { + break; + } + let split: Vec<&str> = input.split(",").collect(); + if split.len() != 3 { + break; + } + let memory = standalone_runtime + .inspect_memory(str::parse(split[0]).unwrap(), + str::parse(split[1]).unwrap(), + str::parse(split[2]).unwrap()); + let mut s = memory + .iter() + .fold(String::from("#"), |mut acc, byte| { + acc.push_str(format!("{:02x}_", byte).as_str()); + acc + }); + s.pop(); + println!("{}", s); + } + Err(error) => return Err(String::from(error.description())), + } + } + } + } + Ok(()) +} + +// Prints out a Wasm module, and for each function the corresponding translation in Cretonne IL. +fn pretty_print_translation(filename: &String, + data: &Vec, + translation: &TranslationResult, + writer_wast: &mut Write, + writer_cretonne: &mut Write) + -> Result<(), io::Error> { + let mut terminal = term::stdout().unwrap(); + let mut parser = Parser::new(data.as_slice()); + let mut parser_writer = Writer::new(writer_wast); + let imports_count = translation + .functions + .iter() + .fold(0, |acc, &ref f| match f { + &FunctionTranslation::Import() => acc + 1, + &FunctionTranslation::Code { .. } => acc, + }); + match parser.read() { + s @ &ParserState::BeginWasm { .. } => parser_writer.write(&s)?, + _ => panic!("modules should begin properly"), + } + loop { + match parser.read() { + s @ &ParserState::BeginSection { code: SectionCode::Code, .. } => { + // The code section begins + parser_writer.write(&s)?; + break; + } + &ParserState::EndWasm => return Ok(()), + s @ _ => parser_writer.write(&s)?, + } + } + let mut function_index = 0; + loop { + match parser.read() { + s @ &ParserState::BeginFunctionBody { .. } => { + terminal.fg(term::color::BLUE).unwrap(); + write!(writer_cretonne, + "====== Function No. {} of module \"{}\" ======\n", + function_index, + filename)?; + terminal.fg(term::color::CYAN).unwrap(); + write!(writer_cretonne, "Wast ---------->\n")?; + terminal.reset().unwrap(); + parser_writer.write(&s)?; + } + s @ &ParserState::EndSection => { + parser_writer.write(&s)?; + break; + } + _ => panic!("wrong content in code section"), + } + { + loop { + match parser.read() { + s @ &ParserState::EndFunctionBody => { + parser_writer.write(&s)?; + break; + } + s @ _ => { + parser_writer.write(&s)?; + } + }; + } + } + let mut function_string = + format!(" {}", + match translation.functions[function_index + imports_count] { + FunctionTranslation::Code { ref il, .. } => il, + FunctionTranslation::Import() => panic!("should not happen"), + } + .display(None)); + function_string.pop(); + let function_str = str::replace(function_string.as_str(), "\n", "\n "); + terminal.fg(term::color::CYAN).unwrap(); + write!(writer_cretonne, "Cretonne IL --->\n")?; + terminal.reset().unwrap(); + write!(writer_cretonne, "{}\n", function_str)?; + function_index += 1; + } + loop { + match parser.read() { + &ParserState::EndWasm => return Ok(()), + s @ _ => parser_writer.write(&s)?, + } + } +} + +/// Pretty-print a verifier error. +pub fn pretty_verifier_error(func: &ir::Function, + isa: Option<&TargetIsa>, + err: verifier::Error) + -> String { + let msg = err.to_string(); + let str1 = match err.location { + AnyEntity::Inst(inst) => { + format!("{}\n{}: {}\n\n", + msg, + inst, + func.dfg.display_inst(inst, isa)) + } + _ => String::from(format!("{}\n", msg)), + }; + format!("{}{}", str1, func.display(isa)) +} diff --git a/lib/wasm2cretonne-util/testsuite/address.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/address.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bf81db7cc8f13a06d581d82873b6f57bc6e97c6b GIT binary patch literal 205 zcmXAhIS#@w6b0Y^CIVpuh>lb#-5{TVgCLQO5XeSs)`p`)V!vjJ89l_NCIA%Bk-p#! z%}kf4Ha9lt`m(Wr5DWx@K+`2pinh%_B8fBbyy62Q?0~fcd9cTJgeK>-Pc&Sj$t4;t vQS1`so$^kNPA-NA#lK;zP|Ap~hYaH+&GJ)mE-%-rZrbkF8+(5|U+>Q!DTW~i literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/binary.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/binary.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/binary.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/binary.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/block.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/block.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a5f96555a0c18309543fb6c1dcaaa1719031347d GIT binary patch literal 709 zcmb_YOHSiJ6s&GL*yK5m`2-NFnGL%LaRXW-<`{?$X&~Sj%XW~mK)waX;U=@2gG_ZC zEM~`o?v`Fvy;rYaH*CFB0934RfE%bb$i}3fSM;vZfm@c%nq~E(xL@VAf*vead9y&U zQ@FZubA;jCxe|lS*3(sCtM&A`bd@dU*x92rTl}X*V`>eZeO0_q<@R}5+RF9f?r>H) z`!t8u|h*`^mo;VO(GcX#J*n;PlxPcYVy82VCpLkwGHZaFV zZR}bLv@q1b*S literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/br.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/br.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f4131e83b634efecf57d1868f781e9c45f8dcf96 GIT binary patch literal 2093 zcmZux+j1L45bc@O&6?58H-VhkYY5;t5ro7zIDtargje3FqRL)N<5*^;b?vSLaYf#D zDjz`n1AG&IL-)+=N{QL>O5LYVpXu&t3xZ_A0D$e(jscE=W%yp>JHz+wG0!qYylmmi z`{6nA0T}m6^hFPT;vZ*ad|1Um_~CJ~JjCZusmw0WD|?tw{i}9ROW$1R{`kpbV9wm% z18&VzL_KFBT59Ip{_7yT)KGT7{+0JGU^-9?@iukPM-pw?xI=9?k^$~gpTi&wUCtIw!T2;HpGIUnGotM^=N@6L zZIUT}h>$h=pVV|$w3Xtr*+d4-7i)ExIIz7TW4?@@em(*7n1*3j28pS$E;4A8j zkY5UD$F!P8u`Zzdrd0#kneWm)Bm~U!9+;t7%(5Pm(&aRW-}Ivk9S3;#eRKC?gN6Du zg|8{=!~*g7)maMP5R&R7Es`D)GK?k{0oG))z9r=xv1yN~VZ4XeI-X}fq0M>~M}Hhm z^d!PMlJS*>z2zy&>yi71u*H7Y%E5eZ_Y2y>u0HJ+w2$`n`?Yz*?yMC)*!W-EZS=FH zDgyA2;hS@NFW=v28*@I$owc$rQ(owsA37azM)fL@!H* z1k}vbk~BiLj|QqxnyN`qtN}e$bwGuP4!}dPye7vwg;*wfo#+Oj1neVI6@|q$@i&_g ztoZt{Dxu|qSZ*m(P*zk1(S7o<@Up*%_^eFDBZpeUs=)`JZCKX_RY4$k6;`0|2#l6M zWrCVj66@m1Yi|ncGu1$3(dVj(L&do1W0m@#j7~Lg4b++yS%jEcC!G%=uDTKo)M9Lg zr*|Z5Qz>-z?8VLde}9s&i#tQNc73=h;Smk6ONFB2ZvM{Tz%xna9=O_)@N@z2@!aY& zoGQvX#vZ$}SG*luC)y31DX99nJF%MfwrT< zt@M@JmGGOR-=I2S#GDBeTOQV&sFc+pdB3~k*C+5Pfe cd_9j_HQF>vyPLy>BJLSZ%|<^;!hlKo54g<9Gynhq literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/br_if.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/br_if.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3c91d8350b51941f3a7fc209e1ec763b9c80a695 GIT binary patch literal 746 zcmZvXVQzvz5QS$JtdG89Dj-VEUlA$Q5{~BV}7n1z^MhNJOkm| zu}DdFmUiy;laI7`*WgALlgq%-!b0{MddG=HRlS+Qu^eXZn{a)nc6Ik2jrlQYo^=VH z8FW!=kWs1YMrY>o{WYe+0MDk)w`=Do%IVi;4|dJMu>{d16M34(dkPJbESlV^o|X<+YS0S3MqnHD8VJqh#`>}6w*yZtXw)K93QJtzRJFnDRk=NElnZ+4V$feor?0Ge4VUt27khTy9fCPQ%IH) literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/br_table.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/br_table.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..09b895d2b020efc7fb0771c73b608f00d8577845 GIT binary patch literal 27334 zcmeI5+j1L45QckprAXFT2RlB165@3Zhy9UrLr26 z5PsdTGf8#nRH@tNdJUiS$)ptWwOO3*}^CXP-BPcuJ)=eiIvjWCBq2h#z zN3|Rc#@SPt)BNk)YIA9SxEn{=Xb8GF8N1o}>R><4`r|l)dZQS+xkfn-lidiKjWF$O z#iQ+SIy?O&&7d_u5eEGpEYv4v9OCri)ZebB6S)Wy;309KAx5TZyY- zm2)SX{hiMCXxM{yI5*oi_q)g0og7%OZtVrDX5m(hJ231;-@%C^nvFiWI_qZ>Snv0m zIFm%#elm2WoT{Nx)UoD_>~S=Nz!_1TMzBtSX%t7>nOpAZ)>KI&Ud9<(hI3-!EUj}p zjAIw`K2czs5S=GPY%!t>E6&`+TKmcLP8XNnn4s`W%Wue>=klBS&i-&4t}LQ|8fBZf z=V9!Y-=THIqp;Trd%Yw|Q}}?^Maq{G=|f&>HcFz2bd{INl4X94_8}!<8h4$cSy8an#3@ zxlnA_9a=E%!;>hPM&6~9wVRB--5f-N5!R9RpGEM=;t#NyoEo4C!K z)z4NZZm*r!`^(cAduDCo?uobR?nImR>$Chf;=d|v%vBT^(CS?6hhNkK4|E;8u7L(V z9v`>$jP>k@_2P*260HV!xlt~X&BG1Pn$NUe0Pg@Fo?Vyvh1Sd9J%30~Q-GFJUuxY1 zuNy!Ez$`blS+QByt+J-|3Qn^FK5p4#iM>$H@$Te@4^^|))NK=JeT}^-f@4bfIEIq> z4KU_+y8Jz$OWxbqmcF%m~yjK}{5HvyhYRIUj1M;OEAtJ`743 zrS&4(=uk3wgSyffkAi#-DjUU`)|z@b%+13*-jhqf2`r(G=5d;;Vm@rqpz6>z6|I*@ zJ7(tSF66}`m4*qyR?qz&a#M4GoNgu*7fYTPO(M3SINYePYtv#Qy+b%2={ zSXrkMsi1=gFTelwK5Lh-g9kOyINUhJ_EkW;P1RW2b=)}Fz2kEMM0*!lL17BDbV zxrr!Fq&t5_Clml%dZ6IW8V1$&Tp_iM4m%w1#^&YzG4=`xjJd?FU%SAIY`t==MlP_`#pRu$JbBwF{PFNxYU2TIVbcu{J2mZT zdh65V!wzldLGzxXBi=SGaykE27ffN#c~JPBn)=}>tJ?eUssFDuND14LxZAxw4Y_cd Y)A*naSkqH^29qg6j_i0EpB4Ifyw`NEJ}c&c&R72#JV*keCsZ0Yf=w zDrbyk81XRjLXH4!&juki-AI|~Ocr_mEn~^ZX zs1=Vy^H$_~{jooZHdF$TvHTbC_DTYfQOHLbey?*i>_#E0NkYzAf^f`wl902Zd2Rt8@o?_KwXu&rHnyHGO6)1iCg-a^?LOea(KvhoXdvQN@Bpz$ct0Ov!Z z2~Dv56GUjye*k!WsG|-x{L=)pWK--ZW=e9?Fa-@;=@jccgEBA!&6m&nno3~~TGi~^ z-KhFqQzjkP^gM9LO_S5G+0Nifdzh@+DwVjD-o(K#8Tv(#l9m0Ra`6;XGq6!)V#63SW+d#LX6%aX4}Z$Rpz$KZ3lk< DRUwL> literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/call_indirect.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/call_indirect.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..974170480d6e1e5141097c849762179588f4f02e GIT binary patch literal 873 zcmZvYy>in)5Xb+g59{L;*=OT8e1s@Y2n}w4q07W4JH1QjENpAyA#psG3}nWZ--3pc zhKhnGV1|Y_-~r$jXsFnel^G1t%--((?!UX*MU-4H0PIDz5A?xSWEt6#Y|&>c0xToK zmW0@Xn6^|(K=J`sHwlrZt;SqBqeM4*ZLGI*-m<*Wl3EWsu+v6gzco?(7Bo&4a@S z9Y$A3DDnhOT1E1A%8uL(W( zx-h_9>I!L!61W2yNJE%8{8B&p2(80z$@q;yWc-dkAI&?OW5E(I&qAU@@(ON zdsHE$Oh>rj^+|9%)RuCK9@Y{SEjV=!aB8gNBg&QRl`N~7_;x{mpum2PX)zUnd?D4s H3+4X+%%7up literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/comments.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/comments.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/comments.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/comments.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/conversions.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/conversions.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ddebe20d3da3c434a4ef856e29e9b07b63265bd8 GIT binary patch literal 737 zcmZ9JNm9Z<6o$JK!W1N6oN(m{Sg7UBvj7Ec6$=8@1a*;+V|WB7oagy0-o*F*xaee& zuR34KUBr@h*LLwtx5n@IpV|%kjJuJ(% z3kAn1mdX`=s&27jIi+8_)fn#Ww$*W{fyFU&Nl@9;E!p%E6 zuhyhfbGfCwqg*{xn~uQCJIXaOwP~)Ica&T9_}qyekH(?Gbo@Hsc%V{2krkhXR>L4v zdZJZ`xv%y^LhNHc2|Sh&n9@Dw})&%q1u61)a)z+3PR dyayk_7w`>y0H45D@E!aBpTTeN2mA!T+&^ASy5Il+ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/custom_section.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/custom_section.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/custom_section.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/custom_section.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/custom_section.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/custom_section.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8b19588df228b76c6747d04aee091cbe268592d0 GIT binary patch literal 44 zcmZQbEY4+QU|?WmXG~zKuV<`hW@2Pu=VD|_Oi2kT&u3uZ;$&oJP+(AC%;E+BoDK$l literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/endianness.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/endianness.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d26c99d06ca8963854764317f8cb4a9080570374 GIT binary patch literal 682 zcmYj~+fKqj5Qb-VTiCLoiy(N}3okrG4F~Hfnv@rzBuawOkWkWecK>BOcx-w75DIymK~bbGi1NtmZi7x9XHd$JwuK<`ID@iCXQ3j7 z<{s>bq1gz#Qr`)uN~=t(YOX*g*|kDaEmC{#2Z5|n>dY*l?u+R}R!LJlo>tsfr0A5g zHt{I+p8NhLE8uID>N!G=F1Ca|95IkPadNGrZ8CG%$svWixiH}+p}E3*IOgf-ry}zt u%YG?xPsvTPcSv^>H{>x*mN|N#cpHNciMcV^E3UJ)F|QNz+u%)N{>XpzRCK%m literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..505f4fe7a4c9c1e252f2b27dbd01f94db0dffd1c GIT binary patch literal 35 pcmZQbEY4+QU|?WmVN76PU}j=uVCQ6FOauxfF)(nkFfuW40{}g)0-^u_ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.10.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.10.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.11.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.11.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.18.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.18.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.19.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.19.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f1bae6aa2461d7e46b66fb2f05a8136f367c1af1 GIT binary patch literal 27 icmZQbEY4+QU|?WlW2|RzWZ-7!WMWKYW?)QWW&i*&8UpwL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0ec4916edc579c4262a52224fe1d7778b0289417 GIT binary patch literal 39 rcmZQbEY4+QU|?WmVN76PU}k1wU|{EDVoU^zBrz~@u`@9-a03wlN2~(2 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.20.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.20.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3c2410be16b0f8e9b19e9f2ff2b3f14be1e95c81 GIT binary patch literal 32 jcmZQbEY4+QU|?Y5W~yg!WZ(u9?3_%DiOdX)Nz9A@NG$`( literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.21.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.21.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.22.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.22.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.23.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.23.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.24.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.24.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.25.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.25.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.26.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.26.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a2f90fe1130c8556d4a774932ad9fc3d753a85d6 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|RzWZ-6JWn@fbW&i*sg#wKL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.27.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.27.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a9893a2a7e14e8b2070bf911beb21f4019e2ade4 GIT binary patch literal 23 ecmZQbEY4+QU|?WlW2|Rz)Z%7mWn@fcW&i*u3j(_U literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.28.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.28.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.29.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.29.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8fc92d022fbf4d1072da17bc8e0840054b51ddc GIT binary patch literal 8 PcmZQbEY4+QU|;|M2ZjMd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.36.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.36.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.37.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.37.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1f2cfccf74d67b516d42994740aeaf9a74955374 GIT binary patch literal 25 gcmZQbEY4+QU|?WjVJu)^VCQ6FOk`wWOk!jJ04PQRl>h($ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.38.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.38.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.39.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.39.wasm new file mode 100644 index 0000000000000000000000000000000000000000..48fb7293d12811b4118adf016e2d1eb6ac262a4b GIT binary patch literal 22 dcmZQbEY4+QU|?WjWh`K1U}R@yWK3ja001Bq0xtjn literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.4.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.4.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.40.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.40.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.41.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.41.wasm new file mode 100644 index 0000000000000000000000000000000000000000..48fb7293d12811b4118adf016e2d1eb6ac262a4b GIT binary patch literal 22 dcmZQbEY4+QU|?WjWh`K1U}R@yWK3ja001Bq0xtjn literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.42.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.42.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.43.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.43.wasm new file mode 100644 index 0000000000000000000000000000000000000000..48fb7293d12811b4118adf016e2d1eb6ac262a4b GIT binary patch literal 22 dcmZQbEY4+QU|?WjWh`K1U}R@yWK3ja001Bq0xtjn literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.44.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.44.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.45.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.45.wasm new file mode 100644 index 0000000000000000000000000000000000000000..48fb7293d12811b4118adf016e2d1eb6ac262a4b GIT binary patch literal 22 dcmZQbEY4+QU|?WjWh`K1U}R@yWK3ja001Bq0xtjn literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.46.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.46.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.47.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.47.wasm new file mode 100644 index 0000000000000000000000000000000000000000..48fb7293d12811b4118adf016e2d1eb6ac262a4b GIT binary patch literal 22 dcmZQbEY4+QU|?WjWh`K1U}R@yWK3ja001Bq0xtjn literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.48.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.48.wasm new file mode 100644 index 0000000000000000000000000000000000000000..539a89bf190ae897dfed7d0ee431aada5c83d162 GIT binary patch literal 21 ccmZQbEY4+QU|?WjVJu)^U}t4yOk`vL03JC4EC2ui literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.49.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.49.wasm new file mode 100644 index 0000000000000000000000000000000000000000..48fb7293d12811b4118adf016e2d1eb6ac262a4b GIT binary patch literal 22 dcmZQbEY4+QU|?WjWh`K1U}R@yWK3ja001Bq0xtjn literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.5.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.5.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.55.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.55.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.56.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.56.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6696dc1f5fc9b94f45aefa88f80c275fa40f0a4a GIT binary patch literal 24 dcmZQbEY4+QU|?Wn29oTYOpJ+442(%k3;-bG0w(|f literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.57.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.57.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.58.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.58.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f6bda9d18c5c0b1982ac2aefce047cd2aff19860 GIT binary patch literal 21 ccmZQbEY4+QU|?WnVPs@rWM^e$Ok`pJ02%rLzyJUM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.59.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.59.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.6.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.6.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.60.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.60.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f6bda9d18c5c0b1982ac2aefce047cd2aff19860 GIT binary patch literal 21 ccmZQbEY4+QU|?WnVPs@rWM^e$Ok`pJ02%rLzyJUM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.61.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.61.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.62.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.62.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f6bda9d18c5c0b1982ac2aefce047cd2aff19860 GIT binary patch literal 21 ccmZQbEY4+QU|?WnVPs@rWM^e$Ok`pJ02%rLzyJUM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.63.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.63.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.64.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.64.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f6bda9d18c5c0b1982ac2aefce047cd2aff19860 GIT binary patch literal 21 ccmZQbEY4+QU|?WnVPs@rWM^e$Ok`pJ02%rLzyJUM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.65.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.65.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.66.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.66.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f6bda9d18c5c0b1982ac2aefce047cd2aff19860 GIT binary patch literal 21 ccmZQbEY4+QU|?WnVPs@rWM^e$Ok`pJ02%rLzyJUM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.67.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.67.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2981c2461cd601057c5978198cb8df7c23f69791 GIT binary patch literal 20 ZcmZQbEY4+QU|?Wn29oTojEsp)3;-CA0lfeK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.68.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.68.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f6bda9d18c5c0b1982ac2aefce047cd2aff19860 GIT binary patch literal 21 ccmZQbEY4+QU|?WnVPs@rWM^e$Ok`pJ02%rLzyJUM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.7.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.7.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.8.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.8.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fff82363ca62dc4496fb9301cf6962c5c76f789f GIT binary patch literal 31 mcmZQbEY4+QU|?WmVN76PU}j=uU}t4yOk`l-Vqs)r;06FOYXUj| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.9.wasm b/lib/wasm2cretonne-util/testsuite/exports.wast.9.wasm new file mode 100644 index 0000000000000000000000000000000000000000..615bb04c6f314a50e4422b82631aed6d05fb6479 GIT binary patch literal 39 ucmZQbEY4+QU|?WmV@zPIXRK#tVq{=vWn@fcVBq3naAeHl=LP^@P6Q_a literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/f32.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/f32.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9bde5ff26087b744a6b50fcfeb472786caa95e30 GIT binary patch literal 196 zcmXYmTMmLS6h!B8sa47%*pP7xRKSFULTst=hb8!aZP%vhe4LY+fp&%fP?^=)vd~gf z0GBFk2nU78tzzA_06V`2vGD^kZu=8BcSk!TXVZ@mZ1fqcyy^M@QXGb9vRI4R`mw=A nj-9rhn^6=yB~hWGXYwogo%~TE{EyVW$-lT>BCnCR$h-Ojj4UB3 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/f32_bitwise.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/f32_bitwise.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..76817cfe134b8a57861c606f077212d1ac52e42c GIT binary patch literal 77 zcmZQbEY4+QU|?Y6VM<`EWvoqLss)nFEX+V5b_r(Y#H3;d2IjofbOuI_`3s a1|}|1W>y9ThHh>U*~85aBo!EEa037mo((1d literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/f32_cmp.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/f32_cmp.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..54042ae83968bec6813c29f2ca3c13325db9bde4 GIT binary patch literal 110 zcmXZPO$vY@5QgDz#&RGJkyq$E{n=u0V?pFrx_Uysn}-+2a}od&3S0`kMr_@hjF$BV h_)-w34CbUT95XtOihh`^z{At*ewXIj+?!i7xDOT54CDX+ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/f64.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/f64.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..69c3e69ca4513c6e95ad5ec8dbe488a082514ca8 GIT binary patch literal 196 zcmXYmTMmLS6h!B8sa47%*pP7xRKSFULTst=gPr((?bD{|e4LY+fp&%fP?^=)vd~gf z0GBFk2nU78tzzA_06V`2vGD^kZu=8BcSk!TXVZ@mZ1fqcyy^M@QXGb9vRI4R`mw=A nj-9rhn^6=yB~hWGXYwogo%~TE{EyVW$-lT>BCnCR$h-OjoyQ@* literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/f64_bitwise.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/f64_bitwise.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8c71ce01a4ac3579aa39b9ff8890516f14d1a8b6 GIT binary patch literal 77 zcmZQbEY4+QU|?Y6VM<`EVXR4DssWPBEX+V5b_r(Y#H3;d2IjofbOuI_`3s a1|}|1W>y9ThMC+Tauzo`kW^q?#ti@x`wdF~ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/f64_cmp.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/f64_cmp.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..db5c2f565fb6b779ff6788368df5e1511f271365 GIT binary patch literal 110 zcmXZPK?;B{361)_4aiq2Kh(|Q+BQHr(Bf2g?H(M({djM$kjWQ^ThhDEmpjt&n`#CFhjr>5 zm5I#f6kh9JI)lEXcmqpED^ewuVRTo8C<$!DTL}`MmRc9k+Q>haIT;FBhD9=0#KEn`0Q2fSu2d;kCd literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..93fc9ddbc27a18126091b9d552a760220b50dca9 GIT binary patch literal 60 zcmZQbEY4+QU|?Y6U`$}Hsi|SCVP;}vU>9Q)N;5Oj%gc{X&d)0;N=z=v%+F(B;NoHA PVo+dEU|giYw167`aMBEh literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..638a2124f4b5c7ebfaed6f86914390b9710fdf14 GIT binary patch literal 84 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWS3^*N;5Xn%gc{X%S~ip;7T(y dfpHkQ#F@Al6c`j3rzkK@;)XC6DKIVI1_0YY5S0J` literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.10.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.10.wasm new file mode 100644 index 0000000000000000000000000000000000000000..63bf9ab6eaa04e48967b4283962655316ca69c0c GIT binary patch literal 104 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=dBNi#Om%gc{X%g;%P&r3}=h%YWp lVqg$SGc&;yX5^A);$mzoY!n~> literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.14.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.14.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6ec49ca9fa77ad46a76298f37a81d305eadd2786 GIT binary patch literal 94 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=dBNi#Om%gc{X%g;%PFD^}rFHX%# bV_*PW@chwWLIS3O*1yq%gc{XOH3}wFJfTe iO*1n=;xlr|GVwAfFeorhQD6iTlei(gMIhdy1>6Ap*%ZA1 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.18.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.18.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ed67b7aff3095184fcc67d84fd8acc1bbfc8feba GIT binary patch literal 98 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWY=U8Ofxpp%gc{X$t*4@%1kOP kNo8OVOfxe<6Jz8OXX0W|U{GM3q`)+V8^TPW@chwWH)9KOEWgo%gc{1N=+}yFD-~q n$t;V{EzMzI5KA*N!6w1TCCa0Z&jFo~NF$aDcQL0|zl0K{4q#sB~S literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.20.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.20.wasm new file mode 100644 index 0000000000000000000000000000000000000000..054222cdcfcdde1978025d78129a1bec29bbc3ec GIT binary patch literal 108 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWH)9KOEWgo%gc{1N=+}yFD;1A pEzOBf$t+`F5KA*N!6w1TCCy}1eO{gV60(gW@2DuH)aw`Gd9x8%a1QgO)M_XPtHs% uNsUiTNnv0ROEWXUCc(%h%f!o|z@Wf5Nr7pS0`nwp2ycM`(*gzN1>6A6L>&+S literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.22.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.22.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4d5a0ac68ebaebfbed7b6b58cc87090a57c5c672 GIT binary patch literal 116 zcmZQbEY4+QU|?VrWJ+ME1p>y}1eO{gV60(gW@2DuH)aw`Gd9x8%a1QgO)M_XPtHs% uNsZ4f&0$~=OEWXUCc(%h%f!o|z@Wf5MS*FG0`nAZ2yc-B(;@}tMce?~mmOvR literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.23.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.23.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fe96aa795bf511a85e66ea51fb07c66d66ef2b85 GIT binary patch literal 98 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=U8Ofxpp%gc{X%g;%PPsuEcH(+28 hOfxe<6Jz9(X5wN{U~mSS!!VVb56E-@F+pH4HvsN#64U?y literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.24.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.24.wasm new file mode 100644 index 0000000000000000000000000000000000000000..37810664073f476ae40e58f55f9aa1464aa9c841 GIT binary patch literal 104 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=dBNi#Om%gc{X%g;%PPsuEc&r3}= lU|a0Z&zFqNAR$aDcRL7-tVHvqe97E}NL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.25.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.25.wasm new file mode 100644 index 0000000000000000000000000000000000000000..57ab86234e67ee64a16c80ea20b710622cccdfc2 GIT binary patch literal 110 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj#~XA(&>Hqy(>k59|bNr^AXkI$$q q$S+}F5J@vL!4ziXl4s&$PyoUy3XBSjQzlL1hHw{wxQiCd=LP_|qZk54QpD9W$M%uOswjW0?~&IIz3 X6LT0CxOf=37@Qdx8tfGqrg8%S6SNRG literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.27.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.27.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a5b348cb7dd9a311876fb4c796278178188167cd GIT binary patch literal 127 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWOrdwN;5Xn%gc{XEGQ_-ugJ_zEJ=+o zN=?oL@{$vC;)@H5N*EZV)67gjiqcApF=ZLKWSO`boEaDz>=hU$PUYrjZ~ H`HQ&$a1$am literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.28.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.28.wasm new file mode 100644 index 0000000000000000000000000000000000000000..72c035d96fae7570475a8b64a0c154f7a8cbe878 GIT binary patch literal 75 zcmZQbEY4+QU|?WmV@zPIWvpdpVq{=fV^m5rHqy(>k54QpD9W$M%uOswjV~@NDv2*j dP0j=gB`4-EFmUlQax*wHFf`aJFif4u4FH=L68-=H literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.29.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.29.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9e81c03093af4485ff5feda9f83a2d3db6c05277 GIT binary patch literal 142 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtP`-2xb}^>E-3er{(9Q#HSe>#TPR$ wV2G76FbZaxnIP1enE=%>VThG7FmuVWurVkwY+At$qBgGtQ#&_+sa+en0YlCrxBvhE literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..58fb0986884a79f15972b59b0100bb8afc4279d5 GIT binary patch literal 104 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=dBNi#Om%gc{X%g;%PuSzY-k1sAw lVqg$SGc&;yX5^A);$mk59|bNr_KPNr^8m kO=4gWPBSyX5M|^NXX0W|U{GM3q`)|t8^Tk59|bNr^8mO^Q!U kNnv0RPBSyX5M|^NXX0W|U{GM3tiU*l8^Tk59|bNr}%b&52LR kEMs60PBSyX5M|^NXX0W|U{GM3qQE$n8^Tk59|bNr_L%EQ`-A k&0$~=PBSyX5M|^NXX0W|U{GM3s=zpf8^TR literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.35.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.35.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c3abad956d3e0ee07dc9aaa551bf97be6a266cc2 GIT binary patch literal 58 zcmZQbEY4+QU|?WmV@zPIVXR?hVq{>KVie2Ek59|bNr_KM&CM@KjV~z52hj`+TpWyS L3KVie2Ek59|bNr^8g%FoR&NsUiQ1yKwPTpWyS L3%`Zuf&&{kzO^HuT qNnv1+!6F);oS&OpT9TT=z{n-a#L1w*puo6$!8UFXYqtU;kOcthEghEt literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.38.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.38.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ca28d5e4d1e170825cd7cfae31423d0f24dfd1ab GIT binary patch literal 59 zcmZQbEY4+QU|?WmXG~zKsb#EXW@2Pu7iSdC%a2b<&CM@KjnB=jNKJ_^E=^)!;NoWF OWKdvGVBEcM8#e%R84a@l literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.39.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.39.wasm new file mode 100644 index 0000000000000000000000000000000000000000..20d1715bd5bdb4f594f44836068c1196493db446 GIT binary patch literal 735 zcmaiwSqj216h&{Wa~<0W$aXV4ef}kxL<5%5}12~JrpF6YmA?eT4KselRxLkss zng{^GPIbjnEW08Up;dFDCy3~LnR}%j*K0K!qv-m9^n&p~&S%>1OC|e(zZlNuQUQ#k z6OLdU)1#4M9Mhv=xn8IJXHS}OOpii_aguwojAMEP|3MIEb?{lS{BI#VKSZgJWYHg`&d-6c+Y!tP3m OaCpS&NH{zxJ?%F@l;|1& literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.4.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.4.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8c4561e20503025aba87bff65fd53ef00ea3909a GIT binary patch literal 104 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=dBNi#Om%gc{X%g;%PFD^}ruSzY- kXJ8OXGc&;yX5^A);$l!>a0Z&jFqxYV$aDcQL0};_0MS?$3IG5A literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.40.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.40.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d363f76fb0a43cf6ef7c1a7f7fca2bd6e9da7b16 GIT binary patch literal 116 zcmWN{F%E)I5QO2`*=GoDXsOr2#uG>frMJKk63|E?T9S7P?#oX3&5!5$5ddQBnK$&3 zWz(b~rzEkW)-*5k576&_2_2?!c!TFgOE_Hagk424TWy`r8w2-HXO~`=5XYl}nHAB; FYX{304#ofg literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.41.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.41.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c6b9e24fb7b90a707195412ae109802208c32db9 GIT binary patch literal 116 zcmWN{F%E)25QX74Giz3z*jf(-l_#Ksl3OexB(4Su!ETd1j2CrY`Mn?0z7haZPFxET zRVot1K-_?Wk^_)9O4eyY1#}>;jum^SdK!Ja=7|8%OtTZ=*6OYV^L*^tPbCQucsmPoZWKNniHvnz{Ph0>1 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.44.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.44.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9650b94d6ef65a3ed6edc4a56b8550fa2bc39553 GIT binary patch literal 330 zcmZ9EK?=e!5Jmr_sf`wq-XJG%q2g^c5!*}l?DS28#6 mEx5~FwR)4;z{1i=CyUZjUC-`%Ek$tv literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.45.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.45.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d8d69bbb208ff97d2db8a8348d6f5c3452258c67 GIT binary patch literal 440 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHS=HvhhF)#uF`%gv=nKWY~y}bPRwEUcu_?(jX z;?$hfM&8Z`bqwg}Kp^u{>R+s*n}DP5JhDjRjzs;N|VsR6huh(e}rwB>O{ zTYjSVyXjj2SF{x}#cs{GqOEB0!gvW14({1g&`Rh-2}1l>rzY6hF&pd09Hb*#*?mX1 a&&@S35{Ba0Z&jFol~B$aDcQL0}O#0MgeM6aWAK literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.50.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.50.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3fd1baca496625adba491b9fe2af1d256e473c7a GIT binary patch literal 58 zcmZQbEY4+QU|?WmV@zPIWvpdpVq{m;1XdJVo+dk{0{^S Ng`1p#8ct8*1^`)u4lMux literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.52.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.52.wasm new file mode 100644 index 0000000000000000000000000000000000000000..61021fcf69b5e7178ae277b781687fcc6733b75c GIT binary patch literal 64 zcmZQbEY4+QU|?WmXG~zKuV<`fW@2Pu7hvSc$tlZ?FDNoHH!wD1VBivG6lG9ga6J9L Ty`*T90;6L?!vyxiO_R6*m}d`+ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.53.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.53.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4e3bde4866aefd2f84b6e59332e20a971ced7609 GIT binary patch literal 114 zcmZQbEY4+QU|?VrXHHrQ?1C7- literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.54.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.54.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1572cc4298e33508637510149314629e6fb1a604 GIT binary patch literal 47 zcmZQbEY4+QU|?WmV@zPIWvpdpVq{?FW@OLID@!dZPGw-=;$h@saAsg=uvcK1$_)Up C-Up@t literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.55.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.55.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fa832af0e49353c352a5b5eecd9f8f7cd14d35ff GIT binary patch literal 98 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWLIYrNHaEyFD@)9iOghlSh#8OIcg literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.56.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.56.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ef8bec6e156ac2617059afc1f00c76b2f50d6e43 GIT binary patch literal 126 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWH)6JPct^s%gc{X%g;%PFG@|$EP#*< t4B}~KCRk+{xwM%C7@Qdx8tlP@0>jj)+!72fKr4XYgFT#~z_55RHvoI?9t{8h literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.57.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.57.wasm new file mode 100644 index 0000000000000000000000000000000000000000..15959e63292825423f6021762c1cd8f0cf0fed51 GIT binary patch literal 120 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj#qW|B%XHqy(>k59}=PfaRH%uJ3? tOH3}wFUrhIXJC*@Gc&;@$;c(k#LJ+-puji@L`S@fI%P1^_Ru9$EkZ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.58.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.58.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b8870a873c7cea29be912ef9733d076a4b814ef0 GIT binary patch literal 120 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj#qW|B%XHqy(>k59}=PfaRH%uJ3? uOH3}wFUrhIXJC*@Gc&;@$;c(k#LJ)ngi{n46&R;X=7#VVfq07+asvP_M3>=WnGs^eiZ$EMQ9;bG6Ek83mtszA!IfC>7V`@44}=r^Wg{}*uH&(}*UfyJ04u%m()FO?ta`EwW251l1d<{GAOn*i1d|~I zAOJxGY9Rn10wDt+0YQIiA^{Bm3kx6u3n&2reF8uOKp+Aj04f3ik|6>h06_$5Apjr& zL4Rr?0U!Yl01FEs0t*X=2mt^=01JQ!#Kl)6rG&@}ZMaJ=@^%*-%1Txq8*XtTT^Am1 zzs(d}$01cwIh-G|QShUa*TF2VeyAuq0U`7XSd;idcb|hfYG1nd3fNI4iVt*WnnZP$qJ2KXVWG5|bk|VRjNoz?%h=>#T;}ls;AHjOg9u{FO((Rs$g* zOy<9P_J7s7a~#1GkHjaIv%NF_ctKylTF&-oZgZL!W^e=e&L^W%vY$J@`>yTNTKG3t z@bzx*u}yG2RHp%&g$PKI^_>i0hg;cH9#L(GjtGpUaXMBAXGV{Y6Dln1EFZLDD{nyx z#AQOFF>5S+KD%|ShLa2S62P7N?gEU?9P%Eacq7}y9!Ue!am)x%#n@c! z)wN};waFpU$tiZ;@Gu%%Jn}o+Iyr(m76@;s@xEzknQ^8C(-e~`7V<9<$9^EYkwotJ zD_pZ78Ae`@+mX|Jcx$&30FUdviuKek#K9ilb7NUY zNdpSE&m_;B4n%sM6#GKIojEX`psO#@uAXXxL*rF_g}-Bp_?8eK{%z+X8{uh2ibTlB z`3x%=2c-f&@-PsZg^FMAfkmKqu>%luORkumN57gaI@J#YX5&3m*+9-8Z!#|-DlBSVzto%`P=mK>Hw(h(V286-PSbKC9y``oT_p4V6ceD z;0sw=B~K}4`QtAB>DuKOPbXsZ&ZHrgbjMd@Cz?$CRDEI2!>or!ocjL>%6XS(By`Hu zA%i9*84@>S8Rcc*bajs1$nSK=*dZ%<1W`t1U;|zH*z{$jo?Ro_*NT#Wq~3Tu@mE4^ z&B-s-q@Jrl^vfI9qssj2rvb8>X--C)7*5@Jr>dq>IUesY%$vPi?o1(VFaUC}Mqdce z#kt@O)^H{hRdw&`tBZRP+-(}NYQ!bas^gzYNr}rhn+m4^U~t^G$b;cR L^}elQl&y`SSTY!m literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.62.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.62.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3e6561f5f4e9f2fbfe3b38a9f7ef777700d530ec GIT binary patch literal 2221 zcmV;e2vYX|VRLN(000012LWILe}4gd0|Np800jgA0RaIAAp#9%HZ(44VQ67)UvqVB z000eUHZ(49Y+-3`UvqVB009bO0yhBzd;&lNKp+Dk04oCkAOxWz1fU`UAOoQw1fd}W zAOJxKY9Rn10wDt+0YQIiA^{Bm3kx6u3n&2rd;&lNKp+Aj04oCkpdkVv06_?9Apjr& zL4Rr?0U!Yl01FEs0t*X=5CH%|01JQ+69t4qU7{?;L)%rU&DvGtbNpD=y43*vgEc~k zixXWqN=FTHbD7 zD3TcL2(IT<+Oei(rE*Y58@Q=d*QN$r4?$5JRBAE^WM8g;R2Wtwg*qJXNwJ znshpz;n)vw{HZ$4lS}O5e%)Os&G?|591*}*F8#<8GR!&}eWO8D?ZbjeDg++ybGa@q z<6rdO=CU&kIX`biY6HPSx%#ol?WI_mf95$8o%!zz$5t&| z@SR=W{J5+_%NHAM6$^A-(=PaHhP1kKQd60!b|6K`K!9r$?V5c=9t7+zr-A*ka{+h6uK3OZswdfjP%|TB3+?*P-hK2dg;`vE`KU|Fg@yB5=j2x z7|oOVv^3zI_R9WDJ9uphTo|8Wx!ttj4|d=iX$@Xjb8ub` zEunq?AnTDdlB_s#Dj$wyyJRP|3@MO>2uD+wpJ|cZiX(3}5?E?Z- znh#J;6IwN)BBbAj!gP5SxM^viQe6>by<3y~X`u8I(1B6i`V1ArddXsXsU6AYTC-c1 z0z70Q5(0+sdvKG4LCG#OAK z3cOI-JTTTeF_euu{_hl5olBDdmLv>s{k$V~0&MG2a)1$jfI1TrI2|}il|UzoM0@Dm zZH$bLJhihJ4)zEdgh4g`i@1|O>fuoxiKUG~XesJ1!Fp2bkX=pIfaBM7NY3rk0UV86 zrIZbL&+!HmFPH+PZ7yu`)pRL|O!))##~7`8@M!p4)>(&c);|Wmh9KiXks|YMi|}Y1 zSEu~2e4d#LmKup)<_&xZQJ|t{nnwVWYxyYfXxn1(;AJdRGvKGn5kfHbuK~ejJu(4r zt1iCNi@p@Y|Lptl`Na$cZvhEw<1{f0A7-XPyc&l|78Z=KkPS493gHP}5Tt8X%<##( z+_+rM_RNiSezOON1TVB+$>T~Ra~Wc6nge!3hMZvh+j5U5pxv{@+W-0NYO#CM&rmnX zdUy=vI=1!Z^q6i!eKm87MZi{<&27%ltueF1RQm7L!1QWgROd-PGYT*`{n-Wor9W3j z6t$5T%4?V-dS$b00<_F|U0B$&)s(LEZFOb$lD~dOOg!$}tno5k`)@%UG{}Qb!dL;m z1wLjLrUY$WPlgF6f+Z5oI?c{&yvnR7o9=2QhMq-m#?rkzeM03Yug4wqfylFVil{cD zt}5mj;Wi?z_9b$#Ig0_BB720xEnjk^n5-3W=hPVDebO*|GYHMf#3F@=b%Dl8bXbkx z=&Z%7bz1Z&67U#BQu3FwlRBX+kc%G>Ytg}Qy z;JjTMoJm+p92rT$I_mZ7JAtW7a16|z8q+VgQZFDo>tsvt@wUy$i;ucsFx=S+4Lc5>H`V;_m(?@d0%`>FrQX6 z-n4VnmGBh9bT18~9ZrogB(oZXg<@KJJQO(>=56aY!T?xX3*$VBV&V8TOIG5CV2i$| zIFWZ$sahl@ke{VOck^61-~V(6cA?E5Vhe$RXs-pqz<(z0z>%YBeJNo@C*@a&+zpom z8Z)UxirNAYML&*Jrgn@?bwx~I?3W>`%`7m$F!&W)CE%|(9J1`09GX=eOE+ggSB(xO&+e3FtW5CQk&;195JOM0;F;S)9`3!@60fHnS?@H;ycA& z5?m-vwhm6nT<8Ic#pmRmLhfCh#Ov#52`7@3nh{iU#1eq}eg4G=V1G72;1IgjD{S$! z7yc$R<4gFci7@!wxQ?CRD2hj#e*y vuOftKrHhyLr2VMObwQSf{U<#shK+wr)Q)pO^d92fSSOdxoRrUc1TWm}vyure literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.63.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.63.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fba3bcf97217a9467b201796bb7c348e74fda8c4 GIT binary patch literal 96 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj#~WfD#^Hqy(>k59|bNr}%(O^+`w gO=4gWPBSyX5M|^NX5wH_U{GM3+`|oFES$v+06Z}k$N&HU literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.64.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.64.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ca400ce4cbd33d821ad612c36af1202a58e92329 GIT binary patch literal 96 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj#~WfD#^Hqy(>k59|bNr}%(O^;7Z gNnv0RPBSyX5M|^NX5wH_U{GM3)WZ#7ESSX&05Y-`ga7~l literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.65.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.65.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f5ddcf462e2d66abb79febbabceb49e097bcb3e9 GIT binary patch literal 106 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj!9WfD&_Hqy(>k59|bNr_KPNr}%( mO$T8H2JtjA6Ra|fT%t^z3-#*%S?<< jOi5v25KA*N!6w1TrO71Bpa6uEkP$aTY=Hs;0&xQX%@!Pm literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.68.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.68.wasm new file mode 100644 index 0000000000000000000000000000000000000000..39d64d72d6e88914668280d5438f7331fb02796e GIT binary patch literal 102 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWY=X9PBS*r%gc{X%g;%PPsuC; jVFm`_G&2(nQARFtCN2gA1_j2c3QSYEAy}1eO{gV60(gW@2Du*Jlz*Gd9x8%a2dX&q;~TEzOBf q$t){oU=T?&Gr<&QPW@chwWY=dBNi#Om%gc{X%g;%PPfSUP pPsuDRW?&FWGc&;yX5^A(;$=`^P+*#>zz8HJaYJ~ELA=EaxBk59|bNr}%b&518A mEGj8xU=U3+Gr=Ox$R*0e$)Lb6QGs#d6mAe}z5?U?Mce=dV;UX+ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.73.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.73.wasm new file mode 100644 index 0000000000000000000000000000000000000000..abbf750b18416c151980a101b1cfcfc01fb23a08 GIT binary patch literal 102 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj!9WD-p?Hqy(>k59|bNr_L%EQ>EL mEGj8xU=U3+Gr=Ox$R*0e$)Lb6QGs#dRBjM!z5?U?#oPb^Z5j#y literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.74.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.74.wasm new file mode 100644 index 0000000000000000000000000000000000000000..add2c0ab20d3b2a7c5e23a087b68ea431c1d32ca GIT binary patch literal 112 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj!9XOc)WHqy(>k59|bNr}%b&518A rEGmgl$t+`FkVrE#!7jzfCCS9ipunKOIB|*s<5X@4d;TH?#>LzKK6xD4 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.75.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.75.wasm new file mode 100644 index 0000000000000000000000000000000000000000..315d6494e24c4eeefa1b1bc9b2af9640fc61e974 GIT binary patch literal 134 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWcOuKPBS*r%gc{X%PB3+h|kO` zNiE7vP036wNsTWqP0GtJ%1z8+U{FpoGa;yokxQJ3i$Q@wfpLlg(-dw9bCCkmB5nW; C_9gKE literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.76.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.76.wasm new file mode 100644 index 0000000000000000000000000000000000000000..10b819568083a14070762737ec43c0c904f28500 GIT binary patch literal 246 zcmZQbEY4+QU|?VrW=>$Lt!1oFV5+GBleLVs%pB|>DMm&n_Ih^yG-D&ZqSWO4l+3*J z_|!rM22_!pR0c*gkrDVW@chwWOrneO*1yq%gc{Xttd&&OG!p*8^T@- literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.79.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.79.wasm new file mode 100644 index 0000000000000000000000000000000000000000..687aad0de19a7254dd9173177f7fbddeef36ad8e GIT binary patch literal 388 zcmZ9HJ#NB45QX3DuEBVPK#D}YZJHD~<~o=k(?lr(E>hSBpbXeG#y&&tmszi|h^N>$ z^WMxSEjFi50zj{$m~zTvm2$TLDaqAe3W89mnW7I}89m8he N;K8UX$Efd({sS*FUoZdw literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.8.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.8.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bac9a68a7761684d6bf72a7cc05a40dc7f0655eb GIT binary patch literal 102 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=X9PBS*r%gc{X%g;%PPsuEc&(BL` mU=U6-Gr zC^bUh9+ZNI4OLlRVCljA3T&vR6@=CSRy7s6i+00k%xw+410#5KPi5K!#J{}`21e7$ K!p;Xa&F%*`N-Pur literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.81.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.81.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e6489b12ee5f94834103f420882da096349644eb GIT binary patch literal 126 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj!9XOc)WHqy(>k59|bNr^8mO)`wn zEzOBfOi5v2kVrE#!7jzfrOqV4puphFz|denS%Gni0^=lZ5uktz&>|rCV82j-aghS! G0&W1RydIYT literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.82.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.82.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b0ba33f992bc218bf25d314f61cda63f94a8aa44 GIT binary patch literal 231 zcmZQbEY4+QU|?Y6XG&nMt*vFOPhhU8sbQ>VW@Q12GO~9vvdE_y8|mfc$EW4zq{Jtt zq{Qc>#^>he<(K5=WhQ5qR5CDNs7Oas!6=_*W`frYCJYtmW-xPEvhXq}FeorhQeXrU U@!ZI~1SoF-NNzzY3NMWt0OamKtN;K2 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.83.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.83.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b34801139900aa6d2f8c94eda5c7b4052dd641c7 GIT binary patch literal 216 zcmYMrK@Ng25QX74twJgB4xE667;nIWLnt*mH6#KHyM%Cco0y@q`u<5C9^a7w&`CM9 zuCzI|EVSvu#3v|TJ{aqpb3M(W{{$cfgZ6j_s1yus{q-W7Lkh-mkGRQFFml@&g9;Vh T+VYQHa2GhiQ^10kz_;ZOzk@13 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.84.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.84.wasm new file mode 100644 index 0000000000000000000000000000000000000000..413a38416431151746f4521c3fb2caa8d2eec396 GIT binary patch literal 142 zcmZQbEY4+QU|?Y6WJ+LQtOep4W@aV^Ms`^y?lfZ~z0`u@%$)o@1_tglGZQ$Ekt>u* zp23-cp}`(VI5>bQuWeC6+91e@&iWV+f$PEBf CcNzHr literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.85.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.85.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7f542c9297aa9a9530157bd4cb97115c413501cf GIT binary patch literal 160 zcmZQbEY4+QU|?Y6WJ+LQtOep4W@aV^Ms`^y?lfZ~z0`u@%$)o@1_tglGZQ$Ekt>}^ zi;=0;nSr6fUXg*>L4iSu5y)Y%pQ6M77Muho<9Hdk6&Sg#7@2BZfU-dF0j?Y>@W6hN O5(8Xv0i2Zr(Fy?h*&CSv literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.86.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.86.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f8608e0c5977cf2e814203b70f8e4a4b0a3ff5d2 GIT binary patch literal 337 zcmZw9K@Ng25QX74MFl17H~=Sbp~l-tYoJvUkyyJifp9KwXGhbOoy8>IpUL3->E@9Ys8G>~m$La!l2r%QEE%M}=Rpn21Zh?q)UtGtVKp}Y0DqlRl>h($ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.87.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.87.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8bc5cd88ff1b4c67167a2381f05278920fecddb5 GIT binary patch literal 964 zcmaKq%}&BV6ov1!Xcbh%65N!ePhk9uTjI}q3jkPPbt4{0zUgF4}dTvoX&Niqpb4vQ#1 zvRZAQ93L32<=xn(XEiA^7;RWy*YG^kWdIubO@leiY8suaO?0%mx%>sG+CDp7F+tYQ zUpgVDqrGy%S~-HCZKucNVD})n{QiVAYL)Ud|$1`cm#0&QF zud-SK9VR-Nyo}C$-~Ur?3n^vBiz^Cd9!G@=zXP2FpW_Rj#rmDWLt4g$u(*s}!P~f? zV)rHg9pNdNp#lm%H_}>^^J~vy&JVbe*5cfpIiBGMb4|nfS2!2zU1ZiEb~r?^V9q=KK(rSGrg%l`phzeQO9 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.88.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.88.wasm new file mode 100644 index 0000000000000000000000000000000000000000..66f2bfa699669a5fa735908740e7e27aeb3d94b3 GIT binary patch literal 135 zcmZQbEY4+QU|?Y6WlZ3xfda-FW@aV^26iJR;gtN6_=2MRl+xsq_|%HT+=84`1_p`p z%#w`wq|Cg;qRRN<(%ksu{G6P`g5p#LMlMq(Nd^T51(rn$j0&ua7AP<&umMSC1@=V? LxG`lQ5(~Hi(Gw&O literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.89.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.89.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7fefd64cbeba5d8bc2773f324c0d3aa463cef60f GIT binary patch literal 102 zcmZQbEY4+QU|?Y6WlCVGt!1oDV5+HMtYKzmVqj!fXA(#=HquMZ&nqcPOfE4>%S~ip m5J)pKK^0=;l4s&$PyoUy3XBSjQzlR3hHw{wxQiCf=LP_Urxy+Y literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.9.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.9.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1f070302c4d6093d1a6190e1bce9ec807df7097f GIT binary patch literal 104 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWY=dBNi#Om%gc{X%g;%PPsuEc&r3}= oWMB|UGc&;yX5^A);$l!>aAsg=*gut<56E-@8Vm#<_AllJ0M0EKL;wH) literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.90.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.90.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7e53886a4c2031b9f68b603142681332a1d1d0c8 GIT binary patch literal 120 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWVdIMNi#OmOUW$DEY8f&i%+VI tFV0QO$%)S^%}q)zVqlO-Gc&<0&B!Ir#KoY%puniWG<7mJjJbFrHvpXCAO8RV literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.91.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.91.wasm new file mode 100644 index 0000000000000000000000000000000000000000..97da0e6c0445c979ad40717aa8c476a3f5079b3a GIT binary patch literal 98 zcmZQbEY4+QU|?Y6XG&nMt*vFOO<=C6sbQ>PW@chwWLIYrNHaFlOV7_qNzIEdN-W9D jXJ8OWGc!RIV&sx!;$~1_P+(MGnmB0+H;g@h!6I$|WK|aL literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.92.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.92.wasm new file mode 100644 index 0000000000000000000000000000000000000000..be60329b83218b39c9dc5db26eff89a5c83da1b8 GIT binary patch literal 126 zcmZQbEY4+QU|?Y6W=deJWvoqLtYNHSW@chwWLIYrNHaFlE6&U*OD&4eO-;-zW?&FV zGc!RIV&t-95@&D*nqjZN0D@D1>;{KP6DLjKmS=DQiUYv|sN5nbU%+9(`~{1+0k;Di AH2?qr literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.93.wasm b/lib/wasm2cretonne-util/testsuite/float_exprs.wast.93.wasm new file mode 100644 index 0000000000000000000000000000000000000000..da5cc6ed34e98a76be577cd9c040721080466d65 GIT binary patch literal 58 zcmZQbEY4+QU|?WmXG~zKsbQ>VW@2Pu=VRn5$j{6xiBHQfEn;Bc5@r-+P+(ACT#s#PX3=|oe926KF z89jIzpkg2F6`2sy%*+l7j0!LT1qTIYsGNesA|)2MmIaHUOaTW4n5clmg2fAACLx4H qunS9ogcmJPWQ5!B!G3`vGu-M8_KOsmfG(}iQexm`;O16fk59|bNr_L+&r8WH z$;{77%!$v;OJ-n@PBSyXDa*)Z$|TC50EEsA3=Q^^6c~Xt14FDdw=7uH1;k@u_+Y<4 Nff34PU`Uqc1^|0LAj$v$ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_literals.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/float_literals.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7ce5d39c8355ca57acfe5eedb272fe99785c5c7e GIT binary patch literal 1930 zcmb_cOKTHR6h4zSP13w$qOI>X)^}@dV|_&kG!@)+QQVGWJ1K+7OvyxT1ejFA)3-6?LJwh;z=(oS7Tj>cR^H=X~dU-~H~rhXKuQC;*_uLbpI^yWgXM zYh@SMWhuL^$n#h^Qk$KbGBpz*gVCC08!h9Wt|FumWa_ILL!l&f)iuMUmVt514YOu7 z8(O`-q24jA`=+Yd>KgUfs53}9ZdtWV-6mc*fxL!cFH<^H;8b!D@o~vUl<4lZhH11m zAVzJ>WPF%#W-=a+;&u=8rUi*ChD;?aNf^@zOA!SPLz<4$aBsk)K_(^qyO%;20a*cP z>nd5)AxD}G7qIW%?ltWpp;l8f>V~UdCmY`8mHh0LF^Zyqe<%GX&M{H z{4diqPCC*w6%)wgX*yQmRB|xkK{5@37mfr>{I?3 zDlB&538;#{{g3wMPDyvC%6GxR8^T3xooDMeOSvzi5{pKL!h_#LvQSjEKEC=Y+Qg>4 zw(XK&8$iH;oH$e&nXE4T8XK)pg13n)z{!Y2*h~kLE#XZtrmqJ$l7O5z|7X*O@1IP( KzVUWxGyWT-L^E>$ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/float_memory.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..05258ff16bc89f23b1929feea06daf07c2ad791d GIT binary patch literal 161 zcmXwvK?=e!5Jmqa8PhcN9^%4~EQO|9X^!GW1!oWkKL5SXV8|l@pvLx~ zmt^e$yJ3(qh8@ZIGgtT1@$j668&Ey|NI}=UOml(>I{(=6oN`*=>Xtn^>1O^6XHdY2kCMG6U7DgaoH)rKYGc(c4$xlpSVBpAv zF&Q~QqQxcoMX3x-oFHKco0+vJwK%nefrZPSm7T$nL7SO@8$@dYX#pVZ0<;eZzSLU+ f1%!YCP7M(7>pz1zP>2&G1ZHvbF#_4#954+49fKZX literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/float_memory.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9174f1c7b776064fd1c0af4b6a3b2900510b6a70 GIT binary patch literal 162 zcmXwzK?=e!5Jmqa9n+Y44{_nbLUkLwNDD>~EQO|9NsiKs7MvgseExfXH5lSZ0I1PD z=p#uzpmz*XN^wPU=F0W6y`10E@Br%9AIX+AF4G*KY?*(oc@8lwsP-KjcseP#(nsk= c`VOq=CUOw*Ip@uEVzw9xIY;ntEPt)(3yLHflK=n! literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/float_memory.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3e56542a049e005200b433cf5617e63b67fb8235 GIT binary patch literal 175 zcmXwy!3u&v5QhKRb<)kC=q)7Z6iKHrdJu&+2ok~d7}-O6>QXZ;eK7nV-(}E*mH^Pg z)}V*nvj*&pLB<$PNJvZ0uKV5g-Zd8>yZx1%)-ZIvhjE&tTkd`E1CsfXQ&hCE$aM?Z fmF!vm3DbCxu$FLoMcyBjlANZW~D21k5X}y_8@I>Ma;=t#>_Zjs0P5`K~ z9q1vMJHW0OWQ<`)a{j=@Wnb@ZLw^Q}>mMoTnx|n*FhS=ZTOLzR6I|J_M~kgN)ZR!} c(kH@uK8mbG+UN56_(m+KK`E9b6rKKk0e`g{FaQ7m literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.5.wasm b/lib/wasm2cretonne-util/testsuite/float_memory.wast.5.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d384d39e300707e00579c5969f78b7311002aba4 GIT binary patch literal 174 zcmXwy+X})k5JYE_u1$=H_!lDhBo?1U@k3fL1wkn^eH1VME$VKi1H&8+EQ=~#2>?xO z3A#z|C16JkGRAO5lDuG7Z`P|zQ=I^J9+14+)HQ8{@v8r9xs54y$i_R)vB%OP=q_Yu fvPXRgydPT;a}kHvv(M*?LX=n4Q<#!)aKph5AQ&Eb literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/float_misc.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/float_misc.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..351164b0a8ffedc505b68a2908cc3aef09e0346c GIT binary patch literal 578 zcmX|*X->m16oj9hmNZS+m$DS8C*TJl&eOC_sz_-HSrAC=34pTi`+5aVz`gJr8!UNd z?0J?y^0S^GB2gBu1M;VzlWB&jD>lKT&A2 zNQ^e;as7`rk7=MSINWHo8nQ-Pbou@dZApKr(3W*lXe-qpQMai<4RPjbKR141{L(F} z{uFuuJv7Y?PDjuY^cZ?#`YYqt#$SxT8hzHqvSK0qI#Pp1Dc{^|Y# DlvQuc literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/forward.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/forward.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7ac7c552c444e26f403cf100d61212c5cb3f85c9 GIT binary patch literal 82 zcmZQbEY4+QU|?WmV@zPIXRK#tW@2Dq=VM|?ElbT~U|`NqNnv2*(q+X=)_d9dP&WzJY;PKpZ&Ue1^o%SB0;35!9* zPY7}JIE)G(Z=-eQCxlke4Jwkcuvp@^RF;(`ewQi1$WSRY3X>n^kwuopLb@RigkdcF zval(-q=_4(o4x%#w^#4_!_m0zZ>-Z}d1UiEJ)T3l;H=~kaB7y)8F_pAgH9rJdJY0> zH4m}W6LSz)XQa~V`?tLgJ!u}eL${xqp`Axi@l+0B*{AaeIL}y0Co{vda}Ze1<{_4P zZVm$Ld8xG24A%^=e=z=%UMSykdlRpIYv1qCizOBCmcBIgQTlS~vvPgv1AoO*{9Ua5 zrjj^kW+4`OwKDX^li{GQ8opLeL(Z+w0;p|F18#GE79jJwmDZ?6T)8QChn{;g6@8a*h8Qy=&{rs5y!U8ttW@H4-gs$QJ35^ON()1^~0*4W+cc`5PT6LTi<%Z#4EgX^FG)=qy(@m;Hf1uB#7~111zv5*#fAQlLc9DuNa(BCa@+M}VTxVw)sPq)ezxpiFEF zl}k*hq5x(sr)<-=HCtS++1hr^F2L$P)NF&T)Co#Q2f*`-**}&gr+4Xx>X1roO{YPf4zjbL|b?7{M>}MM6EynUHkOAe|8#(Dzica zMXX~96Ch`uotm?OI-8a?T+(1Nx-5tQqpXo61n4KBk>oVOx3M7|?Q?9rP-i35*$8zu zg3o5|)FCXYP}MQWZv?f_E%`N%akn_@;Hg^2FftNTvi5(-1(10QMYT%u+<$LE)e|Hi E0ykHxxc~qF literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8916eb2579094336e567358da3e6e353dbca43d8 GIT binary patch literal 133 zcmXws%L>9U5Jm4KGm~Nv#I0Qjeo6nNA|pPqDM`d-f4xBw&OMidb9oT}I)oAUL(7OZ zTE2qYQ&(oYR4Y~He4i@R2L-W+hMs+z4BppI0!zJRGYDVPJ3}`ob%+hdu#Evn4D|TD Kdq!=&LtFt}85PU` literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.8.wasm b/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.8.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6826a72c9a9b70f8386c5c97e819fac54ff1fbbd GIT binary patch literal 119 zcmW-X!3lsc5CnHGiKhkyJMe86>BQhSf}p;1#ZB2L`qyobmspE1uIY~Vj*7ze2U literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/get_local.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/get_local.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..cb39060eec6acd4b843df6af0c1fe6b13dc35b31 GIT binary patch literal 398 zcmZXQO-chn5QVGyZ#q5T0eV%lHyf2~4gTysLK-tdAcG;gQ0VEtfF}qj9>JY5xtwKY z78xvf{iv@WMNtr*8Uz5GxdmWmC05Id3(O2KF~OD*MrJy-F=rSYqbwbukV>1vPKtMQ zC(o~&db)nR59{gb{0v|OW*30R;DHlj1UMmkunF7H9A>By;DkN~e>2PoaH4RBJ(|@^ zy#%Yaby&j0eV{XlnV@7#y|ukXHU6-NRa literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/i32.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/i32.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b516c4dfd026fdb00acb7ef688f4de8d712f7141 GIT binary patch literal 482 zcmYk#Sx&<+6a~=hAq_NTo`<4trp&_Y>2hVP0?lwW3SB`b1iUcOk`7b3R{*2+agjuc0|8}gRCBOl2p@}7JkpUIct2Yww&umAu6 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/i64.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/i64.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1eeba31df4f1fef71760d620e7cb503a1e0479c9 GIT binary patch literal 493 zcmYk#XHLUF5C!13#WBS67Ftk_L~nnHv8@EjC9%B-4A}H`FAl;@*j+}5`SCuD-f9Pa zkuU(PBm2CrS>0z8jollu)z$U?VT^I{H|`hf3O@`1QK)k;k*FBl2`?_s3J9x$bn+@_~FLpU7wOg?uI7$anIC{PY}s_u6J<^Ty^j^*iJ)xkv7kN8~YiKpv7OWyT5QX1RVq|ON)cI)(bQ5P?_zoh99-uuyVL4$^BeG*ia)BTSm}BHPIZPKl zKo8Iv%68f;Dngt$-y4odLa5p$0igGG2FxhvOJQb$&Y+Nt3Xn00j?|d!iP-w(y1N4n z+QsU6Q&$a)U2f`bf#hK2+sFwG#)#?)VNYH%o4?B1xtdk}Cs_adHW z)(x0HMr#Zm(YG%;sC0u!+>rUJ)`khCyYeJgCh_el>Cws#rt*wCN}Bo zHUtf_Fp^DscW-SzYU_*Hzgzb;XXH3C97k}yOIhbxY4>sb!Ax@UUs+zB^(N5QTS4%v cb5%b+I<&~(ttEpRo+W-In literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..44cafbbedabac1176c00766de3706f929aea1828 GIT binary patch literal 346 zcmaiwF^a-q5QX2HKQW16Ar|((orM;<%a*HAD^C#-1qCIT*u}Uf5zk=ftvrIGcCm2a z4R1ak%rKZ`8v>y95+0GTR2@_W5;>}oLLEz%a$?|T>0+KQvfNE~t8ET^1X4|yDsZa6 zt%A2L#(CWEwcKy#-PP|;_fH#Y#t7)ROex|u#e1Ik-AC|p8mR|95hE0udLkY2U~y^D zB7#NC^%!W6B1h*3t64^(91S4sk%cn)V_*OPdwB>- literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.11.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.11.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fd7a0a7965454fdd097633ff377774d493866c97 GIT binary patch literal 30 lcmZQbEY4+QU|?WmWlUhKXJF!GWGP84E@4S4%}Zur002zT1+D-9 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.12.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.12.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a67a230aa16745f38fb0f7f2428c613055117a60 GIT binary patch literal 30 lcmZQbEY4+QU|?WmWlUgTtY_k7WGP84E@4S4%}Zur002x-1+D-9 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.13.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.13.wasm new file mode 100644 index 0000000000000000000000000000000000000000..fc0333420aacbab37436d83f2756ed050877fc85 GIT binary patch literal 31 mcmZQbEY4+QU|?WmV@zPIXRK%9Wn?KyEiPe6E6q!0U;qGD5e6>+ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.14.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.14.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1b10aa7138f035af20a3a4c72622f34ef80a3275 GIT binary patch literal 33 ocmZQbEY4+QU|?WmVN76PU=n0xDM>9Z;YcgZOV-UaHez4^09-Z(FaQ7m literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.15.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.15.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1d576d9bd1add2f661601ae960c53700431b5fac GIT binary patch literal 34 pcmZQbEY4+QU|?WmWlUgTtY;ErWGP84F5yTk%}dtJG&W*j003wC2C)DD literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.16.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.16.wasm new file mode 100644 index 0000000000000000000000000000000000000000..01e697f21c789b5ad41f9f1a5e8746880ee446e4 GIT binary patch literal 34 pcmZQbEY4+QU|?WmWlUhKWndCyWGP84F5yTk%}dtJG&W*j003xD2Co1B literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.17.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.17.wasm new file mode 100644 index 0000000000000000000000000000000000000000..885d5bd9b212d3be421a93ee19cfafd1cc906e3b GIT binary patch literal 34 pcmZQbEY4+QU|?WmWlUhKV_*_wWGP84F5yTk%}dtJG&W*j003xY2Cx7C literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.18.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.18.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d6647f6b58fb03298663a7aaf7400a06012d302e GIT binary patch literal 35 qcmZQbEY4+QU|?WmV@zPIXRK!uWMnBxEiU0mE6q#R%``S*U;qGgz6UY@ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.19.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.19.wasm new file mode 100644 index 0000000000000000000000000000000000000000..55f8407e748c00be75fd9fac9ad0fa72f7e9040b GIT binary patch literal 34 pcmZQbEY4+QU|?WmVN76PU=m_vDM>9Z;Y=&dOV+i^G&W*j003hv259ZVM!~^OJ-mI078)kEdT%j literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.20.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.20.wasm new file mode 100644 index 0000000000000000000000000000000000000000..83d154c9d5f4ad28dadd5d347acab6c7b335f957 GIT binary patch literal 35 qcmZQbEY4+QU|?WmWlUhKXJ8UyWGP84F5yfo%}ds`%QQA(U;qGb?*{Pz literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.21.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.21.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6072362cc60810f621a72d931330c0638da21012 GIT binary patch literal 35 qcmZQbEY4+QU|?WmWlUgTtYs2nWGP84F5yfo%}ds`%QQA(U;qGbMF#Hx literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.22.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.22.wasm new file mode 100644 index 0000000000000000000000000000000000000000..dd6531f10bf2356143f91e3ecb4f80f411be7b68 GIT binary patch literal 35 qcmZQbEY4+QU|?WmWlUgTtYZ>lWGP84F5yfo%}ds`%QQA(U;qGbS_bd{ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.23.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.23.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1104039dad71f1c3b81535ffcb3afdc81f56860c GIT binary patch literal 36 rcmZQbEY4+QU|?WmV@zPIXRK!uVq_^vEiU0qE6q#RwaYX%VqgFOe69Z;Y};eOV-UaHqx~N5)2Fgf(Hk4 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.25.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.25.wasm new file mode 100644 index 0000000000000000000000000000000000000000..76bf13a6c0d60d90589926418439114bf3fd5c79 GIT binary patch literal 39 scmZQbEY4+QU|?WmWlUhKXJ8UzWGP84F5yip%}dtJG&a(;0}>1j0FciI^Z)<= literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.26.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.26.wasm new file mode 100644 index 0000000000000000000000000000000000000000..dd4e01d42dc6b7ba83158344d15147ba43793075 GIT binary patch literal 39 scmZQbEY4+QU|?WmWlUgTtY;EqWGP84F5yip%}dtJG&a(;0}>1j0FX}y^Z)<= literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.27.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.27.wasm new file mode 100644 index 0000000000000000000000000000000000000000..23488f40a8777ed2f66246c8442492c5bd883ad7 GIT binary patch literal 36 rcmZQbEY4+QU|?WmWlUgTtY;EtWGP84F5ybg$xlkm(akhAVqgFOc})j; literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.28.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.28.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7b046aaa672cbefe0c15a68e8625c10c657da97c GIT binary patch literal 37 scmZQbEY4+QU|?WmVN76PU=n3yDM>9Z;VDT>%1PBVG|9Z;mJ+S%`d9dHPX$@OJiUF0Dh|n^8f$< literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8d7f4e4af60e1c7e48f1e97a45a4f29fa69a22cd GIT binary patch literal 34 pcmZQbEY4+QU|?WmWlUhKXJ8U!WGP84F5yTk%}dtJG&W*j003xt2C)DD literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.30.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.30.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c2c821468ffd90d7101995e9d8029c6cbe2232b9 GIT binary patch literal 35 qcmZQbEY4+QU|?WmVN76PU=n8JC@x4%E=eseVN1`+PfE;TU;qGdI|r!% literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.31.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.31.wasm new file mode 100644 index 0000000000000000000000000000000000000000..638e1684612483b20fdefc62d40e5ef91b8980bb GIT binary patch literal 34 pcmZQbEY4+QU|?WmVN76PU=m{FC@x4%E=eseVJ%5a%1LEl003yK2R;A* literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.32.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.32.wasm new file mode 100644 index 0000000000000000000000000000000000000000..89bfb0a9bf051d5f46c2f51a202f0e05c2c82587 GIT binary patch literal 35 qcmZQbEY4+QU|?WmVN76PU=n8JC@x4%E=eseVarX;%`d8CU;qGd+6U19 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.33.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.33.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ab0911f6fab0c527cd2f10db7105923b999ccb2b GIT binary patch literal 197 zcmZQbEY4+QU|?WmWlUgTtY<1_<0vjjO)g0-E@4a0$xlkmVXkMOo=`2OSv3sItSmqm wFt97Ku%@S$=o&CEfN4XZqd@!$1|~3F$-vAd%EH2+%)kvK7(oORh+yUh0H--IaR2}S literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.34.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.34.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e382fa9b0f2745e97c381fa9539448e6ec2ea48e GIT binary patch literal 30 lcmZQbEY4+QU|?VpVPq*uEiU0o&&f|p%+bv>He#-4003hN2NnPT literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.35.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.35.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bf0e2b1528426308e7d2489f93b8582bc75e70d9 GIT binary patch literal 30 lcmZQbEY4+QU|?VpVPq*uEiU0o&&f|p%+XCVHe#-2003h12N3`O literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.36.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.36.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0bd2ff71c43675c84e39506d9609badf07f148f2 GIT binary patch literal 27 icmZQbEY4+QU|?VpWMnBxEiPd%&CAZqFVADHX8-_Cga)(# literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.37.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.37.wasm new file mode 100644 index 0000000000000000000000000000000000000000..82f7f25d0a18a808734d6cb9bc5c90bfeac375a8 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPd%&CAZqFVADHX8-_l<_Kp1 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.38.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.38.wasm new file mode 100644 index 0000000000000000000000000000000000000000..cd5a9c959feddc8557c46be05143a99523e5b973 GIT binary patch literal 24 fcmZQbEY4+QU|?Y4V`M2wEiPe6E6q!0u4e!MIiv+j literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.39.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.39.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4760358307c087d21a858807389bd4a940a44486 GIT binary patch literal 32 ncmZQbEY4+QU|?VpV`M2wEiU0HNleN~)ipHG&CE+ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.44.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.44.wasm new file mode 100644 index 0000000000000000000000000000000000000000..27aa8136efeeb19e8dc618b28e6161c149418c86 GIT binary patch literal 90 zcmWm4I}Ua>Isz=*uc_^G`LHvtJ9CM3{vb+wsl5ZgllbSi)&aB!%Dw$+?^Zhh+U hE}4~X*G7MHwlpc00xXvB6h-AWJ>$X(h=G@%Q(oC%4Eq28 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.45.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.45.wasm new file mode 100644 index 0000000000000000000000000000000000000000..27aa8136efeeb19e8dc618b28e6161c149418c86 GIT binary patch literal 90 zcmWm4I}Ua>Isz=*uc_^G`LHvtJ9CM3{vb+wsl5ZgllbSi)&aB!%Dw$+?^Zhh+U hE}4~X*G7MHwlpc00xXvB6h-AWJ>$X(h=G@%Q(oC%4Eq28 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.49.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.49.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3cf0083692a28d5522187d3c699aae1a5b04acb7 GIT binary patch literal 33 ocmZQbEY4+QU|?VpXJjc!EiU0HNleN~)ipHG&CE+NKGzDEiPd#NleN~Wh`Li5&-~iod;_G literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.56.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.56.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2a6b158a1aa9910060f48a85ce6a1aabc00882a2 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPd#NleN~Wh`K16#)QllLux1 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.57.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.57.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a098343f396baf0c4502176a215b7ab483661c99 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPd#NleN~Wh`K15CH&gi3ec- literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.58.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.58.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bf2cd933c84051f581046235381f96caf1df49fa GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPd#NleN~Wh`Lik^}&5q6csQ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.59.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.59.wasm new file mode 100644 index 0000000000000000000000000000000000000000..60cf1991dd29d3738e98402af8ece66d804efef0 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPd#NleN~Wh`K1l>`88mNKGzDEiPd#NleN~Wh`Li;s*e3mHexJb-~s?@3I`AX literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.68.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.68.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5101c52d66b73fbcad66e1df54196070ba292079 GIT binary patch literal 33 ocmZQbEY4+QU|?VpXJjc!EiU27P0h_Os?;^o&CE+^*2z3Ae literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.70.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.70.wasm new file mode 100644 index 0000000000000000000000000000000000000000..551315ad8233d4773f0b20536f93da678981879c GIT binary patch literal 73 zcmV~$F%Ezr5Cp*8({RW&Hujc&!=Ff$PJ$5{E57awV$B3V$Ag_+7W(?6v!&WxPPx3F YkTM9Np5tBO1%!!;6`R0Ji9HzSAO8gle*gdg literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.71.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.71.wasm new file mode 100644 index 0000000000000000000000000000000000000000..551315ad8233d4773f0b20536f93da678981879c GIT binary patch literal 73 zcmV~$F%Ezr5Cp*8({RW&Hujc&!=Ff$PJ$5{E57awV$B3V$Ag_+7W(?6v!&WxPPx3F YkTM9Np5tBO1%!!;6`R0Ji9HzSAO8gle*gdg literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.75.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.75.wasm new file mode 100644 index 0000000000000000000000000000000000000000..01e7c8deb2f995231c3aa6283127e235e6ea3355 GIT binary patch literal 32 ncmZQbEY4+QU|?VpV`M2wEiU27P0h_Os?;^o&CE+^^2z>wm literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.80.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.80.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9c81a86595df6f746cb8e5632cb0e2d3d085f435 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPfpP0h_Os$^nhWC8$hy$5pu literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.81.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.81.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4d0cb1a9d4f6d472673254295726db393750765c GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPfpP0h_Os$^nhU;+Sdy9aUr literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.82.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.82.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0fd00827c512650270b5f27d82b75f68de954576 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPfpP0h_Os$^nhWCj3lz6W#w literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.83.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.83.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1d55b9bcff678d277efb014e255330b94424c0a2 GIT binary patch literal 31 mcmZQbEY4+QU|?VpW#lL>NKGzDEiPfpP0h_Os$^nhUNKGzDEiPd%&CAZqFVACQU<3eh<_G8i literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.86.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.86.wasm new file mode 100644 index 0000000000000000000000000000000000000000..969740f29a7ac7551c10d5d4ded6f25529495c7d GIT binary patch literal 32 ncmZQbEY4+QU|?VpV`M2wEiU27P0h_Os?;^o&CE+NKGzDEiPfpP0h_Os$^nhWCQ?jya#ds literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.9.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.9.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2a48ca29583ce4245deac137896095a7dabf0eb9 GIT binary patch literal 32 ncmZQbEY4+QU|?WmVN76PU=m9ZVK2?g&dV>)V_*OPST+Wi literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.90.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.90.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3c7cc625f820e7439e60f04b0bdba1df8f3aca80 GIT binary patch literal 28 jcmZQbEY4+QU|?VpVq_^vEiU0mE6q#R%``S*VqgRSQMd)p literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.91.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.91.wasm new file mode 100644 index 0000000000000000000000000000000000000000..788006a408489e88f485b7c8d743b9e72079b219 GIT binary patch literal 30 lcmZQbEY4+QU|?VpVPq*uEiU0o&&f|p%+bv>HezC61OQ?Q2A2Q; literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.92.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.92.wasm new file mode 100644 index 0000000000000000000000000000000000000000..de0a76f947b68ec70adc83506da19e0000008388 GIT binary patch literal 32 ncmZQbEY4+QU|?VpV`M2wEiU0HNleN~)ipHG&CE+NKGzDEiPfpP0h_Os$^nhWCQ?jya#ds literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.98.wasm b/lib/wasm2cretonne-util/testsuite/imports.wast.98.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2b92957d10272188d5e24a0fec80601105bd46f1 GIT binary patch literal 63 zcmZQbEY4+QU|?WmV@zPIXRK!uW#lL>NKGzDEiPfpP0h_Os$^nhU}gpq>>P|N=|%bF Q3=CWxjBE@F3=Rz30FY-3H~;_u literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..363ee730eb2b0ab82a57cc9898c4a75ef186f1ea GIT binary patch literal 200 zcmZQbEY4+QU|?Y6WlCVGuV<`JV5+NQtY>Cr0g5rQ=dws-8XM{5<;SPx=cL3Z=N7~l z$LFV|6{nUkFkncP!lW1_GR;h|>te!?g6m@DvSi_9P+)Lm%u-+k5+2;hJWnvsiLpk3 L5l951@Itr&c|$X5 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b764f351817e745ea824fab5357307d72e14adfd GIT binary patch literal 61 zcmZQbEY4+QU|?WmV@zPIW2|FlVq{>KWt7Y`GttY-k59|bNr^8nN-T&^ttd&&ONlRL QVBq3lWMfcZSiXiE0Eo~IH~;_u literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.10.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.10.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f83f6b8d3dc83049e3eca9413401db0840036534 GIT binary patch literal 92 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@chwWY=X9&NMdC%gc{X%g;%PFG|ggFOD~2 dU=YqUGr7`_r#TUmLFfhP*r9d7d lccz&MT#gCO1IaOSDYCFLC@?rO7`_r#TUmLGcdq;r9d7d lccz&MT#gCO1IaOSDYCFLC@?rO=W;{nJTUFVTo0uixB<+o7Z3md literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.13.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.13.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d07ab17528f51e82e866397d33ae788e7a181671 GIT binary patch literal 122 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtS^+aAz7D>7`_r#TUn$GBCh-r9d7d lccz&MT#gCO1IaOSDYCFLC@?s(=5j;mJTUFVS`VcgxB<@l7a#xt literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.14.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.14.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0df895aa46346078cd4cb1b97eeea8f852361968 GIT binary patch literal 122 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtS^+aAz7D>7`_r#TUn$Gcdq;r9d7d lccz&MT#gCO1IaOSDYCFLC@?s(=W;{nJTUFVUJs=kxB<~i7cc+- literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.15.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.15.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ef7d53fd6c88d06bd86138988d0d25865cc62302 GIT binary patch literal 122 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtS^+aAz7D=@q5s#uvvMGcdq;r9d7d lccz&MT#gCO1IaOSDYCFLC@?rO=W|2o0x<2w+z6$cxBKWt7Y`GttY-k59|bNr^8nN-T&^ttd&&ONlRK QVBq3lWMfcZSiY7U0Ep}kI{*Lx literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..df82315c1faba269feb56bc4b0444aeae4d1c4d5 GIT binary patch literal 174 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtR7Ih-Mla>E-3er{(9Q#207e0C7=# zF#`iu@lpmx(M&TFEb5uCikC7lbLp~hF(@!NGL|?pmU6?HWniWgW1ACWJA&E44FHB^ BECv7o literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.4.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.4.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0ac75120194976576a507dd4230f36629da528cd GIT binary patch literal 174 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtR7Ih-Mla>E-3er{(9Q#2052#TNs~ z90mpq@lvojqiCj?2^RHC7~&B1%v`!GTnq{fj*O*_j3wL#Mth{*ajALV(b7h E0fyHs1^@s6 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.5.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.5.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8321359ba15df4ee42556a92f9ced07e4a9cf69d GIT binary patch literal 174 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtR7Ih-Mla>E-3er{(9Q#HVDI#TUot zmgX=pV2GE3#Ti92%}lVUXTlJNsAuNVW#M8_U~pv1b!5xohA{JBOeeN_C$>7Us1sWQ GhzS6Hw=3cR literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.6.wasm b/lib/wasm2cretonne-util/testsuite/int_exprs.wast.6.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d29c13d5157703ae2f8c89c8c37be38efaa497c5 GIT binary patch literal 166 zcmZQbEY4+QU|?Y6W=deJXRJ?PtYfTWW@Q12FtVqzh-DfZ>E-3er{(9Q#HVDI#TUmH ur{<(FFkncOLL?Z)GR;h|>0rW;fa+l8QeE-3er{(9Q#22OJ#uvvI ur{<(FFkncOLL?Z)GR;h|>0rW;fa+l8QeE-3er{(9Q#OIdg#HVDI z#TPR$U==TAU=+&voS6q^IReDmJSP*`Syh=ew}Cb8o8I6iUtqZ=`d@;-9aZS!KSE!BC5iaR7dhUFOx+c&x3g! zB^iHfSi4Q@GLQ=CYI1ei)-wQ6&jdst%ajX!z3EorJ~ouT-T`P|Z`R+T-k}AXzTS}| z`A$6tYeT(b{ad&idC#wJYqE_Ac`Mss{>!5PJvB|U1Y+5h0 z`YZub1SPh#GyCT4%-fy8)#i%;klbBDx=ZP#OHRf_5Mf*F+xsqKKU+1|p8>yWny+V& zXs2+|o+F9&0%^2w;a$Bzmeh+W_Oi`gy}iE0C~N22Z|xfU*{uCoFWw;Uoik|fOi}dC zIS$;c#o_+u_Ufj&tJj}!RJ8M(`J!Ie+qzlfIDhbCh3kS82vB_~(gjS2BwdLh%L!=w zyR<4%I4wxgizhjSDA}Wdy-N@e{Uvc}i6ciQ5{pa8;#^9+XK@*d=SV14sZ8%>5K;w6 zD_-D-pTHED>|Y=s9y=pX#%Fx0meF-|)!AI6^muopoN_zjHsZ%SoP5K}eyh(I z?PrXQV~7pZ=cbr#+uUo_w!g|VUFijB|Y@^$# Po^WG{D4mZ0U8?>B1TBno literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/left-to-right.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/left-to-right.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..29a2475c866eaacfd301d1d169b28e01f679cce5 GIT binary patch literal 3015 zcmcJQS63TX5Xa|ATJ1_J$nJ4Di8<-TPH_^0gK@=+9rqIVZq*gYmIO#zf)f>to${Ri znEv2TmyeK{xm!kl$Xj&a^8d}wotgW;3qZNsQUIWiPLxukks6g!!y&DM0j>Q$t^g&V zA8Lr-1N`n&Do`dag8?r6KBS+jO{}C;DwR%WG6sLL*{orhrfC?KWf?{;Zx{4~|830} zYDHnMv;t*5vx;V8X1Z9e)!AI7>1AMqxC6+ULs_F<1jWtu(gGp(hM zawdufus4bZ@CnOB%AM7ca`#2i0Oq1-0Q+^<-g3u>19|GvB|QfR?HKmV!Xe&KEjOER z*rf*~i;Z@z(ebN2IHGs@O@eu|F}s(YJj#lk^jMLT3pskKojexn0FJZPPJSBe08Z#? z=;X;P?L_}hafqBets9_|XLyTt@@%ZTaE>>jljm8WlNT&1!%kk*lHB<$iUx2giUx3* zB|CXVOUk_(MFY4NMFY65yRwrv^3)@A@}?aFI(aLhnQ6Gq``On!E~Sn8dRN!czTPwI zXgs~4_gRszMONf%i52<^OAGncB1|Q}* zRS!NgDxG4Z4jals(JNP)K0H>rNd+F*7N}G@V`Rm&-g?ZfjERIF+N19R{gn82o2G z3R2l5C`)IbD~-m*Uj=Y1$94)2b~yO&C4`3(ni5(P+7gx}tVmdu(2>xU(37wxA&_uF z!bu6IB%G6QTEck=XCz#Za8|-a0<@H^!WUH#x-RITB8q%;UPZvyfVVl5E&$b;i~=uA z#XTIy?MoO)7)lsP*pTp8!lr~L5}r!pUZc4Z&;g*E! z5^fWOH{k9|3(ws{@&??)d;?MD5xy(F546~MExHeaEv`!Lg92NV``G4e|1NHi@V$f| zB>X7h7YTPH+?8-oLQ%qf2_*?XN%)x{JmdDc#gGxcj+ z6yY}szf1T-!rv0g5-Jj^5_}0Y33Um7O8AQ)Jmb`UzHz=soYZ+EIwQf$ZaO(51zzDZ dZh_QD|K@qHI>8zCHjZH&|Kd$51vd4t{{_&4-24Cl literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f9137a286a8e2a6913d63985e87228f3d64069ec GIT binary patch literal 43 ycmZQbEY4+QU|?WmWlUgTtY>CsVqjqBU}Q;7%*kP3;NoUtVGv;CW?^t-;syY4w*+ed literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8d2a6f57629d0d977b4f751c574c99da9ee4bfe1 GIT binary patch literal 87 zcmZQbEY4+QU|?WmWlUgTtY_k8Wb#d8NlwhkVPIfpVFt>uD>AeDrs+Y1ctDf_jLQg7 V$iyYU%)%hRzzrmrfrKM7HvoX24c`C& literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.15.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.15.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4616241323dd4671fbf3fdb250f17e304e00a2f4 GIT binary patch literal 71 zcmV~$%ME}a6h*;vA3uS_Sb_~ugq=vDJBbSmpmk^H-w>dvQ&ZEK2wqZ1tGRIx!#{7o UpkyU>8q~Upqtt@T`4H2Re=Ohz-2eap literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.16.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.16.wasm new file mode 100644 index 0000000000000000000000000000000000000000..5d1cf1eb973c72f36eac9e966b0bae0f52352430 GIT binary patch literal 83 zcmZQbEY4+QU|?WmV@zPIXRK%9W@PfsWy#4;OkrSPW@2PuWoBexWEWy$_s!LVh(aV8 exi}fw859_F85p>^c^DZSxwzRreEjtJ%U1vt$`B3! literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.17.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.17.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f61d36722bbca02e2f012d85eaf4f53fa3ac4ddc GIT binary patch literal 70 zcmZQbEY4+QU|?WmV@zPIXRK%9W@PfsWzJ2_Wny4tW@2Pu=U`;X$xlpSVBq3pWM@!d a&}CrY=H_E$aAf7?Sg>%>;w4L$Ee8N(i41lC literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2aba3e42d79a1fa9db9a9337f80e17ff6b4ef7d1 GIT binary patch literal 46 ucmZQbEY4+QU|?WmWlUhKXJ8UyCoWME@stY>i4;%4V#VoA@*Phw_ZPERdiVBlh7WMNQd G;06G$js|)F literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.6.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.6.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7b2e142a3bf48779a10c6cbb3ebec580c9b87935 GIT binary patch literal 93 zcmZQbEY4+QU|?WmWlUgTtY;EsV)9LANzchoVy9JRUG g6T7jabW^NGNbrHMC e?5V5Jj(n@fLFl8%A^AUvvpLy+59$3b5N4Y(gK;2m;+++l*7EJg^J{ajrWaaCQ=h3Wkw0G_n~1#E?l zO?r`WwdYhTw#6A#Q;L{#p?kV(8rR2uK(`_PRezZsgBTDlBE;#YhQZ^2j0+-taV6pd DHX{;H literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.9.wasm b/lib/wasm2cretonne-util/testsuite/linking.wast.9.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c881d376680b525a3a27db866688bc2586350cf9 GIT binary patch literal 82 zcmWN?%L#xm6h*;vU*azTL2STQEWjR&Zd^nV%s#E_Y=-pS0u*dVbyroj*=#Gi;LnwG d=%I1WP}j?!DQ9fx#j0o`jOJO}*s(A{`~d#y3KjqW literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/loop.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/loop.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3e69bf667d73ea91a92e440003e0d04ca1df9229 GIT binary patch literal 1060 zcmcIi&yUhT6n<|8D9dZv0*h<*)E+z;l8y1c3`WnMJZLl&7zDSJrWDO4Tk6ron;ZQH z^$+n)Tbgk5V)-%O``-7x_hzOL>^2AhIA}}(rhxer4|tf0EW=D*3^;T02qCTC*l+4h zoZbPpckAfmJ`55N&CNbc*C1Pw-lcj0jmARj7@BUd8||YYxf^X`odnSWT0N#_>l4m8 zXD(%Dt!np~nQu37km#B`>&y}z+>T~ULWj!Wa}e$|P`8W{9qYghU{~pN6zK$<&U_oC zqcZ4Pdb!l|bO-0nFROK^!IjHx0zJzN&s&0i=UZzafZ8C(#9tx?NHSLz(PPFglpm%_ zpxQ)3khw{TF&V83>WrP=k!g|tj~{pVh1}xbpAOH$E{9gsE1@hWZBR&c@+Dn@%v^W@ zFy{T&=mS0H&B?KiHUN=<3LQK`=t7OLEBMXfj2aN12yb8Yjg3PxRVqRaY*ZHt9K7O% zYh9@!Gmn(1r7l%jMh)4tM9dZV;1y<(E3^gEpN$(kJW^T-dl_9K-u;8nRp0S}k2{Vb3VX-uCi^bx|MyCG&BO*r0D|&)>n4piw=D}-+ z1s)!JUM85a!3dF_q9{}o;fq2kJb1%e^n}rh3Eumn(!NEJmDTvp0Nxqd(N!~|K<^#t cNl*On`OgsJhpLM7;3t#j*1z67RAWf`3l;vdZ~y=R literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..93b6da7662850205693eaed349c8fcfa08400f8f GIT binary patch literal 14 TcmZQbEY4+QU|?WnVFXeD4^aV5 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e47e0cf8d471811f2d7a9e1fb7d07ab015c58f72 GIT binary patch literal 14 VcmZQbEY4+QU|?WnVPs@r1ON|G0Z#w` literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.15.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.15.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6591570f72fe0964d2a0dad182461854f82ab4f4 GIT binary patch literal 55 zcmWN;Ar62r5CFklXlcSditrN<6^K(c1jE;1hI6EV)dSyol3M(=uw~D2JssA?!bU`s Ho$&qux=RL# literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..64ffe0388be981dbeeed2b03985a55a4c5258356 GIT binary patch literal 15 WcmZQbEY4+QU|?WnWn^S*U;+RUssYjf literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d3e64161468541fa78f435bd9d7243d4b0904fde GIT binary patch literal 16 XcmZQbEY4+QU|?WnV`OA#XkY;V6n_F# literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.39.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.39.wasm new file mode 100644 index 0000000000000000000000000000000000000000..77ba1f041e07a1a024c1751761567ebd9a3df91d GIT binary patch literal 33 kcmZQbEY4+QU|?WnVPs@v;ud6LaAe?SPE1M$(!aAf9YN=#yKWZ-5>Nd*8j+XI*Y literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.41.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.41.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8b535ce73c7ddbdf4c65d9d79719bd8f860d4015 GIT binary patch literal 35 qcmZQbEY4+QU|?WnVPs@v;udCRaAe?SOk{9m;$}=@aAf3WOa=fyP6L7f literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.49.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.49.wasm new file mode 100644 index 0000000000000000000000000000000000000000..60ae51fd8b28e2ff62e4643a3c0d2a53343088b8 GIT binary patch literal 35 ocmZQbEY4+QU|?WmVN76PU}j=uU}XmKxwsfP7#taN85pFv0XSd+XaE2J literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.50.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.50.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ebf4341625bc5ea73d0354b3216f92dc08625ba4 GIT binary patch literal 35 ocmZQbEY4+QU|?WmVN76PU}j=uU}XmKxwsfP7#tb&85yLw0XS{~YXATM literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.51.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.51.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2da5f34088248d0ba151ae9abd7d25400957c3cc GIT binary patch literal 35 ocmZQbEY4+QU|?WmVN76PU}j=uU}XmKxwsfP7#tZim>8tE0XR8tE0XSI#X8-^I literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.6.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.6.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f9528a2f711bce5020e44b08c6b80b77639a9cef GIT binary patch literal 23 ccmZQbEY4+QU|?WnVFUthc18wA25!bg03DA45dZ)H literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.62.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.62.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b59b22057569e514dd964064f9baf55a87171c45 GIT binary patch literal 685 zcmZvZyH3ME6h!ClZk}tBf=EzqIwal!!W(%L1Qj$Cl*AB(A_6E1?hxirDDw%Fhyqbj z^Eq(WV9SxXlI1x&bH`d4!SIFv(4^{sB7Pn^6tSR3dX&|QvjmTfCnpJ1c%h2w2KNC_ z2>QLN+siJf6y7FUsEc5D52<@AjkEq;(A_v2LS-mK&P%-ICY$*zhA8M>b2Z&8W++6- zOT2iqoW&42MgH^#3wj3 z{TKQ!B&mUJlZ9V*|V>U_Gc!-LkAbX!m) z9{y{2;SOHk1Po^3I)tI~|2j5ci>;66XkD_Hm2mnsQ2svuz<8?KmReZnyHxG_6K%67 zaiy+s-e8NdI5&=b$v=0S*j=D8TugDt?JlL^7?)Grb-Rr;9OFugdxG6nku=A+#;9v- SK<8|$J=%w@X?1E(j(z})d~)^x literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.8.wasm b/lib/wasm2cretonne-util/testsuite/memory.wast.8.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1d5ecb7b107e5c958b16aafbd1762b79b27fe6f4 GIT binary patch literal 35 qcmZQbEY4+QU|?WnVPs@v;udCRaAe?SOk{9m8cT^mfK;Uq-~*fyU*TP>6IjbT ztC`uc4BKxB03FF1s6hkYT}=ZG*{lGO$S9Y@HG3+`ci#o$Uwro#A2#hhu#CQsrjMcX zCI-_4>p;gN*?ITeI@`vH73Ih}Z{6faR%~t4ggeM`8jNi=wokeF!8aAB(0r~1w47i* z!Qlw%1p1H2Z}|iJ8#c}_1=Kl-VVqXbx!!ZVrjVqqs6s`L>WpZ|+h0E)pCV~$&Dwun b1tp;u>LTUHg=8UhUe#+hSER2JAiebq`u0Eo literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/memory_trap.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/memory_trap.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7171e8856684b398d90b693a69d1c60d9ac2b6ec GIT binary patch literal 113 zcmXBIK?=e!6h+bZ{!f%P1zmx3C@#Qh?Mj-0MUaL-Do97aOSjb&3hnY7s ze(%}H%NYScdUQl;Qgc{wSaMj5jJgJc6^A8<1j!8hAZzH7B zB<_FA^D#iD=|Gf(7RohfpQ73p;ID!SU5--bq`kf*Yu;>GayN zg0E!~v(}(3D{Q!YYxy<*oM6-Cb40!6^7o5++vOh+^^T|SXV`5%&;(J6Jx{l1*l%8_ zqJy$+7ek$pbm>nz76FbV;hK v8@5Aunf7kAcgH4*^q$QU)r0mPwfDq&{bK_|_%wfc9Z!n4cMO03%(eUnNr-Y^ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/names.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6e34bb08ade7aa8df7fe1137d25695d04ab90f61 GIT binary patch literal 36 rcmZQbEY4+QU|?WmWlUgTtY>CoWMF4!WKPS^XJFuBV`O1)WZ(t>QX2#3 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/names.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c6ac4b4d26130d129c85309670546552139cb3cd GIT binary patch literal 36 rcmZQbEY4+QU|?WmWlUgTtY>CoWMF4!WKPS^XJFuBV`O1)WaI__QXK>6 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/names.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..cadc3c568dff04cc449a02fb458449116f1a96cd GIT binary patch literal 7488 zcmeI#_kSDreaG=PaI`E-^4@mh$noB?<4h;9-JK41rX$Oi-8yya(KhKwGkhQc?s41` z1h~LWf*asE>9nPT_VSj#@|4F>lK1TA^KSc7AN>dVs2-1dzHrC;-hFYv9Vx8+$zve^ zq0>SSAoQg5v~}2$)&KwZfA9*N{_$#lxS=%&g>PAdaOi$mp{tP>x(BCS`Kv4Mx$5d` zest}P*WYmeO%L4it)Kt$7vKNk55DomyRQ4@x9`6B*4tKNnRVaV`*8a5?>zqSBac7w z)1P5^_>qTk#>t4SU z=UOH@i1Vyqr~v2ZJsb>0aDnyk!;j#?b7Xc_-f7EDUw+1lm8;G?>ug+f&N=6vcm4$z zUUcy#mtJ=H=RSYM7jSWS^^>?{wP$+~FW)QhqF&4^^oqPb(Z9(QERWy%w+4YxCk>yO+RamjrG)aK{37Ja8uhcQSCN0(UxaXK=av zC=hfHQRBpnQ)rwb;}jdG#5kqKDKk#F zaVm^cX`Cv2UWPcvsWwiHacYfIXPkQDG#IDRI8DZBHcpFiT8-0YoVan?jg!C?%Z!sW zP6xhlq{BoyO{B|2x^dI0CC9c;C0zIID!FzyN5bFpt8#^YVXie- z?oZ~La_zYyf6(vo2XeK!;#^&>(r@(J{aJt1ACoqVZGMBFT$^jm_4$>4RE9SCZmuJj z$l3mME+RwI{)9iAYxc)w=d2%>L(D&#i{?uG78#V1SA%|$>^SYu;H~C52vg&9oaWs}Z z7V8cclepCiGB$1#qga!7DjBtLyQF9ocZB_7d?~a7cUs2j#+T(|?Za0>XX2~jGgshi z;T31%>!B;~jnGQm65sV!YTZQ z*pJ_|OnEnc%QD^5_-)xTj^DA&NFTl_r+@bfmnn4#9I8JScg9n z>+#281O7y8#Gi^y_%pE?-xXW%=VB}VLTtldigEmv*pBas34C8n;s;^}ekgY0M`9Q5 z7rXI**n_oV3J;3Ccu4HSUyJ?t8*u;+i-Y*FIE23yhw+Fwg1-|-@%QpB7V)U);<3D? zgseQSaq&dn(x|NcgT}=_<}JCh_D>oYKgl~$C2K#`xcKM16FTl+G%o%%?}U!~H;s#* z<(<%R|E_WI^SqNf?iU&tzsx(S<9?-a@nqgf9cPEME+SGLna{c?2<4s9u~F7VOsb;` zSr=_7X{6-c zCL9O&XJ-@CF-GVg%D-K4!!MWW*@4q3}i%4@a+(-+N_xhzOOlFVx1=wIJ(7VK_DY6g*e4l@VZS6D!vV>73Yaw2hwApw~lf-dJ+{WvgB;JrDlXz3oS%kMF-AM$Jo-Qm& z`s#5)(qD#?nhut)>}unikY+TG1dW89}3F6iu2mnl)o+(Tt;2 zGl4eEB;uMWv}>l3(99sI$)H1%MW<#KU79&`OXNfC30ueQQluocBj}aXm!MD5l0?5G zX=6asg+WP297B@Md<;vv1~4M&?!>61rx0mLDvB{lpN;Xb6)cQlLgMCQGAuWpEw`Jt z)W$TM#EiDwMn)@NudKGF0khgV8*^Iu`pj!(HVaz$$}DPAHe5D|by_*=dhL)6Pn))} zfsJ6JcHG7$X|)~0W^H}}wrHch*s7Ih$2M(Y0k&(4ZS2s>e0?^JoVHx9^_bKgo6`p2C@r<-auG_QH zhTeKSr|mDq^V-2)yr5mE!x3rgSOiDeZWw91d~C<0iJ}x<49g>O7%zpxJ6rLxuB7ow zIP4AJRaucY`kEwv0>@>4r(14US(VH6hOU;U@urL`#9LxD0=Yxxi?O6B!3j+%PHM_< zN>h%vH5GVAQ;ByqRd`RbSd8~ICHO#7iVrnq_()TZk2MweL{o`RHC6ablYwnXvWRGA zk*}FUfo2|2OMcVHb10^5mHSoO)`lW&yaC19b_XSvWh$mos+mQZq`wX2a*W0pDkPNy zsI=NbH|&_*F~3yTShW<7P0BJYOGcJi6InF*l_uVA;)5nJ8ce!DW;)2W1oI`qLh@8n z{+fLq6J(l#OeV<82ATOFvlwLUAe$d#qd~Sf z$hHO9_F#S?ScnG;UGjS{F3W;9XY6KU$BjK};-jdN30}V=yJJo!SSgd5kY!4i8CkL> zvS9KnOuWy;2TWolm|P4pQ$e;lm@f_%5;8&g!MKS}nncPZ`bDxnQ9+Sm>0=waPN*$^RajjNNAJ ztcj1nv6e5z2g+pWMzvn}fil!sVQ&Go)@iq!Nc0ZWS=Q~h-GO?`Ix;+g2FcVU8s+1d zSU{5{&zmtc%Sa~{K?^&DR(2X~+SCH#+Uy+KE%`el9|3+TFmsHeXKvRc7O(%vlgBZ3#mtjO+J1LB6T9B5r zy7L&bLU&`_3VSXlGE6bHL zloiTKWtDQKa+Y$oa*lGYa-MR&a)EN8a*=Yea*1-Oa+z|u@;T-6$`#5Nl-0_W$`_TZ zl&h6%lxvmilE*s(elPy7CR>F6Eob zx0G)ycdtAR_pXDwo_cfx-AFgl&2$UhO1IJNbO-fmj_#zp=x(})?xp+aetLi&q=)EX z`V@VdK0}|S&(Y`U3-ky*N)0_mU!*V5m+33?Rr(q|PG6^Q&^PH@G@wiL1U*Sl(YNV4 z^j-QMeV=|nKcpYgkLf4$Q~DXTLwfxqG@lmGD2>rVT11O!2`!~%w47GZN?Ju7T1{(c zEv=*Vw1GC#CfZC}Xe(`_aoSE3G)X&XC+(u$w1=i>FYTlKbbt=hAv#P)=qOFoF*;5s z=p>z@({zSrXqL{>IXX`l=puFLI=Y^EbOYT;H_^>>3*Ab$(d~2x^=Xdoq`T;Dx`*zi z`{;gpfF7iW=wbR4eVRT)pQX>y=jjXd2t7&-Jw{)oFVUCjEA&030Q zOY{UiNl($Y={xjY`W}6sen3B@AJLELC-hVL8MVXw{?mL~K%+E93uzH8rX{qLmeF!r zK`Ut$b!auMp|!M**3$;sNSkOgZK18SjmBv^P0%Fmpq;dfcGDi3qP?___R|46NQdY! z9igK%O~>dsouHF+icZrRnxR=bOXui3U7(B9rR(T=>d_5!Bi%$d(=Bu>-A1?59n_~e zx|8mryXhXfm+qtc=>d9>9-@coQ}k*241Jb9N1vxJ&?EFHHS`#Lk-kJ1*^j zeVx8R-=uHRfG*J!^dvn+-=^=-cj1Wio`2DB(w17rwj26-& zT1-o5DJ`Srw1QUBD(cW`T0?7T9j&Jgw2?N^X4*nqX&a5xcAB6`+Ce*M7wx7!G(~%9 zAMK|DbdV0wVLC!bX_}7FaXLXK=@gx&Gc-f9be7K1dAdLssY}<<_0*#q=tjDUZl+u4 hR=SODr#q-mb95)&MR(IZbT8dU_tOLPAU$NQ{13W=fyV#< literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/names.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bb835113839ad841652cea588b9a81cf446b3247 GIT binary patch literal 88 zcmZQbEY4+QU|?Y6VoG4FXGmbGuV-LVX5uI=NKGzDEiPd#D9X$$VPL=zU}j=uWanmN Z2g@59F)(rQFmf>{FbFUxFbXho0|14i5<~z1 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/nop.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/nop.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c59a8cd83c39d23f4969b87ff5eed73b65d520bf GIT binary patch literal 1496 zcmZ8hS#sJy5be$kZsz%3L7pR zQpE}OEK~b5HDmHPB1=YW(WQwP8Vw=-@Cb~Jq&_t;ODB0ai#6vx73Uqpsn1SLk&*qJ)|l#i zn{K477qspjVdw&CZ63z4ytbDn9Kov!a<$jAC>9$IZ)lqBCQKis&|8{Du%UX#Ge<0= z9lYn)ESSANP=7NajJ%K3L9Vl0%s){F3=Pd^@|q{>yD*hSeWBHr1iKGT48}j3as0e=YqOYULAaVTKv4SmFIy{#NaQ}tD#t3 zc!x+~fDvV57(ic)2$Uh7^gYZZWqJ3#1F#CRN4-@@B`krQ4_vKHaa9oIb}6?@c>}ph zjmLX6x8sa`@X#{=XRO?P?1PKbP+9DziINtVqSgpC?Aa;>DT<_I?@=V#h2~Tm7OUh|_ z;XI_l4Qa+`EUv{ZX&P?Iw2eAFlo`9-S<9RqN}JpBz%z+iM!(3EKvpIYWUR8-aboIF z)wXByW3TPJ9@<@ai7_{hi0;TWxaZkvJ>ESWV0kABaD_v@ ghX?x?d?D&bB7heH_2!BXLMW$$(ZM)B81EzEH|i2#U;qFB literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/resizing.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/resizing.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..44fec0b34207b8d431a24fe281b352a216b71d13 GIT binary patch literal 181 zcmYLc)0gD)3&G9E&$xj=D5oAssm zZ4}QnG}^R%5+{SdO1EUaCD`6%y@kf#TIp?qHVfh;nU#LzryI=n4repSxVyZ0l7`lL z$>dIt#-nhMOoy;$avN*~>uW~N0z+8YM%S`=bP`k3rbGQ6HoxPndu!{w&L*(x%Sbz^ z&gbd0Jj#^@F^h9-mmojYQ`jkijx!Bc*_i2A5Aw3yYn>I7X;a27mr;rgah>ZNL~&et z_P7fMbBk_piwrU?`eCDlM|9S4YMw;1g`1lfc{A=G*Dq{v+FKX%nEbadkv!A+5zRb` z%kuZRt~iN?VKf}3I?Lb=*Tu~5OX|RsnkT7VQg=DFKSwv?tu7{j?!C2cP zE|})W^2K^C|L zIVc-F!!3xd7$6C-#xokHP<3ol5|g+^0a6ZCLnT_Ut^5*KNwvbgejVHVPbK?>?6ST= zdc&}z!%S64Y||eh6IJN@h_b~8s}u@6B_yE$2)O74OB4u?PRVDd;{_0i9SQ?bRkWd^ z{wN=JWCc_~)v6|yq})!xu~04Q*dlgR8!2vsTBozIs#^typf-$Y35D7u-#O^0Eo3N# zI8$BRv(I|*<@5i~@qorzgZ?hwr|Q>0?P58;tG#C~3!7A=x{ikn5TNpZr8bm1d-xl@ z8wJgb^8L}nPn3nqt`q7sjS0Yk0y*ruo+?$?n9AgBdOz%eMNUW1R|=ari`azM z^G#c4ue5^u<2Ag^oWGy7$@0?N;D=YWLV|=6CyxG80G#b=90hhxumxhvV~zb{U!5TO E0Kcj;P5=M^ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/set_local.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/set_local.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6f60f4507b5573666eec5159a2a262c1f07bcc4d GIT binary patch literal 454 zcmY*UOKQU~5S_6s*^y{_1!+;pCX0UP+IF(<6>M4u3^5I9pbhwQj&xCagzQ=z=y7_E zj$}jPfycagdh;Yf_F5tU;B}A!q}UP~LQSY3IKl z*yb>ie<(x5HUy!2Dx>6BE0D#nRO@qA6ln zdkt1Lu1~^Kh)1_Eg5RHTG6_O(a)jz}2%z}=ufcdq8#}jghzw7J5rbPtCe+YrJZev~ TAMJl@e$=AVL8rs7p$_UF|H@!_ literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/simple.wasm b/lib/wasm2cretonne-util/testsuite/simple.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4e7c773ae2c432bc10c47603cac2c1d0b352955e GIT binary patch literal 41 ncmZQbEY4+QU|?WuX=rF*U`$|OU~U4l7`QsYbTf!#VB`h>$!Q7F literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/skip-stack-guard-page.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/skip-stack-guard-page.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..79d487b41b604eb92ab819e354973332562b9273 GIT binary patch literal 18586 zcmW;SWt42uS%l%^XGo9$0fK)O4-h;YoOpt3aES{c5F$9_+T6MB?(XjH?(XjHu2j{p zdU~yyKeJZ%ncn+NKK1plq)C#bE!ij2ZzZ2hl046nw9L~at>2Qq`5T}6`ZwPDg>Qc9 zYoC4ZS3mWI&%O8SU;ffpljIF&`lQ*LlSn?0e6)D~o6?d>zx<};jc?rCCr!t+{NJ0> z4g(^^9H;@^G3W;^X>R{&3E8C zG~bEu)O;7dOYr^W*q&O&~M?G;hUQHE+Y)G;hb-HSfSXH1EVaHSfZ^ zH1Ec{HSfWDG(Ul#(EKERQu9;zDb0KFUd>PAr!_x=pV9m*epd5y_&Lqb-creZ{RmHzlq<}{1$#o^M1Tv z^8tK7^Fe%2^C5gl^I?2g^AUVR^HF?M^D%r(^KpD!^V|4s&F|oMG{1}A)%+fQPxJfu zea#=>4>W&>Kh*pY{z&u3_+!nV;7>Gvia*u-8U9T334B8H=lFBYU*In^e~G`;{1yI6 z^Vj%m&EMc}G=Gb~)%+d)PV@Kpd(A)KA2grDCpDkKr!=3&r!}9!XEdM1XEmS0=QN+k z=QUrz7c^hQ7d2nPmo)!~f7E;#U)KB+{z>x{d`0um_-D<(;9oRf#aA`|ihtF74PVoI z9beac1K-em6W`SQ8~#o6@A!Akf8akf|B3(9{1^U9^WXSy&HvzkH2;hL)%+j+PxCE& zOVchzJHPeXr)Y047M!yvOjQ%P58v{}dFb1X=Xbeg*$QYbrurVaX z5MyYHp~kQj!;Il6h8rVNj4(!~7-@`3G0GU7Vze&x$XJ|Wv9Tn@5@TtKrN*)p%Z%kImK!TltT0xlSZS`JlA*qvgxu_whIV{eMR#=aE$jQuJ08wXMxFb<|TXdFs$$T*zh zuyG{C5#wlzqsFlm$Bg4CjvFUZoG?zNIBA?pamqNI;nW}qH&Wa%Zl<_t+)8oFxSis*aVNzc z<8F$(#=R8xjQc6>8xK-EFdn9OXgo^s$atLMvGF9u6XR)$r^d4s&y43Oo*OSxyf9v- zcxk*!@yd9e;3@`>}7-$U2 zFvu93VX!eI!w_R=hM~r=48x4!8HO7pGK?@rW*BLV$}q|ponf>wCc_wGY=*JMxD4Zr z@fpS&6EaLNCT5suOv*6Hn4DpVxyHN<^Njf!<{Jw#EHD;kSZFNDu*g`PVX?6!!xCd@hNZ@`49kq=8I~I> zGORFGW>{&g%CO2S18!~J#HfGppY|607*qmXru_eP6 zV{3-3#954=M zIA|QoaL72E;jnQe!x7_XhNH%@49AS)8IBt#GMq3@W;kh_%5cg!o#C``Cc_!yY=*PO zxeVuw^BK+?7cyKhE@rrBT*`3CxSZj#aV5hQ<7$Sh#yT-i?_l)})?i&v>JTM++cxXJz@W^g;M=zsyj^0L}9DR(wIrV|0$u#+V#q zjIlY!8sl<|Gsfo_Z%oKB!I+q1qA@AQBx7=p$;Ol%Q;exOrW(_7Of#nEm~PC-F~gXd zW2P}H$1G!Zj@ibX9CM7hIp!Mka?CU4=a_FS$g#j!m}8-_D90jWagN2tk{nBnr8$-w z%W^C;mgiV*tjMv#Seav`u`0(ZV|9+z#+n>!jI}w|8tZbbGuG!=Z*0i1!PuB%qp>N+ zCS!As&Bm4-Ta2wawi?@VY%{j!*lz5|vBTJzW2dnz$1Y=cj@`zd9D9ttIrbX+a_lqq z=h$x?$Z^0pnB$;vD90h=aE`;qksL>iqdATm$8sDqj^{XToXBy)IGN+5aVp0t<8+SG z#+e*vjI%k;8s~DHGtTEYZ(PW6!MK>?qH!t5CF63A%f^))SB$GUt{T^JTr;lcxNh9Y zal^Qovp`RySAkwe?*hGzJ_Y(1eGBw8 z`W5JB^e@og7*JqX0+Wp?1*RBN3rsbp6_{pB zFEHJhQDBBKv%pMaR)JZ@>;kimIR)kza|_Hh<`tM{%r7wCSWsYrv9Q2GV^M)c#^M5t zjU@$^7)uK*HI@}vW-KqT+*nayg|V{0N@G=lRmSQ9tBo}U));FGtTomZSZAy+u-@2E zV1u!-z(!+JflbEd0-KF31-2Ml3v4yE71(BMFR@Tq2I8fk#aj?Ka<4}P^#^C~ojUxq)7)J{nHI5ZHW*jeY+&EF-gmJRK zN#j(3Q^x56r;RfO&KPG4oHfoBIA@$MaNf93;DT|nz(wOyflJ2a0+)>|1+ExZ3tTm> z6}V2R(Ut+v5p~M7ZVu^{yq!N>i$t5NmQ%X!R zrk0p$Oe-N}MsymN;viD{;;^U*f!Rp~MB_Vu_2!r4pBn%Ox%w zS4vzlu9moJTq|+SxL)GAaihcy<7SDQ#;p>!jN2t{8+S_FG47VQYuqbw&$wUWzVV>M z1LI+dhsL84kBrA99ve?eJTab@cxpT=@yvK$;<@pn#0%qPiI>K!60eNcC0-ltDzx*x zPWuY&jSdw$7#%BgG&)u2WVBXjH9A-5Y_wHqGrCmhVsx$0)#z5Co6)^OccVvz9!Ad! zJ&j%!dKtYd^fvlb=wtM)(AVf!p`X#eLVsgGg#pIE3ImNn6$TlDD-1SK?$O5H}jY$CGmTjlW*M_9%r@p!m}AVXFxQw@VV*I+ z!hB;vg$2gK3JZ-z6&4wbD=apaR9Ipxt+3QsR$-a3yuxy0MTHf{$_guuRTWklt1GNF z)>K$ytgW!tSXW`4vA)83V?%`v#>NU8jZGCc8JjC?HnvpQVr;Fj)!0^Ho3XvZc4J3{ z9mdWIJB?ixb{V@X>^AmP*kkOiu-DjEVV|+T!hYjGg#*UH3I~lt6%HAPD;zeCR5)TB zt#H&hR^ga&yuxwgM1>Q^$qFZpQx#4brz@N`&Qv&KoUL%yI9K7EalXQN<3fcC#>EO3 zjY}0S8J8l{b8^+BFH;r2rZW*^L+&1o1xMSR{aM!q3 z;hu57!hPdGg$KsN3J;A(6&@LnD?B!yRCr=Mt?<-%R^gfPyux$iMTHl}%L*@zR~23v zuPeMZ+SO?13+47T+8Z5ebTB&B=xB7R(aC77(Q0(A(b;IL(Pnh1(Z%RmqpQ)aMmM8- zjqXN|8a<4jHF_GoYV#)ujtjFB})8l!5AGDg=JZH%cg#u!^;tTC>}IAeT`@y3K2 z6O4&9CK{7!Ofn|dm~2d`F~yi#W2!N&#x!Gkjp@dW8Z(TUHD(&KYRocb*O+b0sWHcx zTVt*>P z#x`SnjqS#c8as@gHFg@iYV0z0*Vt|Bsj)5jqApZ8aIraHEtTWYTPnz*SKxm zsd2}+TjQ>Auf{#&evSLagBlNvhczA=k7_(J9@ltmJgM=-cv|DB@vO!(<9Ute#)}#+ zjF&ZD8n0@+GG5nsZM17aJC`i&ThQL<(1H#|#};%nI<=sa(b|GmqjL*78*MFUGrF{( zi_x_OU5#!n=w@_pL3g7^3wjtmThPk$=p!lICVILGf+$8LS;zyc^R6a& z7w6qg@@~$1nuL8&{6v$m4~m~`681syQ%%A?DBjy7?1SQ`n}mH({7jRu4~m~{681sy zb4|iND1N?4*ayWgGzt5l_{An+9~AFvlJ{|bsY%!eMbjkggW{K)gndx_N|Ue;ieGIK z_CfJ$O~O7Xe!WT92gPqR3HzY<%_d5za@O~A8(S6bAG!?*ayY$Gzt5l_}wO99~8gWBkp!m}!VILHK)+FqM;uB5s3C^E43HzY< zizZpKFrOaX#N9pXYp`Nxs1OVv~H4^Q9(X9~A%C zBJ?1SQKO~O7XzTPD4 zgW?-a!agXz*(BfO{9BW-4~lrMf4JS6TO8#L?59q(O2k4^b`6M{e=O<0AV08P#8oE z5(X24g(1WcVJI`>CNWc(Ma&Xr6SIXm#2jHRF;|#J z%oFAl^MwV(0%0MsP*_AP5*8DSg(buiVJWdxSVk-pmJ`c`6~qc*C9zUiMXVB56RU+a z#2R5Ou~t||tP|D~>xB)(24N$yQP@Om5;hZ?g)PJuVJop!*hXv=X7A`-KC<0pTEVP&h;!5)KoGg(Jif;V5xbI7S>3juXd) z6T}JOBymzWMVt~&6Q_kU#2Mi%aaK4-oDqubQMg205-t;$g)77r;VN-e zxJFzPt`paV8^jIaCUH}^Mcfi@6Ssvs#2w)-aaXuU+!O8-_k{<<1K}a@PtMZ6MT6R(AKeB`&&Grv92Ug$t{5IPbag-%2# zp_OPAIuo6RHlj`FLZ}alu7vua=tigyitdE^py)xU4~m|I`k?4Vs1J(Xg!-W9L#Pjm zzJ&Uq=trmzivEQ9pcp`?4~l_=`k)v@s1J(4#9(0vp*|>v66%9u7@L>VskoF-90mj1|TaD5etX zgJK$?J}9OW>Vskip*|>P66%9u7NI^UW)tdzVh*7`DCQFCgJK?`J}BlB>Vskdp*|=U z66%9u5urXP788qwC4~B*SW2i5ie-fQpjb|*4~i9p`k+`zs1J%&g!-UZO{fowHN+ZW zEwNTuN30Xp6YGTy#0Fs_u~FDWY!WsTn}sce`k>fKs1J&5g!-V^PN)xx9fbOz*h#1l zid}^Ipx8~Q4~jj6`k>fLs1J&Lg!-V^PpA)y1BCjZI7p}uibI6@pg2q%7LE|=gW@Qm zJ}8b6>Vx7qp*|>15bA^CB%wYiP7&&Z;xwT?D9#XPgtNq1;T&;JI8U4xE)W-li^N6Y z5^+hmOk5VO5bA^CDxp3ot`X{k;yR%|C~gqygW@KkJ}7Px>Vx7op*|??5bA^CE}=ds z?h)#P;y$51C>{{%gW@5fJ}4d$>Vx7j@mP35s1J&#g!-U(MyL;p=Y;y8ctNNSikF1? zpm;^74~o}>`k-jX?;7oLqCL@G=sVsk!p*|>v6Y7Iv1ff1CMiT0SVici1C`J?NgJKLZMi@(s z6~+Vsky zp*|>P6Y7Iv4xv6M<`U|IVjiJBDCQIDgJJ=pJ}4Fv>Vsktp*|=U6N`lxB)(24N$yQP@Om z5;hZ?g)M~opx8>N4~lJs`k>fOs1J%Ag!-V^NvIEsU4;6e*iEPpiamt-px8^O4~l(+ z`k>fPs1J$*g!-U3NT?5rLxlREI7}QCju7gD;wYg$D2@^8gW@=$J}6EQ>Vx7Wp*|>1 z5$c2DG@(8y&JbsWv&3299C1!KPn;Jn5Eq1t#6{r}aY?vLTo$en>Vx7cp*|?C5$c2D zI-x!&ZV>8&;wGU!C~gtzgW@)!J}B-G>Vx7gp*|??5$c2DKA}D+9uVq-;vu0vC>{~& zgW@sqSa?FH4~nOR`k;75s1J(gg!-U(L8uRkmxTJDctxlWir0ktplHWKj&?fbXiu~k zIuIR%jzmYH6VXX%C0d2fL}#ImXcM{+U4*VgSD_oxP3TT^7kUsqgq}oCp%>9h=uPw% z`Vf7DzC>T4AJI?fPxKcC5CepP#6V#XF-RCp3>JnELxiEkP+=G`Oc+iK7e){xgptHZ zVH7b+7)^{8#t>tKvBX$m95GH9PmC8P5EF!n#6)2dF-e$AOctgPQ-rC+RACx1O_)wh z7iJJMgqg%lVHPnau}D}C5C?>V z#6jT@aY#5!92SlcM}(uqQQ;VIOgK&)7fui-gpp zcul+(+VOO?osL)A6YYf#LrMf4JS6TO8#L?59q(O2k4^b`6M{e=O<0AV08P#8oE5(X24g(1WcVJI`>CNWc(Ma&Xr6SIXm#2jHRF;|#J%oFAl^MwV(0%0Ms zP*_AP5*8DSg(buiVJWdxSVk-pmJ`c`6~qc*C9zUiMXVB56RU+a#2R5Ou~t||tP|D~ z>xB)(24N$yQP@Om5;hZ?g)PJuVJop!*hXv=X7A`-KC<0pTEVP&h;!5)KoGg(Jif;V5xbI7S>3juXd)6T}JOBymzWMVt~& z6Q_kU#2Mi%aaK4-oDqubQMg205-t;$g)77r;VN-exJFzPt`paV8^jIa zCUH}^Mcfi@6Ssvs#2w)-aaXuU+!O8-_k{<<1K}a@PtMZ6MT6R(AKyw+)_d!6<~d!Yl-LFhVu*ep*|>j6Y7Ja51~FN`V#7cq936?DEbrX zgJJ-oJ}3qf>Vsksp*|=E6N7~zg!-TuN~jNtVTAgi7*41UiV=kRpcqN04~kKQ`k)w1 zs1J%U#28^LF;*Byj1$HaVsk>p*|>95$c0tHK9Hz)(~riwZvLs9kEVWPplU< z5F3Pz#71Egu}RoWY!Vsk{p*|?K5$c0tJE1-(b`a`=VkeVsl0p*|?~5$c0tKcPM-4iM^t;vk_uC=L%F?;XH9(xIkPGE)o}oOT;DNGI3eBLZ}al ztAzTXxJIZCitB{>ptwP(4~m{66%BE8KFKXo)hYW;sv2TC|(lkgW?sTJ}6!j>Vu*k z?~~iVu*Wp*|@366%AZAE7=d`V;DdVgR8&CVsl7p*|?)5bA?s zE}=ds<`L?HVm_fhC>9XvgJL0}J}4Fu>Vsl2u~=9_s1J&zg!-UZMyL;p<%Ig6SV5={ zij{==pjbty4~o@<`k+`ttP$1{YlU^hI$=GrUf4iv5H=DUg-ygJVKcE=*g~ieimk*} zVH=@7D7F*ogJK7vJ}7n)>Vskzp*|>f6Y7Iv51~FN_7duYVjrPCDE1TTgW>?8J}3?n z>Vx7Cp*|=M6NiN(g!-U3N~jNtV}$ykI8LY!iW7wTpg2jW4~kQS`k**Xs1J%W#2Mi% zaaK4-oDqubQMg205-t;$g)4;mptwq06|NELgW@`&J}7Pw>Vx7Yp*|>X z5$c2DHlaQ!?hxvO;x3^+DDDyJgW^7+J}4d#>Vx7Tp*|=c5$c2DG4WV#ID{{nGAG9Ul| literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/start.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..21b795c5e456f72e117567ac86df7b36b83996a9 GIT binary patch literal 94 zcmXAfK@LDb5Cp4t2(c0V;Oql#`x7D}BwV=rx(QvEu1+&uhX9pfgb_WK86wZ*De7R` fIfJds?H`4zvxemKkhBjF%ikI=?MIA-e2Ypi%;pH2 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.4.wasm b/lib/wasm2cretonne-util/testsuite/start.wast.4.wasm new file mode 100644 index 0000000000000000000000000000000000000000..21b795c5e456f72e117567ac86df7b36b83996a9 GIT binary patch literal 94 zcmXAfK@LDb5Cp4t2(c0V;Oql#`x7D}BwV=rx(QvEu1+&uhX9pfgb_WK86wZ*De7R` fIfJds?H`4zvxemKkhBjF%ikI=?MIA-e2Ypi%;pH2 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.5.wasm b/lib/wasm2cretonne-util/testsuite/start.wast.5.wasm new file mode 100644 index 0000000000000000000000000000000000000000..7a4b81947e28de963076ea16a4048e9bc64c62a1 GIT binary patch literal 55 zcmZQbEY4+QU|?Y6U`k-DXGmaRU=m{FC@x4%E=eseVJ#@i%qwAFU}j=u#i}5VfzvL!C>qV* zVm=L4yA1&_*q)iW=g6@Kv#lHm(_A^X+i&)OTfe;wpj{33F!kN@uw4(Z^DnwvGN%I; zG|S6~XlJ0YuBvB^B7!I-L_99&D|x5%T~56{b)}!ONs&P}CD;xj- literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/tee_local.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/tee_local.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c7490e67c6e10d3f775572d7836ee41b4fc4f7ad GIT binary patch literal 594 zcmY+Ay>7xl49D$UatXP9>^GfmVqxfqx;CLO7gg#`)CLI&R0LElQa)aQ7l5f-S2j>0 z^%43^^@X5(PDokQqXp)Dvh;9&Y-epK({A`sg+@3Ynpio#UOR=4@x5_CM xFO*v-@2j?rcswkM8Va*#ps}o9;s@FaZOwL}qwQ@yr8H9tvtUub#*8`$ia$G9g((04 literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/traps.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6b36d5c04320db66a3ef0400b2ad626ae571db10 GIT binary patch literal 146 zcmZQbEY4+QU|?Y6W=deHuV+YLs;gsQW@Q12FtYoy2;}9*rzEH9Wf~jlrDT@H7c(%R r3zaf3!iCICpsJYAg-RKixl~y=7!(*37;~k#A&fj2qaMa+km3damH!_q literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.1.wasm b/lib/wasm2cretonne-util/testsuite/traps.wast.1.wasm new file mode 100644 index 0000000000000000000000000000000000000000..31d416dfdffdb1eae3655f0a08d2aa34c28155e3 GIT binary patch literal 146 zcmZQbEY4+QU|?Y6W=deHuV+YLs;gsQW@Q12FtYoy2;}9*rzEH9Wf~jl6{Y6J7c(%R r3zaf3!iCICpsJYAg-RKixl~y=7!(*381tpLA&deTqY=hvlHvvcm<1m; literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.2.wasm b/lib/wasm2cretonne-util/testsuite/traps.wast.2.wasm new file mode 100644 index 0000000000000000000000000000000000000000..74e1fe5bf30c8b03371f76e5571bdf3dab1ec5f8 GIT binary patch literal 293 zcmZQbEY4+QU|?Y6WJ+MHWk_JGVPNLu0P=x?jEw9j89BuA^5avIQ}r^9jr2;2O7oKA zi{sObjTjg(B}%~(jF=KY9cCsBOqdct9Y6_YxP+MriXAMN5)eCBF(n{&uwhC->|p1z i;s8tYZHH2CRq1HpF4G?Oh6gL2huS(7U literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.3.wasm b/lib/wasm2cretonne-util/testsuite/traps.wast.3.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6d98e644425f6e1d27a8bd0662053b468af29788 GIT binary patch literal 458 zcmZvXOAdlS5JamP5d?)qcdlIeMfpg)4HD7BL}fu{%~`yupwq+vtR632-8D#yCjlUx z&QwCUPWAKm>4HGUTXWo3o@AG+cMk*ak*66jYZ*UI3Q&)g(59`~Q#C5OQex2DiKE{g zz1gTt7*ye5kCiam_S?zqu@a-^yxLoLvbs{j=1w<$`?xXOa$=Q^i3OG#=5`GpDcCb? cq0Ek9C6tLcbz}%Q6*3$+l}_1VVtYn0+;bu!K-WmBsSQ38+eBdmIe1<&u=J6hRl@2>f#y z4#n#p*@lYBviiM#uV=bPC5p1KA|iFOx=*z46-7b&qzc^MasSxYMFBM9kG&VWq_p-t ztx3C^1NYOqqJ3N%1?37V{0KnvRq@qSDdl^LRPZO*spem&wljLXO}Y%9Jt5D9L$H-H z1nW1VtTQ~E^gF{*n&q@)W_UarP*9(tI6|^I7av9Op{3;}(!Dt8e=6NI);ogjhV>4L zzrN7B0*wXHOOsEQn(T;^WLg$%@pc4ry%jDmNXo6w#m>CO;$GTuwNJ_7>E3AA=_iu` zotCu(JHh%Xk+VPuCp)w1*gWdRnBin#ztF~ioO7Qo-A?BUY}2`of8CCCHO_CI%tVtx zxx!`(+0^ET>7?BLg&LUI-Y=FQ|6nI{sRTC8EL~<}W@FpW%aOHL7EETP^()+ZDH7r; z&!!*6ap}3nlM~EizRqJ7BxAm@UcxA7yfAe0%t>C}ptUs}zR)Xu4nv?`Q4rw4p?850ZNLm8Um zoN9|J9ZsV3YbTl76bD0(jzFN0igNM+d6L;Oo~i zhNx16JkBj zPCNsmUjSGb1WFy#6*5aicX3X5Y@yK>(Hk_3>q2+eBkTn79SP9b3n+wniE*N>pKJ?j hAMtoqqhqGtVx+SY*Si+meT`N^de6za;yLV literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne-util/testsuite/unwind.wast.0.wasm b/lib/wasm2cretonne-util/testsuite/unwind.wast.0.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6057472111dbec2219224e1dd44312a62868469a GIT binary patch literal 1713 zcmaJ>%W~R45S#@kAO?^@5)aAPi4&6(-w=!B1K8j#NN!_$~lIv>Oiq4Bfrb|Ju^KyG3X;}W*`8SK&!8fE57qI{aOl14MEyl@J9942WT!7FdG+i$zDncVy-$G9EhxgqnkgVv-OOZrC#m||7%+>Gt1ME8@CGPNpJWS%xZYt z+pFDv|3g3WifOl2iKb!irWGj9dOt34JnF9V_hQ}xzqu5EZS~s)?5M&ws6<^t9{_s{ z`|4!y$)xtiy>_4$U6Rr_|?(;C9=88DH>jeD64aiGNV)D`GTbFG#L z*$Zm1&n>>_19w@+>(ac~p2o3lI^0QKms%7F8qEz#lBGO+gVW_)(!(8D|*PoRf zet(2DPEL|DD81H*UIn4{Y-cta&geq+o5K9K1V*0T0RaZi^%N>iS|w!nvVDn2; literal 0 HcmV?d00001 diff --git a/lib/wasm2cretonne/.gitignore b/lib/wasm2cretonne/.gitignore new file mode 100644 index 0000000000..4308d82204 --- /dev/null +++ b/lib/wasm2cretonne/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/lib/wasm2cretonne/Cargo.toml b/lib/wasm2cretonne/Cargo.toml new file mode 100644 index 0000000000..ef82b76118 --- /dev/null +++ b/lib/wasm2cretonne/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "wasm2cretonne" +version = "0.0.0" +authors = ["The Cretonne Project Developers"] +publish = false + +[dependencies] +wasmparser = "0.6.1" +cretonne = { path = "../cretonne" } +cretonne-frontend = { path = "../frontend" } diff --git a/lib/wasm2cretonne/src/code_translator.rs b/lib/wasm2cretonne/src/code_translator.rs new file mode 100644 index 0000000000..208c531812 --- /dev/null +++ b/lib/wasm2cretonne/src/code_translator.rs @@ -0,0 +1,1375 @@ +//! This module contains the bulk of the interesting code performing the translation between +//! WebAssembly and Cretonne IL. +//! +//! The translation is done in one pass, opcode by opcode. Two main data structures are used during +//! code translations: the value stack and the control stack. The value stack mimics the execution +//! of the WebAssembly stack machine: each instruction result is pushed onto the stack and +//! instruction arguments are popped off the stack. Similarly, when encountering a control flow +//! block, it is pushed onto the control stack and popped off when encountering the corresponding +//! `End`. +//! +//! Another data structure, the translation state, records information concerning unreachable code +//! status and about if inserting a return at the end of the function is necessary. +//! +//! Some of the WebAssembly instructions need information about the runtime to be translated: +//! +//! - the loads and stores need the memory base address; +//! - the `get_global` et `set_global` instructions depends on how the globals are implemented; +//! - `current_memory` and `grow_memory` are runtime functions; +//! - `call_indirect` has to translate the function index into the address of where this +//! is; +//! +//! That is why `translate_function_body` takes an object having the `WasmRuntime` trait as +//! argument. +use cretonne::ir::{Function, Signature, Value, Type, InstBuilder, FunctionName, Ebb, FuncRef, + SigRef, ExtFuncData, Inst, MemFlags}; +use cretonne::ir::types::*; +use cretonne::ir::immediates::{Ieee32, Ieee64, Offset32}; +use cretonne::ir::condcodes::{IntCC, FloatCC}; +use cton_frontend::{ILBuilder, FunctionBuilder}; +use wasmparser::{Parser, ParserState, Operator, WasmDecoder, MemoryImmediate}; +use translation_utils::{f32_translation, f64_translation, type_to_type, translate_type, Local, + GlobalIndex, FunctionIndex, SignatureIndex}; +use std::collections::HashMap; +use runtime::WasmRuntime; +use std::u32; + + +/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following +/// fields: +/// +/// - `destination`: reference to the `Ebb` that will hold the code after the control block; +/// - `return_values`: types of the values returned by the control block; +/// - `original_stack_size`: size of the value stack at the beginning of the control block. +/// +/// Moreover, the `if` frame has the `branch_inst` field that points to the `brz` instruction +/// separating the `true` and `false` branch. The `loop` frame has a `header` field that references +/// the `Ebb` that contains the beginning of the body of the loop. +#[derive(Debug)] +enum ControlStackFrame { + If { + destination: Ebb, + branch_inst: Inst, + return_values: Vec, + original_stack_size: usize, + reachable: bool, + }, + Block { + destination: Ebb, + return_values: Vec, + original_stack_size: usize, + reachable: bool, + }, + Loop { + destination: Ebb, + header: Ebb, + return_values: Vec, + original_stack_size: usize, + reachable: bool, + }, +} + +/// Helper methods for the control stack objects. +impl ControlStackFrame { + fn return_values(&self) -> &[Type] { + match self { + &ControlStackFrame::If { ref return_values, .. } | + &ControlStackFrame::Block { ref return_values, .. } | + &ControlStackFrame::Loop { ref return_values, .. } => return_values.as_slice(), + } + } + fn following_code(&self) -> Ebb { + match self { + &ControlStackFrame::If { destination, .. } | + &ControlStackFrame::Block { destination, .. } | + &ControlStackFrame::Loop { destination, .. } => destination, + } + } + fn br_destination(&self) -> Ebb { + match self { + &ControlStackFrame::If { destination, .. } | + &ControlStackFrame::Block { destination, .. } => destination, + &ControlStackFrame::Loop { header, .. } => header, + } + } + fn original_stack_size(&self) -> usize { + match self { + &ControlStackFrame::If { original_stack_size, .. } | + &ControlStackFrame::Block { original_stack_size, .. } | + &ControlStackFrame::Loop { original_stack_size, .. } => original_stack_size, + } + } + fn is_loop(&self) -> bool { + match self { + &ControlStackFrame::If { .. } | + &ControlStackFrame::Block { .. } => false, + &ControlStackFrame::Loop { .. } => true, + } + } + + fn is_reachable(&self) -> bool { + match self { + &ControlStackFrame::If { reachable, .. } | + &ControlStackFrame::Block { reachable, .. } | + &ControlStackFrame::Loop { reachable, .. } => reachable, + } + } + + fn set_reachable(&mut self) { + match self { + &mut ControlStackFrame::If { ref mut reachable, .. } | + &mut ControlStackFrame::Block { ref mut reachable, .. } | + &mut ControlStackFrame::Loop { ref mut reachable, .. } => *reachable = true, + } + } +} + +/// Contains information passed along during the translation and that records: +/// +/// - if the last instruction added was a `return`; +/// - the depth of the two unreachable control blocks stacks, that are manipulated when translating +/// unreachable code; +/// - all the `Ebb`s referenced by `br_table` instructions, because those are always reachable even +/// if they are at a point of the code that would have been unreachable otherwise. +struct TranslationState { + last_inst_return: bool, + phantom_unreachable_stack_depth: usize, + real_unreachable_stack_depth: usize, +} + +/// Holds mappings between the function and signatures indexes in the Wasm module and their +/// references as imports of the Cretonne IL function. +#[derive(Clone,Debug)] +pub struct FunctionImports { + /// Mappings index in function index space -> index in function local imports + pub functions: HashMap, + /// Mappings index in signature index space -> index in signature local imports + pub signatures: HashMap, +} + +impl FunctionImports { + fn new() -> FunctionImports { + FunctionImports { + functions: HashMap::new(), + signatures: HashMap::new(), + } + } +} + +/// Returns a well-formed Cretonne IL function from a wasm function body and a signature. +pub fn translate_function_body(parser: &mut Parser, + function_index: FunctionIndex, + sig: Signature, + locals: &Vec<(usize, Type)>, + exports: &Option>, + signatures: &Vec, + functions: &Vec, + il_builder: &mut ILBuilder, + runtime: &mut WasmRuntime) + -> Result<(Function, FunctionImports), String> { + runtime.next_function(); + // First we build the Function object with its name and signature + let mut func = Function::new(); + let args_num: usize = sig.argument_types.len(); + let args_types: Vec = sig.argument_types + .iter() + .map(|arg| arg.value_type) + .collect(); + func.signature = sig.clone(); + match exports { + &None => (), + &Some(ref exports) => { + match exports.get(&function_index) { + None => (), + Some(name) => func.name = FunctionName::new(name.clone()), + } + } + } + let mut func_imports = FunctionImports::new(); + let mut stack: Vec = Vec::new(); + let mut control_stack: Vec = Vec::new(); + // We introduce a arbitrary scope for the FunctionBuilder object + { + let mut builder = FunctionBuilder::new(&mut func, il_builder); + let first_ebb = builder.create_ebb(); + builder.switch_to_block(first_ebb, &[]); + builder.seal_block(first_ebb); + for i in 0..args_num { + // First we declare the function arguments' as non-SSA vars because they will be + // accessed by get_local + let arg_value = builder.arg_value(i as usize); + builder.declare_var(Local(i as u32), args_types[i]); + builder.def_var(Local(i as u32), arg_value); + } + // We also declare and initialize to 0 the local variables + let mut local_index = args_num; + for &(loc_count, ty) in locals { + let val = match ty { + I32 => builder.ins().iconst(ty, 0), + I64 => builder.ins().iconst(ty, 0), + F32 => builder.ins().f32const(Ieee32::with_bits(0)), + F64 => builder.ins().f64const(Ieee64::with_bits(0)), + _ => panic!("should not happen"), + }; + for _ in 0..loc_count { + builder.declare_var(Local(local_index as u32), ty); + builder.def_var(Local(local_index as u32), val); + local_index += 1; + } + } + let mut state = TranslationState { + last_inst_return: false, + phantom_unreachable_stack_depth: 0, + real_unreachable_stack_depth: 0, + }; + // We initialize the control stack with the implicit function block + let end_ebb = builder.create_ebb(); + control_stack.push(ControlStackFrame::Block { + destination: end_ebb, + original_stack_size: 0, + return_values: sig.return_types + .iter() + .map(|argty| argty.value_type) + .collect(), + reachable: false, + }); + // Now the main loop that reads every wasm instruction and translates it + loop { + let parser_state = parser.read(); + match *parser_state { + ParserState::CodeOperator(ref op) => { + if state.phantom_unreachable_stack_depth + + state.real_unreachable_stack_depth > 0 { + translate_unreachable_operator(op, + &mut builder, + &mut stack, + &mut control_stack, + &mut state) + } else { + translate_operator(op, + &mut builder, + runtime, + &mut stack, + &mut control_stack, + &mut state, + &sig, + &functions, + &signatures, + &exports, + &mut func_imports) + } + } + + ParserState::EndFunctionBody => break, + _ => return Err(String::from("wrong content in function body")), + } + } + // In WebAssembly, the final return instruction is implicit so we need to build it + // explicitely in Cretonne IL. + if !state.last_inst_return && !builder.is_filled() && + (!builder.is_unreachable() || !builder.is_pristine()) { + let cut_index = stack.len() - sig.return_types.len(); + let return_vals = stack.split_off(cut_index); + builder.ins().return_(return_vals.as_slice()); + } + // Because the function has an implicit block as body, we need to explicitely close it. + let frame = control_stack.pop().unwrap(); + builder.switch_to_block(frame.following_code(), frame.return_values()); + builder.seal_block(frame.following_code()); + // If the block is reachable we also have to include a return instruction in it. + if !builder.is_unreachable() { + stack.truncate(frame.original_stack_size()); + stack.extend_from_slice(builder.ebb_args(frame.following_code())); + let cut_index = stack.len() - sig.return_types.len(); + let return_vals = stack.split_off(cut_index); + builder.ins().return_(return_vals.as_slice()); + } + } + Ok((func, func_imports)) +} + +/// Translates wasm operators into Cretonne IL instructions. Returns `true` if it inserted +/// a return. +fn translate_operator(op: &Operator, + builder: &mut FunctionBuilder, + runtime: &mut WasmRuntime, + stack: &mut Vec, + control_stack: &mut Vec, + state: &mut TranslationState, + sig: &Signature, + functions: &Vec, + signatures: &Vec, + exports: &Option>, + func_imports: &mut FunctionImports) { + state.last_inst_return = false; + // This big match treats all Wasm code operators. + match *op { + /********************************** Locals **************************************** + * `get_local` and `set_local` are treated as non-SSA variables and will completely + * diseappear in the Cretonne Code + ***********************************************************************************/ + Operator::GetLocal { local_index } => stack.push(builder.use_var(Local(local_index))), + Operator::SetLocal { local_index } => { + let val = stack.pop().unwrap(); + builder.def_var(Local(local_index), val); + } + Operator::TeeLocal { local_index } => { + let val = stack.last().unwrap(); + builder.def_var(Local(local_index), *val); + } + /********************************** Globals **************************************** + * `get_global` and `set_global` are handled by the runtime. + ***********************************************************************************/ + Operator::GetGlobal { global_index } => { + let val = runtime.translate_get_global(builder, global_index as GlobalIndex); + stack.push(val); + } + Operator::SetGlobal { global_index } => { + let val = stack.pop().unwrap(); + runtime.translate_set_global(builder, global_index as GlobalIndex, val); + } + /********************************* Stack misc *************************************** + * `drop`, `nop`, `unreachable` and `select`. + ***********************************************************************************/ + Operator::Drop => { + stack.pop(); + } + Operator::Select => { + let cond = stack.pop().unwrap(); + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().select(cond, arg2, arg1)); + } + Operator::Nop => { + // We do nothing + } + Operator::Unreachable => { + builder.ins().trap(); + state.real_unreachable_stack_depth = 1; + } + /***************************** Control flow blocks ********************************** + * When starting a control flow block, we create a new `Ebb` that will hold the code + * after the block, and we push a frame on the control stack. Depending on the type + * of block, we create a new `Ebb` for the body of the block with an associated + * jump instruction. + * + * The `End` instruction pops the last control frame from the control stack, seals + * the destination block (since `br` instructions targeting it only appear inside the + * block and have already been translated) and modify the value stack to use the + * possible `Ebb`'s arguments values. + ***********************************************************************************/ + Operator::Block { ty } => { + let next = builder.create_ebb(); + match type_to_type(&ty) { + Ok(ty_cre) => { + builder.append_ebb_arg(next, ty_cre); + } + Err(_) => {} + } + control_stack.push(ControlStackFrame::Block { + destination: next, + return_values: translate_type(ty).unwrap(), + original_stack_size: stack.len(), + reachable: false, + }); + } + Operator::Loop { ty } => { + let loop_body = builder.create_ebb(); + let next = builder.create_ebb(); + match type_to_type(&ty) { + Ok(ty_cre) => { + builder.append_ebb_arg(next, ty_cre); + } + Err(_) => {} + } + builder.ins().jump(loop_body, &[]); + control_stack.push(ControlStackFrame::Loop { + destination: next, + header: loop_body, + return_values: translate_type(ty).unwrap(), + original_stack_size: stack.len(), + reachable: false, + }); + builder.switch_to_block(loop_body, &[]); + } + Operator::If { ty } => { + let val = stack.pop().unwrap(); + let if_not = builder.create_ebb(); + let jump_inst = builder.ins().brz(val, if_not, &[]); + // Here we append an argument to an Ebb targeted by an argumentless jump instruction + // But in fact there are two cases: + // - either the If does not have a Else clause, in that case ty = EmptyBlock + // and we add nothing; + // - either the If have an Else clause, in that case the destination of this jump + // instruction will be changed later when we translate the Else operator. + match type_to_type(&ty) { + Ok(ty_cre) => { + builder.append_ebb_arg(if_not, ty_cre); + } + Err(_) => {} + } + control_stack.push(ControlStackFrame::If { + destination: if_not, + branch_inst: jump_inst, + return_values: translate_type(ty).unwrap(), + original_stack_size: stack.len(), + reachable: false, + }); + } + Operator::Else => { + // We take the control frame pushed by the if, use its ebb as the else body + // and push a new control frame with a new ebb for the code after the if/then/else + // At the end of the then clause we jump to the destination + let (destination, return_values, branch_inst) = match &control_stack[control_stack.len() - + 1] { + &ControlStackFrame::If { + destination, + ref return_values, + branch_inst, + .. + } => (destination, return_values, branch_inst), + _ => panic!("should not happen"), + }; + let cut_index = stack.len() - return_values.len(); + let jump_args = stack.split_off(cut_index); + builder.ins().jump(destination, jump_args.as_slice()); + // We change the target of the branch instruction + let else_ebb = builder.create_ebb(); + builder.change_jump_destination(branch_inst, else_ebb); + builder.seal_block(else_ebb); + builder.switch_to_block(else_ebb, &[]); + } + Operator::End => { + let frame = control_stack.pop().unwrap(); + if !builder.is_unreachable() || !builder.is_pristine() { + let cut_index = stack.len() - frame.return_values().len(); + let jump_args = stack.split_off(cut_index); + builder + .ins() + .jump(frame.following_code(), jump_args.as_slice()); + } + builder.switch_to_block(frame.following_code(), frame.return_values()); + builder.seal_block(frame.following_code()); + // If it is a loop we also have to seal the body loop block + match frame { + ControlStackFrame::Loop { header, .. } => builder.seal_block(header), + _ => {} + } + stack.truncate(frame.original_stack_size()); + stack.extend_from_slice(builder.ebb_args(frame.following_code())); + } + /**************************** Branch instructions ********************************* + * The branch instructions all have as arguments a target nesting level, which + * corresponds to how many control stack frames do we have to pop to get the + * destination `Ebb`. + * + * Once the destination `Ebb` is found, we sometimes have to declare a certain depth + * of the stack unreachable, because some branch instructions are terminator. + * + * The `br_table` case is much more complicated because Cretonne's `br_table` instruction + * does not support jump arguments like all the other branch instructions. That is why, in + * the case where we would use jump arguments for every other branch instructions, we + * need to split the critical edges leaving the `br_tables` by creating one `Ebb` per + * table destination; the `br_table` will point to these newly created `Ebbs` and these + * `Ebb`s contain only a jump instruction pointing to the final destination, this time with + * jump arguments. + * + * This system is also implemented in Cretonne's SSA construction algorithm, because + * `use_var` located in a destination `Ebb` of a `br_table` might trigger the addition + * of jump arguments in each predecessor branch instruction, one of which might be a + * `br_table`. + ***********************************************************************************/ + Operator::Br { relative_depth } => { + let i = control_stack.len() - 1 - (relative_depth as usize); + let frame = &mut control_stack[i]; + let jump_args = if frame.is_loop() { + Vec::new() + } else { + let cut_index = stack.len() - frame.return_values().len(); + stack.split_off(cut_index) + }; + builder + .ins() + .jump(frame.br_destination(), jump_args.as_slice()); + // We signal that all the code that follows until the next End is unreachable + frame.set_reachable(); + state.real_unreachable_stack_depth = 1 + relative_depth as usize; + } + Operator::BrIf { relative_depth } => { + let val = stack.pop().unwrap(); + let i = control_stack.len() - 1 - (relative_depth as usize); + let frame = &mut control_stack[i]; + let cut_index = stack.len() - frame.return_values().len(); + let jump_args = stack.split_off(cut_index); + builder + .ins() + .brnz(val, frame.br_destination(), jump_args.as_slice()); + // The values returned by the branch are still available for the reachable + // code that comes after it + frame.set_reachable(); + stack.extend(jump_args); + } + Operator::BrTable { ref table } => { + let (depths, default) = table.read_table(); + let mut min_depth = default; + for depth in depths.iter() { + if *depth < min_depth { + min_depth = *depth; + } + } + let jump_args_count = control_stack[control_stack.len() - 1 - (min_depth as usize)] + .return_values() + .len(); + if jump_args_count == 0 { + // No jump arguments + let val = stack.pop().unwrap(); + if depths.len() > 0 { + let jt = builder.create_jump_table(); + for (index, depth) in depths.iter().enumerate() { + let i = control_stack.len() - 1 - (*depth as usize); + let frame = &mut control_stack[i]; + let ebb = frame.br_destination(); + builder.insert_jump_table_entry(jt, index, ebb); + frame.set_reachable(); + } + builder.ins().br_table(val, jt); + } + let i = control_stack.len() - 1 - (default as usize); + let frame = &mut control_stack[i]; + let ebb = frame.br_destination(); + builder.ins().jump(ebb, &[]); + state.real_unreachable_stack_depth = 1 + min_depth as usize; + frame.set_reachable(); + } else { + // Here we have jump arguments, but Cretonne's br_table doesn't support them + // We then proceed to split the edges going out of the br_table + let val = stack.pop().unwrap(); + let cut_index = stack.len() - jump_args_count; + let jump_args = stack.split_off(cut_index); + if depths.len() > 0 { + let jt = builder.create_jump_table(); + let dest_ebbs: HashMap = depths + .iter() + .enumerate() + .fold(HashMap::new(), |mut acc, (index, &depth)| { + if acc.get(&(depth as usize)).is_none() { + let branch_ebb = builder.create_ebb(); + builder.insert_jump_table_entry(jt, index, branch_ebb); + acc.insert(depth as usize, branch_ebb); + return acc; + }; + let branch_ebb = acc.get(&(depth as usize)).unwrap().clone(); + builder.insert_jump_table_entry(jt, index, branch_ebb); + acc + }); + builder.ins().br_table(val, jt); + let default_ebb = control_stack[control_stack.len() - 1 - (default as usize)] + .br_destination(); + builder.ins().jump(default_ebb, jump_args.as_slice()); + stack.extend(jump_args.clone()); + for (depth, dest_ebb) in dest_ebbs { + builder.switch_to_block(dest_ebb, &[]); + builder.seal_block(dest_ebb); + let i = control_stack.len() - 1 - (depth as usize); + let frame = &mut control_stack[i]; + let real_dest_ebb = frame.br_destination(); + builder.ins().jump(real_dest_ebb, jump_args.as_slice()); + frame.set_reachable(); + } + state.real_unreachable_stack_depth = 1 + min_depth as usize; + } else { + let ebb = control_stack[control_stack.len() - 1 - (default as usize)] + .br_destination(); + builder.ins().jump(ebb, jump_args.as_slice()); + stack.extend(jump_args); + state.real_unreachable_stack_depth = 1 + min_depth as usize; + } + } + } + Operator::Return => { + let return_count = sig.return_types.len(); + let cut_index = stack.len() - return_count; + let return_args = stack.split_off(cut_index); + builder.ins().return_(return_args.as_slice()); + state.last_inst_return = true; + state.real_unreachable_stack_depth = 1; + } + /************************************ Calls **************************************** + * The call instructions pop off their arguments from the stack and append their + * return values to it. `call_indirect` needs runtime support because there is an + * argument referring to an index in the external functions table of the module. + ************************************************************************************/ + Operator::Call { function_index } => { + let args_num = args_count(function_index as usize, functions, signatures); + let cut_index = stack.len() - args_num; + let call_args = stack.split_off(cut_index); + let internal_function_index = find_function_import(function_index as usize, + builder, + func_imports, + functions, + exports, + signatures); + let call_inst = builder + .ins() + .call(internal_function_index, call_args.as_slice()); + let ret_values = builder.inst_results(call_inst); + for val in ret_values { + stack.push(*val); + } + } + Operator::CallIndirect { + index, + table_index: _, + } => { + // index is the index of the function's signature and table_index is the index + // of the table to search the function in + // TODO: have runtime support for tables + let sigref = find_signature_import(index as usize, builder, func_imports, signatures); + let args_num = builder.signature(sigref).unwrap().argument_types.len(); + let index_val = stack.pop().unwrap(); + let cut_index = stack.len() - args_num; + let call_args = stack.split_off(cut_index); + let ret_values = + runtime.translate_call_indirect(builder, sigref, index_val, call_args.as_slice()); + for val in ret_values { + stack.push(*val); + } + } + /******************************* Memory management *********************************** + * Memory management is handled by runtime. It is usually translated into calls to + * special functions. + ************************************************************************************/ + Operator::GrowMemory { reserved: _ } => { + let val = stack.pop().unwrap(); + stack.push(runtime.translate_grow_memory(builder, val)); + } + Operator::CurrentMemory { reserved: _ } => { + stack.push(runtime.translate_current_memory(builder)); + } + /******************************* Load instructions *********************************** + * Wasm specifies an integer alignment flag but we drop it in Cretonne. + * The memory base address is provided by the runtime. + * TODO: differentiate between 32 bit and 64 bit architecture, to put the uextend or not + ************************************************************************************/ + Operator::I32Load8U { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().uload8(I32, memflags, addr, memoffset)) + } + Operator::I32Load16U { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().uload8(I32, memflags, addr, memoffset)) + } + Operator::I32Load8S { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().sload8(I32, memflags, addr, memoffset)) + } + Operator::I32Load16S { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().sload8(I32, memflags, addr, memoffset)) + } + Operator::I64Load8U { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().uload8(I64, memflags, addr, memoffset)) + } + Operator::I64Load16U { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().uload16(I64, memflags, addr, memoffset)) + } + Operator::I64Load8S { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().sload8(I64, memflags, addr, memoffset)) + } + Operator::I64Load16S { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().sload16(I64, memflags, addr, memoffset)) + } + Operator::I64Load32S { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().sload32(memflags, addr, memoffset)) + } + Operator::I64Load32U { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().uload32(memflags, addr, memoffset)) + } + Operator::I32Load { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().load(I32, memflags, addr, memoffset)) + } + Operator::F32Load { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().load(F32, memflags, addr, memoffset)) + } + Operator::I64Load { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().load(I64, memflags, addr, memoffset)) + } + Operator::F64Load { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + stack.push(builder.ins().load(F64, memflags, addr, memoffset)) + } + /****************************** Store instructions *********************************** + * Wasm specifies an integer alignment flag but we drop it in Cretonne. + * The memory base address is provided by the runtime. + * TODO: differentiate between 32 bit and 64 bit architecture, to put the uextend or not + ************************************************************************************/ + Operator::I32Store { memory_immediate: MemoryImmediate { flags: _, offset } } | + Operator::I64Store { memory_immediate: MemoryImmediate { flags: _, offset } } | + Operator::F32Store { memory_immediate: MemoryImmediate { flags: _, offset } } | + Operator::F64Store { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let val = stack.pop().unwrap(); + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + builder.ins().store(memflags, val, addr, memoffset); + } + Operator::I32Store8 { memory_immediate: MemoryImmediate { flags: _, offset } } | + Operator::I64Store8 { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let val = stack.pop().unwrap(); + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + builder.ins().istore8(memflags, val, addr, memoffset); + } + Operator::I32Store16 { memory_immediate: MemoryImmediate { flags: _, offset } } | + Operator::I64Store16 { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let val = stack.pop().unwrap(); + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + builder.ins().istore16(memflags, val, addr, memoffset); + } + Operator::I64Store32 { memory_immediate: MemoryImmediate { flags: _, offset } } => { + let val = stack.pop().unwrap(); + let address_i32 = stack.pop().unwrap(); + let base = runtime.translate_memory_base_address(builder, 0); + let address_i64 = builder.ins().uextend(I64, address_i32); + let addr = builder.ins().iadd(base, address_i64); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + builder.ins().istore32(memflags, val, addr, memoffset); + } + /****************************** Nullary Operators ************************************/ + Operator::I32Const { value } => stack.push(builder.ins().iconst(I32, value as i64)), + Operator::I64Const { value } => stack.push(builder.ins().iconst(I64, value)), + Operator::F32Const { value } => { + stack.push(builder.ins().f32const(f32_translation(value))); + } + Operator::F64Const { value } => { + stack.push(builder.ins().f64const(f64_translation(value))); + } + /******************************* Unary Operators *************************************/ + Operator::I32Clz => { + let arg = stack.pop().unwrap(); + let val = builder.ins().clz(arg); + stack.push(builder.ins().sextend(I32, val)); + } + Operator::I64Clz => { + let arg = stack.pop().unwrap(); + let val = builder.ins().clz(arg); + stack.push(builder.ins().sextend(I64, val)); + } + Operator::I32Ctz => { + let val = stack.pop().unwrap(); + let short_res = builder.ins().ctz(val); + stack.push(builder.ins().sextend(I32, short_res)); + } + Operator::I64Ctz => { + let val = stack.pop().unwrap(); + let short_res = builder.ins().ctz(val); + stack.push(builder.ins().sextend(I64, short_res)); + } + Operator::I32Popcnt => { + let arg = stack.pop().unwrap(); + let val = builder.ins().popcnt(arg); + stack.push(builder.ins().sextend(I32, val)); + } + Operator::I64Popcnt => { + let arg = stack.pop().unwrap(); + let val = builder.ins().popcnt(arg); + stack.push(builder.ins().sextend(I64, val)); + } + Operator::I64ExtendSI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().sextend(I64, val)); + } + Operator::I64ExtendUI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().uextend(I64, val)); + } + Operator::I32WrapI64 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().ireduce(I32, val)); + } + Operator::F32Sqrt | + Operator::F64Sqrt => { + let arg = stack.pop().unwrap(); + stack.push(builder.ins().sqrt(arg)); + } + Operator::F32Ceil | + Operator::F64Ceil => { + let arg = stack.pop().unwrap(); + stack.push(builder.ins().ceil(arg)); + } + Operator::F32Floor | + Operator::F64Floor => { + let arg = stack.pop().unwrap(); + stack.push(builder.ins().floor(arg)); + } + Operator::F32Trunc | + Operator::F64Trunc => { + let arg = stack.pop().unwrap(); + stack.push(builder.ins().trunc(arg)); + } + Operator::F32Nearest | + Operator::F64Nearest => { + let arg = stack.pop().unwrap(); + stack.push(builder.ins().nearest(arg)); + } + Operator::F32Abs | Operator::F64Abs => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fabs(val)); + } + Operator::F32Neg | Operator::F64Neg => { + let arg = stack.pop().unwrap(); + stack.push(builder.ins().fneg(arg)); + } + Operator::F64ConvertUI64 | + Operator::F64ConvertUI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_from_uint(F64, val)); + } + Operator::F64ConvertSI64 | + Operator::F64ConvertSI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_from_sint(F64, val)); + } + Operator::F32ConvertSI64 | + Operator::F32ConvertSI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_from_sint(F32, val)); + } + Operator::F32ConvertUI64 | + Operator::F32ConvertUI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_from_uint(F32, val)); + } + Operator::F64PromoteF32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fpromote(F64, val)); + } + Operator::F32DemoteF64 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fdemote(F32, val)); + } + Operator::I64TruncSF64 | + Operator::I64TruncSF32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_to_sint(I64, val)); + } + Operator::I32TruncSF64 | + Operator::I32TruncSF32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_to_sint(I32, val)); + } + Operator::I64TruncUF64 | + Operator::I64TruncUF32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_to_uint(I64, val)); + } + Operator::I32TruncUF64 | + Operator::I32TruncUF32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().fcvt_to_uint(I32, val)); + } + Operator::F32ReinterpretI32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().bitcast(F32, val)); + } + Operator::F64ReinterpretI64 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().bitcast(F64, val)); + } + Operator::I32ReinterpretF32 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().bitcast(I32, val)); + } + Operator::I64ReinterpretF64 => { + let val = stack.pop().unwrap(); + stack.push(builder.ins().bitcast(I64, val)); + } + /****************************** Binary Operators ************************************/ + Operator::I32Add | Operator::I64Add => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().iadd(arg1, arg2)); + } + Operator::I32And | Operator::I64And => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().band(arg1, arg2)); + } + Operator::I32Or | Operator::I64Or => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().bor(arg1, arg2)); + } + Operator::I32Xor | Operator::I64Xor => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().bxor(arg1, arg2)); + } + Operator::I32Shl | Operator::I64Shl => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().ishl(arg1, arg2)); + } + Operator::I32ShrS | + Operator::I64ShrS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().sshr(arg1, arg2)); + } + Operator::I32ShrU | + Operator::I64ShrU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().ushr(arg1, arg2)); + } + Operator::I32Rotl | + Operator::I64Rotl => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().rotl(arg1, arg2)); + } + Operator::I32Rotr | + Operator::I64Rotr => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().rotr(arg1, arg2)); + } + Operator::F32Add | Operator::F64Add => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fadd(arg1, arg2)); + } + Operator::I32Sub | Operator::I64Sub => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().isub(arg1, arg2)); + } + Operator::F32Sub | Operator::F64Sub => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fsub(arg1, arg2)); + } + Operator::I32Mul | Operator::I64Mul => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().imul(arg1, arg2)); + } + Operator::F32Mul | Operator::F64Mul => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fmul(arg1, arg2)); + } + Operator::F32Div | Operator::F64Div => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fdiv(arg1, arg2)); + } + Operator::I32DivS | + Operator::I64DivS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().sdiv(arg1, arg2)); + } + Operator::I32DivU | + Operator::I64DivU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().udiv(arg1, arg2)); + } + Operator::I32RemS | + Operator::I64RemS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().srem(arg1, arg2)); + } + Operator::I32RemU | + Operator::I64RemU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().urem(arg1, arg2)); + } + Operator::F32Min | Operator::F64Min => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fmin(arg1, arg2)); + } + Operator::F32Max | Operator::F64Max => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fmax(arg1, arg2)); + } + Operator::F32Copysign | + Operator::F64Copysign => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + stack.push(builder.ins().fcopysign(arg1, arg2)); + } + /**************************** Comparison Operators **********************************/ + Operator::I32LtS | Operator::I64LtS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::SignedLessThan, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32LtU | Operator::I64LtU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::UnsignedLessThan, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32LeS | Operator::I64LeS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::SignedLessThanOrEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32LeU | Operator::I64LeU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder + .ins() + .icmp(IntCC::UnsignedLessThanOrEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32GtS | Operator::I64GtS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::SignedGreaterThan, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32GtU | Operator::I64GtU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::UnsignedGreaterThan, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32GeS | Operator::I64GeS => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder + .ins() + .icmp(IntCC::SignedGreaterThanOrEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32GeU | Operator::I64GeU => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder + .ins() + .icmp(IntCC::UnsignedGreaterThanOrEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32Eqz | Operator::I64Eqz => { + let arg = stack.pop().unwrap(); + let val = builder.ins().icmp_imm(IntCC::Equal, arg, 0); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32Eq | Operator::I64Eq => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::Equal, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::F32Eq | Operator::F64Eq => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().fcmp(FloatCC::Equal, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::I32Ne | Operator::I64Ne => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().icmp(IntCC::NotEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::F32Ne | Operator::F64Ne => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().fcmp(FloatCC::NotEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::F32Gt | Operator::F64Gt => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().fcmp(FloatCC::GreaterThan, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::F32Ge | Operator::F64Ge => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().fcmp(FloatCC::GreaterThanOrEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::F32Lt | Operator::F64Lt => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().fcmp(FloatCC::LessThan, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + Operator::F32Le | Operator::F64Le => { + let arg2 = stack.pop().unwrap(); + let arg1 = stack.pop().unwrap(); + let val = builder.ins().fcmp(FloatCC::LessThanOrEqual, arg1, arg2); + stack.push(builder.ins().bint(I32, val)); + } + } +} + +/// Deals with a Wasm instruction located in an unreachable portion of the code. Most of them +/// are dropped but special ones like `End` or `Else` signal the potential end of the unreachable +/// portion so the translation state muts be updated accordingly. +fn translate_unreachable_operator(op: &Operator, + builder: &mut FunctionBuilder, + stack: &mut Vec, + control_stack: &mut Vec, + state: &mut TranslationState) { + // We don't translate because the code is unreachable + // Nevertheless we have to record a phantom stack for this code + // to know when the unreachable code ends + match *op { + Operator::If { ty: _ } | + Operator::Loop { ty: _ } | + Operator::Block { ty: _ } => { + state.phantom_unreachable_stack_depth += 1; + } + Operator::End => { + if state.phantom_unreachable_stack_depth > 0 { + state.phantom_unreachable_stack_depth -= 1; + } else { + // This End corresponds to a real control stack frame + // We switch to the destination block but we don't insert + // a jump instruction since the code is still unreachable + let frame = control_stack.pop().unwrap(); + + builder.switch_to_block(frame.following_code(), &[]); + builder.seal_block(frame.following_code()); + match frame { + // If it is a loop we also have to seal the body loop block + ControlStackFrame::Loop { header, .. } => builder.seal_block(header), + // If it is a if then the code after is reachable again + ControlStackFrame::If { .. } => { + state.real_unreachable_stack_depth = 1; + } + _ => {} + } + if frame.is_reachable() { + state.real_unreachable_stack_depth = 1; + } + // Now we have to split off the stack the values not used + // by unreachable code that hasn't been translated + stack.truncate(frame.original_stack_size()); + // And add the return values of the block but only if the next block is reachble + // (which corresponds to testing if the stack depth is 1) + if state.real_unreachable_stack_depth == 1 { + stack.extend_from_slice(builder.ebb_args(frame.following_code())); + } + state.real_unreachable_stack_depth -= 1; + state.last_inst_return = false; + } + } + Operator::Else => { + if state.phantom_unreachable_stack_depth > 0 { + // This is part of a phantom if-then-else, we do nothing + } else { + // Encountering an real else means that the code in the else + // clause is reachable again + let (branch_inst, original_stack_size) = match &control_stack[control_stack.len() - + 1] { + &ControlStackFrame::If { + branch_inst, + original_stack_size, + .. + } => (branch_inst, original_stack_size), + _ => panic!("should not happen"), + }; + // We change the target of the branch instruction + let else_ebb = builder.create_ebb(); + builder.change_jump_destination(branch_inst, else_ebb); + builder.seal_block(else_ebb); + builder.switch_to_block(else_ebb, &[]); + // Now we have to split off the stack the values not used + // by unreachable code that hasn't been translated + stack.truncate(original_stack_size); + state.real_unreachable_stack_depth = 0; + state.last_inst_return = false; + } + } + _ => { + // We don't translate because this is unreachable code + } + } +} + +fn args_count(index: FunctionIndex, + functions: &Vec, + signatures: &Vec) + -> usize { + signatures[functions[index] as usize].argument_types.len() +} + +// Given a index in the function index space, search for it in the function imports and if it is +// not there add it to the function imports. +fn find_function_import(index: FunctionIndex, + builder: &mut FunctionBuilder, + func_imports: &mut FunctionImports, + functions: &Vec, + exports: &Option>, + signatures: &Vec) + -> FuncRef { + match func_imports.functions.get(&index) { + Some(local_index) => return *local_index, + None => {} + } + // We have to import the function + let sig_index = functions[index]; + match func_imports.signatures.get(&(sig_index as usize)) { + Some(local_sig_index) => { + let local_func_index = + builder.import_function(ExtFuncData { + name: match exports { + &None => FunctionName::new(""), + &Some(ref exports) => { + match exports.get(&index) { + None => FunctionName::new(""), + Some(name) => { + FunctionName::new(name.clone()) + } + } + } + }, + signature: *local_sig_index, + }); + func_imports.functions.insert(index, local_func_index); + return local_func_index; + } + None => {} + }; + // We have to import the signature + let sig_local_index = builder.import_signature(signatures[sig_index as usize].clone()); + func_imports + .signatures + .insert(sig_index as usize, sig_local_index); + let local_func_index = + builder.import_function(ExtFuncData { + name: match exports { + &None => FunctionName::new(""), + &Some(ref exports) => { + match exports.get(&index) { + None => FunctionName::new(""), + Some(name) => FunctionName::new(name.clone()), + } + } + }, + signature: sig_local_index, + }); + func_imports.functions.insert(index, local_func_index); + local_func_index +} + +fn find_signature_import(sig_index: SignatureIndex, + builder: &mut FunctionBuilder, + func_imports: &mut FunctionImports, + signatures: &Vec) + -> SigRef { + match func_imports.signatures.get(&(sig_index as usize)) { + Some(local_sig_index) => return *local_sig_index, + None => {} + } + let sig_local_index = builder.import_signature(signatures[sig_index as usize].clone()); + func_imports + .signatures + .insert(sig_index as usize, sig_local_index); + sig_local_index +} diff --git a/lib/wasm2cretonne/src/lib.rs b/lib/wasm2cretonne/src/lib.rs new file mode 100644 index 0000000000..5006e5c9aa --- /dev/null +++ b/lib/wasm2cretonne/src/lib.rs @@ -0,0 +1,27 @@ +//! Performs the translation from a wasm module in binary format to the in-memory representation +//! of the Cretonne IL. More particularly, it translates the code of all the functions bodies and +//! interacts with a runtime implementing the [`WasmRuntime`](trait.WasmRuntime.html) trait to +//! deal with tables, globals and linear memory. +//! +//! The crate provides a `DummyRuntime` trait that will allow to translate the code of the +//! functions but will fail at execution. You should use +//! [`wasmstandalone::StandaloneRuntime`](../wasmstandalone/struct.StandaloneRuntime.html) to be +//! able to execute the translated code. +//! +//! The main function of this module is [`translate_module`](fn.translate_module.html). + +extern crate wasmparser; +extern crate cton_frontend; +extern crate cretonne; + +mod module_translator; +mod translation_utils; +mod code_translator; +mod runtime; +mod sections_translator; + +pub use module_translator::{translate_module, TranslationResult, FunctionTranslation, + ImportMappings}; +pub use runtime::{WasmRuntime, DummyRuntime}; +pub use translation_utils::{Local, FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, RawByte, + MemoryAddress, SignatureIndex, Global, GlobalInit, Table, Memory}; diff --git a/lib/wasm2cretonne/src/module_translator.rs b/lib/wasm2cretonne/src/module_translator.rs new file mode 100644 index 0000000000..1c22655300 --- /dev/null +++ b/lib/wasm2cretonne/src/module_translator.rs @@ -0,0 +1,288 @@ +//! Translation skeletton that traverses the whole WebAssembly module and call helper functions +//! to deal with each part of it. +use wasmparser::{ParserState, SectionCode, ParserInput, Parser, WasmDecoder}; +use sections_translator::{SectionParsingError, parse_function_signatures, parse_import_section, + parse_function_section, parse_export_section, parse_memory_section, + parse_global_section, parse_table_section, parse_elements_section, + parse_data_section}; +use translation_utils::{type_to_type, Import, SignatureIndex, FunctionIndex, invert_hashmaps}; +use cretonne::ir::{Function, Type, FuncRef, SigRef}; +use code_translator::translate_function_body; +use cton_frontend::ILBuilder; +use std::collections::HashMap; +use runtime::WasmRuntime; + +/// Output of the [`translate_module`](fn.translate_module.html) function. Contains the translated +/// functions and when present the index of the function defined as `start` of the module. +pub struct TranslationResult { + pub functions: Vec, + pub start_index: Option, +} + +/// A function in a WebAssembly module can be either imported, or defined inside it. If it is +/// defined inside it, then the translation in Cretonne IL is available as well as the mappings +/// between Cretonne imports and indexes in the function index space. +#[derive(Clone)] +pub enum FunctionTranslation { + Code { + il: Function, + imports: ImportMappings, + }, + Import(), +} + +#[derive(Clone,Debug)] +/// Mappings describing the relations between imports of the Cretonne IL functions and the +/// functions in the WebAssembly module. +pub struct ImportMappings { + /// Find the index of a function in the WebAssembly module thanks to a `FuncRef`. + pub functions: HashMap, + /// Find the index of a signature in the WebAssembly module thanks to a `SigRef`. + pub signatures: HashMap, +} + +impl ImportMappings { + pub fn new() -> ImportMappings { + ImportMappings { + functions: HashMap::new(), + signatures: HashMap::new(), + } + } +} + +/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cretonne IL +/// [`Function`](../cretonne/ir/function/struct.Function.html). +/// Returns the functions and also the mappings for imported functions and signature between the +/// indexes in the wasm module and the indexes inside each functions. +pub fn translate_module(data: &Vec, + runtime: &mut WasmRuntime) + -> Result { + let mut parser = Parser::new(data.as_slice()); + match *parser.read() { + ParserState::BeginWasm { .. } => {} + ref s @ _ => panic!("modules should begin properly: {:?}", s), + } + let mut signatures = None; + let mut functions: Option> = None; + let mut globals = Vec::new(); + let mut exports: Option> = None; + let mut next_input = ParserInput::Default; + let mut function_index: FunctionIndex = 0; + let mut function_imports_count = 0; + let mut start_index: Option = None; + loop { + match *parser.read_with_input(next_input) { + ParserState::BeginSection { code: SectionCode::Type, .. } => { + match parse_function_signatures(&mut parser) { + Ok(sigs) => signatures = Some(sigs), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the type section: {}", s)) + } + }; + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Import, .. } => { + match parse_import_section(&mut parser) { + Ok(imps) => { + for import in imps { + match import { + Import::Function { sig_index } => { + functions = match functions { + None => Some(vec![sig_index as SignatureIndex]), + Some(mut funcs) => { + funcs.push(sig_index as SignatureIndex); + Some(funcs) + } + }; + function_index += 1; + } + Import::Memory(mem) => { + runtime.declare_memory(mem); + } + Import::Global(glob) => { + runtime.declare_global(glob.clone()); + globals.push(glob); + } + Import::Table(tab) => { + runtime.declare_table(tab); + } + } + } + } + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the import section: {}", s)) + } + } + function_imports_count = function_index; + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Function, .. } => { + match parse_function_section(&mut parser) { + Ok(funcs) => { + match functions { + None => functions = Some(funcs), + Some(ref mut imps) => imps.extend(funcs), + } + } + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the function section: {}", s)) + } + } + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Table, .. } => { + match parse_table_section(&mut parser, runtime) { + Ok(()) => (), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the table section: {}", s)) + } + } + } + ParserState::BeginSection { code: SectionCode::Memory, .. } => { + match parse_memory_section(&mut parser) { + Ok(mems) => { + for mem in mems { + runtime.declare_memory(mem); + } + } + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the memory section: {}", s)) + } + } + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Global, .. } => { + match parse_global_section(&mut parser, runtime) { + Ok(mut globs) => globals.append(&mut globs), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the global section: {}", s)) + } + } + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Export, .. } => { + match parse_export_section(&mut parser) { + Ok(exps) => exports = Some(exps), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the export section: {}", s)) + } + } + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Start, .. } => { + match *parser.read() { + ParserState::StartSectionEntry(index) => { + start_index = Some(index as FunctionIndex) + } + _ => return Err(String::from("wrong content in the start section")), + } + match *parser.read() { + ParserState::EndSection => {} + _ => return Err(String::from("wrong content in the start section")), + } + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Element, .. } => { + match parse_elements_section(&mut parser, runtime, &globals) { + Ok(()) => (), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the element section: {}", s)) + } + } + next_input = ParserInput::Default; + } + ParserState::BeginSection { code: SectionCode::Code, .. } => { + // The code section begins + break; + } + ParserState::EndSection => { + next_input = ParserInput::Default; + } + ParserState::EndWasm => { + return Ok(TranslationResult { + functions: Vec::new(), + start_index: None, + }) + } + ParserState::BeginSection { code: SectionCode::Data, .. } => { + match parse_data_section(&mut parser, runtime, &globals) { + Ok(()) => (), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the data section: {}", s)) + } + } + } + _ => return Err(String::from("wrong content in the preamble")), + }; + } + // At this point we've entered the code section + // First we check that we have all that is necessary to translate a function. + let signatures = match signatures { + None => Vec::new(), + Some(sigs) => sigs, + }; + let functions = match functions { + None => return Err(String::from("missing a function section")), + Some(functions) => functions, + }; + let mut il_functions: Vec = Vec::new(); + il_functions.resize(function_imports_count, FunctionTranslation::Import()); + let mut il_builder = ILBuilder::new(); + runtime.begin_translation(); + loop { + let locals: Vec<(usize, Type)> = match *parser.read() { + ParserState::BeginFunctionBody { ref locals, .. } => { + locals + .iter() + .map(|&(index, ref ty)| { + (index as usize, + match type_to_type(ty) { + Ok(ty) => ty, + Err(()) => panic!("unsupported type for local variable"), + }) + }) + .collect() + } + ParserState::EndSection => break, + _ => return Err(String::from(format!("wrong content in code section"))), + }; + let signature = signatures[functions[function_index as usize] as usize].clone(); + match translate_function_body(&mut parser, + function_index, + signature, + &locals, + &exports, + &signatures, + &functions, + &mut il_builder, + runtime) { + Ok((il_func, imports)) => { + il_functions.push(FunctionTranslation::Code { + il: il_func, + imports: invert_hashmaps(imports), + }) + } + Err(s) => return Err(s), + } + function_index += 1; + } + loop { + match *parser.read() { + ParserState::BeginSection { code: SectionCode::Data, .. } => { + match parse_data_section(&mut parser, runtime, &globals) { + Ok(()) => (), + Err(SectionParsingError::WrongSectionContent(s)) => { + return Err(format!("wrong content in the data section: {}", s)) + } + } + } + ParserState::EndWasm => { + return Ok(TranslationResult { + functions: il_functions, + start_index, + }) + } + _ => (), + } + } +} diff --git a/lib/wasm2cretonne/src/runtime/dummy.rs b/lib/wasm2cretonne/src/runtime/dummy.rs new file mode 100644 index 0000000000..5969c0ee8f --- /dev/null +++ b/lib/wasm2cretonne/src/runtime/dummy.rs @@ -0,0 +1,93 @@ +use runtime::WasmRuntime; +use translation_utils::{Local, Global, Memory, Table, GlobalIndex, TableIndex, FunctionIndex, + MemoryIndex}; +use cton_frontend::FunctionBuilder; +use cretonne::ir::{Value, InstBuilder, SigRef}; +use cretonne::ir::immediates::{Ieee32, Ieee64}; +use cretonne::ir::types::*; + +/// This runtime implementation is a "naïve" one, doing essentially nothing and emitting +/// placeholders when forced to. Don't try to execute code translated with this runtime, it is +/// essentially here for translation debug purposes. +pub struct DummyRuntime { + globals: Vec, +} + +impl DummyRuntime { + /// Allocates the runtime data structures. + pub fn new() -> DummyRuntime { + DummyRuntime { globals: Vec::new() } + } +} + +impl WasmRuntime for DummyRuntime { + fn translate_get_global(&self, + builder: &mut FunctionBuilder, + global_index: GlobalIndex) + -> Value { + let ref glob = self.globals.get(global_index as usize).unwrap(); + match glob.ty { + I32 => builder.ins().iconst(glob.ty, -1), + I64 => builder.ins().iconst(glob.ty, -1), + F32 => builder.ins().f32const(Ieee32::with_bits(0xbf800000)), // -1.0 + F64 => { + builder + .ins() + .f64const(Ieee64::with_bits(0xbff0000000000000)) + } // -1.0 + _ => panic!("should not happen"), + } + } + + fn translate_set_global(&self, _: &mut FunctionBuilder, _: GlobalIndex, _: Value) { + // We do nothing + } + fn translate_grow_memory(&mut self, builder: &mut FunctionBuilder, _: Value) -> Value { + builder.ins().iconst(I32, -1) + } + fn translate_current_memory(&mut self, builder: &mut FunctionBuilder) -> Value { + builder.ins().iconst(I32, -1) + } + fn translate_call_indirect<'a>(&self, + builder: &'a mut FunctionBuilder, + sig_ref: SigRef, + index_val: Value, + call_args: &[Value]) + -> &'a [Value] { + let call_inst = builder.ins().call_indirect(sig_ref, index_val, call_args); + builder.inst_results(call_inst) + } + fn translate_memory_base_address(&self, + builder: &mut FunctionBuilder, + _: MemoryIndex) + -> Value { + builder.ins().iconst(I64, 0) + } + fn declare_global(&mut self, global: Global) { + self.globals.push(global); + } + fn declare_table(&mut self, _: Table) { + //We do nothing + } + fn declare_table_elements(&mut self, _: TableIndex, _: usize, _: &[FunctionIndex]) { + //We do nothing + } + fn declare_memory(&mut self, _: Memory) { + //We do nothing + } + fn declare_data_initialization(&mut self, + _: MemoryIndex, + _: usize, + _: &[u8]) + -> Result<(), String> { + // We do nothing + Ok(()) + } + + fn begin_translation(&mut self) { + // We do nothing + } + fn next_function(&mut self) { + // We do nothing + } +} diff --git a/lib/wasm2cretonne/src/runtime/mod.rs b/lib/wasm2cretonne/src/runtime/mod.rs new file mode 100644 index 0000000000..9f2a41d4f9 --- /dev/null +++ b/lib/wasm2cretonne/src/runtime/mod.rs @@ -0,0 +1,5 @@ +mod spec; +mod dummy; + +pub use runtime::spec::WasmRuntime; +pub use runtime::dummy::DummyRuntime; diff --git a/lib/wasm2cretonne/src/runtime/spec.rs b/lib/wasm2cretonne/src/runtime/spec.rs new file mode 100644 index 0000000000..24e98b0d58 --- /dev/null +++ b/lib/wasm2cretonne/src/runtime/spec.rs @@ -0,0 +1,61 @@ +//! All the runtime support necessary for the wasm to cretonne translation is formalized by the +//! trait `WasmRuntime`. +use cton_frontend::FunctionBuilder; +use cretonne::ir::{Value, SigRef}; +use translation_utils::{Local, FunctionIndex, TableIndex, GlobalIndex, MemoryIndex, Global, Table, + Memory}; + +/// An object satisfyng the `WasmRuntime` trait can be passed as argument to the +/// [`translate_module`](fn.translate_module.html) function. These methods should not be called +/// by the user, they are only for the `wasm2cretonne` internal use. +pub trait WasmRuntime { + /// Declares a global to the runtime. + fn declare_global(&mut self, global: Global); + /// Declares a table to the runtime. + fn declare_table(&mut self, table: Table); + /// Fills a declared table with references to functions in the module. + fn declare_table_elements(&mut self, + table_index: TableIndex, + offset: usize, + elements: &[FunctionIndex]); + /// Declares a memory to the runtime + fn declare_memory(&mut self, memory: Memory); + /// Fills a declared memory with bytes at module instantiation. + fn declare_data_initialization(&mut self, + memory_index: MemoryIndex, + offset: usize, + data: &[u8]) + -> Result<(), String>; + /// Call this function after having declared all the runtime elements but prior to the + /// function body translation. + fn begin_translation(&mut self); + /// Call this function between each function body translation. + fn next_function(&mut self); + /// Translates a `get_global` wasm instruction. + fn translate_get_global(&self, + builder: &mut FunctionBuilder, + global_index: GlobalIndex) + -> Value; + /// Translates a `set_global` wasm instruction. + fn translate_set_global(&self, + builder: &mut FunctionBuilder, + global_index: GlobalIndex, + val: Value); + /// Translates a `grow_memory` wasm instruction. Returns the old size (in pages) of the memory. + fn translate_grow_memory(&mut self, builder: &mut FunctionBuilder, val: Value) -> Value; + /// Translates a `current_memory` wasm instruction. Returns the size in pages of the memory. + fn translate_current_memory(&mut self, builder: &mut FunctionBuilder) -> Value; + /// Returns the base address of a wasm memory as a Cretonne `Value`. + fn translate_memory_base_address(&self, + builder: &mut FunctionBuilder, + index: MemoryIndex) + -> Value; + /// Translates a `call_indirect` wasm instruction. It involves looking up the value contained + /// it the table at location `index_val` and calling the corresponding function. + fn translate_call_indirect<'a>(&self, + builder: &'a mut FunctionBuilder, + sig_ref: SigRef, + index_val: Value, + call_args: &[Value]) + -> &'a [Value]; +} diff --git a/lib/wasm2cretonne/src/sections_translator.rs b/lib/wasm2cretonne/src/sections_translator.rs new file mode 100644 index 0000000000..11edd08f30 --- /dev/null +++ b/lib/wasm2cretonne/src/sections_translator.rs @@ -0,0 +1,367 @@ +//! Helper functions to gather information for each of the non-function sections of a +//! WebAssembly module. +//! +//! The code of theses helper function is straightforward since it is only about reading metadata +//! about linear memories, tables, globals, etc. and storing them for later use. +//! +//! The special case of the initialize expressions for table elements offsets or global variables +//! is handled, according to the semantics of WebAssembly, to only specific expressions that are +//! interpreted on the fly. +use translation_utils::{type_to_type, Import, TableIndex, FunctionIndex, GlobalIndex, + SignatureIndex, MemoryIndex, Global, GlobalInit, Table, TableElementType, + Memory}; +use cretonne::ir::{Signature, ArgumentType, CallConv}; +use cretonne; +use wasmparser::{Parser, ParserState, FuncType, ImportSectionEntryType, ExternalKind, WasmDecoder, + MemoryType, Operator}; +use wasmparser; +use std::collections::HashMap; +use std::str::from_utf8; +use runtime::WasmRuntime; + +pub enum SectionParsingError { + WrongSectionContent(String), +} + +/// Reads the Type Section of the wasm module and returns the corresponding function signatures. +pub fn parse_function_signatures(parser: &mut Parser) + -> Result, SectionParsingError> { + let mut signatures: Vec = Vec::new(); + loop { + match *parser.read() { + ParserState::EndSection => break, + ParserState::TypeSectionEntry(FuncType { + form: wasmparser::Type::Func, + ref params, + ref returns, + }) => { + let mut sig = Signature::new(CallConv::Native); + sig.argument_types + .extend(params + .iter() + .map(|ty| { + let cret_arg: cretonne::ir::Type = match type_to_type(ty) { + Ok(ty) => ty, + Err(()) => panic!("only numeric types are supported in\ + function signatures"), + }; + ArgumentType::new(cret_arg) + })); + sig.return_types + .extend(returns + .iter() + .map(|ty| { + let cret_arg: cretonne::ir::Type = match type_to_type(ty) { + Ok(ty) => ty, + Err(()) => panic!("only numeric types are supported in\ + function signatures"), + }; + ArgumentType::new(cret_arg) + })); + signatures.push(sig); + } + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + } + } + Ok(signatures) +} + +/// Retrieves the imports from the imports section of the binary. +pub fn parse_import_section(parser: &mut Parser) -> Result, SectionParsingError> { + let mut imports = Vec::new(); + loop { + match *parser.read() { + ParserState::ImportSectionEntry { + ty: ImportSectionEntryType::Function(sig), .. + } => imports.push(Import::Function { sig_index: sig }), + ParserState::ImportSectionEntry { + ty: ImportSectionEntryType::Memory(MemoryType { limits: ref memlimits }), .. + } => { + imports.push(Import::Memory(Memory { + pages_count: memlimits.initial as usize, + maximum: memlimits.maximum.map(|x| x as usize), + })) + } + ParserState::ImportSectionEntry { + ty: ImportSectionEntryType::Global(ref ty), .. + } => { + imports.push(Import::Global(Global { + ty: type_to_type(&ty.content_type).unwrap(), + mutability: ty.mutability != 0, + initializer: GlobalInit::Import(), + })); + } + ParserState::ImportSectionEntry { + ty: ImportSectionEntryType::Table(ref tab), .. + } => { + imports.push(Import::Table(Table { + ty: match type_to_type(&tab.element_type) { + Ok(t) => TableElementType::Val(t), + Err(()) => TableElementType::Func(), + }, + size: tab.limits.initial as usize, + maximum: tab.limits.maximum.map(|x| x as usize), + })); + } + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(imports) +} + +/// Retrieves the correspondances between functions and signatures from the function section +pub fn parse_function_section(parser: &mut Parser) + -> Result, SectionParsingError> { + let mut funcs = Vec::new(); + loop { + match *parser.read() { + ParserState::FunctionSectionEntry(sigindex) => funcs.push(sigindex as SignatureIndex), + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(funcs) +} + +/// Retrieves the names of the functions from the export section +pub fn parse_export_section(parser: &mut Parser) + -> Result, SectionParsingError> { + let mut exports: HashMap = HashMap::new(); + loop { + match *parser.read() { + ParserState::ExportSectionEntry { + field, + ref kind, + index, + } => { + match kind { + &ExternalKind::Function => { + exports.insert(index as FunctionIndex, + String::from(from_utf8(field).unwrap())); + () + } + _ => (),//TODO: deal with other kind of exports + } + } + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(exports) +} + +/// Retrieves the size and maximum fields of memories from the memory section +pub fn parse_memory_section(parser: &mut Parser) -> Result, SectionParsingError> { + let mut memories: Vec = Vec::new(); + loop { + match *parser.read() { + ParserState::MemorySectionEntry(ref ty) => { + memories.push(Memory { + pages_count: ty.limits.initial as usize, + maximum: ty.limits.maximum.map(|x| x as usize), + }) + } + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(memories) +} + +/// Retrieves the size and maximum fields of memories from the memory section +pub fn parse_global_section(parser: &mut Parser, + runtime: &mut WasmRuntime) + -> Result, SectionParsingError> { + let mut globals = Vec::new(); + loop { + let (content_type, mutability) = match *parser.read() { + ParserState::BeginGlobalSectionEntry(ref ty) => (ty.content_type, ty.mutability), + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::BeginInitExpressionBody => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + } + let initializer = match *parser.read() { + ParserState::InitExpressionOperator(Operator::I32Const { value }) => { + GlobalInit::I32Const(value) + } + ParserState::InitExpressionOperator(Operator::I64Const { value }) => { + GlobalInit::I64Const(value) + } + ParserState::InitExpressionOperator(Operator::F32Const { value }) => { + GlobalInit::F32Const(value.bits()) + } + ParserState::InitExpressionOperator(Operator::F64Const { value }) => { + GlobalInit::F64Const(value.bits()) + } + ParserState::InitExpressionOperator(Operator::GetGlobal { global_index }) => { + GlobalInit::GlobalRef(global_index as GlobalIndex) + } + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + + }; + match *parser.read() { + ParserState::EndInitExpressionBody => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + } + let global = Global { + ty: type_to_type(&content_type).unwrap(), + mutability: mutability != 0, + initializer: initializer, + }; + runtime.declare_global(global.clone()); + globals.push(global); + match *parser.read() { + ParserState::EndGlobalSectionEntry => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + } + } + Ok(globals) +} + +pub fn parse_data_section(parser: &mut Parser, + runtime: &mut WasmRuntime, + globals: &Vec) + -> Result<(), SectionParsingError> { + loop { + let memory_index = match *parser.read() { + ParserState::BeginDataSectionEntry(memory_index) => memory_index, + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::BeginInitExpressionBody => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + let offset = match *parser.read() { + ParserState::InitExpressionOperator(Operator::I32Const { value }) => { + if value < 0 { + return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + } else { + value as usize + } + } + ParserState::InitExpressionOperator(Operator::GetGlobal { global_index }) => { + match globals[global_index as usize].initializer { + GlobalInit::I32Const(value) => { + if value < 0 { + return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + } else { + value as usize + } + } + GlobalInit::Import() => { + return Err(SectionParsingError::WrongSectionContent(String::from("imported globals not supported",),),) + } // TODO: add runtime support + _ => panic!("should not happen"), + } + } + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::EndInitExpressionBody => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + { + let data = match *parser.read() { + ParserState::DataSectionEntryBody(data) => data, + ref s @ _ => { + return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))) + } + }; + match runtime.declare_data_initialization(memory_index as MemoryIndex, offset, data) { + Ok(()) => (), + Err(s) => return Err(SectionParsingError::WrongSectionContent(format!("{}", s))), + }; + } + match *parser.read() { + ParserState::EndDataSectionEntry => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(()) +} + +/// Retrieves the tables from the table section +pub fn parse_table_section(parser: &mut Parser, + runtime: &mut WasmRuntime) + -> Result<(), SectionParsingError> { + loop { + match *parser.read() { + ParserState::TableSectionEntry(ref table) => { + runtime.declare_table(Table { + ty: match type_to_type(&table.element_type) { + Ok(t) => TableElementType::Val(t), + Err(()) => TableElementType::Func(), + }, + size: table.limits.initial as usize, + maximum: table.limits.maximum.map(|x| x as usize), + }) + } + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(()) +} + +/// Retrieves the tables from the table section +pub fn parse_elements_section(parser: &mut Parser, + runtime: &mut WasmRuntime, + globals: &Vec) + -> Result<(), SectionParsingError> { + loop { + let table_index = match *parser.read() { + ParserState::BeginElementSectionEntry(ref table_index) => *table_index as TableIndex, + ParserState::EndSection => break, + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::BeginInitExpressionBody => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + let offset = match *parser.read() { + ParserState::InitExpressionOperator(Operator::I32Const { value }) => { + if value < 0 { + return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + } else { + value as usize + } + } + ParserState::InitExpressionOperator(Operator::GetGlobal { global_index }) => { + match globals[global_index as usize].initializer { + GlobalInit::I32Const(value) => { + if value < 0 { + return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + } else { + value as usize + } + } + GlobalInit::Import() => 0, // TODO: add runtime support + _ => panic!("should not happen"), + } + } + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::EndInitExpressionBody => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::ElementSectionEntryBody(ref elements) => { + let elems: Vec = + elements.iter().map(|&x| x as FunctionIndex).collect(); + runtime.declare_table_elements(table_index, offset, elems.as_slice()) + } + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + match *parser.read() { + ParserState::EndElementSectionEntry => (), + ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))), + }; + } + Ok(()) +} diff --git a/lib/wasm2cretonne/src/translation_utils.rs b/lib/wasm2cretonne/src/translation_utils.rs new file mode 100644 index 0000000000..840e568787 --- /dev/null +++ b/lib/wasm2cretonne/src/translation_utils.rs @@ -0,0 +1,138 @@ +///! Helper functions and structures for the translation. +use wasmparser; +use cretonne; +use std::u32; +use code_translator; +use module_translator; + +/// Index of a function (imported or defined) inside the WebAssembly module. +pub type FunctionIndex = usize; +/// Index of a table (imported or defined) inside the WebAssembly module. +pub type TableIndex = usize; +/// Index of a global variable (imported or defined) inside the WebAssembly module. +pub type GlobalIndex = usize; +/// Index of a linear memory (imported or defined) inside the WebAssembly module. +pub type MemoryIndex = usize; +/// Index of a signature (imported or defined) inside the WebAssembly module. +pub type SignatureIndex = usize; +/// Raw byte read from memory. +pub type RawByte = u8; +/// Pointer referring to a memory address. +pub type MemoryAddress = usize; + +/// WebAssembly import. +#[derive(Debug,Clone,Copy)] +pub enum Import { + Function { sig_index: u32 }, + Memory(Memory), + Global(Global), + Table(Table), +} + +/// WebAssembly global. +#[derive(Debug,Clone,Copy)] +pub struct Global { + pub ty: cretonne::ir::Type, + pub mutability: bool, + pub initializer: GlobalInit, +} + +/// Globals are initialized via the four `const` operators or by referring to another import. +#[derive(Debug,Clone,Copy)] +pub enum GlobalInit { + I32Const(i32), + I64Const(i64), + F32Const(u32), + F64Const(u64), + Import(), + GlobalRef(GlobalIndex), +} + +/// WebAssembly table. +#[derive(Debug,Clone,Copy)] +pub struct Table { + pub ty: TableElementType, + pub size: usize, + pub maximum: Option, +} + +/// WebAssembly table element. Can be a function or a scalar type. +#[derive(Debug,Clone,Copy)] +pub enum TableElementType { + Val(cretonne::ir::Type), + Func(), +} + +/// WebAssembly linear memory. +#[derive(Debug,Clone,Copy)] +pub struct Memory { + pub pages_count: usize, + pub maximum: Option, +} + +/// Wrapper to a `get_local` and `set_local` index. They are WebAssembly's non-SSA variables. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct Local(pub u32); +impl cretonne::entity_ref::EntityRef for Local { + fn new(index: usize) -> Self { + assert!(index < (u32::MAX as usize)); + Local(index as u32) + } + + fn index(self) -> usize { + self.0 as usize + } +} +impl Default for Local { + fn default() -> Local { + Local(u32::MAX) + } +} + +/// Helper function translating wasmparser types to Cretonne types when possible. +pub fn type_to_type(ty: &wasmparser::Type) -> Result { + match *ty { + wasmparser::Type::I32 => Ok(cretonne::ir::types::I32), + wasmparser::Type::I64 => Ok(cretonne::ir::types::I64), + wasmparser::Type::F32 => Ok(cretonne::ir::types::F32), + wasmparser::Type::F64 => Ok(cretonne::ir::types::F64), + _ => Err(()), + } +} + +/// Turns a `wasmparser` `f32` into a `Cretonne` one. +pub fn f32_translation(x: wasmparser::Ieee32) -> cretonne::ir::immediates::Ieee32 { + cretonne::ir::immediates::Ieee32::with_bits(x.bits()) +} + +/// Turns a `wasmparser` `f64` into a `Cretonne` one. +pub fn f64_translation(x: wasmparser::Ieee64) -> cretonne::ir::immediates::Ieee64 { + cretonne::ir::immediates::Ieee64::with_bits(x.bits()) +} + +/// Translate a `wasmparser` type into its `Cretonne` equivalent, when possible +pub fn translate_type(ty: wasmparser::Type) -> Result, ()> { + match ty { + wasmparser::Type::EmptyBlockType => Ok(Vec::new()), + wasmparser::Type::I32 => Ok(vec![cretonne::ir::types::I32]), + wasmparser::Type::F32 => Ok(vec![cretonne::ir::types::F32]), + wasmparser::Type::I64 => Ok(vec![cretonne::ir::types::I64]), + wasmparser::Type::F64 => Ok(vec![cretonne::ir::types::F64]), + _ => panic!("unsupported return value type"), + } +} + +/// Inverts the key-value relation in the imports hashmap. Indeed, these hashmaps are built by +/// feeding the function indexes in the module but are used by the runtime with the `FuncRef` as +/// keys. +pub fn invert_hashmaps(imports: code_translator::FunctionImports) + -> module_translator::ImportMappings { + let mut new_imports = module_translator::ImportMappings::new(); + for (func_index, func_ref) in imports.functions.iter() { + new_imports.functions.insert(*func_ref, *func_index); + } + for (sig_index, sig_ref) in imports.signatures.iter() { + new_imports.signatures.insert(*sig_ref, *sig_index); + } + new_imports +} diff --git a/lib/wasmstandalone/.gitignore b/lib/wasmstandalone/.gitignore new file mode 100644 index 0000000000..4308d82204 --- /dev/null +++ b/lib/wasmstandalone/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/lib/wasmstandalone/Cargo.toml b/lib/wasmstandalone/Cargo.toml new file mode 100644 index 0000000000..a1f35f92ff --- /dev/null +++ b/lib/wasmstandalone/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wasmstandalone" +version = "0.0.0" +authors = ["The Cretonne Project Developers"] +publish = false + +[dependencies] +cretonne = { path = "../cretonne" } +cretonne-frontend = { path = "../frontend" } +wasm2cretonne = { path = "../wasm2cretonne" } +region = "0.0.8" diff --git a/lib/wasmstandalone/src/execution.rs b/lib/wasmstandalone/src/execution.rs new file mode 100644 index 0000000000..9d8a3f2839 --- /dev/null +++ b/lib/wasmstandalone/src/execution.rs @@ -0,0 +1,256 @@ +use cretonne::Context; +use cretonne::settings; +use cretonne::isa::{self, TargetIsa}; +use cretonne::verify_function; +use cretonne::verifier; +use cretonne::settings::Configurable; +use cretonne::result::CtonError; +use cretonne::ir::entities::AnyEntity; +use cretonne::ir::{self, Ebb, FuncRef, JumpTable, Function}; +use cretonne::binemit::{RelocSink, Reloc, CodeOffset}; +use wasm2cretonne::{TranslationResult, FunctionTranslation, ImportMappings, FunctionIndex}; +use std::mem::transmute; +use region::Protection; +use region::protect; +use std::collections::HashMap; +use std::ptr::write_unaligned; +use std::fmt::Write; + +type RelocRef = u16; + +// Implementation of a relocation sink that just saves all the information for later +struct StandaloneRelocSink { + ebbs: HashMap, + funcs: HashMap, + jts: HashMap, +} + +// Contains all the metadata necessary to perform relocations +enum FunctionMetaData { + Import(), + Local { + relocs: StandaloneRelocSink, + imports: ImportMappings, + il_func: Function, + }, +} + +impl RelocSink for StandaloneRelocSink { + fn reloc_ebb(&mut self, offset: CodeOffset, reloc: Reloc, ebb: Ebb) { + self.ebbs.insert(reloc.0, (ebb, offset)); + } + fn reloc_func(&mut self, offset: CodeOffset, reloc: Reloc, func: FuncRef) { + self.funcs.insert(reloc.0, (func, offset)); + } + fn reloc_jt(&mut self, offset: CodeOffset, reloc: Reloc, jt: JumpTable) { + self.jts.insert(reloc.0, (jt, offset)); + } +} + +impl StandaloneRelocSink { + fn new() -> StandaloneRelocSink { + StandaloneRelocSink { + ebbs: HashMap::new(), + funcs: HashMap::new(), + jts: HashMap::new(), + } + } +} + +/// Structure containing the compiled code of the functions, ready to be executed. +pub struct ExecutableCode { + functions_code: Vec>, + start_index: FunctionIndex, +} + +/// Executes a module that has been translated with the `StandaloneRuntime` runtime implementation. +pub fn compile_module(trans_result: &TranslationResult) -> Result { + let mut shared_builder = settings::builder(); + shared_builder + .enable("enable_verifier") + .expect("Missing enable_verifier setting"); + shared_builder + .set("is_64bit", "1") + .expect("Missing 64bits setting"); + let isa = match isa::lookup("intel") { + Err(_) => { + panic!() // The target ISA is not available. + } + Ok(mut isa_builder) => { + isa_builder + .enable("haswell") + .expect("Missing haswell setting"); + isa_builder.finish(settings::Flags::new(&shared_builder)) + } + }; + let mut functions_metatada = Vec::new(); + let mut functions_code = Vec::new(); + for (function_index, function) in trans_result.functions.iter().enumerate() { + let mut context = Context::new(); + let (il, imports) = match function { + &FunctionTranslation::Import() => { + if trans_result.start_index.is_some() && + trans_result.start_index.unwrap() == function_index { + return Err(String::from("start function should not be an import")); + } else { + functions_code.push(Vec::new()); + functions_metatada.push(FunctionMetaData::Import()); + continue; + } + } + &FunctionTranslation::Code { + ref il, + ref imports, + .. + } => (il.clone(), imports.clone()), + }; + verify_function(&il, None).unwrap(); + context.func = il; + let code_size = context + .compile(&*isa) + .map_err(|e| pretty_error(&context.func, Some(&*isa), e))? as + usize; + if code_size == 0 { + return Err(String::from("no code generated by Cretonne")); + } + let mut code_buf: Vec = Vec::with_capacity(code_size); + code_buf.resize(code_size, 0); + let mut relocsink = StandaloneRelocSink::new(); + context.emit_to_memory(code_buf.as_mut_ptr(), &mut relocsink, &*isa); + functions_metatada.push(FunctionMetaData::Local { + relocs: relocsink, + imports: imports, + il_func: context.func, + }); + functions_code.push(code_buf); + } + relocate(&functions_metatada, &mut functions_code); + // After having emmitted the code to memory, we deal with relocations + match trans_result.start_index { + None => Err(String::from("No start function defined, aborting execution")), + Some(index) => { + Ok(ExecutableCode { + functions_code, + start_index: index, + }) + } + } +} + +// Jumps to the code region of memory and execute the start function of the module. +pub fn execute(exec: ExecutableCode) -> Result<(), String> { + let code_buf = &exec.functions_code[exec.start_index]; + unsafe { + match protect(code_buf.as_ptr(), + code_buf.len(), + Protection::ReadWriteExecute) { + Ok(()) => (), + Err(err) => { + return Err(format!("failed to give executable permission to code: {}", + err.description())) + } + }; + // Rather than writing inline assembly to jump to the code region, we use the fact that + // the Rust ABI for calling a function with no arguments and no return matches the one of + // the generated code.Thanks to this, we can transmute the code region into a first-class + // Rust function and call it. + // TODO: the Rust callee-saved registers will be overwritten by the executed code, inline + // assembly spilling these registers to the stack and restoring them after the call is + // needed. + let start_func = transmute::<_, fn()>(code_buf.as_ptr()); + // The code below saves the Intel callee-saved registers. It is not activate because + // inline ASM is not supported in the release version of the Rust compiler. + /*asm!("push rax + push rcx + push rdx + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + " :::: "intel", "volatile");*/ + start_func(); + /*asm!("pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rdx + pop rcx + pop rax + " :::: "intel", "volatile");*/ + Ok(()) + } +} + +/// Performs the relocations inside the function bytecode, provided the necessary metadata +fn relocate(functions_metatada: &Vec, functions_code: &mut Vec>) { + // The relocations are relative to the relocation's address plus four bytes + for (func_index, function_in_memory) in functions_metatada.iter().enumerate() { + match function_in_memory { + &FunctionMetaData::Import() => continue, + &FunctionMetaData::Local { + ref relocs, + ref imports, + ref il_func, + } => { + for (_, &(func_ref, offset)) in relocs.funcs.iter() { + let target_func_index = imports.functions[&func_ref]; + let target_func_address: isize = functions_code[target_func_index].as_ptr() as + isize; + unsafe { + let reloc_address: isize = functions_code[func_index] + .as_mut_ptr() + .offset(offset as isize + 4) as + isize; + let reloc_delta_i32: i32 = (target_func_address - reloc_address) as i32; + write_unaligned(reloc_address as *mut i32, reloc_delta_i32); + } + } + for (_, &(ebb, offset)) in relocs.ebbs.iter() { + unsafe { + let reloc_address: isize = functions_code[func_index] + .as_mut_ptr() + .offset(offset as isize + 4) as + isize; + let target_ebb_address: isize = + functions_code[func_index] + .as_ptr() + .offset(il_func.offsets[ebb] as isize) as + isize; + let reloc_delta_i32: i32 = (target_ebb_address - reloc_address) as i32; + write_unaligned(reloc_address as *mut i32, reloc_delta_i32); + } + } + // TODO: deal with jumptable relocations + } + } + } +} + +/// Pretty-print a verifier error. +pub fn pretty_verifier_error(func: &Function, + isa: Option<&TargetIsa>, + err: verifier::Error) + -> String { + let mut msg = err.to_string(); + match err.location { + AnyEntity::Inst(inst) => { + write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap() + } + _ => msg.push('\n'), + } + write!(msg, "{}", func.display(isa)).unwrap(); + msg +} + +/// Pretty-print a Cretonne error. +pub fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String { + if let CtonError::Verifier(e) = err { + pretty_verifier_error(func, isa, e) + } else { + err.to_string() + } +} diff --git a/lib/wasmstandalone/src/lib.rs b/lib/wasmstandalone/src/lib.rs new file mode 100644 index 0000000000..75479a33a5 --- /dev/null +++ b/lib/wasmstandalone/src/lib.rs @@ -0,0 +1,15 @@ +//! Standalone JIT-style runtime for WebAssembly using Cretonne. Provides functions to translate +//! `get_global`, `set_global`, `current_memory`, `grow_memory`, `call_indirect` that hardcode in +//! the translation the base addresses of regions of memory that will hold the globals, tables and +//! linear memories. + +extern crate cretonne; +extern crate wasm2cretonne; +extern crate cton_frontend; +extern crate region; + +mod execution; +mod standalone; + +pub use execution::{compile_module, execute, ExecutableCode}; +pub use standalone::StandaloneRuntime; diff --git a/lib/wasmstandalone/src/standalone.rs b/lib/wasmstandalone/src/standalone.rs new file mode 100644 index 0000000000..1c3fd79bf8 --- /dev/null +++ b/lib/wasmstandalone/src/standalone.rs @@ -0,0 +1,332 @@ +use wasm2cretonne::{Local, FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, RawByte, + MemoryAddress, Global, GlobalInit, Table, Memory, WasmRuntime}; +use cton_frontend::FunctionBuilder; +use cretonne::ir::{MemFlags, Value, InstBuilder, SigRef, FuncRef, ExtFuncData, FunctionName, + Signature, ArgumentType, CallConv}; +use cretonne::ir::types::*; +use cretonne::ir::condcodes::IntCC; +use cretonne::ir::immediates::Offset32; +use std::mem::transmute; +use std::ptr::copy_nonoverlapping; +use std::ptr::write; + +#[derive(Clone, Debug)] +enum TableElement { + Trap(), + Function(FunctionIndex), +} + +struct GlobalInfo { + global: Global, + offset: usize, +} + +struct GlobalsData { + data: Vec, + info: Vec, +} + +struct TableData { + data: Vec, + elements: Vec, + info: Table, +} + +struct MemoryData { + data: Vec, + info: Memory, +} + +const PAGE_SIZE: usize = 65536; + +/// Object containing the standalone runtime information. To be passed after creation as argument +/// to [`wasm2cretonne::translatemodule`](../wasm2cretonne/fn.translate_module.html). +pub struct StandaloneRuntime { + globals: GlobalsData, + tables: Vec, + memories: Vec, + instantiated: bool, + has_current_memory: Option, + has_grow_memory: Option, +} + +impl StandaloneRuntime { + /// Allocates the runtime data structures. + pub fn new() -> StandaloneRuntime { + StandaloneRuntime { + globals: GlobalsData { + data: Vec::new(), + info: Vec::new(), + }, + tables: Vec::new(), + memories: Vec::new(), + instantiated: false, + has_current_memory: None, + has_grow_memory: None, + } + } +} + +/// This trait is useful for +/// [`wasm2cretonne::translatemodule`](../wasm2cretonne/fn.translate_module.html) because it +/// tells how to translate runtime-dependent wasm instructions. These functions should not be +/// called by the user. +impl WasmRuntime for StandaloneRuntime { + fn translate_get_global(&self, + builder: &mut FunctionBuilder, + global_index: GlobalIndex) + -> Value { + debug_assert!(self.instantiated); + let ty = self.globals.info[global_index as usize].global.ty; + let offset = self.globals.info[global_index as usize].offset; + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + let addr: i64 = unsafe { transmute(self.globals.data.as_ptr()) }; + let addr_val = builder.ins().iconst(I64, addr); + builder.ins().load(ty, memflags, addr_val, memoffset) + } + fn translate_set_global(&self, + builder: &mut FunctionBuilder, + global_index: GlobalIndex, + val: Value) { + let offset = self.globals.info[global_index as usize].offset; + let memflags = MemFlags::new(); + let memoffset = Offset32::new(offset as i32); + let addr: i64 = unsafe { transmute(self.globals.data.as_ptr()) }; + let addr_val = builder.ins().iconst(I64, addr); + builder.ins().store(memflags, val, addr_val, memoffset); + } + fn translate_memory_base_address(&self, + builder: &mut FunctionBuilder, + memory_index: MemoryIndex) + -> Value { + let addr: i64 = unsafe { transmute(self.memories[memory_index].data.as_ptr()) }; + builder.ins().iconst(I64, addr) + } + fn translate_grow_memory(&mut self, + builder: &mut FunctionBuilder, + pages: Value) + -> Value { + debug_assert!(self.instantiated); + let grow_mem_func = match self.has_grow_memory { + Some(grow_mem_func) => grow_mem_func, + None => { + let sig_ref = + builder.import_signature(Signature { + call_conv: CallConv::Native, + argument_bytes: None, + argument_types: vec![ArgumentType::new(I32)], + return_types: vec![ArgumentType::new(I32)], + }); + builder.import_function(ExtFuncData { + name: FunctionName::new("grow_memory"), + signature: sig_ref, + }) + } + }; + self.has_grow_memory = Some(grow_mem_func); + let call_inst = builder.ins().call(grow_mem_func, &[pages]); + *builder.inst_results(call_inst).first().unwrap() + } + fn translate_current_memory(&mut self, builder: &mut FunctionBuilder) -> Value { + debug_assert!(self.instantiated); + let cur_mem_func = match self.has_current_memory { + Some(cur_mem_func) => cur_mem_func, + None => { + let sig_ref = builder.import_signature(Signature { + call_conv: CallConv::Native, + argument_bytes: None, + argument_types: Vec::new(), + return_types: + vec![ArgumentType::new(I32)], + }); + builder.import_function(ExtFuncData { + name: FunctionName::new("current_memory"), + signature: sig_ref, + }) + } + }; + self.has_current_memory = Some(cur_mem_func); + let call_inst = builder.ins().call(cur_mem_func, &[]); + *builder.inst_results(call_inst).first().unwrap() + } + fn translate_call_indirect<'a>(&self, + builder: &'a mut FunctionBuilder, + sig_ref: SigRef, + index_val: Value, + call_args: &[Value]) + -> &'a [Value] { + let trap_ebb = builder.create_ebb(); + let continue_ebb = builder.create_ebb(); + let size_val = builder.ins().iconst(I32, self.tables[0].info.size as i64); + let zero_val = builder.ins().iconst(I32, 0); + builder + .ins() + .br_icmp(IntCC::UnsignedLessThan, index_val, zero_val, trap_ebb, &[]); + builder + .ins() + .br_icmp(IntCC::UnsignedGreaterThanOrEqual, + index_val, + size_val, + trap_ebb, + &[]); + builder.seal_block(trap_ebb); + let offset_val = builder.ins().imul_imm(index_val, 4); + let base_table_addr: i64 = unsafe { transmute(self.tables[0].data.as_ptr()) }; + let table_addr_val = builder.ins().iconst(I32, base_table_addr); + let table_entry_addr_val = builder.ins().iadd(table_addr_val, offset_val); + let memflags = MemFlags::new(); + let memoffset = Offset32::new(0); + let table_entry_val = builder + .ins() + .load(I32, memflags, table_entry_addr_val, memoffset); + let call_inst = builder + .ins() + .call_indirect(sig_ref, table_entry_val, call_args); + builder.ins().jump(continue_ebb, &[]); + builder.seal_block(continue_ebb); + builder.switch_to_block(trap_ebb, &[]); + builder.ins().trap(); + builder.switch_to_block(continue_ebb, &[]); + builder.inst_results(call_inst) + } + + fn begin_translation(&mut self) { + debug_assert!(!self.instantiated); + self.instantiated = true; + // At instantiation, we allocate memory for the globals, the memories and the tables + // First the globals + let mut globals_data_size = 0; + for globalinfo in self.globals.info.iter_mut() { + globalinfo.offset = globals_data_size; + globals_data_size += globalinfo.global.ty.bytes() as usize; + } + self.globals.data.resize(globals_data_size as usize, 0); + for globalinfo in self.globals.info.iter() { + match globalinfo.global.initializer { + GlobalInit::I32Const(val) => unsafe { + write(self.globals + .data + .as_mut_ptr() + .offset(globalinfo.offset as isize) as + *mut i32, + val) + }, + GlobalInit::I64Const(val) => unsafe { + write(self.globals + .data + .as_mut_ptr() + .offset(globalinfo.offset as isize) as + *mut i64, + val) + }, + GlobalInit::F32Const(val) => unsafe { + write(self.globals + .data + .as_mut_ptr() + .offset(globalinfo.offset as isize) as + *mut f32, + transmute(val)) + }, + GlobalInit::F64Const(val) => unsafe { + write(self.globals + .data + .as_mut_ptr() + .offset(globalinfo.offset as isize) as + *mut f64, + transmute(val)) + }, + GlobalInit::Import() => { + // We don't initialize, this is inter-module linking + // TODO: support inter-module imports + } + GlobalInit::GlobalRef(index) => { + let ref_offset = self.globals.info[index].offset; + let size = globalinfo.global.ty.bytes(); + unsafe { + let dst = self.globals + .data + .as_mut_ptr() + .offset(globalinfo.offset as isize); + let src = self.globals.data.as_ptr().offset(ref_offset as isize); + copy_nonoverlapping(src, dst, size as usize) + } + } + } + } + } + fn next_function(&mut self) { + self.has_current_memory = None; + self.has_grow_memory = None; + } + fn declare_global(&mut self, global: Global) { + debug_assert!(!self.instantiated); + self.globals + .info + .push(GlobalInfo { + global: global, + offset: 0, + }); + } + fn declare_table(&mut self, table: Table) { + debug_assert!(!self.instantiated); + let mut elements_vec = Vec::with_capacity(table.size as usize); + elements_vec.resize(table.size as usize, TableElement::Trap()); + let mut addresses_vec = Vec::with_capacity(table.size as usize); + addresses_vec.resize(table.size as usize, 0); + self.tables + .push(TableData { + info: table, + data: addresses_vec, + elements: elements_vec, + }); + } + fn declare_table_elements(&mut self, + table_index: TableIndex, + offset: usize, + elements: &[FunctionIndex]) { + debug_assert!(!self.instantiated); + for (i, elt) in elements.iter().enumerate() { + self.tables[table_index].elements[offset as usize + i] = TableElement::Function(*elt); + } + } + fn declare_memory(&mut self, memory: Memory) { + debug_assert!(!self.instantiated); + let mut memory_vec = Vec::with_capacity(memory.pages_count as usize * PAGE_SIZE); + memory_vec.resize(memory.pages_count as usize * PAGE_SIZE, 0); + self.memories + .push(MemoryData { + info: memory, + data: memory_vec, + }); + } + fn declare_data_initialization(&mut self, + memory_index: MemoryIndex, + offset: usize, + data: &[u8]) + -> Result<(), String> { + if offset + data.len() > self.memories[memory_index].info.pages_count * PAGE_SIZE { + return Err(String::from("initialization data out of bounds")); + } + self.memories[memory_index].data[offset..offset + data.len()].copy_from_slice(data); + Ok(()) + } +} + +/// Convenience functions for the user to be called after execution for debug purposes. +impl StandaloneRuntime { + /// Returns a slice of the contents of allocated linear memory. + pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] { + &self.memories + .get(memory_index) + .expect(format!("no memory for index {}", memory_index).as_str()) + .data + [address..address + len] + } + /// Shows the value of a global variable. + pub fn inspect_global(&self, global_index: usize) -> &[u8] { + let (offset, len) = (self.globals.info[global_index].offset, + self.globals.info[global_index].global.ty.bytes() as usize); + &self.globals.data[offset..offset + len] + } +} From dfdab56a5480e92727f1e7e6dfc27234c2026e77 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Thu, 10 Aug 2017 16:30:09 -0700 Subject: [PATCH 965/968] Integrated wasm test suite translation as cretonne test --- lib/wasm2cretonne/src/code_translator.rs | 4 +- lib/wasm2cretonne/src/sections_translator.rs | 15 ++- lib/wasm2cretonne/tests/testsuite.rs | 102 ++++++++++++++++++ .../testsuite/address.wast.0.wasm | Bin .../testsuite/binary.wast.0.wasm | Bin .../testsuite/binary.wast.1.wasm | Bin .../testsuite/binary.wast.2.wasm | Bin .../testsuite/binary.wast.3.wasm | Bin .../testsuite/block.wast.0.wasm | Bin .../testsuite/br.wast.0.wasm | Bin .../testsuite/br_if.wast.0.wasm | Bin .../testsuite/br_table.wast.0.wasm | Bin .../testsuite/break-drop.wast.0.wasm | Bin .../testsuite/call.wast.0.wasm | Bin .../testsuite/call_indirect.wast.0.wasm | Bin .../testsuite/comments.wast.0.wasm | Bin .../testsuite/comments.wast.1.wasm | Bin .../testsuite/comments.wast.2.wasm | Bin .../testsuite/comments.wast.3.wasm | Bin .../testsuite/conversions.wast.0.wasm | Bin .../testsuite/custom_section.wast.0.wasm | Bin .../testsuite/custom_section.wast.1.wasm | Bin .../testsuite/custom_section.wast.2.wasm | Bin .../testsuite/endianness.wast.0.wasm | Bin .../testsuite/exports.wast.0.wasm | Bin .../testsuite/exports.wast.1.wasm | Bin .../testsuite/exports.wast.10.wasm | Bin .../testsuite/exports.wast.11.wasm | Bin .../testsuite/exports.wast.18.wasm | Bin .../testsuite/exports.wast.19.wasm | Bin .../testsuite/exports.wast.2.wasm | Bin .../testsuite/exports.wast.20.wasm | Bin .../testsuite/exports.wast.21.wasm | Bin .../testsuite/exports.wast.22.wasm | Bin .../testsuite/exports.wast.23.wasm | Bin .../testsuite/exports.wast.24.wasm | Bin .../testsuite/exports.wast.25.wasm | Bin .../testsuite/exports.wast.26.wasm | Bin .../testsuite/exports.wast.27.wasm | Bin .../testsuite/exports.wast.28.wasm | Bin .../testsuite/exports.wast.29.wasm | Bin .../testsuite/exports.wast.3.wasm | Bin .../testsuite/exports.wast.36.wasm | Bin .../testsuite/exports.wast.37.wasm | Bin .../testsuite/exports.wast.38.wasm | Bin .../testsuite/exports.wast.39.wasm | Bin .../testsuite/exports.wast.4.wasm | Bin .../testsuite/exports.wast.40.wasm | Bin .../testsuite/exports.wast.41.wasm | Bin .../testsuite/exports.wast.42.wasm | Bin .../testsuite/exports.wast.43.wasm | Bin .../testsuite/exports.wast.44.wasm | Bin .../testsuite/exports.wast.45.wasm | Bin .../testsuite/exports.wast.46.wasm | Bin .../testsuite/exports.wast.47.wasm | Bin .../testsuite/exports.wast.48.wasm | Bin .../testsuite/exports.wast.49.wasm | Bin .../testsuite/exports.wast.5.wasm | Bin .../testsuite/exports.wast.55.wasm | Bin .../testsuite/exports.wast.56.wasm | Bin .../testsuite/exports.wast.57.wasm | Bin .../testsuite/exports.wast.58.wasm | Bin .../testsuite/exports.wast.59.wasm | Bin .../testsuite/exports.wast.6.wasm | Bin .../testsuite/exports.wast.60.wasm | Bin .../testsuite/exports.wast.61.wasm | Bin .../testsuite/exports.wast.62.wasm | Bin .../testsuite/exports.wast.63.wasm | Bin .../testsuite/exports.wast.64.wasm | Bin .../testsuite/exports.wast.65.wasm | Bin .../testsuite/exports.wast.66.wasm | Bin .../testsuite/exports.wast.67.wasm | Bin .../testsuite/exports.wast.68.wasm | Bin .../testsuite/exports.wast.7.wasm | Bin .../testsuite/exports.wast.8.wasm | Bin .../testsuite/exports.wast.9.wasm | Bin .../testsuite/f32.wast.0.wasm | Bin .../testsuite/f32_bitwise.wast.0.wasm | Bin .../testsuite/f32_cmp.wast.0.wasm | Bin .../testsuite/f64.wast.0.wasm | Bin .../testsuite/f64_bitwise.wast.0.wasm | Bin .../testsuite/f64_cmp.wast.0.wasm | Bin .../testsuite/fac.wast.0.wasm | Bin .../testsuite/float_exprs.wast.0.wasm | Bin .../testsuite/float_exprs.wast.1.wasm | Bin .../testsuite/float_exprs.wast.10.wasm | Bin .../testsuite/float_exprs.wast.11.wasm | Bin .../testsuite/float_exprs.wast.12.wasm | Bin .../testsuite/float_exprs.wast.13.wasm | Bin .../testsuite/float_exprs.wast.14.wasm | Bin .../testsuite/float_exprs.wast.15.wasm | Bin .../testsuite/float_exprs.wast.16.wasm | Bin .../testsuite/float_exprs.wast.17.wasm | Bin .../testsuite/float_exprs.wast.18.wasm | Bin .../testsuite/float_exprs.wast.19.wasm | Bin .../testsuite/float_exprs.wast.2.wasm | Bin .../testsuite/float_exprs.wast.20.wasm | Bin .../testsuite/float_exprs.wast.21.wasm | Bin .../testsuite/float_exprs.wast.22.wasm | Bin .../testsuite/float_exprs.wast.23.wasm | Bin .../testsuite/float_exprs.wast.24.wasm | Bin .../testsuite/float_exprs.wast.25.wasm | Bin .../testsuite/float_exprs.wast.26.wasm | Bin .../testsuite/float_exprs.wast.27.wasm | Bin .../testsuite/float_exprs.wast.28.wasm | Bin .../testsuite/float_exprs.wast.29.wasm | Bin .../testsuite/float_exprs.wast.3.wasm | Bin .../testsuite/float_exprs.wast.30.wasm | Bin .../testsuite/float_exprs.wast.31.wasm | Bin .../testsuite/float_exprs.wast.32.wasm | Bin .../testsuite/float_exprs.wast.33.wasm | Bin .../testsuite/float_exprs.wast.34.wasm | Bin .../testsuite/float_exprs.wast.35.wasm | Bin .../testsuite/float_exprs.wast.36.wasm | Bin .../testsuite/float_exprs.wast.37.wasm | Bin .../testsuite/float_exprs.wast.38.wasm | Bin .../testsuite/float_exprs.wast.39.wasm | Bin .../testsuite/float_exprs.wast.4.wasm | Bin .../testsuite/float_exprs.wast.40.wasm | Bin .../testsuite/float_exprs.wast.41.wasm | Bin .../testsuite/float_exprs.wast.42.wasm | Bin .../testsuite/float_exprs.wast.43.wasm | Bin .../testsuite/float_exprs.wast.44.wasm | Bin .../testsuite/float_exprs.wast.45.wasm | Bin .../testsuite/float_exprs.wast.46.wasm | Bin .../testsuite/float_exprs.wast.47.wasm | Bin .../testsuite/float_exprs.wast.48.wasm | Bin .../testsuite/float_exprs.wast.49.wasm | Bin .../testsuite/float_exprs.wast.5.wasm | Bin .../testsuite/float_exprs.wast.50.wasm | Bin .../testsuite/float_exprs.wast.51.wasm | Bin .../testsuite/float_exprs.wast.52.wasm | Bin .../testsuite/float_exprs.wast.53.wasm | Bin .../testsuite/float_exprs.wast.54.wasm | Bin .../testsuite/float_exprs.wast.55.wasm | Bin .../testsuite/float_exprs.wast.56.wasm | Bin .../testsuite/float_exprs.wast.57.wasm | Bin .../testsuite/float_exprs.wast.58.wasm | Bin .../testsuite/float_exprs.wast.59.wasm | Bin .../testsuite/float_exprs.wast.6.wasm | Bin .../testsuite/float_exprs.wast.60.wasm | Bin .../testsuite/float_exprs.wast.61.wasm | Bin .../testsuite/float_exprs.wast.62.wasm | Bin .../testsuite/float_exprs.wast.63.wasm | Bin .../testsuite/float_exprs.wast.64.wasm | Bin .../testsuite/float_exprs.wast.65.wasm | Bin .../testsuite/float_exprs.wast.66.wasm | Bin .../testsuite/float_exprs.wast.67.wasm | Bin .../testsuite/float_exprs.wast.68.wasm | Bin .../testsuite/float_exprs.wast.69.wasm | Bin .../testsuite/float_exprs.wast.7.wasm | Bin .../testsuite/float_exprs.wast.70.wasm | Bin .../testsuite/float_exprs.wast.71.wasm | Bin .../testsuite/float_exprs.wast.72.wasm | Bin .../testsuite/float_exprs.wast.73.wasm | Bin .../testsuite/float_exprs.wast.74.wasm | Bin .../testsuite/float_exprs.wast.75.wasm | Bin .../testsuite/float_exprs.wast.76.wasm | Bin .../testsuite/float_exprs.wast.77.wasm | Bin .../testsuite/float_exprs.wast.78.wasm | Bin .../testsuite/float_exprs.wast.79.wasm | Bin .../testsuite/float_exprs.wast.8.wasm | Bin .../testsuite/float_exprs.wast.80.wasm | Bin .../testsuite/float_exprs.wast.81.wasm | Bin .../testsuite/float_exprs.wast.82.wasm | Bin .../testsuite/float_exprs.wast.83.wasm | Bin .../testsuite/float_exprs.wast.84.wasm | Bin .../testsuite/float_exprs.wast.85.wasm | Bin .../testsuite/float_exprs.wast.86.wasm | Bin .../testsuite/float_exprs.wast.87.wasm | Bin .../testsuite/float_exprs.wast.88.wasm | Bin .../testsuite/float_exprs.wast.89.wasm | Bin .../testsuite/float_exprs.wast.9.wasm | Bin .../testsuite/float_exprs.wast.90.wasm | Bin .../testsuite/float_exprs.wast.91.wasm | Bin .../testsuite/float_exprs.wast.92.wasm | Bin .../testsuite/float_exprs.wast.93.wasm | Bin .../testsuite/float_exprs.wast.94.wasm | Bin .../testsuite/float_exprs.wast.95.wasm | Bin .../testsuite/float_literals.wast.0.wasm | Bin .../testsuite/float_memory.wast.0.wasm | Bin .../testsuite/float_memory.wast.1.wasm | Bin .../testsuite/float_memory.wast.2.wasm | Bin .../testsuite/float_memory.wast.3.wasm | Bin .../testsuite/float_memory.wast.4.wasm | Bin .../testsuite/float_memory.wast.5.wasm | Bin .../testsuite/float_misc.wast.0.wasm | Bin .../testsuite/forward.wast.0.wasm | Bin .../testsuite/func.wast.0.wasm | Bin .../testsuite/func_ptrs.wast.0.wasm | Bin .../testsuite/func_ptrs.wast.8.wasm | Bin .../testsuite/func_ptrs.wast.9.wasm | Bin .../testsuite/get_local.wast.0.wasm | Bin .../testsuite/globals.wast.0.wasm | Bin .../testsuite/globals.wast.16.wasm | Bin .../testsuite/globals.wast.19.wasm | Bin .../testsuite/i32.wast.0.wasm | Bin .../testsuite/i64.wast.0.wasm | Bin .../testsuite/if.wast.0.wasm | Bin .../testsuite/imports.wast.0.wasm | Bin .../testsuite/imports.wast.1.wasm | Bin .../testsuite/imports.wast.10.wasm | Bin .../testsuite/imports.wast.11.wasm | Bin .../testsuite/imports.wast.12.wasm | Bin .../testsuite/imports.wast.13.wasm | Bin .../testsuite/imports.wast.14.wasm | Bin .../testsuite/imports.wast.15.wasm | Bin .../testsuite/imports.wast.16.wasm | Bin .../testsuite/imports.wast.17.wasm | Bin .../testsuite/imports.wast.18.wasm | Bin .../testsuite/imports.wast.19.wasm | Bin .../testsuite/imports.wast.2.wasm | Bin .../testsuite/imports.wast.20.wasm | Bin .../testsuite/imports.wast.21.wasm | Bin .../testsuite/imports.wast.22.wasm | Bin .../testsuite/imports.wast.23.wasm | Bin .../testsuite/imports.wast.24.wasm | Bin .../testsuite/imports.wast.25.wasm | Bin .../testsuite/imports.wast.26.wasm | Bin .../testsuite/imports.wast.27.wasm | Bin .../testsuite/imports.wast.28.wasm | Bin .../testsuite/imports.wast.29.wasm | Bin .../testsuite/imports.wast.3.wasm | Bin .../testsuite/imports.wast.30.wasm | Bin .../testsuite/imports.wast.31.wasm | Bin .../testsuite/imports.wast.32.wasm | Bin .../testsuite/imports.wast.33.wasm | Bin .../testsuite/imports.wast.34.wasm | Bin .../testsuite/imports.wast.35.wasm | Bin .../testsuite/imports.wast.36.wasm | Bin .../testsuite/imports.wast.37.wasm | Bin .../testsuite/imports.wast.38.wasm | Bin .../testsuite/imports.wast.39.wasm | Bin .../testsuite/imports.wast.4.wasm | Bin .../testsuite/imports.wast.40.wasm | Bin .../testsuite/imports.wast.41.wasm | Bin .../testsuite/imports.wast.42.wasm | Bin .../testsuite/imports.wast.43.wasm | Bin .../testsuite/imports.wast.44.wasm | Bin .../testsuite/imports.wast.45.wasm | Bin .../testsuite/imports.wast.49.wasm | Bin .../testsuite/imports.wast.5.wasm | Bin .../testsuite/imports.wast.50.wasm | Bin .../testsuite/imports.wast.51.wasm | Bin .../testsuite/imports.wast.52.wasm | Bin .../testsuite/imports.wast.53.wasm | Bin .../testsuite/imports.wast.54.wasm | Bin .../testsuite/imports.wast.55.wasm | Bin .../testsuite/imports.wast.56.wasm | Bin .../testsuite/imports.wast.57.wasm | Bin .../testsuite/imports.wast.58.wasm | Bin .../testsuite/imports.wast.59.wasm | Bin .../testsuite/imports.wast.6.wasm | Bin .../testsuite/imports.wast.60.wasm | Bin .../testsuite/imports.wast.61.wasm | Bin .../testsuite/imports.wast.62.wasm | Bin .../testsuite/imports.wast.63.wasm | Bin .../testsuite/imports.wast.64.wasm | Bin .../testsuite/imports.wast.65.wasm | Bin .../testsuite/imports.wast.66.wasm | Bin .../testsuite/imports.wast.67.wasm | Bin .../testsuite/imports.wast.68.wasm | Bin .../testsuite/imports.wast.69.wasm | Bin .../testsuite/imports.wast.7.wasm | Bin .../testsuite/imports.wast.70.wasm | Bin .../testsuite/imports.wast.71.wasm | Bin .../testsuite/imports.wast.75.wasm | Bin .../testsuite/imports.wast.76.wasm | Bin .../testsuite/imports.wast.77.wasm | Bin .../testsuite/imports.wast.78.wasm | Bin .../testsuite/imports.wast.79.wasm | Bin .../testsuite/imports.wast.8.wasm | Bin .../testsuite/imports.wast.80.wasm | Bin .../testsuite/imports.wast.81.wasm | Bin .../testsuite/imports.wast.82.wasm | Bin .../testsuite/imports.wast.83.wasm | Bin .../testsuite/imports.wast.84.wasm | Bin .../testsuite/imports.wast.85.wasm | Bin .../testsuite/imports.wast.86.wasm | Bin .../testsuite/imports.wast.87.wasm | Bin .../testsuite/imports.wast.88.wasm | Bin .../testsuite/imports.wast.89.wasm | Bin .../testsuite/imports.wast.9.wasm | Bin .../testsuite/imports.wast.90.wasm | Bin .../testsuite/imports.wast.91.wasm | Bin .../testsuite/imports.wast.92.wasm | Bin .../testsuite/imports.wast.93.wasm | Bin .../testsuite/imports.wast.94.wasm | Bin .../testsuite/imports.wast.95.wasm | Bin .../testsuite/imports.wast.96.wasm | Bin .../testsuite/imports.wast.97.wasm | Bin .../testsuite/imports.wast.98.wasm | Bin .../testsuite/int_exprs.wast.0.wasm | Bin .../testsuite/int_exprs.wast.1.wasm | Bin .../testsuite/int_exprs.wast.10.wasm | Bin .../testsuite/int_exprs.wast.11.wasm | Bin .../testsuite/int_exprs.wast.12.wasm | Bin .../testsuite/int_exprs.wast.13.wasm | Bin .../testsuite/int_exprs.wast.14.wasm | Bin .../testsuite/int_exprs.wast.15.wasm | Bin .../testsuite/int_exprs.wast.16.wasm | Bin .../testsuite/int_exprs.wast.17.wasm | Bin .../testsuite/int_exprs.wast.18.wasm | Bin .../testsuite/int_exprs.wast.2.wasm | Bin .../testsuite/int_exprs.wast.3.wasm | Bin .../testsuite/int_exprs.wast.4.wasm | Bin .../testsuite/int_exprs.wast.5.wasm | Bin .../testsuite/int_exprs.wast.6.wasm | Bin .../testsuite/int_exprs.wast.7.wasm | Bin .../testsuite/int_exprs.wast.8.wasm | Bin .../testsuite/int_exprs.wast.9.wasm | Bin .../testsuite/int_literals.wast.0.wasm | Bin .../testsuite/labels.wast.0.wasm | Bin .../testsuite/left-to-right.wast.0.wasm | Bin .../testsuite/linking.wast.0.wasm | Bin .../testsuite/linking.wast.1.wasm | Bin .../testsuite/linking.wast.15.wasm | Bin .../testsuite/linking.wast.16.wasm | Bin .../testsuite/linking.wast.17.wasm | Bin .../testsuite/linking.wast.2.wasm | Bin .../testsuite/linking.wast.3.wasm | Bin .../testsuite/linking.wast.4.wasm | Bin .../testsuite/linking.wast.5.wasm | Bin .../testsuite/linking.wast.6.wasm | Bin .../testsuite/linking.wast.7.wasm | Bin .../testsuite/linking.wast.8.wasm | Bin .../testsuite/linking.wast.9.wasm | Bin .../testsuite/loop.wast.0.wasm | Bin .../testsuite/memory.wast.0.wasm | Bin .../testsuite/memory.wast.1.wasm | Bin .../testsuite/memory.wast.15.wasm | Bin .../testsuite/memory.wast.2.wasm | Bin .../testsuite/memory.wast.3.wasm | Bin .../testsuite/memory.wast.39.wasm | Bin .../testsuite/memory.wast.40.wasm | Bin .../testsuite/memory.wast.41.wasm | Bin .../testsuite/memory.wast.49.wasm | Bin .../testsuite/memory.wast.50.wasm | Bin .../testsuite/memory.wast.51.wasm | Bin .../testsuite/memory.wast.52.wasm | Bin .../testsuite/memory.wast.6.wasm | Bin .../testsuite/memory.wast.62.wasm | Bin .../testsuite/memory.wast.8.wasm | Bin .../testsuite/memory_redundancy.wast.0.wasm | Bin .../testsuite/memory_trap.wast.0.wasm | Bin .../testsuite/memory_trap.wast.1.wasm | Bin .../testsuite/names.wast.0.wasm | Bin .../testsuite/names.wast.1.wasm | Bin .../testsuite/names.wast.2.wasm | Bin .../testsuite/names.wast.3.wasm | Bin .../testsuite/nop.wast.0.wasm | Bin .../testsuite/reloc.wasm | Bin .../testsuite/resizing.wast.0.wasm | Bin .../testsuite/resizing.wast.1.wasm | Bin .../testsuite/resizing.wast.2.wasm | Bin .../testsuite/return.wast.0.wasm | Bin .../testsuite/select.wast.0.wasm | Bin .../testsuite/set_local.wast.0.wasm | Bin .../testsuite/simple.wasm | Bin .../skip-stack-guard-page.wast.0.wasm | Bin .../testsuite/stack.wast.0.wasm | Bin .../testsuite/start.wast.3.wasm | Bin .../testsuite/start.wast.4.wasm | Bin .../testsuite/start.wast.5.wasm | Bin .../testsuite/start.wast.6.wasm | Bin .../testsuite/start.wast.7.wasm | Bin .../testsuite/start.wast.8.wasm | Bin .../testsuite/switch.wast.0.wasm | Bin .../testsuite/tee_local.wast.0.wasm | Bin .../testsuite/traps.wast.0.wasm | Bin .../testsuite/traps.wast.1.wasm | Bin .../testsuite/traps.wast.2.wasm | Bin .../testsuite/traps.wast.3.wasm | Bin .../testsuite/unreachable.wast.0.wasm | Bin .../testsuite/unwind.wast.0.wasm | Bin test-all.sh | 3 +- 376 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 lib/wasm2cretonne/tests/testsuite.rs rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/address.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/binary.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/binary.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/binary.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/binary.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/block.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/br.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/br_if.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/br_table.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/break-drop.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/call.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/call_indirect.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/comments.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/comments.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/comments.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/comments.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/conversions.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/custom_section.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/custom_section.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/custom_section.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/endianness.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.10.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.11.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.18.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.19.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.20.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.21.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.22.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.23.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.24.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.25.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.26.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.27.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.28.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.29.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.36.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.37.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.38.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.39.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.40.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.41.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.42.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.43.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.44.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.45.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.46.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.47.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.48.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.49.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.55.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.56.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.57.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.58.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.59.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.60.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.61.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.62.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.63.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.64.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.65.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.66.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.67.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.68.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.7.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/exports.wast.9.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/f32.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/f32_bitwise.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/f32_cmp.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/f64.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/f64_bitwise.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/f64_cmp.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/fac.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.10.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.11.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.12.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.13.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.14.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.15.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.16.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.17.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.18.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.19.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.20.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.21.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.22.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.23.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.24.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.25.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.26.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.27.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.28.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.29.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.30.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.31.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.32.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.33.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.34.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.35.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.36.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.37.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.38.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.39.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.40.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.41.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.42.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.43.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.44.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.45.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.46.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.47.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.48.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.49.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.50.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.51.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.52.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.53.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.54.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.55.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.56.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.57.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.58.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.59.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.60.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.61.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.62.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.63.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.64.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.65.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.66.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.67.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.68.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.69.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.7.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.70.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.71.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.72.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.73.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.74.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.75.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.76.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.77.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.78.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.79.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.80.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.81.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.82.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.83.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.84.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.85.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.86.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.87.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.88.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.89.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.9.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.90.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.91.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.92.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.93.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.94.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_exprs.wast.95.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_literals.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_memory.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_memory.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_memory.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_memory.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_memory.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_memory.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/float_misc.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/forward.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/func.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/func_ptrs.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/func_ptrs.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/func_ptrs.wast.9.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/get_local.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/globals.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/globals.wast.16.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/globals.wast.19.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/i32.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/i64.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/if.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.10.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.11.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.12.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.13.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.14.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.15.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.16.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.17.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.18.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.19.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.20.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.21.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.22.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.23.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.24.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.25.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.26.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.27.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.28.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.29.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.30.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.31.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.32.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.33.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.34.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.35.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.36.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.37.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.38.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.39.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.40.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.41.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.42.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.43.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.44.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.45.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.49.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.50.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.51.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.52.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.53.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.54.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.55.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.56.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.57.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.58.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.59.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.60.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.61.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.62.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.63.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.64.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.65.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.66.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.67.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.68.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.69.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.7.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.70.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.71.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.75.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.76.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.77.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.78.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.79.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.80.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.81.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.82.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.83.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.84.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.85.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.86.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.87.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.88.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.89.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.9.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.90.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.91.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.92.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.93.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.94.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.95.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.96.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.97.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/imports.wast.98.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.10.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.11.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.12.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.13.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.14.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.15.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.16.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.17.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.18.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.7.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_exprs.wast.9.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/int_literals.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/labels.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/left-to-right.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.15.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.16.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.17.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.7.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/linking.wast.9.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/loop.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.15.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.39.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.40.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.41.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.49.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.50.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.51.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.52.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.62.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory_redundancy.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory_trap.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/memory_trap.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/names.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/names.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/names.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/names.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/nop.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/reloc.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/resizing.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/resizing.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/resizing.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/return.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/select.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/set_local.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/simple.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/skip-stack-guard-page.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/stack.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/start.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/start.wast.4.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/start.wast.5.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/start.wast.6.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/start.wast.7.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/start.wast.8.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/switch.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/tee_local.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/traps.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/traps.wast.1.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/traps.wast.2.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/traps.wast.3.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/unreachable.wast.0.wasm (100%) rename lib/{wasm2cretonne-util => wasm2cretonne}/testsuite/unwind.wast.0.wasm (100%) diff --git a/lib/wasm2cretonne/src/code_translator.rs b/lib/wasm2cretonne/src/code_translator.rs index 208c531812..090d2bbbb1 100644 --- a/lib/wasm2cretonne/src/code_translator.rs +++ b/lib/wasm2cretonne/src/code_translator.rs @@ -420,8 +420,8 @@ fn translate_operator(op: &Operator, // We take the control frame pushed by the if, use its ebb as the else body // and push a new control frame with a new ebb for the code after the if/then/else // At the end of the then clause we jump to the destination - let (destination, return_values, branch_inst) = match &control_stack[control_stack.len() - - 1] { + let i = control_stack.len() - 1; + let (destination, return_values, branch_inst) = match &control_stack[i] { &ControlStackFrame::If { destination, ref return_values, diff --git a/lib/wasm2cretonne/src/sections_translator.rs b/lib/wasm2cretonne/src/sections_translator.rs index 11edd08f30..c9954e00c6 100644 --- a/lib/wasm2cretonne/src/sections_translator.rs +++ b/lib/wasm2cretonne/src/sections_translator.rs @@ -239,7 +239,8 @@ pub fn parse_data_section(parser: &mut Parser, let offset = match *parser.read() { ParserState::InitExpressionOperator(Operator::I32Const { value }) => { if value < 0 { - return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + return Err(SectionParsingError::WrongSectionContent(String::from("negative \ + offset value"))); } else { value as usize } @@ -248,13 +249,15 @@ pub fn parse_data_section(parser: &mut Parser, match globals[global_index as usize].initializer { GlobalInit::I32Const(value) => { if value < 0 { - return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + return Err(SectionParsingError::WrongSectionContent(String::from("\ + negative offset value"))); } else { value as usize } } GlobalInit::Import() => { - return Err(SectionParsingError::WrongSectionContent(String::from("imported globals not supported",),),) + return Err(SectionParsingError::WrongSectionContent(String::from("\ + imported globals not supported"))) } // TODO: add runtime support _ => panic!("should not happen"), } @@ -326,7 +329,8 @@ pub fn parse_elements_section(parser: &mut Parser, let offset = match *parser.read() { ParserState::InitExpressionOperator(Operator::I32Const { value }) => { if value < 0 { - return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + return Err(SectionParsingError::WrongSectionContent(String::from("negative \ + offset value"))); } else { value as usize } @@ -335,7 +339,8 @@ pub fn parse_elements_section(parser: &mut Parser, match globals[global_index as usize].initializer { GlobalInit::I32Const(value) => { if value < 0 { - return Err(SectionParsingError::WrongSectionContent(String::from("negative offset value",),),); + return Err(SectionParsingError::WrongSectionContent(String::from("\ + negative offset value"))); } else { value as usize } diff --git a/lib/wasm2cretonne/tests/testsuite.rs b/lib/wasm2cretonne/tests/testsuite.rs new file mode 100644 index 0000000000..bbad41d56b --- /dev/null +++ b/lib/wasm2cretonne/tests/testsuite.rs @@ -0,0 +1,102 @@ +extern crate wasm2cretonne; +extern crate cretonne; + +use wasm2cretonne::{translate_module, FunctionTranslation, DummyRuntime, WasmRuntime}; +use std::path::PathBuf; +use std::fs::File; +use std::error::Error; +use std::io; +use std::io::BufReader; +use std::io::prelude::*; +use std::fs; +use cretonne::ir; +use cretonne::ir::entities::AnyEntity; +use cretonne::isa::TargetIsa; +use cretonne::verifier; + +#[test] +fn testsuite() { + let mut paths: Vec<_> = fs::read_dir("testsuite") + .unwrap() + .map(|r| r.unwrap()) + .collect(); + paths.sort_by_key(|dir| dir.path()); + for path in paths { + let path = path.path(); + match handle_module(path) { + Ok(()) => (), + Err(message) => println!("{}", message), + }; + } +} + +fn read_wasm_file(path: PathBuf) -> Result, io::Error> { + let mut buf: Vec = Vec::new(); + let file = File::open(path)?; + let mut buf_reader = BufReader::new(file); + buf_reader.read_to_end(&mut buf)?; + Ok(buf) +} + +fn handle_module(path: PathBuf) -> Result<(), String> { + let data = match path.extension() { + None => { + return Err(String::from("the file extension is not wasm or wast")); + } + Some(ext) => { + match ext.to_str() { + Some("wasm") => { + match read_wasm_file(path.clone()) { + Ok(data) => data, + Err(err) => { + return Err(String::from(err.description())); + } + } + } + None | Some(&_) => { + return Err(String::from("the file extension is not wasm or wast")); + } + } + } + }; + let mut dummy_runtime = DummyRuntime::new(); + let translation = { + let mut runtime: &mut WasmRuntime = &mut dummy_runtime; + match translate_module(&data, runtime) { + Ok(x) => x, + Err(string) => { + return Err(string); + } + } + }; + for func in translation.functions.iter() { + let il = match func { + &FunctionTranslation::Import() => continue, + &FunctionTranslation::Code { ref il, .. } => il.clone(), + }; + match verifier::verify_function(&il, None) { + Ok(()) => (), + Err(err) => return Err(pretty_verifier_error(&il, None, err)), + } + } + Ok(()) +} + + +/// Pretty-print a verifier error. +pub fn pretty_verifier_error(func: &ir::Function, + isa: Option<&TargetIsa>, + err: verifier::Error) + -> String { + let msg = err.to_string(); + let str1 = match err.location { + AnyEntity::Inst(inst) => { + format!("{}\n{}: {}\n\n", + msg, + inst, + func.dfg.display_inst(inst, isa)) + } + _ => String::from(format!("{}\n", msg)), + }; + format!("{}{}", str1, func.display(isa)) +} diff --git a/lib/wasm2cretonne-util/testsuite/address.wast.0.wasm b/lib/wasm2cretonne/testsuite/address.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/address.wast.0.wasm rename to lib/wasm2cretonne/testsuite/address.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.0.wasm b/lib/wasm2cretonne/testsuite/binary.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/binary.wast.0.wasm rename to lib/wasm2cretonne/testsuite/binary.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.1.wasm b/lib/wasm2cretonne/testsuite/binary.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/binary.wast.1.wasm rename to lib/wasm2cretonne/testsuite/binary.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.2.wasm b/lib/wasm2cretonne/testsuite/binary.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/binary.wast.2.wasm rename to lib/wasm2cretonne/testsuite/binary.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/binary.wast.3.wasm b/lib/wasm2cretonne/testsuite/binary.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/binary.wast.3.wasm rename to lib/wasm2cretonne/testsuite/binary.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/block.wast.0.wasm b/lib/wasm2cretonne/testsuite/block.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/block.wast.0.wasm rename to lib/wasm2cretonne/testsuite/block.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/br.wast.0.wasm b/lib/wasm2cretonne/testsuite/br.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/br.wast.0.wasm rename to lib/wasm2cretonne/testsuite/br.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/br_if.wast.0.wasm b/lib/wasm2cretonne/testsuite/br_if.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/br_if.wast.0.wasm rename to lib/wasm2cretonne/testsuite/br_if.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/br_table.wast.0.wasm b/lib/wasm2cretonne/testsuite/br_table.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/br_table.wast.0.wasm rename to lib/wasm2cretonne/testsuite/br_table.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/break-drop.wast.0.wasm b/lib/wasm2cretonne/testsuite/break-drop.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/break-drop.wast.0.wasm rename to lib/wasm2cretonne/testsuite/break-drop.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/call.wast.0.wasm b/lib/wasm2cretonne/testsuite/call.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/call.wast.0.wasm rename to lib/wasm2cretonne/testsuite/call.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/call_indirect.wast.0.wasm b/lib/wasm2cretonne/testsuite/call_indirect.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/call_indirect.wast.0.wasm rename to lib/wasm2cretonne/testsuite/call_indirect.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.0.wasm b/lib/wasm2cretonne/testsuite/comments.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/comments.wast.0.wasm rename to lib/wasm2cretonne/testsuite/comments.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.1.wasm b/lib/wasm2cretonne/testsuite/comments.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/comments.wast.1.wasm rename to lib/wasm2cretonne/testsuite/comments.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.2.wasm b/lib/wasm2cretonne/testsuite/comments.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/comments.wast.2.wasm rename to lib/wasm2cretonne/testsuite/comments.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/comments.wast.3.wasm b/lib/wasm2cretonne/testsuite/comments.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/comments.wast.3.wasm rename to lib/wasm2cretonne/testsuite/comments.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/conversions.wast.0.wasm b/lib/wasm2cretonne/testsuite/conversions.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/conversions.wast.0.wasm rename to lib/wasm2cretonne/testsuite/conversions.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/custom_section.wast.0.wasm b/lib/wasm2cretonne/testsuite/custom_section.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/custom_section.wast.0.wasm rename to lib/wasm2cretonne/testsuite/custom_section.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/custom_section.wast.1.wasm b/lib/wasm2cretonne/testsuite/custom_section.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/custom_section.wast.1.wasm rename to lib/wasm2cretonne/testsuite/custom_section.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/custom_section.wast.2.wasm b/lib/wasm2cretonne/testsuite/custom_section.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/custom_section.wast.2.wasm rename to lib/wasm2cretonne/testsuite/custom_section.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/endianness.wast.0.wasm b/lib/wasm2cretonne/testsuite/endianness.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/endianness.wast.0.wasm rename to lib/wasm2cretonne/testsuite/endianness.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.0.wasm b/lib/wasm2cretonne/testsuite/exports.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.0.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.1.wasm b/lib/wasm2cretonne/testsuite/exports.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.1.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.10.wasm b/lib/wasm2cretonne/testsuite/exports.wast.10.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.10.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.10.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.11.wasm b/lib/wasm2cretonne/testsuite/exports.wast.11.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.11.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.11.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.18.wasm b/lib/wasm2cretonne/testsuite/exports.wast.18.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.18.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.18.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.19.wasm b/lib/wasm2cretonne/testsuite/exports.wast.19.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.19.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.19.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.2.wasm b/lib/wasm2cretonne/testsuite/exports.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.2.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.20.wasm b/lib/wasm2cretonne/testsuite/exports.wast.20.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.20.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.20.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.21.wasm b/lib/wasm2cretonne/testsuite/exports.wast.21.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.21.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.21.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.22.wasm b/lib/wasm2cretonne/testsuite/exports.wast.22.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.22.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.22.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.23.wasm b/lib/wasm2cretonne/testsuite/exports.wast.23.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.23.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.23.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.24.wasm b/lib/wasm2cretonne/testsuite/exports.wast.24.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.24.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.24.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.25.wasm b/lib/wasm2cretonne/testsuite/exports.wast.25.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.25.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.25.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.26.wasm b/lib/wasm2cretonne/testsuite/exports.wast.26.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.26.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.26.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.27.wasm b/lib/wasm2cretonne/testsuite/exports.wast.27.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.27.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.27.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.28.wasm b/lib/wasm2cretonne/testsuite/exports.wast.28.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.28.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.28.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.29.wasm b/lib/wasm2cretonne/testsuite/exports.wast.29.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.29.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.29.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.3.wasm b/lib/wasm2cretonne/testsuite/exports.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.3.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.36.wasm b/lib/wasm2cretonne/testsuite/exports.wast.36.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.36.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.36.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.37.wasm b/lib/wasm2cretonne/testsuite/exports.wast.37.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.37.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.37.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.38.wasm b/lib/wasm2cretonne/testsuite/exports.wast.38.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.38.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.38.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.39.wasm b/lib/wasm2cretonne/testsuite/exports.wast.39.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.39.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.39.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.4.wasm b/lib/wasm2cretonne/testsuite/exports.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.4.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.40.wasm b/lib/wasm2cretonne/testsuite/exports.wast.40.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.40.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.40.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.41.wasm b/lib/wasm2cretonne/testsuite/exports.wast.41.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.41.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.41.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.42.wasm b/lib/wasm2cretonne/testsuite/exports.wast.42.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.42.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.42.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.43.wasm b/lib/wasm2cretonne/testsuite/exports.wast.43.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.43.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.43.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.44.wasm b/lib/wasm2cretonne/testsuite/exports.wast.44.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.44.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.44.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.45.wasm b/lib/wasm2cretonne/testsuite/exports.wast.45.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.45.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.45.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.46.wasm b/lib/wasm2cretonne/testsuite/exports.wast.46.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.46.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.46.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.47.wasm b/lib/wasm2cretonne/testsuite/exports.wast.47.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.47.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.47.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.48.wasm b/lib/wasm2cretonne/testsuite/exports.wast.48.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.48.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.48.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.49.wasm b/lib/wasm2cretonne/testsuite/exports.wast.49.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.49.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.49.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.5.wasm b/lib/wasm2cretonne/testsuite/exports.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.5.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.55.wasm b/lib/wasm2cretonne/testsuite/exports.wast.55.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.55.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.55.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.56.wasm b/lib/wasm2cretonne/testsuite/exports.wast.56.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.56.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.56.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.57.wasm b/lib/wasm2cretonne/testsuite/exports.wast.57.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.57.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.57.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.58.wasm b/lib/wasm2cretonne/testsuite/exports.wast.58.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.58.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.58.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.59.wasm b/lib/wasm2cretonne/testsuite/exports.wast.59.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.59.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.59.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.6.wasm b/lib/wasm2cretonne/testsuite/exports.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.6.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.60.wasm b/lib/wasm2cretonne/testsuite/exports.wast.60.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.60.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.60.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.61.wasm b/lib/wasm2cretonne/testsuite/exports.wast.61.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.61.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.61.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.62.wasm b/lib/wasm2cretonne/testsuite/exports.wast.62.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.62.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.62.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.63.wasm b/lib/wasm2cretonne/testsuite/exports.wast.63.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.63.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.63.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.64.wasm b/lib/wasm2cretonne/testsuite/exports.wast.64.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.64.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.64.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.65.wasm b/lib/wasm2cretonne/testsuite/exports.wast.65.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.65.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.65.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.66.wasm b/lib/wasm2cretonne/testsuite/exports.wast.66.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.66.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.66.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.67.wasm b/lib/wasm2cretonne/testsuite/exports.wast.67.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.67.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.67.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.68.wasm b/lib/wasm2cretonne/testsuite/exports.wast.68.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.68.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.68.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.7.wasm b/lib/wasm2cretonne/testsuite/exports.wast.7.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.7.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.7.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.8.wasm b/lib/wasm2cretonne/testsuite/exports.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.8.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/exports.wast.9.wasm b/lib/wasm2cretonne/testsuite/exports.wast.9.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/exports.wast.9.wasm rename to lib/wasm2cretonne/testsuite/exports.wast.9.wasm diff --git a/lib/wasm2cretonne-util/testsuite/f32.wast.0.wasm b/lib/wasm2cretonne/testsuite/f32.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/f32.wast.0.wasm rename to lib/wasm2cretonne/testsuite/f32.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/f32_bitwise.wast.0.wasm b/lib/wasm2cretonne/testsuite/f32_bitwise.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/f32_bitwise.wast.0.wasm rename to lib/wasm2cretonne/testsuite/f32_bitwise.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/f32_cmp.wast.0.wasm b/lib/wasm2cretonne/testsuite/f32_cmp.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/f32_cmp.wast.0.wasm rename to lib/wasm2cretonne/testsuite/f32_cmp.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/f64.wast.0.wasm b/lib/wasm2cretonne/testsuite/f64.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/f64.wast.0.wasm rename to lib/wasm2cretonne/testsuite/f64.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/f64_bitwise.wast.0.wasm b/lib/wasm2cretonne/testsuite/f64_bitwise.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/f64_bitwise.wast.0.wasm rename to lib/wasm2cretonne/testsuite/f64_bitwise.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/f64_cmp.wast.0.wasm b/lib/wasm2cretonne/testsuite/f64_cmp.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/f64_cmp.wast.0.wasm rename to lib/wasm2cretonne/testsuite/f64_cmp.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/fac.wast.0.wasm b/lib/wasm2cretonne/testsuite/fac.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/fac.wast.0.wasm rename to lib/wasm2cretonne/testsuite/fac.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.0.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.1.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.1.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.10.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.10.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.10.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.10.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.11.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.11.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.11.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.11.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.12.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.12.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.12.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.12.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.13.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.13.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.13.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.13.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.14.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.14.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.14.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.14.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.15.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.15.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.15.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.15.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.16.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.16.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.16.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.16.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.17.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.17.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.17.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.17.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.18.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.18.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.18.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.18.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.19.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.19.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.19.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.19.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.2.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.2.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.20.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.20.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.20.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.20.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.21.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.21.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.21.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.21.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.22.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.22.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.22.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.22.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.23.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.23.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.23.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.23.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.24.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.24.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.24.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.24.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.25.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.25.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.25.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.25.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.26.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.26.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.26.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.26.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.27.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.27.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.27.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.27.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.28.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.28.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.28.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.28.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.29.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.29.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.29.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.29.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.3.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.3.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.30.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.30.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.30.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.30.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.31.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.31.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.31.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.31.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.32.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.32.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.32.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.32.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.33.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.33.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.33.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.33.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.34.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.34.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.34.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.34.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.35.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.35.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.35.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.35.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.36.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.36.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.36.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.36.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.37.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.37.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.37.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.37.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.38.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.38.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.38.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.38.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.39.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.39.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.39.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.39.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.4.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.4.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.40.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.40.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.40.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.40.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.41.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.41.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.41.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.41.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.42.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.42.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.42.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.42.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.43.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.43.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.43.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.43.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.44.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.44.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.44.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.44.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.45.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.45.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.45.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.45.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.46.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.46.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.46.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.46.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.47.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.47.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.47.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.47.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.48.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.48.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.48.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.48.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.49.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.49.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.49.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.49.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.5.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.5.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.50.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.50.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.50.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.50.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.51.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.51.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.51.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.51.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.52.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.52.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.52.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.52.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.53.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.53.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.53.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.53.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.54.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.54.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.54.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.54.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.55.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.55.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.55.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.55.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.56.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.56.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.56.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.56.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.57.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.57.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.57.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.57.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.58.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.58.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.58.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.58.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.59.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.59.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.59.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.59.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.6.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.6.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.60.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.60.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.60.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.60.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.61.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.61.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.61.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.61.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.62.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.62.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.62.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.62.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.63.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.63.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.63.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.63.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.64.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.64.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.64.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.64.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.65.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.65.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.65.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.65.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.66.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.66.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.66.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.66.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.67.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.67.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.67.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.67.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.68.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.68.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.68.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.68.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.69.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.69.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.69.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.69.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.7.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.7.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.7.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.7.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.70.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.70.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.70.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.70.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.71.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.71.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.71.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.71.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.72.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.72.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.72.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.72.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.73.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.73.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.73.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.73.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.74.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.74.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.74.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.74.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.75.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.75.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.75.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.75.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.76.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.76.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.76.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.76.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.77.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.77.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.77.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.77.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.78.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.78.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.78.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.78.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.79.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.79.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.79.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.79.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.8.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.8.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.80.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.80.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.80.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.80.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.81.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.81.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.81.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.81.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.82.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.82.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.82.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.82.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.83.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.83.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.83.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.83.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.84.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.84.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.84.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.84.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.85.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.85.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.85.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.85.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.86.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.86.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.86.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.86.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.87.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.87.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.87.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.87.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.88.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.88.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.88.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.88.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.89.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.89.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.89.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.89.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.9.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.9.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.9.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.9.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.90.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.90.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.90.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.90.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.91.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.91.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.91.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.91.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.92.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.92.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.92.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.92.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.93.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.93.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.93.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.93.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.94.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.94.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.94.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.94.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_exprs.wast.95.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.95.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_exprs.wast.95.wasm rename to lib/wasm2cretonne/testsuite/float_exprs.wast.95.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_literals.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_literals.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_literals.wast.0.wasm rename to lib/wasm2cretonne/testsuite/float_literals.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_memory.wast.0.wasm rename to lib/wasm2cretonne/testsuite/float_memory.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.1.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_memory.wast.1.wasm rename to lib/wasm2cretonne/testsuite/float_memory.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.2.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_memory.wast.2.wasm rename to lib/wasm2cretonne/testsuite/float_memory.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.3.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_memory.wast.3.wasm rename to lib/wasm2cretonne/testsuite/float_memory.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.4.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_memory.wast.4.wasm rename to lib/wasm2cretonne/testsuite/float_memory.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_memory.wast.5.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_memory.wast.5.wasm rename to lib/wasm2cretonne/testsuite/float_memory.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/float_misc.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_misc.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/float_misc.wast.0.wasm rename to lib/wasm2cretonne/testsuite/float_misc.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/forward.wast.0.wasm b/lib/wasm2cretonne/testsuite/forward.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/forward.wast.0.wasm rename to lib/wasm2cretonne/testsuite/forward.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/func.wast.0.wasm b/lib/wasm2cretonne/testsuite/func.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/func.wast.0.wasm rename to lib/wasm2cretonne/testsuite/func.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.0.wasm b/lib/wasm2cretonne/testsuite/func_ptrs.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/func_ptrs.wast.0.wasm rename to lib/wasm2cretonne/testsuite/func_ptrs.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.8.wasm b/lib/wasm2cretonne/testsuite/func_ptrs.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/func_ptrs.wast.8.wasm rename to lib/wasm2cretonne/testsuite/func_ptrs.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/func_ptrs.wast.9.wasm b/lib/wasm2cretonne/testsuite/func_ptrs.wast.9.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/func_ptrs.wast.9.wasm rename to lib/wasm2cretonne/testsuite/func_ptrs.wast.9.wasm diff --git a/lib/wasm2cretonne-util/testsuite/get_local.wast.0.wasm b/lib/wasm2cretonne/testsuite/get_local.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/get_local.wast.0.wasm rename to lib/wasm2cretonne/testsuite/get_local.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/globals.wast.0.wasm b/lib/wasm2cretonne/testsuite/globals.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/globals.wast.0.wasm rename to lib/wasm2cretonne/testsuite/globals.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/globals.wast.16.wasm b/lib/wasm2cretonne/testsuite/globals.wast.16.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/globals.wast.16.wasm rename to lib/wasm2cretonne/testsuite/globals.wast.16.wasm diff --git a/lib/wasm2cretonne-util/testsuite/globals.wast.19.wasm b/lib/wasm2cretonne/testsuite/globals.wast.19.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/globals.wast.19.wasm rename to lib/wasm2cretonne/testsuite/globals.wast.19.wasm diff --git a/lib/wasm2cretonne-util/testsuite/i32.wast.0.wasm b/lib/wasm2cretonne/testsuite/i32.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/i32.wast.0.wasm rename to lib/wasm2cretonne/testsuite/i32.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/i64.wast.0.wasm b/lib/wasm2cretonne/testsuite/i64.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/i64.wast.0.wasm rename to lib/wasm2cretonne/testsuite/i64.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/if.wast.0.wasm b/lib/wasm2cretonne/testsuite/if.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/if.wast.0.wasm rename to lib/wasm2cretonne/testsuite/if.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.0.wasm b/lib/wasm2cretonne/testsuite/imports.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.0.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.1.wasm b/lib/wasm2cretonne/testsuite/imports.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.1.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.10.wasm b/lib/wasm2cretonne/testsuite/imports.wast.10.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.10.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.10.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.11.wasm b/lib/wasm2cretonne/testsuite/imports.wast.11.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.11.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.11.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.12.wasm b/lib/wasm2cretonne/testsuite/imports.wast.12.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.12.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.12.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.13.wasm b/lib/wasm2cretonne/testsuite/imports.wast.13.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.13.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.13.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.14.wasm b/lib/wasm2cretonne/testsuite/imports.wast.14.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.14.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.14.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.15.wasm b/lib/wasm2cretonne/testsuite/imports.wast.15.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.15.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.15.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.16.wasm b/lib/wasm2cretonne/testsuite/imports.wast.16.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.16.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.16.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.17.wasm b/lib/wasm2cretonne/testsuite/imports.wast.17.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.17.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.17.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.18.wasm b/lib/wasm2cretonne/testsuite/imports.wast.18.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.18.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.18.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.19.wasm b/lib/wasm2cretonne/testsuite/imports.wast.19.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.19.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.19.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.2.wasm b/lib/wasm2cretonne/testsuite/imports.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.2.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.20.wasm b/lib/wasm2cretonne/testsuite/imports.wast.20.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.20.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.20.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.21.wasm b/lib/wasm2cretonne/testsuite/imports.wast.21.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.21.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.21.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.22.wasm b/lib/wasm2cretonne/testsuite/imports.wast.22.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.22.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.22.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.23.wasm b/lib/wasm2cretonne/testsuite/imports.wast.23.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.23.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.23.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.24.wasm b/lib/wasm2cretonne/testsuite/imports.wast.24.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.24.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.24.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.25.wasm b/lib/wasm2cretonne/testsuite/imports.wast.25.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.25.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.25.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.26.wasm b/lib/wasm2cretonne/testsuite/imports.wast.26.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.26.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.26.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.27.wasm b/lib/wasm2cretonne/testsuite/imports.wast.27.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.27.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.27.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.28.wasm b/lib/wasm2cretonne/testsuite/imports.wast.28.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.28.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.28.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.29.wasm b/lib/wasm2cretonne/testsuite/imports.wast.29.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.29.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.29.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.3.wasm b/lib/wasm2cretonne/testsuite/imports.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.3.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.30.wasm b/lib/wasm2cretonne/testsuite/imports.wast.30.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.30.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.30.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.31.wasm b/lib/wasm2cretonne/testsuite/imports.wast.31.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.31.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.31.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.32.wasm b/lib/wasm2cretonne/testsuite/imports.wast.32.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.32.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.32.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.33.wasm b/lib/wasm2cretonne/testsuite/imports.wast.33.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.33.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.33.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.34.wasm b/lib/wasm2cretonne/testsuite/imports.wast.34.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.34.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.34.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.35.wasm b/lib/wasm2cretonne/testsuite/imports.wast.35.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.35.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.35.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.36.wasm b/lib/wasm2cretonne/testsuite/imports.wast.36.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.36.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.36.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.37.wasm b/lib/wasm2cretonne/testsuite/imports.wast.37.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.37.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.37.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.38.wasm b/lib/wasm2cretonne/testsuite/imports.wast.38.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.38.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.38.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.39.wasm b/lib/wasm2cretonne/testsuite/imports.wast.39.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.39.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.39.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.4.wasm b/lib/wasm2cretonne/testsuite/imports.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.4.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.40.wasm b/lib/wasm2cretonne/testsuite/imports.wast.40.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.40.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.40.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.41.wasm b/lib/wasm2cretonne/testsuite/imports.wast.41.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.41.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.41.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.42.wasm b/lib/wasm2cretonne/testsuite/imports.wast.42.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.42.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.42.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.43.wasm b/lib/wasm2cretonne/testsuite/imports.wast.43.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.43.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.43.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.44.wasm b/lib/wasm2cretonne/testsuite/imports.wast.44.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.44.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.44.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.45.wasm b/lib/wasm2cretonne/testsuite/imports.wast.45.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.45.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.45.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.49.wasm b/lib/wasm2cretonne/testsuite/imports.wast.49.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.49.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.49.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.5.wasm b/lib/wasm2cretonne/testsuite/imports.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.5.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.50.wasm b/lib/wasm2cretonne/testsuite/imports.wast.50.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.50.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.50.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.51.wasm b/lib/wasm2cretonne/testsuite/imports.wast.51.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.51.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.51.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.52.wasm b/lib/wasm2cretonne/testsuite/imports.wast.52.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.52.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.52.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.53.wasm b/lib/wasm2cretonne/testsuite/imports.wast.53.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.53.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.53.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.54.wasm b/lib/wasm2cretonne/testsuite/imports.wast.54.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.54.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.54.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.55.wasm b/lib/wasm2cretonne/testsuite/imports.wast.55.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.55.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.55.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.56.wasm b/lib/wasm2cretonne/testsuite/imports.wast.56.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.56.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.56.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.57.wasm b/lib/wasm2cretonne/testsuite/imports.wast.57.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.57.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.57.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.58.wasm b/lib/wasm2cretonne/testsuite/imports.wast.58.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.58.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.58.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.59.wasm b/lib/wasm2cretonne/testsuite/imports.wast.59.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.59.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.59.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.6.wasm b/lib/wasm2cretonne/testsuite/imports.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.6.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.60.wasm b/lib/wasm2cretonne/testsuite/imports.wast.60.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.60.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.60.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.61.wasm b/lib/wasm2cretonne/testsuite/imports.wast.61.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.61.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.61.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.62.wasm b/lib/wasm2cretonne/testsuite/imports.wast.62.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.62.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.62.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.63.wasm b/lib/wasm2cretonne/testsuite/imports.wast.63.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.63.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.63.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.64.wasm b/lib/wasm2cretonne/testsuite/imports.wast.64.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.64.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.64.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.65.wasm b/lib/wasm2cretonne/testsuite/imports.wast.65.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.65.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.65.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.66.wasm b/lib/wasm2cretonne/testsuite/imports.wast.66.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.66.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.66.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.67.wasm b/lib/wasm2cretonne/testsuite/imports.wast.67.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.67.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.67.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.68.wasm b/lib/wasm2cretonne/testsuite/imports.wast.68.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.68.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.68.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.69.wasm b/lib/wasm2cretonne/testsuite/imports.wast.69.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.69.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.69.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.7.wasm b/lib/wasm2cretonne/testsuite/imports.wast.7.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.7.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.7.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.70.wasm b/lib/wasm2cretonne/testsuite/imports.wast.70.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.70.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.70.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.71.wasm b/lib/wasm2cretonne/testsuite/imports.wast.71.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.71.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.71.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.75.wasm b/lib/wasm2cretonne/testsuite/imports.wast.75.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.75.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.75.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.76.wasm b/lib/wasm2cretonne/testsuite/imports.wast.76.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.76.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.76.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.77.wasm b/lib/wasm2cretonne/testsuite/imports.wast.77.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.77.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.77.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.78.wasm b/lib/wasm2cretonne/testsuite/imports.wast.78.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.78.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.78.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.79.wasm b/lib/wasm2cretonne/testsuite/imports.wast.79.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.79.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.79.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.8.wasm b/lib/wasm2cretonne/testsuite/imports.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.8.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.80.wasm b/lib/wasm2cretonne/testsuite/imports.wast.80.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.80.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.80.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.81.wasm b/lib/wasm2cretonne/testsuite/imports.wast.81.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.81.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.81.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.82.wasm b/lib/wasm2cretonne/testsuite/imports.wast.82.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.82.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.82.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.83.wasm b/lib/wasm2cretonne/testsuite/imports.wast.83.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.83.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.83.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.84.wasm b/lib/wasm2cretonne/testsuite/imports.wast.84.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.84.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.84.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.85.wasm b/lib/wasm2cretonne/testsuite/imports.wast.85.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.85.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.85.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.86.wasm b/lib/wasm2cretonne/testsuite/imports.wast.86.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.86.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.86.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.87.wasm b/lib/wasm2cretonne/testsuite/imports.wast.87.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.87.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.87.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.88.wasm b/lib/wasm2cretonne/testsuite/imports.wast.88.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.88.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.88.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.89.wasm b/lib/wasm2cretonne/testsuite/imports.wast.89.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.89.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.89.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.9.wasm b/lib/wasm2cretonne/testsuite/imports.wast.9.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.9.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.9.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.90.wasm b/lib/wasm2cretonne/testsuite/imports.wast.90.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.90.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.90.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.91.wasm b/lib/wasm2cretonne/testsuite/imports.wast.91.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.91.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.91.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.92.wasm b/lib/wasm2cretonne/testsuite/imports.wast.92.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.92.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.92.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.93.wasm b/lib/wasm2cretonne/testsuite/imports.wast.93.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.93.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.93.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.94.wasm b/lib/wasm2cretonne/testsuite/imports.wast.94.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.94.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.94.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.95.wasm b/lib/wasm2cretonne/testsuite/imports.wast.95.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.95.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.95.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.96.wasm b/lib/wasm2cretonne/testsuite/imports.wast.96.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.96.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.96.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.97.wasm b/lib/wasm2cretonne/testsuite/imports.wast.97.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.97.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.97.wasm diff --git a/lib/wasm2cretonne-util/testsuite/imports.wast.98.wasm b/lib/wasm2cretonne/testsuite/imports.wast.98.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/imports.wast.98.wasm rename to lib/wasm2cretonne/testsuite/imports.wast.98.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.0.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.0.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.1.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.1.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.10.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.10.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.10.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.10.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.11.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.11.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.11.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.11.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.12.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.12.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.12.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.12.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.13.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.13.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.13.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.13.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.14.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.14.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.14.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.14.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.15.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.15.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.15.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.15.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.16.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.16.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.16.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.16.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.17.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.17.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.17.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.17.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.18.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.18.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.18.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.18.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.2.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.2.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.3.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.3.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.4.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.4.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.5.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.5.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.6.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.6.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.7.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.7.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.7.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.7.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.8.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.8.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_exprs.wast.9.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.9.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_exprs.wast.9.wasm rename to lib/wasm2cretonne/testsuite/int_exprs.wast.9.wasm diff --git a/lib/wasm2cretonne-util/testsuite/int_literals.wast.0.wasm b/lib/wasm2cretonne/testsuite/int_literals.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/int_literals.wast.0.wasm rename to lib/wasm2cretonne/testsuite/int_literals.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/labels.wast.0.wasm b/lib/wasm2cretonne/testsuite/labels.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/labels.wast.0.wasm rename to lib/wasm2cretonne/testsuite/labels.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/left-to-right.wast.0.wasm b/lib/wasm2cretonne/testsuite/left-to-right.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/left-to-right.wast.0.wasm rename to lib/wasm2cretonne/testsuite/left-to-right.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.0.wasm b/lib/wasm2cretonne/testsuite/linking.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.0.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.1.wasm b/lib/wasm2cretonne/testsuite/linking.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.1.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.15.wasm b/lib/wasm2cretonne/testsuite/linking.wast.15.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.15.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.15.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.16.wasm b/lib/wasm2cretonne/testsuite/linking.wast.16.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.16.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.16.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.17.wasm b/lib/wasm2cretonne/testsuite/linking.wast.17.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.17.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.17.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.2.wasm b/lib/wasm2cretonne/testsuite/linking.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.2.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.3.wasm b/lib/wasm2cretonne/testsuite/linking.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.3.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.4.wasm b/lib/wasm2cretonne/testsuite/linking.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.4.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.5.wasm b/lib/wasm2cretonne/testsuite/linking.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.5.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.6.wasm b/lib/wasm2cretonne/testsuite/linking.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.6.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.7.wasm b/lib/wasm2cretonne/testsuite/linking.wast.7.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.7.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.7.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.8.wasm b/lib/wasm2cretonne/testsuite/linking.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.8.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/linking.wast.9.wasm b/lib/wasm2cretonne/testsuite/linking.wast.9.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/linking.wast.9.wasm rename to lib/wasm2cretonne/testsuite/linking.wast.9.wasm diff --git a/lib/wasm2cretonne-util/testsuite/loop.wast.0.wasm b/lib/wasm2cretonne/testsuite/loop.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/loop.wast.0.wasm rename to lib/wasm2cretonne/testsuite/loop.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.0.wasm b/lib/wasm2cretonne/testsuite/memory.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.0.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.1.wasm b/lib/wasm2cretonne/testsuite/memory.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.1.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.15.wasm b/lib/wasm2cretonne/testsuite/memory.wast.15.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.15.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.15.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.2.wasm b/lib/wasm2cretonne/testsuite/memory.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.2.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.3.wasm b/lib/wasm2cretonne/testsuite/memory.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.3.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.39.wasm b/lib/wasm2cretonne/testsuite/memory.wast.39.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.39.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.39.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.40.wasm b/lib/wasm2cretonne/testsuite/memory.wast.40.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.40.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.40.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.41.wasm b/lib/wasm2cretonne/testsuite/memory.wast.41.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.41.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.41.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.49.wasm b/lib/wasm2cretonne/testsuite/memory.wast.49.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.49.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.49.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.50.wasm b/lib/wasm2cretonne/testsuite/memory.wast.50.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.50.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.50.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.51.wasm b/lib/wasm2cretonne/testsuite/memory.wast.51.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.51.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.51.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.52.wasm b/lib/wasm2cretonne/testsuite/memory.wast.52.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.52.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.52.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.6.wasm b/lib/wasm2cretonne/testsuite/memory.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.6.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.62.wasm b/lib/wasm2cretonne/testsuite/memory.wast.62.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.62.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.62.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory.wast.8.wasm b/lib/wasm2cretonne/testsuite/memory.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory.wast.8.wasm rename to lib/wasm2cretonne/testsuite/memory.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory_redundancy.wast.0.wasm b/lib/wasm2cretonne/testsuite/memory_redundancy.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory_redundancy.wast.0.wasm rename to lib/wasm2cretonne/testsuite/memory_redundancy.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory_trap.wast.0.wasm b/lib/wasm2cretonne/testsuite/memory_trap.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory_trap.wast.0.wasm rename to lib/wasm2cretonne/testsuite/memory_trap.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/memory_trap.wast.1.wasm b/lib/wasm2cretonne/testsuite/memory_trap.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/memory_trap.wast.1.wasm rename to lib/wasm2cretonne/testsuite/memory_trap.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.0.wasm b/lib/wasm2cretonne/testsuite/names.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/names.wast.0.wasm rename to lib/wasm2cretonne/testsuite/names.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.1.wasm b/lib/wasm2cretonne/testsuite/names.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/names.wast.1.wasm rename to lib/wasm2cretonne/testsuite/names.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.2.wasm b/lib/wasm2cretonne/testsuite/names.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/names.wast.2.wasm rename to lib/wasm2cretonne/testsuite/names.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/names.wast.3.wasm b/lib/wasm2cretonne/testsuite/names.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/names.wast.3.wasm rename to lib/wasm2cretonne/testsuite/names.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/nop.wast.0.wasm b/lib/wasm2cretonne/testsuite/nop.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/nop.wast.0.wasm rename to lib/wasm2cretonne/testsuite/nop.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/reloc.wasm b/lib/wasm2cretonne/testsuite/reloc.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/reloc.wasm rename to lib/wasm2cretonne/testsuite/reloc.wasm diff --git a/lib/wasm2cretonne-util/testsuite/resizing.wast.0.wasm b/lib/wasm2cretonne/testsuite/resizing.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/resizing.wast.0.wasm rename to lib/wasm2cretonne/testsuite/resizing.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/resizing.wast.1.wasm b/lib/wasm2cretonne/testsuite/resizing.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/resizing.wast.1.wasm rename to lib/wasm2cretonne/testsuite/resizing.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/resizing.wast.2.wasm b/lib/wasm2cretonne/testsuite/resizing.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/resizing.wast.2.wasm rename to lib/wasm2cretonne/testsuite/resizing.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/return.wast.0.wasm b/lib/wasm2cretonne/testsuite/return.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/return.wast.0.wasm rename to lib/wasm2cretonne/testsuite/return.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/select.wast.0.wasm b/lib/wasm2cretonne/testsuite/select.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/select.wast.0.wasm rename to lib/wasm2cretonne/testsuite/select.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/set_local.wast.0.wasm b/lib/wasm2cretonne/testsuite/set_local.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/set_local.wast.0.wasm rename to lib/wasm2cretonne/testsuite/set_local.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/simple.wasm b/lib/wasm2cretonne/testsuite/simple.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/simple.wasm rename to lib/wasm2cretonne/testsuite/simple.wasm diff --git a/lib/wasm2cretonne-util/testsuite/skip-stack-guard-page.wast.0.wasm b/lib/wasm2cretonne/testsuite/skip-stack-guard-page.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/skip-stack-guard-page.wast.0.wasm rename to lib/wasm2cretonne/testsuite/skip-stack-guard-page.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/stack.wast.0.wasm b/lib/wasm2cretonne/testsuite/stack.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/stack.wast.0.wasm rename to lib/wasm2cretonne/testsuite/stack.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.3.wasm b/lib/wasm2cretonne/testsuite/start.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/start.wast.3.wasm rename to lib/wasm2cretonne/testsuite/start.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.4.wasm b/lib/wasm2cretonne/testsuite/start.wast.4.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/start.wast.4.wasm rename to lib/wasm2cretonne/testsuite/start.wast.4.wasm diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.5.wasm b/lib/wasm2cretonne/testsuite/start.wast.5.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/start.wast.5.wasm rename to lib/wasm2cretonne/testsuite/start.wast.5.wasm diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.6.wasm b/lib/wasm2cretonne/testsuite/start.wast.6.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/start.wast.6.wasm rename to lib/wasm2cretonne/testsuite/start.wast.6.wasm diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.7.wasm b/lib/wasm2cretonne/testsuite/start.wast.7.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/start.wast.7.wasm rename to lib/wasm2cretonne/testsuite/start.wast.7.wasm diff --git a/lib/wasm2cretonne-util/testsuite/start.wast.8.wasm b/lib/wasm2cretonne/testsuite/start.wast.8.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/start.wast.8.wasm rename to lib/wasm2cretonne/testsuite/start.wast.8.wasm diff --git a/lib/wasm2cretonne-util/testsuite/switch.wast.0.wasm b/lib/wasm2cretonne/testsuite/switch.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/switch.wast.0.wasm rename to lib/wasm2cretonne/testsuite/switch.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/tee_local.wast.0.wasm b/lib/wasm2cretonne/testsuite/tee_local.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/tee_local.wast.0.wasm rename to lib/wasm2cretonne/testsuite/tee_local.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.0.wasm b/lib/wasm2cretonne/testsuite/traps.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/traps.wast.0.wasm rename to lib/wasm2cretonne/testsuite/traps.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.1.wasm b/lib/wasm2cretonne/testsuite/traps.wast.1.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/traps.wast.1.wasm rename to lib/wasm2cretonne/testsuite/traps.wast.1.wasm diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.2.wasm b/lib/wasm2cretonne/testsuite/traps.wast.2.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/traps.wast.2.wasm rename to lib/wasm2cretonne/testsuite/traps.wast.2.wasm diff --git a/lib/wasm2cretonne-util/testsuite/traps.wast.3.wasm b/lib/wasm2cretonne/testsuite/traps.wast.3.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/traps.wast.3.wasm rename to lib/wasm2cretonne/testsuite/traps.wast.3.wasm diff --git a/lib/wasm2cretonne-util/testsuite/unreachable.wast.0.wasm b/lib/wasm2cretonne/testsuite/unreachable.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/unreachable.wast.0.wasm rename to lib/wasm2cretonne/testsuite/unreachable.wast.0.wasm diff --git a/lib/wasm2cretonne-util/testsuite/unwind.wast.0.wasm b/lib/wasm2cretonne/testsuite/unwind.wast.0.wasm similarity index 100% rename from lib/wasm2cretonne-util/testsuite/unwind.wast.0.wasm rename to lib/wasm2cretonne/testsuite/unwind.wast.0.wasm diff --git a/test-all.sh b/test-all.sh index 33e47bc358..3e25c546a9 100755 --- a/test-all.sh +++ b/test-all.sh @@ -40,7 +40,8 @@ if [ -n "$needcheck" ]; then touch $tsfile || echo no target directory fi -PKGS="cretonne cretonne-reader cretonne-tools cretonne-frontend filecheck" +PKGS="cretonne cretonne-reader cretonne-tools cretonne-frontend filecheck \ + wasm2cretonne" cd "$topdir" for PKG in $PKGS do From 5fc61bd6f69c665f60a5611287e2eb8109eba843 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Fri, 11 Aug 2017 11:10:21 -0700 Subject: [PATCH 966/968] Added Fibonacci test case --- lib/wasm2cretonne-util/filetests/arith.wasm | Bin 57 -> 0 bytes lib/wasm2cretonne-util/filetests/call.wasm | Bin 46 -> 0 bytes .../filetests/fibonacci.wast | 22 ++++++++++++++++++ lib/wasm2cretonne-util/filetests/globals.wasm | Bin 49 -> 0 bytes lib/wasm2cretonne-util/filetests/memory.wasm | Bin 76 -> 0 bytes lib/wasm2cretonne-util/filetests/sample.wasm | Bin 45 -> 0 bytes 6 files changed, 22 insertions(+) delete mode 100644 lib/wasm2cretonne-util/filetests/arith.wasm delete mode 100644 lib/wasm2cretonne-util/filetests/call.wasm create mode 100644 lib/wasm2cretonne-util/filetests/fibonacci.wast delete mode 100644 lib/wasm2cretonne-util/filetests/globals.wasm delete mode 100644 lib/wasm2cretonne-util/filetests/memory.wasm delete mode 100644 lib/wasm2cretonne-util/filetests/sample.wasm diff --git a/lib/wasm2cretonne-util/filetests/arith.wasm b/lib/wasm2cretonne-util/filetests/arith.wasm deleted file mode 100644 index a393264a2aac2aad3e262823b3d9549905c3c226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57 zcmV~$+YNvq5Jkar1rp*$d!UrK{GtF`cZND%0gBonOdHxUPF CcmwJH diff --git a/lib/wasm2cretonne-util/filetests/memory.wasm b/lib/wasm2cretonne-util/filetests/memory.wasm deleted file mode 100644 index 0d3074bb7e10f30983be06f9012938252ac944db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmWNGu?>JQ5X5}RM?(265CtU_HQ6K#K*a#8&Rt>4I_~!opwNU`M5{uJ=$lsQ8w}23 S?1i{o-PP>0GqcNI#^L<|0tUwb diff --git a/lib/wasm2cretonne-util/filetests/sample.wasm b/lib/wasm2cretonne-util/filetests/sample.wasm deleted file mode 100644 index 0126305329e95ec56ce7952d525b25ea7acb1045..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 ycmZQbEY4+QU|?WmV@zPIXRK#tVr1YFXB1^nU~pv2W~o;IGO`?5a#$T1xVZs}Z3XWD From 727f297ba99473904d8817b8a5e64fe46a5e0c44 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Fri, 11 Aug 2017 13:38:56 -0700 Subject: [PATCH 967/968] Bugfix: wrong jump arguments for br_if to loops --- lib/wasm2cretonne/src/code_translator.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/wasm2cretonne/src/code_translator.rs b/lib/wasm2cretonne/src/code_translator.rs index 090d2bbbb1..b1647a9aee 100644 --- a/lib/wasm2cretonne/src/code_translator.rs +++ b/lib/wasm2cretonne/src/code_translator.rs @@ -499,8 +499,12 @@ fn translate_operator(op: &Operator, let val = stack.pop().unwrap(); let i = control_stack.len() - 1 - (relative_depth as usize); let frame = &mut control_stack[i]; - let cut_index = stack.len() - frame.return_values().len(); - let jump_args = stack.split_off(cut_index); + let jump_args = if frame.is_loop() { + Vec::new() + } else { + let cut_index = stack.len() - frame.return_values().len(); + stack.split_off(cut_index) + }; builder .ins() .brnz(val, frame.br_destination(), jump_args.as_slice()); @@ -517,9 +521,15 @@ fn translate_operator(op: &Operator, min_depth = *depth; } } - let jump_args_count = control_stack[control_stack.len() - 1 - (min_depth as usize)] - .return_values() - .len(); + let jump_args_count = { + let i = control_stack.len() - 1 - (min_depth as usize); + let min_depth_frame = &control_stack[i]; + if min_depth_frame.is_loop() { + 0 + } else { + min_depth_frame.return_values().len() + } + }; if jump_args_count == 0 { // No jump arguments let val = stack.pop().unwrap(); From 051bc08d23df0930be5e959645c50dd0cdf411d4 Mon Sep 17 00:00:00 2001 From: Denis Merigoux Date: Fri, 11 Aug 2017 15:49:47 -0700 Subject: [PATCH 968/968] Added description and license to Cargo.toml --- lib/wasm2cretonne-util/Cargo.toml | 3 +++ lib/wasm2cretonne/Cargo.toml | 3 +++ lib/wasmstandalone/Cargo.toml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/lib/wasm2cretonne-util/Cargo.toml b/lib/wasm2cretonne-util/Cargo.toml index a4f3eb94a6..f709e4afac 100644 --- a/lib/wasm2cretonne-util/Cargo.toml +++ b/lib/wasm2cretonne-util/Cargo.toml @@ -3,6 +3,9 @@ name = "wasm2cretonne-util" version = "0.0.0" authors = ["The Cretonne Project Developers"] publish = false +description = "Utility binary to translate and execute WebAssembly modules using Cretonne" +repository = "https://github.com/stoklund/cretonne" +license = "Apache-2.0" [[bin]] name = "wasm2cretonne-util" diff --git a/lib/wasm2cretonne/Cargo.toml b/lib/wasm2cretonne/Cargo.toml index ef82b76118..e954c7a7a9 100644 --- a/lib/wasm2cretonne/Cargo.toml +++ b/lib/wasm2cretonne/Cargo.toml @@ -3,6 +3,9 @@ name = "wasm2cretonne" version = "0.0.0" authors = ["The Cretonne Project Developers"] publish = false +description = "Translator from WebAssembly to Cretonne IL" +repository = "https://github.com/stoklund/cretonne" +license = "Apache-2.0" [dependencies] wasmparser = "0.6.1" diff --git a/lib/wasmstandalone/Cargo.toml b/lib/wasmstandalone/Cargo.toml index a1f35f92ff..a8e3cc283b 100644 --- a/lib/wasmstandalone/Cargo.toml +++ b/lib/wasmstandalone/Cargo.toml @@ -3,6 +3,9 @@ name = "wasmstandalone" version = "0.0.0" authors = ["The Cretonne Project Developers"] publish = false +description = "Standalone JIT-style runtime support for WebAsssembly code in Cretonne" +repository = "https://github.com/stoklund/cretonne" +license = "Apache-2.0" [dependencies] cretonne = { path = "../cretonne" }