diff --git a/.gitignore b/.gitignore index 50281a44270e..0872b9b58575 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ -# Generated by Cargo -# will have compiled files and executables +*.pyc +**/*.rs.bk +*.swp +*.swo +tags /target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk +.*.rustfmt +cretonne.dbg* +.mypy_cache +rusty-tags.* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..03dd6f76e64c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: rust +rust: + - stable + - beta +dist: trusty +sudo: false +addons: + apt: + packages: + - python3-pip +install: + - pip3 install --user --upgrade mypy flake8 + - travis_wait ./check-rustfmt.sh --install +script: ./test-all.sh +cache: + cargo: true + directories: + - $HOME/.cache/pip diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..7fdbee1e07e6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[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" } +cretonne-frontend = { path = "lib/frontend" } +wasm2cretonne-util = { path = "lib/wasm2cretonne-util" } +filecheck = { path = "lib/filecheck" } +docopt = "0.8.0" +serde = "1.0.8" +serde_derive = "1.0.8" +num_cpus = "1.5.1" + +[workspace] diff --git a/LICENSE b/LICENSE index 8dada3edaf50..427417b60d59 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/README.rst b/README.rst new file mode 100644 index 000000000000..6f343fb258c3 --- /dev/null +++ b/README.rst @@ -0,0 +1,74 @@ +======================= +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 + +.. 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: + +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, sometimes fail. + +Building Cretonne +----------------- + +Cretonne is using the Cargo package manager format. First, ensure you have +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 + +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 +-------------------------- + +To build the Cretonne documentation, you need the `Sphinx documentation +generator `_:: + + $ pip install sphinx sphinx-autobuild sphinx_rtd_theme + $ cd cretonne/docs + $ make html + $ open _build/html/index.html + +We don't support Sphinx versions before 1.4 since the format of index tuples +has changed. diff --git a/check-rustfmt.sh b/check-rustfmt.sh new file mode 100755 index 000000000000..6e8563900f65 --- /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.4" + +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/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000000..e35d8850c968 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_build diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000000..8cec8c36e648 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,196 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXABUILD = sphinx-autobuild +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." + +autohtml: html + $(SPHINXABUILD) -z ../lib/cretonne/meta --ignore '.*.sw?' -b html -E $(ALLSPHINXOPTS) $(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/compare-llvm.rst b/docs/compare-llvm.rst new file mode 100644 index 000000000000..03b82dc7f712 --- /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 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. + +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 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 +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 aggregate 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 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. + +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/conf.py b/docs/conf.py new file mode 100644 index 000000000000..0603a27bfdf7 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,137 @@ +# -*- 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. + +from __future__ import absolute_import +import sys +import os + + +# 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('.')) + +# Also add the meta directory to sys.path so autodoc can find the Cretonne meta +# language definitions. +sys.path.insert(0, os.path.abspath('../lib/cretonne/meta')) + +# -- General configuration ------------------------------------------------ + +# 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', + 'sphinx.ext.graphviz', + 'sphinx.ext.inheritance_diagram', + 'cton_domain', + 'cton_lexer', +] + +# 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 master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'cretonne' +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 +# 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 + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# 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' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'cretonnedoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +} + +# 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', + author, 'manual'), +] + + +# -- 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) +] + + +# -- 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'), +] + + +# -- Options for Graphviz ------------------------------------------------- + +graphviz_output_format = 'svg' + +inheritance_graph_attrs = dict(rankdir='TD') diff --git a/docs/cton_domain.py b/docs/cton_domain.py new file mode 100644 index 000000000000..a03acf116082 --- /dev/null +++ b/docs/cton_domain.py @@ -0,0 +1,385 @@ +# -*- 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. +# +from __future__ import absolute_import + +import re + +from docutils import nodes +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.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. + + 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): + """ + 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, '', None)) + + +# 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.""" + + 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() + parse_type(name, signode) + 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',)), + 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',)), + ] + + 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 CtonInstGroup(CtonObject): + """A Cretonne IL instruction group.""" + + +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, + 'instgroup': CtonInstGroup, + } + + roles = { + 'type': XRefRole(), + 'inst': XRefRole(), + 'instgroup': 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))] + + +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 'base.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) + + +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): + if path: + return path.rstrip('.'), [base] + else: + return 'base.instructions', [base] + + def format_signature(self): + inst = self.object + sig = inst.name + if len(inst.outs) > 0: + sig = ', '.join([op.name for op in inst.outs]) + ' = ' + sig + if len(inst.ins) > 0: + op = inst.ins[0] + sig += ' ' + op.name + # If the first input is variable-args, this is 'return'. No parens. + 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.kind.name == 'variable_args': + sig += '({}...)'.format(op.name) + else: + sig += ', ' + op.name + 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() + inst = self.object + + # Add inputs and outputs. + 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 inst.outs: + if op.is_value(): + typ = op.typevar + else: + typ = op.kind + self.add_line(u':out {} {}: {}'.format( + typ, op.name, op.get_doc()), sourcename) + + # Document type inference for polymorphic instructions. + 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( + inst.ctrl_typevar.name, + inst.ins[tvopnum]), + sourcename) + else: + self.add_line( + u':typevar {}: explicitly provided' + .format(inst.ctrl_typevar.name), + sourcename) + for tv in inst.other_typevars: + self.add_line( + u':typevar {}: from input operand'.format(tv.name), + 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/cton_lexer.py b/docs/cton_lexer.py new file mode 100644 index 000000000000..1024765cbe1f --- /dev/null +++ b/docs/cton_lexer.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# Pygments lexer for Cretonne. +from __future__ import absolute_import + +from pygments.lexer import RegexLexer, bygroups, words +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'] + filenames = ['*.cton'] + + 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), + # 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+)?|s?NaN|Inf)', Number.Float), + (r'[-+]?\d+', Number.Integer), + # Known attributes. + (keywords('uext', 'sext'), Name.Attribute), + # Well known value types. + (r'\b(b\d+|i\d+|f32|f64)(x\d+)?\b', Keyword.Type), + # v = value + # ss = stack slot + # jt = jump table + (r'(v|ss|jt)\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), + (r'[ \t]+', Text), + ], + } + + +def setup(app): + """Setup Sphinx extension.""" + app.add_lexer('cton', CretonneLexer()) + + return {'version': '0.1'} diff --git a/docs/example.c b/docs/example.c new file mode 100644 index 000000000000..052312330183 --- /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 000000000000..0cbd77a6cd9a --- /dev/null +++ b/docs/example.cton @@ -0,0 +1,33 @@ +test verifier + +function %average(i32, i32) -> f32 native { + ss1 = local 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 + jump ebb2(v4) + +ebb2(v5: i32): + v6 = imul_imm v5, 4 + v7 = iadd v1, v6 + v8 = heap_load.f32 v7 ; array[i] + v9 = fpromote.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 = fcvt_from_uint.f64 v2 + v16 = fdiv v14, v15 + v17 = fdemote.f32 v16 + return v17 + +ebb3: + v100 = f32const +NaN + return v100 +} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000000..301346a9e3a2 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,25 @@ +Cretonne Code Generator +======================= + +Contents: + +.. toctree:: + :maxdepth: 1 + + langref + metaref + testing + regalloc + compare-llvm + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +Todo list +========= + +.. todolist:: diff --git a/docs/langref.rst b/docs/langref.rst new file mode 100644 index 000000000000..f20b5b6e840c --- /dev/null +++ b/docs/langref.rst @@ -0,0 +1,924 @@ +*************************** +Cretonne Language Reference +*************************** + +.. default-domain:: cton +.. highlight:: cton + +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. + + +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 simple C function that computes the average of an array of floats: + +.. literalinclude:: example.c + :language: c + +Here is the same function compiled into Cretonne IL: + +.. literalinclude:: example.cton + :language: cton + :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. +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 preamble follows the :term:`function body` which consists of +: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: + +.. productionlist:: + 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 +----------------------------- + +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. + +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. + +It can be difficult to generate correct SSA form if the program being converted +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 data-flow analysis to convert stack +slots to SSA form. + +.. _value-types: + +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. + +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 +------------- + +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. + +.. autoctontype:: i8 +.. autoctontype:: i16 +.. autoctontype:: i32 +.. autoctontype:: i64 + +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. + +.. autoctontype:: f32 +.. autoctontype:: f64 + +SIMD vector types +----------------- + +A SIMD vector type represents a vector of values from one of the scalar types +(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 + + 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:`i32x4`, :type:`i64x8`, and + :type:`i16x4`. + + The size of a SIMD integer vector in memory is :math:`N B\over 8` bytes. + +.. type:: f32x%N + + A SIMD vector of single precision floating point numbers. + + 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:: f64x%N + + A SIMD vector of double precision floating point numbers. + + 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:: 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:`b1x4` result. + + Like the :type:`b1` type, a boolean vector cannot be stored in memory. + +Pseudo-types and type classes +----------------------------- + +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. + +.. 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:`b1` or :type:`b1xN`. + +.. type:: Testable + + Either :type:`b1` 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. + + 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 be omitted. + +.. 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:: 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. + +.. 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 :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 + 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. + +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 + 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 + ``-NaN`` or ``NaN``. Otherwise, ``-NaN:0xT`` where ``T`` are the trailing + significand bits encoded as hexadecimal. + +Signaling NaNs + Displayed as ``-sNaN:0xT``. + + +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. + +.. autoinst:: jump +.. autoinst:: fallthrough +.. autoinst:: brz +.. autoinst:: brnz +.. autoinst:: br_icmp +.. autoinst:: br_table + +.. inst:: JT = jump_table EBB0, EBB1, ..., EBBn + + 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 + indicates an absent entry. + + 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. + +.. autoinst:: trap +.. autoinst:: trapz +.. autoinst:: trapnz + + +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. 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 { "," arg } + retlist : arglist + 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 +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 +preamble`: + +.. 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 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:: + + 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 + v3, v4 = call fn1(v1, v2) + br ebb1(v2, v4) + + ebb2: + return v1 + } + +Indirect function calls use a signature declared in the preamble. + +.. autoinst:: call_indirect + +.. 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`. + + +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. + +.. 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. + + +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 +--------------- + +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 preamble`. Stack slots are not typed, they +simply represent a contiguous sequence of bytes in the stack frame. + +.. inst:: SS = local Bytes, Flags... + + 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. + + :arg Bytes: Stack slot size on bytes. + :flag align(N): Request at least N bytes alignment. + :result SS: Stack slot index. + +.. 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, +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. + +.. autoinst:: stack_addr + +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 + +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 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 preamble. + + 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. + +.. autoinst:: heap_load +.. autoinst:: heap_store + +When optimizing heap accesses, Cretonne may separate the heap bounds checking +and address computations from the memory accesses. + +.. autoinst:: heap_addr + +A small example using heaps:: + + function %vdup(i32, i32) { + h1 = heap "main" + + 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 +========== + +The remaining instruction set is mostly arithmetic. + +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:: bconst + +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 + +Register values can be temporarily diverted to other registers by the +:inst:`regmove` instruction. + +.. autoinst:: regmove + +Vector operations +----------------- + +.. autoinst:: vsplit +.. autoinst:: vconcat +.. autoinst:: vselect +.. autoinst:: splat +.. autoinst:: insertlane +.. autoinst:: extractlane + +Integer operations +------------------ + +.. autoinst:: icmp +.. autoinst:: icmp_imm +.. autoinst:: iadd +.. autoinst:: iadd_imm +.. autoinst:: iadd_cin +.. autoinst:: iadd_cout +.. autoinst:: iadd_carry +.. autoinst:: isub +.. autoinst:: irsub_imm +.. autoinst:: isub_bin +.. autoinst:: isub_bout +.. autoinst:: isub_borrow + +.. 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. + +.. autoinst:: udiv +.. autoinst:: udiv_imm +.. autoinst:: sdiv +.. autoinst:: sdiv_imm +.. autoinst:: urem +.. autoinst:: urem_imm +.. autoinst:: srem +.. autoinst:: srem_imm + +.. 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 matching + targets. + +.. todo:: Saturating arithmetic. + + Mostly for SIMD use, but again these are good patterns for contraction. + Something like ``usatadd``, ``usatsub``, ``ssatadd``, and ``ssatsub`` is a + good start. + +Bitwise operations +------------------ + +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. + +.. autoinst:: band +.. autoinst:: band_imm +.. autoinst:: bor +.. autoinst:: bor_imm +.. autoinst:: bxor +.. autoinst:: bxor_imm +.. autoinst:: bnot +.. 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. +Only the low `B` bits of the shift amount is significant. + +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. + +.. 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. + +.. autoinst:: clz +.. autoinst:: cls +.. autoinst:: ctz +.. autoinst:: popcnt + +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 + +Sign bit manipulations +~~~~~~~~~~~~~~~~~~~~~~ + +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. + +.. autoinst:: fneg +.. autoinst:: fabs +.. autoinst:: fcopysign + +Minimum and maximum +~~~~~~~~~~~~~~~~~~~ + +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. + +When comparing zeroes, these instructions behave as if :math:`-0.0 < 0.0`. + +.. autoinst:: fmin +.. autoinst:: fminnum +.. autoinst:: fmax +.. autoinst:: fmaxnum + +Rounding +~~~~~~~~ + +These instructions round their argument to a nearby integral value, still +represented as a floating point number. + +.. autoinst:: ceil +.. autoinst:: floor +.. autoinst:: trunc +.. autoinst:: nearest + +Conversion operations +--------------------- + +.. autoinst:: bitcast +.. autoinst:: breduce +.. autoinst:: bextend +.. autoinst:: bint +.. autoinst:: bmask +.. autoinst:: ireduce +.. autoinst:: uextend +.. autoinst:: sextend +.. autoinst:: fpromote +.. autoinst:: fdemote +.. autoinst:: fcvt_to_uint +.. autoinst:: fcvt_to_sint +.. 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 +.. autoinst:: iconcat + +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: + +.. autoinstgroup:: isa.intel.instructions.GROUP + +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. + +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 +======== + +.. 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: + + - 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 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. + + 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 preamble 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. + + function body + The extended basic blocks which contain all the executable code in a + function. The function body follows the function preamble. + + 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 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 + 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 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 + frame. Also called a local variable. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000000..3f6fe2e48d8a --- /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 diff --git a/docs/metaref.rst b/docs/metaref.rst new file mode 100644 index 000000000000..05050f8f4072 --- /dev/null +++ b/docs/metaref.rst @@ -0,0 +1,483 @@ +******************************** +Cretonne Meta Language Reference +******************************** + +.. default-domain:: py +.. highlight:: python +.. 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 +modules that form the embedded DSL. + +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 + in the :mod:`base` and :mod:`isa` packages use the classes in the + :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. + +The main driver for this source code generation process is the +: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. + + +.. 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:`base.settings` module. Some settings are specific to a target ISA, +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 +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()) + + +.. module:: cdsl.instructions + +Instruction descriptions +======================== + +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:: InstructionGroup + :members: + +The basic Cretonne instruction set described in :doc:`langref` is defined by the +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. + +.. autoclass:: Operand + +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:`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:: 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 +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. + +There are some practical restrictions on the use of type variables, see +:ref:`restricted-polymorphism`. + +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`. + +.. autoclass:: ImmediateKind + +.. automodule:: base.immediates + :members: + +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. + +.. autoclass:: EntityRefKind + +.. automodule:: base.entities + :members: + +Value 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 +.. autoclass:: ScalarType + :members: +.. autoclass:: VectorType + :members: +.. autoclass:: IntType + :members: +.. autoclass:: FloatType + :members: +.. autoclass:: BoolType + :members: + +.. automodule:: base.types + :members: + +There are no predefined vector types, but they can be created as needed with +the :func:`ScalarType.by` function. + + +.. module:: cdsl.operands + +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 +.. 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:: VARIABLE_ARGS + +.. module:: cdsl.formats + +When an instruction description is created, it is automatically assigned a +predefined instruction format which is an instance of +:class:`InstructionFormat`: + +.. 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*. 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. +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. + + +Encodings +========= + +.. currentmodule:: cdsl.isa + +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 +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 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 +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. + +.. 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. +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] + 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 +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. +- The CPU mode that must be active. +- A :term:`sub-target predicate` that must be satisfied by the currently active + sub-target. + +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: + +- 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. + +.. 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 +======= + +Cretonne can be compiled with support for multiple target instruction set +architectures. Each ISA is represented by a :py:class:`cdsl.isa.TargetISA` instance. + +.. autoclass:: TargetISA + +The definitions for each supported target live in a package under +:file:`lib/cretonne/meta/isa`. + +.. automodule:: isa + :members: + +.. automodule:: isa.riscv +.. automodule:: isa.intel +.. automodule:: isa.arm32 +.. automodule:: isa.arm64 + + +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. diff --git a/docs/regalloc.rst b/docs/regalloc.rst new file mode 100644 index 000000000000..8c73df41ecb3 --- /dev/null +++ b/docs/regalloc.rst @@ -0,0 +1,239 @@ +******************************* +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 practice there are some complications. + +Real-world complications to SSA coloring +---------------------------------------- + +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: + +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 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 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 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: + +- 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 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 + 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 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 + 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. diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 000000000000..edc8ee6570db --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,354 @@ +**************** +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) -> 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 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:`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 +========== + +.. 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 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: + +.. 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:`lib/cretonne/meta/isa/*/settings.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:`lib/cretonne/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 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. + +Comments in :file:`.cton` files are associated with the entity they follow. +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 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 +``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 passes 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. + +`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. + +`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 + } + +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. + +`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/cfg/loop.cton b/filetests/cfg/loop.cton new file mode 100644 index 000000000000..06aa848c7f11 --- /dev/null +++ b/filetests/cfg/loop.cton @@ -0,0 +1,35 @@ +; 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): + v3 = f64const 0x0.0 + brz v2, ebb2 ; unordered: ebb0:$BRZ -> ebb2 + v4 = iconst.i32 0 + jump ebb1(v4) ; unordered: ebb0:$JUMP -> 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) ; unordered: 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/filetests/cfg/traps_early.cton b/filetests/cfg/traps_early.cton new file mode 100644 index 000000000000..814e251f51d6 --- /dev/null +++ b/filetests/cfg/traps_early.cton @@ -0,0 +1,21 @@ +; 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 ; error: terminator instruction was encountered before the end + 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) ; unordered: ebb1:inst5 -> ebb0 + +ebb2: + return v1 +} diff --git a/filetests/cfg/unused_node.cton b/filetests/cfg/unused_node.cton new file mode 100644 index 000000000000..cbde9757bc28 --- /dev/null +++ b/filetests/cfg/unused_node.cton @@ -0,0 +1,21 @@ +; For testing cfg generation where some block is never reached. +test print-cfg + +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 ; unordered: ebb0:inst0 -> ebb2 + trap + +ebb1: + v1 = iconst.i32 1 + v2 = iadd v0, v1 + jump ebb0(v2) ; unordered: ebb1:inst4 -> ebb0 + +ebb2: + return v0 +} diff --git a/filetests/domtree/basic.cton b/filetests/domtree/basic.cton new file mode 100644 index 000000000000..e46c73e67e76 --- /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/filetests/domtree/loops.cton b/filetests/domtree/loops.cton new file mode 100644 index 000000000000..43ad1e1c0882 --- /dev/null +++ b/filetests/domtree/loops.cton @@ -0,0 +1,20 @@ +test domtree + +function %test(i32) { + ebb0(v0: i32): + brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 + jump ebb2 ; dominates: ebb2 + ebb1: + jump ebb3 + ebb2: + brz v0, ebb4 + jump ebb5 + ebb3: + jump ebb4 + ebb4: + brz v0, ebb3 + jump ebb5 + ebb5: + brz v0, ebb4 + return +} diff --git a/filetests/domtree/loops2.cton b/filetests/domtree/loops2.cton new file mode 100644 index 000000000000..eeac8343bda9 --- /dev/null +++ b/filetests/domtree/loops2.cton @@ -0,0 +1,31 @@ +test domtree + +function %test(i32) { + 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: ebb4 ebb7 ebb8 + jump ebb5 ; dominates: ebb5 + 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 +} diff --git a/filetests/domtree/tall-tree.cton b/filetests/domtree/tall-tree.cton new file mode 100644 index 000000000000..89821d07441d --- /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 000000000000..3a9b4fff375d --- /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/filetests/isa/intel/abi64.cton b/filetests/isa/intel/abi64.cton new file mode 100644 index 000000000000..59f310756097 --- /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 = (i32) -> i32 native + ; check: sig0 = (i32 [%rdi]) -> i32 [%rax] native + + sig1 = (i64) -> b1 native + ; check: sig1 = (i64 [%rdi]) -> b1 [%rax] native + + sig2 = (f32, i64) -> f64 native + ; check: sig2 = (f32 [%xmm0], i64 [%rdi]) -> f64 [%xmm0] native + +ebb0: + return +} diff --git a/filetests/isa/intel/binary32-float.cton b/filetests/isa/intel/binary32-float.cton new file mode 100644 index 000000000000..5c0dc43b18a2 --- /dev/null +++ b/filetests/isa/intel/binary32-float.cton @@ -0,0 +1,146 @@ +; 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 + + ; 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 + [-,%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 + + ; 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 +} + +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 + + ; 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 + [-,%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 + + ; 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/binary32.cton b/filetests/isa/intel/binary32.cton new file mode 100644 index 000000000000..af1bf73a9b3e --- /dev/null +++ b/filetests/isa/intel/binary32.cton @@ -0,0 +1,368 @@ +; binary emission of 32-bit code. +test binemit +isa intel haswell + +; 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() { + fn0 = function %foo() + sig0 = () + +ebb0: + ; 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. + + ; asm: addl %esi, %ecx + [-,%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] v20 = ishl v2, v1 ; bin: d3 e6 + ; asm: shll %cl, %ecx + [-,%rcx] v21 = ishl v1, v1 ; bin: d3 e1 + ; asm: shrl %cl, %esi + [-,%rsi] v22 = ushr v2, v1 ; bin: d3 ee + ; asm: shrl %cl, %ecx + [-,%rcx] v23 = ushr v1, v1 ; bin: d3 e9 + ; asm: sarl %cl, %esi + [-,%rsi] v24 = sshr v2, v1 ; bin: d3 fe + ; asm: sarl %cl, %ecx + [-,%rcx] v25 = sshr v1, v1 ; bin: d3 f9 + ; 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. + + ; 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 + + ; 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 + + ; 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 + [-,%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. + + ; 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 + + ; 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: 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 + + ; 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 + + ; 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 + + ; asm: call *%ecx + call_indirect sig0, v1() ; bin: ff d1 + ; 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: + trap ; bin: 0f 0b +} diff --git a/filetests/isa/intel/binary64-float.cton b/filetests/isa/intel/binary64-float.cton new file mode 100644 index 000000000000..64dd1ebd0544 --- /dev/null +++ b/filetests/isa/intel/binary64-float.cton @@ -0,0 +1,169 @@ +; 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 ; bin: f3 48 0f 2a e8 + ; 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 + + ; 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 + + ; 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 +} + +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 ; bin: f2 48 0f 2a e8 + ; 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 + + ; 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 + + ; 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/filetests/isa/intel/binary64.cton b/filetests/isa/intel/binary64.cton new file mode 100644 index 000000000000..fb6f62d928b0 --- /dev/null +++ b/filetests/isa/intel/binary64.cton @@ -0,0 +1,848 @@ +; binary emission of 64-bit code. +test binemit +set is_64bit +isa intel haswell + +; 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 = () + +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: 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. + ; 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 + + ; 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 + + ; 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] v180 = imul v1, v2 ; bin: 48 0f af ce + ; asm: imulq %r10, %rsi + [-,%rsi] v181 = imul v2, v3 ; bin: 49 0f af f2 + ; asm: imulq %rcx, %r10 + [-,%r10] v182 = imul v3, v1 ; bin: 4c 0f af d1 + + [-,%rax] v190 = iconst.i64 1 + [-,%rdx] v191 = iconst.i64 2 + ; asm: idivq %rcx + [-,%rax,%rdx] v192, v193 = x86_sdivmodx v130, v131, v1 ; bin: 48 f7 f9 + ; asm: idivq %rsi + [-,%rax,%rdx] v194, v195 = x86_sdivmodx v130, v131, v2 ; bin: 48 f7 fe + ; asm: idivq %r10 + [-,%rax,%rdx] v196, v197 = x86_sdivmodx v130, v131, v3 ; bin: 49 f7 fa + ; asm: divq %rcx + [-,%rax,%rdx] v198, v199 = x86_udivmodx v130, v131, v1 ; bin: 48 f7 f1 + ; asm: divq %rsi + [-,%rax,%rdx] v200, v201 = x86_udivmodx v130, v131, v2 ; bin: 48 f7 f6 + ; asm: divq %r10 + [-,%rax,%rdx] v202, v203 = x86_udivmodx v130, v131, v3 ; bin: 49 f7 f2 + + ; Bit-counting instructions. + + ; asm: popcntq %rsi, %rcx + [-,%rcx] v210 = popcnt v2 ; bin: f3 48 0f b8 ce + ; asm: popcntq %r10, %rsi + [-,%rsi] v211 = popcnt v3 ; bin: f3 49 0f b8 f2 + ; asm: popcntq %rcx, %r10 + [-,%r10] v212 = popcnt v1 ; bin: f3 4c 0f b8 d1 + + ; asm: lzcntq %rsi, %rcx + [-,%rcx] v213 = clz v2 ; bin: f3 48 0f bd ce + ; asm: lzcntq %r10, %rsi + [-,%rsi] v214 = clz v3 ; bin: f3 49 0f bd f2 + ; asm: lzcntq %rcx, %r10 + [-,%r10] v215 = clz v1 ; bin: f3 4c 0f bd d1 + + ; asm: tzcntq %rsi, %rcx + [-,%rcx] v216 = ctz v2 ; bin: f3 48 0f bc ce + ; asm: tzcntq %r10, %rsi + [-,%rsi] v217 = ctz v3 ; bin: f3 49 0f bc f2 + ; asm: tzcntq %rcx, %r10 + [-,%r10] v218 = ctz v1 ; bin: f3 4c 0f bc d1 + + ; 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 + + ; 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 + ; asm: testq %rsi, %rsi + ; asm: je ebb1 + brz v2, ebb1 ; bin: 48 85 f6 74 16 + ; asm: testq %r10, %r10 + ; asm: je ebb1 + brz v3, ebb1 ; bin: 4d 85 d2 74 11 + ; asm: testq %rcx, %rcx + ; asm: jne ebb1 + brnz v1, ebb1 ; bin: 48 85 c9 75 0c + ; asm: testq %rsi, %rsi + ; asm: jne ebb1 + brnz v2, ebb1 ; bin: 48 85 f6 75 07 + ; asm: testq %r10, %r10 + ; 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. +; +; 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 = () + +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 + + ; 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] v40 = iadd v1, v2 ; bin: 40 01 f1 + ; asm: addl %r10d, %esi + [-,%rsi] v41 = iadd v2, v3 ; bin: 44 01 d6 + ; asm: addl %ecx, %r10d + [-,%r10] v42 = iadd v3, v1 ; bin: 41 01 ca + + ; asm: subl %esi, %ecx + [-,%rcx] v50 = isub v1, v2 ; bin: 40 29 f1 + ; asm: subl %r10d, %esi + [-,%rsi] v51 = isub v2, v3 ; bin: 44 29 d6 + ; asm: subl %ecx, %r10d + [-,%r10] v52 = isub v3, v1 ; bin: 41 29 ca + + ; asm: andl %esi, %ecx + [-,%rcx] v60 = band v1, v2 ; bin: 40 21 f1 + ; asm: andl %r10d, %esi + [-,%rsi] v61 = band v2, v3 ; bin: 44 21 d6 + ; asm: andl %ecx, %r10d + [-,%r10] v62 = band v3, v1 ; bin: 41 21 ca + + ; asm: orl %esi, %ecx + [-,%rcx] v70 = bor v1, v2 ; bin: 40 09 f1 + ; asm: orl %r10d, %esi + [-,%rsi] v71 = bor v2, v3 ; bin: 44 09 d6 + ; asm: orl %ecx, %r10d + [-,%r10] v72 = bor v3, v1 ; bin: 41 09 ca + + ; asm: xorl %esi, %ecx + [-,%rcx] v80 = bxor v1, v2 ; bin: 40 31 f1 + ; asm: xorl %r10d, %esi + [-,%rsi] v81 = bxor v2, v3 ; bin: 44 31 d6 + ; asm: xorl %ecx, %r10d + [-,%r10] v82 = bxor v3, v1 ; bin: 41 31 ca + + ; asm: shll %cl, %esi + [-,%rsi] v90 = ishl v2, v1 ; bin: 40 d3 e6 + ; asm: shll %cl, %r10d + [-,%r10] v91 = ishl v3, v1 ; bin: 41 d3 e2 + ; asm: sarl %cl, %esi + [-,%rsi] v92 = sshr v2, v1 ; bin: 40 d3 fe + ; asm: sarl %cl, %r10d + [-,%r10] v93 = sshr v3, v1 ; bin: 41 d3 fa + ; asm: shrl %cl, %esi + [-,%rsi] v94 = ushr v2, v1 ; bin: 40 d3 ee + ; asm: shrl %cl, %r10d + [-,%r10] v95 = ushr v3, v1 ; bin: 41 d3 ea + + ; asm: roll %cl, %esi + [-,%rsi] v96 = rotl v2, v1 ; bin: 40 d3 c6 + ; asm: roll %cl, %r10d + [-,%r10] v97 = rotl v3, v1 ; bin: 41 d3 c2 + ; asm: rorl %cl, %esi + [-,%rsi] v98 = rotr v2, v1 ; bin: 40 d3 ce + ; asm: rorl %cl, %r10d + [-,%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] v100 = iadd_imm v1, -100000 ; bin: 40 81 c1 fffe7960 + ; asm: addl $100000, %esi + [-,%rsi] v101 = iadd_imm v2, 100000 ; bin: 40 81 c6 000186a0 + ; asm: addl $0x7fffffff, %r10d + [-,%r10] v102 = iadd_imm v3, 0x7fff_ffff ; bin: 41 81 c2 7fffffff + ; asm: addl $100, %r8d + [-,%r8] v103 = iadd_imm v4, 100 ; bin: 41 83 c0 64 + ; asm: addl $-100, %r14d + [-,%r14] v104 = iadd_imm v5, -100 ; bin: 41 83 c6 9c + + ; asm: andl $-100000, %ecx + [-,%rcx] v110 = band_imm v1, -100000 ; bin: 40 81 e1 fffe7960 + ; asm: andl $100000, %esi + [-,%rsi] v111 = band_imm v2, 100000 ; bin: 40 81 e6 000186a0 + ; asm: andl $0x7fffffff, %r10d + [-,%r10] v112 = band_imm v3, 0x7fff_ffff ; bin: 41 81 e2 7fffffff + ; asm: andl $100, %r8d + [-,%r8] v113 = band_imm v4, 100 ; bin: 41 83 e0 64 + ; asm: andl $-100, %r14d + [-,%r14] v114 = band_imm v5, -100 ; bin: 41 83 e6 9c + + ; asm: orl $-100000, %ecx + [-,%rcx] v120 = bor_imm v1, -100000 ; bin: 40 81 c9 fffe7960 + ; asm: orl $100000, %esi + [-,%rsi] v121 = bor_imm v2, 100000 ; bin: 40 81 ce 000186a0 + ; asm: orl $0x7fffffff, %r10d + [-,%r10] v122 = bor_imm v3, 0x7fff_ffff ; bin: 41 81 ca 7fffffff + ; asm: orl $100, %r8d + [-,%r8] v123 = bor_imm v4, 100 ; bin: 41 83 c8 64 + ; asm: orl $-100, %r14d + [-,%r14] v124 = bor_imm v5, -100 ; bin: 41 83 ce 9c + ; asm: ret + + ; asm: xorl $-100000, %ecx + [-,%rcx] v130 = bxor_imm v1, -100000 ; bin: 40 81 f1 fffe7960 + ; asm: xorl $100000, %esi + [-,%rsi] v131 = bxor_imm v2, 100000 ; bin: 40 81 f6 000186a0 + ; asm: xorl $0x7fffffff, %r10d + [-,%r10] v132 = bxor_imm v3, 0x7fff_ffff ; bin: 41 81 f2 7fffffff + ; asm: xorl $100, %r8d + [-,%r8] v133 = bxor_imm v4, 100 ; bin: 41 83 f0 64 + ; asm: xorl $-100, %r14d + [-,%r14] v134 = bxor_imm v5, -100 ; bin: 41 83 f6 9c + + ; Register copies. + + ; asm: movl %esi, %ecx + [-,%rcx] v140 = copy v2 ; bin: 40 89 f1 + ; asm: movl %r10d, %esi + [-,%rsi] v141 = copy v3 ; bin: 44 89 d6 + ; asm: movl %ecx, %r10d + [-,%r10] v142 = copy v1 ; bin: 41 89 ca + + ; More arithmetic. + + ; asm: imull %esi, %ecx + [-,%rcx] v150 = imul v1, v2 ; bin: 40 0f af ce + ; asm: imull %r10d, %esi + [-,%rsi] v151 = imul v2, v3 ; bin: 41 0f af f2 + ; asm: imull %ecx, %r10d + [-,%r10] v152 = imul v3, v1 ; bin: 44 0f af d1 + + [-,%rax] v160 = iconst.i32 1 + [-,%rdx] v161 = iconst.i32 2 + ; asm: idivl %ecx + [-,%rax,%rdx] v162, v163 = x86_sdivmodx v130, v131, v1 ; bin: 40 f7 f9 + ; asm: idivl %esi + [-,%rax,%rdx] v164, v165 = x86_sdivmodx v130, v131, v2 ; bin: 40 f7 fe + ; asm: idivl %r10d + [-,%rax,%rdx] v166, v167 = x86_sdivmodx v130, v131, v3 ; bin: 41 f7 fa + ; asm: divl %ecx + [-,%rax,%rdx] v168, v169 = x86_udivmodx v130, v131, v1 ; bin: 40 f7 f1 + ; asm: divl %esi + [-,%rax,%rdx] v170, v171 = x86_udivmodx v130, v131, v2 ; bin: 40 f7 f6 + ; asm: divl %r10d + [-,%rax,%rdx] v172, v173 = x86_udivmodx v130, v131, v3 ; bin: 41 f7 f2 + + ; 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 + + ; 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 + + ; 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 + + ; 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 + ; asm: testl %esi, %esi + ; asm: je ebb1x + brz v2, ebb1 ; bin: 40 85 f6 74 16 + ; asm: testl %r10d, %r10d + ; asm: je ebb1x + brz v3, ebb1 ; bin: 45 85 d2 74 11 + ; asm: testl %ecx, %ecx + ; asm: jne ebb1x + brnz v1, ebb1 ; bin: 40 85 c9 75 0c + ; asm: testl %esi, %esi + ; asm: jne ebb1x + brnz v2, ebb1 ; bin: 40 85 f6 75 07 + ; asm: testl %r10d, %r10d + ; asm: jne ebb1x + brnz v3, ebb1 ; bin: 45 85 d2 75 02 + + ; asm: jmp ebb2x + jump ebb2 ; bin: eb 01 + + ; asm: ebb1x: +ebb1: + return ; bin: c3 + + ; asm: ebb2x: +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 + + trap ; bin: 0f 0b +} diff --git a/filetests/isa/riscv/abi-e.cton b/filetests/isa/riscv/abi-e.cton new file mode 100644 index 000000000000..df0640228305 --- /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 = (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 new file mode 100644 index 000000000000..c57c09fd973c --- /dev/null +++ b/filetests/isa/riscv/abi.cton @@ -0,0 +1,32 @@ +; Test the legalization of function signatures. +test legalizer +isa riscv + +; regex: V=v\d+ + +function %f() { + sig0 = (i32) -> i32 native + ; check: sig0 = (i32 [%x10]) -> i32 [%x10] native + + 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 = (f32, i64) -> f64 native + ; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] native + + ; Spilling into the stack args. + 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 = (i32x4) native + ; check: sig4 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) native + + ; Splitting vectors, then splitting ints. + 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 new file mode 100644 index 000000000000..1ed2fcabeafa --- /dev/null +++ b/filetests/isa/riscv/binary32.cton @@ -0,0 +1,145 @@ +; Binary emission of 32-bit code. +test binemit +isa riscv + +function %RV32I(i32 link [%x1]) -> i32 link [%x1] { + fn0 = function %foo() + sig0 = () + +ebb0(v9999: i32): + [-,%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 + ; 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 + ; 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 + + ; Integer Register-Immediate Instructions + + ; addi + [-,%x7] v100 = iadd_imm v1, 1000 ; bin: 3e850393 + [-,%x16] v101 = iadd_imm v2, -905 ; bin: c77a8813 + ; 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 + + ; 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 + + ; 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 + + ; 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 + [-,%x16] v151 = copy v2 ; bin: 000a8813 + + ; Control Transfer Instructions + + ; 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 + + ; jalr %x0, %x1, 0 + return v9999 ; bin: 00008067 + +ebb1: + ; 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 + + fallthrough ebb2 + +ebb2: + ; jal %x0, 0x00000 + jump ebb2 ; bin: 0000006f + +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/filetests/isa/riscv/encoding.cton b/filetests/isa/riscv/encoding.cton new file mode 100644 index 000000000000..71961ba353ec --- /dev/null +++ b/filetests/isa/riscv/encoding.cton @@ -0,0 +1,21 @@ +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 + ; check: [Iret#19] + ; sameln: return +} diff --git a/filetests/isa/riscv/expand-i32.cton b/filetests/isa/riscv/expand-i32.cton new file mode 100644 index 000000000000..885d97d30968 --- /dev/null +++ b/filetests/isa/riscv/expand-i32.cton @@ -0,0 +1,38 @@ +; 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=v\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: $v4 = icmp ult $v3, $v1 +; 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 + +function %bitclear(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = band_not v0, v1 + ; check: bnot + ; check: band + return v2 +} diff --git a/filetests/isa/riscv/legalize-abi.cton b/filetests/isa/riscv/legalize-abi.cton new file mode 100644 index 000000000000..f80494cc1a6c --- /dev/null +++ b/filetests/isa/riscv/legalize-abi.cton @@ -0,0 +1,134 @@ +; Test legalizer's handling of ABI boundaries. +test legalizer +isa riscv + +; regex: V=v\d+ +; regex: SS=ss\d+ +; regex: WS=\s+ + +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 + v1 = iadd_imm v0, 1 + ; check: $(v1l=$V), $(v1h=$V) = isplit $v1 + ; check: return $v1l, $v1h, $link + 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 $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($(link=$V): i32): + ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() + ; check: $v1 = iconcat $v1l, $v1h + 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($(link=$V): i32): + ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() + ; check: $v2 = iconcat $v2l, $v2h + 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, $(link=$V): i32): + ; check: $v2 = ireduce.i8 $v2x + ; check: $v3 = ireduce.i8 $v3x + ; check: $(v1x=$V) = uextend.i32 $v1 + ; check: return $v1x, $link + 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($V: i32): + ; nextln: $(rv=$V) = call $fn1() + ; check: $v1 = ireduce.i8 $rv + 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, $(link=$V): i32): + ; check: $(v0a=$V) = iconcat $v0al, $v0ah + ; check: $(v0b=$V) = iconcat $v0bl, $v0bh + ; check: $(v0ab=$V) = vconcat $v0a, $v0b + ; check: $(v0c=$V) = iconcat $v0cl, $v0ch + ; check: $(v0d=$V) = iconcat $v0dl, $v0dh + ; check: $(v0cd=$V) = vconcat $v0c, $v0d + ; check: $v0 = vconcat $v0ab, $v0cd + v1 = bxor v0, v0 + ; 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 + ; 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, $link + return v1 +} + +function %indirect(i32) { + sig1 = () native +ebb0(v0: i32): + call_indirect sig1, v0() + return +} + +; The first argument to call_indirect doesn't get altered. +function %indirect_arg(i32, f32x2) { + sig1 = (f32x2) native +ebb0(v0: i32, v1: f32x2): + call_indirect sig1, v0(v1) + ; 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/filetests/isa/riscv/legalize-i64.cton b/filetests/isa/riscv/legalize-i64.cton new file mode 100644 index 000000000000..e7a3441ee9d9 --- /dev/null +++ b/filetests/isa/riscv/legalize-i64.cton @@ -0,0 +1,64 @@ +; Test the legalization of i64 arithmetic instructions. +test legalizer +isa riscv supports_m=1 + +; regex: V=v\d+ + +function %bitwise_and(i64, i64) -> i64 { +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, $(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, $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, $(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, $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, $(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, $link + +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: $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 +; check: [R#0c +; sameln: $(v3h1=$V) = iadd $v1h, $v2h +; check: $(c_int=$V) = bint.i32 $c +; check: [R#0c +; sameln: $(v3h=$V) = iadd $v3h1, $c_int +; check: $v3 = iconcat $v3l, $v3h +; check: return $v3l, $v3h, $link diff --git a/filetests/isa/riscv/parse-encoding.cton b/filetests/isa/riscv/parse-encoding.cton new file mode 100644 index 000000000000..3fdb8f62d61c --- /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 link [%x1]) -> i32 [%x10], i32 link [%x1] native { + + sig0 = (i32 [%x10]) -> i32 [%x10] native + ; check: sig0 = (i32 [%x10]) -> i32 [%x10] native + + sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native + ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native + + 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 = (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 = (f32 [72], i32 [%x10]) native + ; check: sig4 = (f32 [72], i32 [%x10]) native + + ; Return value on stack + sig5 = () -> f32 [0] native + ; check: sig5 = () -> f32 [0] native + + ; function + signature + fn15 = function %bar(i32 [%x10]) -> b1 [%x10] native + ; check: sig6 = (i32 [%x10]) -> b1 [%x10] native + ; nextln: fn0 = sig6 %bar + +ebb0(v0: i32): + return v0 +} diff --git a/filetests/isa/riscv/regmove.cton b/filetests/isa/riscv/regmove.cton new file mode 100644 index 000000000000..c316f74f21be --- /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/filetests/isa/riscv/split-args.cton b/filetests/isa/riscv/split-args.cton new file mode 100644 index 000000000000..be1370dc1217 --- /dev/null +++ b/filetests/isa/riscv/split-args.cton @@ -0,0 +1,55 @@ +; Test the legalization of EBB arguments that are split. +test legalizer +isa riscv + +; regex: V=v\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, $(link=$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, $link +} + +function %multi(i64) -> i64 { +ebb1(v1: i64): +; check: $ebb1($(v1l=$V): i32, $(v1h=$V): i32, $(link=$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, $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, $(link=$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/filetests/isa/riscv/verify-encoding.cton b/filetests/isa/riscv/verify-encoding.cton new file mode 100644 index 000000000000..b88fdc640233 --- /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 0xf0f0f0f0f0 ; 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/filetests/licm/basic.cton b/filetests/licm/basic.cton new file mode 100644 index 000000000000..37dda60d2afb --- /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 +; 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 000000000000..07efb9ff5fbe --- /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 +; 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 000000000000..4738081d9ab5 --- /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 000000000000..a32dd4b498be --- /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/filetests/parser/branch.cton b/filetests/parser/branch.cton new file mode 100644 index 000000000000..283dd9a03ad8 --- /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() native { +; nextln: ebb0: +; nextln: jump ebb1 +; nextln: +; nextln: ebb1: +; nextln: jump ebb0 +; nextln: } + +; Jumps with 1 arg. +function %onearg(i32) { +ebb0(v90: i32): + jump ebb1(v90) + +ebb1(v91: i32): + jump ebb0(v91) +} +; sameln: function %onearg(i32) native { +; nextln: ebb0($v90: i32): +; nextln: jump ebb1($v90) +; nextln: +; nextln: ebb1($v91: i32): +; nextln: jump ebb0($v91) +; nextln: } + +; Jumps with 2 args. +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) native { +; nextln: ebb0($v90: i32, $v91: f32): +; nextln: jump ebb1($v90, $v91) +; nextln: +; 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(v90: i32): + brz v90, ebb1 + +ebb1: + brnz v90, ebb1() +} +; sameln: function %minimal(i32) native { +; nextln: ebb0($v90: i32): +; nextln: brz $v90, ebb1 +; nextln: +; nextln: ebb1: +; nextln: brnz.i32 $v90, ebb1 +; nextln: } + +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) native { +; nextln: ebb0($v90: i32, $v91: f32): +; nextln: brz $v90, ebb1($v90, $v91) +; nextln: +; nextln: ebb1($v92: i32, $v93: f32): +; nextln: brnz.i32 $v90, ebb0($v92, $v93) +; 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) native { +; nextln: jt0 = jump_table 0 +; nextln: jt1 = jump_table 0, 0, ebb0, ebb3, ebb1, ebb2 +; nextln: +; nextln: ebb0($v3: i32): +; nextln: br_table $v3, 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 000000000000..662925db7f39 --- /dev/null +++ b/filetests/parser/call.cton @@ -0,0 +1,80 @@ +; Parser tests for call and return syntax. +test cat + +function %mini() { +ebb1: + return +} +; sameln: function %mini() native { +; nextln: ebb0: +; nextln: return +; nextln: } + +function %r1() -> i32, f32 spiderwasm { +ebb1: + v1 = iconst.i32 3 + v2 = f32const 0.0 + return v1, v2 +} +; sameln: function %r1() -> i32, f32 spiderwasm { +; nextln: ebb0: +; nextln: $v1 = iconst.i32 3 +; nextln: $v2 = f32const 0.0 +; nextln: return $v1, $v2 +; nextln: } + +function %signatures() { + sig10 = () + sig11 = (i32, f64) -> i32, b1 spiderwasm + fn5 = sig11 %foo + fn8 = function %bar(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: } + +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 = (i64) + sig1 = () -> i32 + sig2 = () -> 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 + +; 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, 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 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 new file mode 100644 index 000000000000..c80889270116 --- /dev/null +++ b/filetests/parser/instruction_encoding.cton @@ -0,0 +1,24 @@ +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) native { +; nextln: $ebb1($v0: i32, $v1: i32): +; nextln: [-,-]$WS $v2 = iadd $v0, $v1 +; nextln: [-]$WS trap +; 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/filetests/parser/keywords.cton b/filetests/parser/keywords.cton new file mode 100644 index 000000000000..a4b894574ea7 --- /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() native diff --git a/filetests/parser/rewrite.cton b/filetests/parser/rewrite.cton new file mode 100644 index 000000000000..c6a04a9e9cc3 --- /dev/null +++ b/filetests/parser/rewrite.cton @@ -0,0 +1,37 @@ +; 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. +test cat + +; Check that defining numbers are rewritten. +function %defs() { +ebb100(v20: i32): + v1000 = iconst.i32x8 5 + v9200 = f64const 0x4.0p0 + trap +} +; sameln: function %defs() native { +; nextln: $ebb100($v20: i32): +; nextln: $v1000 = iconst.i32x8 5 +; nextln: $v9200 = f64const 0x1.0000000000000p2 +; nextln: trap +; nextln: } + +; Using values. +function %use_value() { +ebb100(v20: i32): + v1000 = iadd_imm v20, 5 + v200 = iadd v20, v1000 + jump ebb100(v1000) +} +; sameln: function %use_value() native { +; 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/ternary.cton b/filetests/parser/ternary.cton new file mode 100644 index 000000000000..3f320a61f00b --- /dev/null +++ b/filetests/parser/ternary.cton @@ -0,0 +1,24 @@ +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 +} + +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/filetests/parser/tiny.cton b/filetests/parser/tiny.cton new file mode 100644 index 000000000000..ecd2525ba278 --- /dev/null +++ b/filetests/parser/tiny.cton @@ -0,0 +1,193 @@ +test cat + +; The smallest possible function. +function %minimal() { +ebb0: + trap +} +; sameln: function %minimal() native { +; nextln: ebb0: +; nextln: trap +; nextln: } + +; Create and use values. +; Polymorphic instructions with type suffix. +function %ivalues() { +ebb0: + v0 = iconst.i32 2 + v1 = iconst.i8 6 + v2 = ishl v0, v1 +} +; sameln: function %ivalues() native { +; nextln: ebb0: +; nextln: $v0 = iconst.i32 2 +; nextln: $v1 = iconst.i8 6 +; 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() native { +; 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): + v0 = select v92, v90, v91 +} +; sameln: function %select() native { +; nextln: ebb0($v90: i32, $v91: i32, $v92: b1): +; nextln: $v0 = select $v92, $v90, $v91 +; nextln: } + +; Lane indexes. +function %lanes() { +ebb0: + v0 = iconst.i32x4 2 + v1 = extractlane v0, 3 + v2 = insertlane v0, 1, v1 +} +; sameln: function %lanes() native { +; 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) { +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) native { +; 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(v90: f32, v91: f32): + v0 = fcmp eq v90, v91 + v1 = fcmp uno v90, v91 + v2 = fcmp lt v90, v91 +} +; sameln: function %fcmp(f32, f32) native { +; 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(v90: i32, v91: f32): + v0 = bitcast.i8x4 v90 + v1 = bitcast.i32 v91 +} +; sameln: function %bitcast(i32, f32) native { +; nextln: ebb0($v90: i32, $v91: f32): +; nextln: $v0 = bitcast.i8x4 $v90 +; nextln: $v1 = bitcast.i32 $v91 +; nextln: } + +; Stack slot references +function %stack() { + ss10 = spill_slot 8 + ss2 = local 4 + ss3 = incoming_arg 4, offset 8 + ss4 = outgoing_arg 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() native { +; nextln: $ss10 = spill_slot 8 +; nextln: $ss2 = local 4 +; nextln: $ss3 = incoming_arg 4, offset 8 +; nextln: $ss4 = outgoing_arg 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 + +; 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) native { +; 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) { +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) native { +; 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 + +; 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) native { +; nextln: ebb0($v1: i32): +; nextln: regmove $v1, %10 -> %20 +; nextln: regmove $v1, %20 -> %10 +; nextln: return +; nextln: } diff --git a/filetests/regalloc/basic.cton b/filetests/regalloc/basic.cton new file mode 100644 index 000000000000..89e160d75a35 --- /dev/null +++ b/filetests/regalloc/basic.cton @@ -0,0 +1,80 @@ +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 +; check: [R#0c,%x5] +; sameln: iadd + return +} + +; 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 +} + +; 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 +} + +; 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/filetests/regalloc/coalesce.cton b/filetests/regalloc/coalesce.cton new file mode 100644 index 000000000000..80cd38e62e47 --- /dev/null +++ b/filetests/regalloc/coalesce.cton @@ -0,0 +1,111 @@ +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: + v2 = iadd_imm v0, 8 + ; check: $(cp1=$V) = copy $v1 + ; not: copy + ; check: 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 + ; not: copy + ; check: brnz $v13, $ebb1($nv11b, $v12) + 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/filetests/regalloc/constraints.cton b/filetests/regalloc/constraints.cton new file mode 100644 index 000000000000..720a8862633f --- /dev/null +++ b/filetests/regalloc/constraints.cton @@ -0,0 +1,82 @@ +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 { +ebb0: + v0 = iconst.i32 12 + v1 = iconst.i32 13 + ; not: copy + ; check: isub + v2 = isub v0, v1 + 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: + ; 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 +} + +; 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 +} + +; 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 +} diff --git a/filetests/regalloc/spill.cton b/filetests/regalloc/spill.cton new file mode 100644 index 000000000000..901509a8d4bf --- /dev/null +++ b/filetests/regalloc/spill.cton @@ -0,0 +1,196 @@ +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+ +; regex: WS=\s+ + +isa riscv enable_e + +; 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 %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: ,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 + ; not: spill + 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 + v33 = iadd v13, v14 + ; check: iadd $v13 + 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 + ; 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 +} + +; 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 +} + +; 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 = (i32) native +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 = (i32, i32) native +ebb0(v0: i32): + ; check: $(c=$V) = copy $v0 + call_indirect sig0, v0(v0, v0) + ; 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 +} + +; 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 +} + +; 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/filetests/simple_gvn/basic.cton b/filetests/simple_gvn/basic.cton new file mode 100644 index 000000000000..6ff45d1aefb4 --- /dev/null +++ b/filetests/simple_gvn/basic.cton @@ -0,0 +1,41 @@ +test simple-gvn + +function %simple_redundancy(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = iadd v0, v1 + v3 = iadd v0, v1 + 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 +} + +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 +} diff --git a/filetests/verifier/bad_layout.cton b/filetests/verifier/bad_layout.cton new file mode 100644 index 000000000000..fd597359bebb --- /dev/null +++ b/filetests/verifier/bad_layout.cton @@ -0,0 +1,19 @@ +test verifier + +function %test(i32) { + ebb0(v0: i32): + jump ebb1 ; error: terminator + return + ebb1: + jump ebb2 + brz v0, ebb3 + ebb2: + jump ebb3 + ebb3: + return +} + +function %test(i32) { ; Ok + ebb0(v0: i32): + return +} diff --git a/filetests/verifier/unreachable_code.cton b/filetests/verifier/unreachable_code.cton new file mode 100644 index 000000000000..474eaec6b4c9 --- /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/filetests/wasm/control.cton b/filetests/wasm/control.cton new file mode 100644 index 000000000000..c8e37a536f29 --- /dev/null +++ b/filetests/wasm/control.cton @@ -0,0 +1,50 @@ +; 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) +} + +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 +} + +function %undefined() { +ebb0: + trap +} diff --git a/filetests/wasm/conversions.cton b/filetests/wasm/conversions.cton new file mode 100644 index 000000000000..e742ebba27a1 --- /dev/null +++ b/filetests/wasm/conversions.cton @@ -0,0 +1,94 @@ +; 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 +} + +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 +} + +; 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 + 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). + +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/filetests/wasm/f32-arith.cton b/filetests/wasm/f32-arith.cton new file mode 100644 index 000000000000..ad48b401b8f7 --- /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 000000000000..bc81d69c3eea --- /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/filetests/wasm/i32-arith.cton b/filetests/wasm/i32-arith.cton new file mode 100644 index 000000000000..fe9cf19883eb --- /dev/null +++ b/filetests/wasm/i32-arith.cton @@ -0,0 +1,128 @@ +; Test basic code generation for i32 arithmetic WebAssembly instructions. +test compile + +set is_64bit=0 +isa intel haswell + +set is_64bit=1 +isa intel haswell + +; Constants. + +function %i32_const() -> i32 { +ebb0: + v0 = iconst.i32 0x8765_4321 + return v0 +} + +; Unary operations. + +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): + v1 = popcnt v0 + return v1 +} + +; 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 { +ebb0(v0: i32, v1: i32): + v2 = imul v0, v1 + return v2 +} + +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): + 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 { +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/filetests/wasm/i32-compares.cton b/filetests/wasm/i32-compares.cton new file mode 100644 index 000000000000..228258d279b6 --- /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 +} diff --git a/filetests/wasm/i64-arith.cton b/filetests/wasm/i64-arith.cton new file mode 100644 index 000000000000..4e8cdc06dfcb --- /dev/null +++ b/filetests/wasm/i64-arith.cton @@ -0,0 +1,125 @@ +; 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 %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): + 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 000000000000..3406463f0d28 --- /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 +} diff --git a/format-all.sh b/format-all.sh new file mode 100755 index 000000000000..d9e97f102641 --- /dev/null +++ b/format-all.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Format all sources using rustfmt. + +# Exit immediately on errors. +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 -- "$@" +done diff --git a/lib/cretonne/Cargo.toml b/lib/cretonne/Cargo.toml new file mode 100644 index 000000000000..60e8eb2be9d3 --- /dev/null +++ b/lib/cretonne/Cargo.toml @@ -0,0 +1,19 @@ +[package] +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 +build = "build.rs" + +[lib] +name = "cretonne" + +[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 `tests`. diff --git a/lib/cretonne/build.rs b/lib/cretonne/build.rs new file mode 100644 index 000000000000..2653bf54a01b --- /dev/null +++ b/lib/cretonne/build.rs @@ -0,0 +1,151 @@ +// 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. +// +// 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. + + +use std::env; +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); + + 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"); + + // 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()); + } +} + +/// 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()), + } +} diff --git a/lib/cretonne/meta/base/__init__.py b/lib/cretonne/meta/base/__init__.py new file mode 100644 index 000000000000..132b469ee69f --- /dev/null +++ b/lib/cretonne/meta/base/__init__.py @@ -0,0 +1 @@ +"""Definitions for the base Cretonne language.""" diff --git a/lib/cretonne/meta/base/entities.py b/lib/cretonne/meta/base/entities.py new file mode 100644 index 000000000000..327d45e8bc42 --- /dev/null +++ b/lib/cretonne/meta/base/entities.py @@ -0,0 +1,29 @@ +""" +The `cretonne.entities` module predefines all the Cretonne entity reference +operand types. There are corresponding definitions in the `cretonne.entities` +Rust module. +""" +from __future__ import absolute_import +from cdsl.operands 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.', + default_member='destination') + +#: 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. +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. +func_ref = EntityRefKind('func_ref', 'An external function.') + +#: A reference to a jump table declared in the function preamble. +jump_table = EntityRefKind( + 'jump_table', 'A jump table.', default_member='table') diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py new file mode 100644 index 000000000000..16732c115355 --- /dev/null +++ b/lib/cretonne/meta/base/formats.py @@ -0,0 +1,64 @@ +""" +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 __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 boolean, intcc, floatcc, memflags, regunit +from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot + +Nullary = InstructionFormat() + +Unary = InstructionFormat(VALUE) +UnaryImm = InstructionFormat(imm64) +UnaryIeee32 = InstructionFormat(ieee32) +UnaryIeee64 = InstructionFormat(ieee64) +UnaryBool = InstructionFormat(boolean) + +Binary = InstructionFormat(VALUE, VALUE) +BinaryImm = InstructionFormat(VALUE, imm64) + +# 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) + +# Catch-all for instructions with many outputs and inputs and no immediate +# operands. +MultiAry = InstructionFormat(VARIABLE_ARGS) + +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) +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) +IndirectCall = InstructionFormat(sig_ref, VALUE, VARIABLE_ARGS) + +Load = InstructionFormat(memflags, VALUE, offset32) +Store = InstructionFormat(memflags, VALUE, VALUE, offset32) + +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) + +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 new file mode 100644 index 000000000000..1defe46f6d0b --- /dev/null +++ b/lib/cretonne/meta/base/immediates.py @@ -0,0 +1,111 @@ +""" +The `cretonne.immediates` module predefines all the Cretonne immediate operand +types. +""" +from __future__ import absolute_import +from cdsl.operands 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 = 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 signed offset. +#: +#: This is used to represent an immediate address offset in load/store +#: instructions. +offset32 = ImmediateKind( + 'offset32', + '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. +ieee32 = ImmediateKind('ieee32', 'A 32-bit immediate floating point number.') + +#: A 64-bit immediate floating point operand. +#: +#: 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('bool', '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 +#: and corresponds to the `condcodes::IntCC` Rust type. +intcc = ImmediateKind( + 'intcc', + 'An integer comparison condition code.', + 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. +#: +#: 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.', + 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', + }) + +#: Flags for memory operations like :cton:inst:`load` and :cton:inst:`store`. +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 new file mode 100644 index 000000000000..3a4ce25cda8f --- /dev/null +++ b/lib/cretonne/meta/base/instructions.py @@ -0,0 +1,1621 @@ +""" +Cretonne base instruction set. + +This module defines the basic Cretonne instruction set that all targets +support. +""" +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 f32, f64, b1 +from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 +from base.immediates import boolean, intcc, floatcc, memflags, regunit +from base import entities +from cdsl.ti import WiderOrEq +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( + 'Testable', 'A scalar boolean or integer type', + ints=True, bools=True) +TxN = TypeVar( + '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', + 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 +# +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') + +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_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. + + 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) + +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( + '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, can_trap=True) + +trapz = Instruction( + 'trapz', r""" + Trap when zero. + + if ``c`` is non-zero, execution continues at the following instruction. + """, + ins=c, can_trap=True) + +trapnz = Instruction( + 'trapnz', r""" + Trap when non-zero. + + if ``c`` is zero, execution continues at the following instruction. + """, + ins=c, can_trap=True) + +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, is_return=True, is_terminator=True) + +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, is_call=True) + +SIG = Operand('SIG', entities.sig_ref, doc='function signature') +callee = Operand('callee', iAddr, 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 specified signature. + """, + 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') +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, can_load=True) + +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), can_store=True) + +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, can_load=True) + +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, can_load=True) + +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), can_store=True) + +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, can_load=True) + +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, can_load=True) + +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.i16``. + """, + ins=(Flags, x, p, Offset), can_store=True) + +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, can_load=True) + +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, can_load=True) + +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.i32``. + """, + ins=(Flags, x, p, Offset), can_store=True) + +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. + + 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, can_load=True) + +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), can_store=True) + +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) + +# +# 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, can_load=True) + +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), can_store=True) + +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. +# + +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', 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. +# + +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) + +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 creates a new + SSA value for the spilled input value. + """, + 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), + other_side_effects=True) + +# +# 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') +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) + +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 +# + +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', 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) + +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, can_trap=True) + +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, can_trap=True) + +urem = Instruction( + 'urem', """ + Unsigned integer remainder. + + This operation traps if the divisor is zero. + """, + ins=(x, y), outs=a, can_trap=True) + +srem = Instruction( + 'srem', """ + Signed integer remainder. The result has the sign of the dividend. + + This operation traps if the divisor is zero. + + .. todo:: Integer remainder vs modulus. + + 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) + +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, but does not support vector + 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) + +irsub_imm = Instruction( + 'irsub_imm', """ + Immediate reverse wrapping subtraction: :math:`a := Y - x \pmod{2^B}`. + + 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) + +# +# Integer arithmetic with carry and/or borrow. +# +a = Operand('a', iB) +x = Operand('x', iB) +y = Operand('y', iB) +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', r""" + Add integers with carry in. + + 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, c_in), outs=a) + +iadd_cout = Instruction( + 'iadd_cout', r""" + Add integers with carry out. + + 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, c_out)) + +iadd_carry = Instruction( + 'iadd_carry', r""" + Add integers with carry in and out. + + 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, c_in), outs=(a, c_out)) + +isub_bin = Instruction( + 'isub_bin', r""" + 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', r""" + 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', r""" + 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. +# + +# TODO: Which types should permit boolean operations? Any reason to restrict? +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) + +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) + +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) +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) + +# 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( + '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) + +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`` + 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}. + """, + 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. + """, + 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. + """, + 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. +# + +x = Operand('x', iB) +a = Operand('a', iB) + +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) + +# +# Floating point. +# + +Float = TypeVar( + 'Float', 'A scalar or vector floating point number', + 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) + +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` without 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) + + +# +# Conversions +# + +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) + +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, constraints=WiderOrEq(Bool, BoolTo)) + +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, constraints=WiderOrEq(BoolTo, Bool)) + +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', + 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, constraints=WiderOrEq(Int, IntTo)) + + +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. + + 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, constraints=WiderOrEq(IntTo, Int)) + +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, constraints=WiderOrEq(IntTo, Int)) + +FloatTo = TypeVar( + 'FloatTo', 'A scalar or vector floating point number', + floats=True, simd=True) + +x = Operand('x', Float) +a = Operand('a', FloatTo) + +fpromote = Instruction( + 'fpromote', 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. + + 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 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, constraints=WiderOrEq(FloatTo, Float)) + +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. + + 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 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, constraints=WiderOrEq(Float, FloatTo)) + +x = Operand('x', Float) +a = Operand('a', IntTo) + +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, can_trap=True) + +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, can_trap=True) + +x = Operand('x', Int) +a = Operand('a', FloatTo) + +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) + +# +# Legalization helper instructions. +# + +WideInt = TypeVar( + '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`') +hi = Operand( + 'hi', WideInt.half_width(), 'The high bits of `x`') + +isplit = Instruction( + 'isplit', r""" + 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. + """, + ins=x, outs=(lo, hi)) + + +NarrowInt = TypeVar( + 'NarrowInt', 'An integer type with lanes type to `i32`', + ints=(8, 32), simd=True) +lo = Operand('lo', NarrowInt) +hi = Operand('hi', NarrowInt) +a = Operand( + 'a', NarrowInt.double_width(), + doc='The concatenation of `lo` and `hi`') + +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) + +GROUP.close() diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py new file mode 100644 index 000000000000..c250d53ce2fb --- /dev/null +++ b/lib/cretonne/meta/base/legalize.py @@ -0,0 +1,189 @@ +""" +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 .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 .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 + + +narrow = XFormGroup('narrow', """ + 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. + """) + +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. + + 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') +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') +yh = Var('yh') +al = Var('al') +ah = Var('ah') +cc = Var('cc') + +narrow.legalize( + a << iadd(x, y), + Rtl( + (xl, xh) << isplit(x), + (yl, yh) << isplit(y), + (al, c) << iadd_cout(xl, yl), + ah << iadd_cin(xh, yh, c), + a << iconcat(al, ah) + )) + +narrow.legalize( + a << isub(x, y), + Rtl( + (xl, xh) << isplit(x), + (yl, yh) << isplit(y), + (al, b) << isub_bout(xl, yl), + ah << isub_bin(xh, yh, b), + a << iconcat(al, ah) + )) + +for bitop in [band, bor, bxor]: + narrow.legalize( + a << bitop(x, y), + Rtl( + (xl, xh) << isplit(x), + (yl, yh) << isplit(y), + al << bitop(xl, yl), + ah << bitop(xh, yh), + a << iconcat(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(intcc.ult, a, x) + )) + +expand.legalize( + (a, b) << isub_bout(x, y), + Rtl( + a << isub(x, y), + b << icmp(intcc.ugt, a, x) + )) + +expand.legalize( + a << iadd_cin(x, y, c), + Rtl( + a1 << iadd(x, y), + c_int << bint(c), + a << iadd(a1, c_int) + )) + +expand.legalize( + a << isub_bin(x, y, b), + Rtl( + a1 << isub(x, y), + 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), + c_int << bint(c_in), + (a, c2) << iadd_cout(a1, c_int), + c << bor(c1, c2) + )) + +expand.legalize( + (a, b) << isub_borrow(x, y, b_in), + Rtl( + (a1, b1) << isub_bout(x, y), + b_int << bint(b_in), + (a, b2) << isub_bout(a1, b_int), + b << bor(b1, b2) + )) + +# Expansions for immediate operands that are out of range. +expand.legalize( + a << iadd_imm(x, y), + Rtl( + a1 << iconst(y), + 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( + 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) + )) diff --git a/lib/cretonne/meta/base/semantics.py b/lib/cretonne/meta/base/semantics.py new file mode 100644 index 000000000000..edf4c5f82e15 --- /dev/null +++ b/lib/cretonne/meta/base/semantics.py @@ -0,0 +1,181 @@ +from __future__ import absolute_import +from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\ + bvadd, bvult, bvzeroext, bvsignext +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 +from cdsl.typevar import TypeSet +from cdsl.ti import InTypeset + +x = Var('x') +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') + +ScalarTS = TypeSet(lanes=(1, 1), ints=True, floats=True, bools=True) + +vsplit.set_semantics( + (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( + 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( + 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) + )) + +# +# 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), + 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) + )) + +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( + bvx << prim_to_bv(x), + bvy << bvsignext(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) + )) + +# +# 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/base/settings.py b/lib/cretonne/meta/base/settings.py new file mode 100644 index 000000000000..186c808524e5 --- /dev/null +++ b/lib/cretonne/meta/base/settings.py @@ -0,0 +1,45 @@ +""" +Cretonne shared settings. + +This module defines settings are are relevant for all code generators. +""" +from __future__ import absolute_import +from cdsl.settings import SettingGroup, BoolSetting, EnumSetting + +group = SettingGroup('shared') + +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_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") + +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/lib/cretonne/meta/base/types.py b/lib/cretonne/meta/base/types.py new file mode 100644 index 000000000000..7111626009cf --- /dev/null +++ b/lib/cretonne/meta/base/types.py @@ -0,0 +1,33 @@ +""" +The base.types module predefines all the Cretonne scalar types. +""" +from __future__ import absolute_import +from cdsl.types import IntType, FloatType, BoolType + +#: Boolean. +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. +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. + +#: 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. + """) + +#: 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. + """) diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py new file mode 100644 index 000000000000..a94b9ce706ea --- /dev/null +++ b/lib/cretonne/meta/build.py @@ -0,0 +1,32 @@ +# Second-level build script. +# +# This script is run from lib/cretonne/build.rs to generate Rust files. + +from __future__ import absolute_import +import argparse +import isa +import gen_types +import gen_instr +import gen_settings +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') + +args = parser.parse_args() +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) +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/cdsl/__init__.py b/lib/cretonne/meta/cdsl/__init__.py new file mode 100644 index 000000000000..44445268ea13 --- /dev/null +++ b/lib/cretonne/meta/cdsl/__init__.py @@ -0,0 +1,59 @@ +""" +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: + >>> 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/ast.py b/lib/cretonne/meta/cdsl/ast.py new file mode 100644 index 000000000000..aa53e7b9b514 --- /dev/null +++ b/lib/cretonne/meta/cdsl/ast.py @@ -0,0 +1,501 @@ +""" +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 +from . import instructions +from .typevar import TypeVar +from .predicates import IsEqual, And, TypePredicate + +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, 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 + """ + 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 + an expression. + + Example: + + >>> from base.instructions 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): + # type: (Union[Var, Tuple[Var, ...]], Apply) -> None + if not isinstance(defs, tuple): + self.defs = (defs,) # type: Tuple[Var, ...] + else: + self.defs = defs + assert isinstance(expr, Apply) + 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: + return "({}) << {!s}".format( + ', '.join(map(str, self.defs)), self.expr) + + def copy(self, m): + # 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. + """ + 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) + + 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 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 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) + + 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): + """ + An AST expression. + """ + + +class Var(Expr): + """ + A free variable. + + When variables are used in `XForms` with source and 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, 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 = 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 = self.typevar # type: TypeVar + + def __str__(self): + # type: () -> str + return self.name + + def __repr__(self): + # type: () -> str + s = self.name + 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 src pattern?""" + return self.src_def is None and self.dst_def is None + + def is_output(self): + # type: () -> bool + """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): + # type: () -> bool + """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): + # type: () -> bool + """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 + """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, bitvecs=True) + self.original_typevar = tv + self.typevar = tv + return self.typevar + + def set_typevar(self, tv): + # type: (TypeVar) -> None + self.typevar = tv + + 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 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() + + +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.instructions 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): + # type: (instructions.MaybeBoundInst, Tuple[Expr, ...]) -> None # noqa + if isinstance(inst, instructions.BoundInstruction): + self.inst = inst.inst + self.typevars = inst.typevars + else: + assert isinstance(inst, instructions.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): + # 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) + + 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) + + 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 `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 + + # 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)) + + # 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): + # 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. + """ + 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 in self that correspond to SSA values""" + 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 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 + other. + """ + if self.inst != other.inst: + return None + + # Guaranteed by self.inst == other.inst + assert (len(self.args) == len(other.args)) + + 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 + 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) + + 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 + + +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. + + 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/formats.py b/lib/cretonne/meta/cdsl/formats.py new file mode 100644 index 000000000000..aba83ed7a2a1 --- /dev/null +++ b/lib/cretonne/meta/cdsl/formats.py @@ -0,0 +1,232 @@ +"""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 Dict, List, Tuple, Union, Any, Sequence, Iterable # noqa +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 + 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. + + 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. + + :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 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 (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] + + 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. + self.num_value_operands = 0 + # Does this format use a value list for storing value operands? + self.has_value_list = False + # 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 + if self.typevar_operand is not None: + if not self.has_value_list: + assert self.typevar_operand < self.num_value_operands, \ + "typevar_operand must indicate a 'value' operand" + 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 + + # Compute a signature for the global registry. + imm_kinds = tuple(f.kind for f in self.imm_fields) + 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 '{}'" + .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[FormatField] # 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. + + Updates the fields `self.num_value_operands` and `self.has_value_list`. + + Yields the immediate operand fields. + """ + inum = 0 + for arg in kinds: + if isinstance(arg, OperandKind): + member = arg.default_member + 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: + self.has_value_list = True + else: + yield FormatField(self, inum, k, member) + inum += 1 + + def __str__(self): + # type: () -> str + args = ', '.join( + '{}: {}'.format(f.member, f.kind) for f in self.imm_fields) + return '{}(imms=({}), vals={})'.format( + self.name, args, self.num_value_operands) + + def __getattr__(self, attr): + # type: (str) -> FormatField + """ + Make immediate instruction format members available as attributes. + + Each non-value format member becomes a corresponding `FormatField` + attribute. + """ + 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): + # 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. + """ + # 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 = (imm_kinds, num_values, has_varargs) + if sig in InstructionFormat._registry: + return InstructionFormat._registry[sig] + + # Try another value list format as an alternative. + sig = (imm_kinds, 0, True) + if sig in InstructionFormat._registry: + return InstructionFormat._registry[sig] + + raise RuntimeError( + 'No instruction format matches ' + 'imms={}, vals={}, varargs={}'.format( + imm_kinds, num_values, has_varargs)) + + @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. + 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): + """ + An immediate field in an instruction format. + + This corresponds to a single member of a variant of the `InstructionData` + data type. + + :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, 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.member) + + def rust_name(self): + # type: () -> str + return self.member diff --git a/lib/cretonne/meta/cdsl/instructions.py b/lib/cretonne/meta/cdsl/instructions.py new file mode 100644 index 000000000000..30c27c6649cf --- /dev/null +++ b/lib/cretonne/meta/cdsl/instructions.py @@ -0,0 +1,427 @@ +"""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, Tuple, Any, TYPE_CHECKING # noqa + from typing import Dict # noqa + if TYPE_CHECKING: + from .ast import Expr, Apply, Var, Def # noqa + from .typevar import TypeVar # noqa + from .ti import TypeConstraint # noqa + 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 = Sequence[XForm] + RtlCase = Union[Rtl, Tuple[Rtl, Sequence[TypeConstraint]]] +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 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. + :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. + :param other_side_effects: Instruction has other side effects. + """ + + # 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_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): + # 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) + self.semantics = None # type: InstructionSemantics + + # 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( + 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 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) + + def __str__(self): + # type: () -> str + 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): + # type: () -> str + """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): + # type: () -> None + """ + Check if this instruction is polymorphic, and verify its use of type + variables. + """ + poly_ins = [ + i for i in self.value_opnums + if self.ins[i].typevar.free_typevar()] + poly_outs = [ + i for i, o in enumerate(self.outs) + 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 + + # 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: + opnum = self.value_opnums[self.format.typevar_operand] + tv = self.ins[opnum].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 + 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].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) + 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. + + 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 = [] # type: List[TypeVar] + # Check value inputs. + 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: + continue + # No other derived typevars allowed. + if typ is not tv: + raise RuntimeError( + "{}: type variable {} must be derived from {}" + .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( + "type variable {} can't be used more than once" + .format(tv.name)) + other_tvs.append(tv) + + # Check outputs. + for result in self.outs: + 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: + continue + raise RuntimeError( + "type variable in output not derived from ctrl_typevar") + + 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, ...] + # 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 + + @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 + """ + 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 + """ + assert name != 'any', 'Wildcard not allowed for ctrl_typevar' + 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): + # type: (*Expr) -> Apply + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + from .ast import Apply # noqa + return Apply(self, args) + + 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 + + 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) + + self.semantics = sem + + +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): + # type: () -> str + 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 + """ + 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): + # 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): + # type: (*Expr) -> Apply + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + 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 new file mode 100644 index 000000000000..3e971e7a5a7f --- /dev/null +++ b/lib/cretonne/meta/cdsl/isa.py @@ -0,0 +1,462 @@ +"""Defining instruction set architectures.""" +from __future__ import absolute_import +from collections import OrderedDict +from .predicates import And, TypePredicate +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. +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, PredKey # noqa + from .settings import SettingGroup # 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 + # 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 + + +class TargetISA(object): + """ + A target instruction set architecture. + + 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 + relevant for this ISA. + """ + + def __init__(self, name, instruction_groups): + # type: (str, Sequence[InstructionGroup]) -> None + self.name = name + self.settings = None # type: SettingGroup + self.instruction_groups = instruction_groups + 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 + # Unique copies of all predicates. + self._predicates = dict() # type: Dict[PredKey, PredNode] + + assert InstructionGroup._current is None,\ + "InstructionGroup {} is still open!"\ + .format(InstructionGroup._current.name) + + def __str__(self): + # type: () -> str + return self.name + + def finish(self): + # type: () -> TargetISA + """ + Finish the definition of a target ISA after adding all CPU modes and + settings. + + This computes some derived properties that are used in multiple + places. + + :returns self: + """ + self._collect_encoding_recipes() + self._collect_predicates() + self._collect_regclasses() + self._collect_legalize_codes() + return self + + def _collect_encoding_recipes(self): + # type: () -> None + """ + Collect and number all encoding recipes in use. + """ + 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 + 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 + """ + Collect and number all predicates in use. + + Ensures that all ISA predicates have an assigned bit number in + `self.settings`. + """ + 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 self.instp_number: + # assign predicate number starting from 0. + 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 + # replicated here, which is OK. + if enc.isap: + self.settings.number_predicate(enc.isap) + + def _collect_regclasses(self): + # type: () -> None + """ + Collect and number register classes. + + 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. + """ + # 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() + 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: + self.regclasses.extend( + rc for rc in bank.classes if not rc.is_toprc()) + + 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" + + 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 + """ + 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 + + 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): + """ + 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 `TargetISA`. + + :param name: Short mnemonic name for the CPU mode. + :param target: Associated `TargetISA`. + """ + + def __init__(self, name, isa): + # type: (str, TargetISA) -> None + self.name = name + self.isa = isa + 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 + + def enc(self, *args, **kwargs): + # type: (*Any, **Any) -> None + """ + 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)) + + 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): + """ + 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. + + 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. + - 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 + 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. + :param: emit Rust code for binary emission. + """ + + 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 + emit=None # type: str + ): + # 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 + self.emit = emit + if instp: + assert instp.predicate_context() == format + self.number = None # type: int + + self.ins = self._verify_constraints(ins) + if not format.has_value_list: + assert len(self.ins) == format.num_value_operands + self.outs = self._verify_constraints(outs) + + def __str__(self): + # type: () -> str + return self.name + + def _verify_constraints(self, seq): + # type: (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.ins) + else: + assert (isinstance(c, RegClass) + or isinstance(c, Register) + or isinstance(c, Stack)) + 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) + + 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): + """ + Encoding for a concrete instruction. + + 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. + :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, instp=None, isap=None): + # type: (CPUMode, InstSpec, EncRecipe, int, PredNode, PredNode) -> None # noqa + assert isinstance(cpumode, CPUMode) + assert isinstance(recipe, EncRecipe) + + # 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() + + # 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( + 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 + + # 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) + + 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`. + """ + if self.typevars: + return self.typevars[0] + else: + return None diff --git a/lib/cretonne/meta/cdsl/operands.py b/lib/cretonne/meta/cdsl/operands.py new file mode 100644 index 000000000000..abf409a8c434 --- /dev/null +++ b/lib/cretonne/meta/cdsl/operands.py @@ -0,0 +1,222 @@ +"""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, Dict, TYPE_CHECKING # noqa + OperandSpec = Union['OperandKind', ValueType, TypeVar] + if TYPE_CHECKING: + from .ast import Enumerator, ConstantInt # noqa +except ImportError: + pass + + +# 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) + + +#: 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. + """, + rust_type='&[Value]') + + +# 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, + 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 __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 + """ + 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. +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) + + +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 + + # 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 + + def get_doc(self): + # type: () -> str + if self.__doc__: + return self.__doc__ + if self.kind is VALUE: + return self.typevar.__doc__ + return self.kind.__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 + + def is_varargs(self): + # type: () -> bool + """ + Is this a VARIABLE_ARGS operand? + """ + return self.kind is VARIABLE_ARGS + + 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 diff --git a/lib/cretonne/meta/cdsl/predicates.py b/lib/cretonne/meta/cdsl/predicates.py new file mode 100644 index 000000000000..f1f52322727c --- /dev/null +++ b/lib/cretonne/meta/cdsl/predicates.py @@ -0,0 +1,375 @@ +""" +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 evaluated 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. +""" +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, InstructionContext, FormatField # noqa + from .instructions import Instruction # noqa + from .settings import BoolSetting, SettingGroup # noqa + from .types import ValueType # noqa + from .typevar import TypeVar # noqa + PredContext = Union[SettingGroup, InstructionFormat, + 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 + + +def _is_parent(a, b): + # type: (PredContext, PredContext) -> bool + """ + 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): + # type: (PredContext, PredContext) -> PredContext + """ + If a is a parent of b or b is a parent of a, return the descendant of the + two. + + If neither 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): + # type: (Sequence[PredNode]) -> None + self.parts = parts + self.context = reduce( + _descendant, + (p.predicate_context() for p in parts)) + assert self.context, "Incompatible predicate parts" + self.predkey = None # type: PredKey + + def __str__(self): + # type: () -> str + return '{}({})'.format(type(self).__name__, + ', '.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") + + 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): + """ + Computed predicate that is true if all parts are true. + """ + + 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. + + 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 + + @staticmethod + def combine(*args): + # type: (*PredNode) -> PredNode + """ + 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): + """ + Computed predicate that is true if any parts are true. + """ + + 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) + return s + + +class Not(Predicate): + """ + Computed predicate that is true if its single part is false. + """ + + 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) + + +class FieldPredicate(object): + """ + An instruction predicate that performs a test on a single `FormatField`. + + :param field: The `FormatField` to be tested. + :param function: Boolean predicate function to call. + :param args: Additional arguments for the predicate function. + """ + + def __init__(self, field, function, args): + # type: (FormatField, str, Sequence[Any]) -> None + self.field = field + self.function = function + self.args = args + + def __str__(self): + # type: () -> str + args = (self.field.rust_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. + """ + 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) + + def rust_predicate(self, prec): + # type: (int) -> str + """ + Return a string of Rust code that evaluates this predicate. + """ + # 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 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 + 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): + # type: (FormatField, int, int) -> None + 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): + # type: (FormatField, int, int) -> None + 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 + + +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 + + 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_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) + + @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/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py new file mode 100644 index 000000000000..880f9096d416 --- /dev/null +++ b/lib/cretonne/meta/cdsl/registers.py @@ -0,0 +1,354 @@ +""" +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 +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 allocatable +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, 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 + + +# 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. + + 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='r', 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 + self.classes = list() # type: List[RegClass] + self.toprcs = list() # type: List[RegClass] + self.first_toprc_index = None # type: int + + 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 + + self.index = len(isa.regbanks) + isa.regbanks.append(self) + + def __repr__(self): + # type: () -> str + return ('RegBank({}, units={}, first_unit={})' + .format(self.name, self.units, self.first_unit)) + + def finish_regclasses(self): + # type: () -> None + """ + Compute subclasses and the top-level register class. + + 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 rc in self.classes: + # All register classes must be given a name. + assert rc.name, "Anonymous register class found" + + # 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): + rc1.toprc = rc1 + 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) + rc1.toprc = rc2.toprc + + if rc1.is_toprc(): + self.toprcs.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): + """ + 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 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`. + """ + + 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] + self.toprc = None # type: RegClass + + assert width > 0 + assert start >= 0 and start < bank.units + + if count is None: + count = bank.units // width + self.count = count + + bank.classes.append(self) + + def __str__(self): + # 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 + """ + 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): + # 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 + 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 __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] + """ + 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 + 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 + + 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): + # 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. + 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 + + +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 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 + 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/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py new file mode 100644 index 000000000000..cea8ec7eee8f --- /dev/null +++ b/lib/cretonne/meta/cdsl/settings.py @@ -0,0 +1,407 @@ +"""Classes for describing settings and groups of settings.""" +from __future__ import absolute_import +from collections import OrderedDict +from .predicates import Predicate + +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, PredKey # noqa +except ImportError: + pass + + +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): + # type: (str) -> None + self.name = None # type: str # Assigned later by `extract_names()`. + 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): + # type: () -> str + return '{}.{}'.format(self.group.name, self.name) + + def default_byte(self): + # 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): + """ + 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): + # 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. + """ + if self.default: + return 1 << self.bit_offset + 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_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) + + def rust_predicate(self, prec): + # type: (int) -> str + """ + 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): + # 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 + + 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): + """ + 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): + # 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 + + def byte_for_value(self, value): + # type: (Any) -> int + return self.values.index(value) + + +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): + # type: (str, SettingGroup) -> None + self.name = name + self.parent = parent + self.settings = [] # type: List[Setting] + # Named predicates computed from settings in this group or its + # parents. + 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. + # - Added anonymous predicates, see `number_predicate()`. + # - 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 + + self.open() + + def open(self): + # type: () -> None + """ + 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): + # type: (Dict[str, Any]) -> 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): + self.named_predicates[name] = obj + if isinstance(obj, Preset): + assert obj.name is None, obj.name + obj.name = name + + self.layout() + + @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 + + @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 + """ + 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): + # type: () -> None + """ + 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 name, pred in self.named_predicates.items(): + self.number_predicate(pred) + + def byte_size(self): + # type: () -> int + """ + 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 + + +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/cdsl/test_ast.py b/lib/cretonne/meta/cdsl/test_ast.py new file mode 100644 index 000000000000..750142af0a7a --- /dev/null +++ b/lib/cretonne/meta/cdsl/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.instructions 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/lib/cretonne/meta/cdsl/test_package.py b/lib/cretonne/meta/cdsl/test_package.py new file mode 100644 index 000000000000..b66d60d69464 --- /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/test_ti.py b/lib/cretonne/meta/cdsl/test_ti.py new file mode 100644 index 000000000000..bffbbd527df6 --- /dev/null +++ b/lib/cretonne/meta/cdsl/test_ti.py @@ -0,0 +1,605 @@ +from __future__ import absolute_import +from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint,\ + 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, TypesEqual, WiderOrEq +from unittest import TestCase +from functools import reduce + +try: + from .ti import TypeMap, ConstraintList, VarTyping, TypingOrError # noqa + from typing import List, Dict, Tuple, TYPE_CHECKING, cast # noqa +except ImportError: + TYPE_CHECKING = False + + +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 + + # Translate our constraints using m, and sort + me_equiv_constr = sorted([constr.translate(m) + for constr in me.constraints], key=repr) + # Sort other's constraints + other_equiv_constr = sorted([constr.translate(other) + for constr in other.constraints], key=repr) + return me_equiv_constr == other_equiv_constr + + +def check_typing(got_or_err, expected, symtab=None): + # 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 + 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 = [constr.translate(subst_m) for constr 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: (VarTyping, 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: (VarTyping, 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", "", 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` <: `1`: " + + "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, + }, [TypesEqual(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, + }, [])) + + 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 " + + "`i16` and `i32`") + + 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): + # 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.ti.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.ti.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) + + 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, + }, [WiderOrEq(i32t, itype)]), 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, + }, [WiderOrEq(i32t, itype)]), 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) + + # Note no constraints here since they are all trivial + 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/test_typevar.py b/lib/cretonne/meta/cdsl/test_typevar.py new file mode 100644 index 000000000000..dca5ba1b48c0 --- /dev/null +++ b/lib/cretonne/meta/cdsl/test_typevar.py @@ -0,0 +1,266 @@ +from __future__ import absolute_import +from unittest import TestCase +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): + 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.ints.remove(64) + # Can't rehash after modification. + 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)) + + def test_preimage(self): + t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32)) + + # LANEOF + self.assertEqual(TypeSet(lanes=True, ints=(8, 8), floats=(32, 32)), + t.preimage(TypeVar.LANEOF)) + # Inverse of empty set is still empty across LANEOF + self.assertEqual(TypeSet(), + TypeSet().preimage(TypeVar.LANEOF)) + + # ASBOOL + t = TypeSet(lanes=(1, 4), bools=(1, 64)) + self.assertEqual(t.preimage(TypeVar.ASBOOL), + TypeSet(lanes=(1, 4), ints=True, bools=True, + floats=True)) + + # Half/Double Vector + t = TypeSet(lanes=(1, 1), ints=(8, 8)) + t1 = TypeSet(lanes=(256, 256), ints=(8, 8)) + 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.preimage(TypeVar.DOUBLEVECTOR), + TypeSet(lanes=(1, 8), ints=(8, 16), floats=(32, 32))) + 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.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.preimage(TypeVar.DOUBLEWIDTH), + TypeSet(lanes=(1, 16), ints=(8, 8), floats=(32, 32))) + 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) + 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()), '`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()), '`double_width(x3)`') + with self.assertRaises(AssertionError): + x3.half_width() + + def test_singleton(self): + x = TypeVar.singleton(i32) + self.assertEqual(str(x), '`i32`') + 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(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) + + def test_stress_constrain_types(self): + # 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 = [()] + [(x,) for x in funcs] + 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.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.preimage(func), + reversed(i1), + intersect) + ts2_src = reduce(lambda ts, func: ts.preimage(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)) + + # 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() == 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/test_xform.py b/lib/cretonne/meta/cdsl/test_xform.py new file mode 100644 index 000000000000..952d8c90cb6f --- /dev/null +++ b/lib/cretonne/meta/cdsl/test_xform.py @@ -0,0 +1,94 @@ +from __future__ import absolute_import +from unittest import TestCase +from doctest import DocTestSuite +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 + + +def load_tests(loader, tests, ignore): + tests.addTests(DocTestSuite(xform)) + return tests + + +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): + 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) + + 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 diff --git a/lib/cretonne/meta/cdsl/ti.py b/lib/cretonne/meta/cdsl/ti.py new file mode 100644 index 000000000000..b2673366bec8 --- /dev/null +++ b/lib/cretonne/meta/cdsl/ti.py @@ -0,0 +1,886 @@ +""" +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, 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: + TYPE_CHECKING = False + pass + + +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. + """ + return [] == list(filter(lambda x: x.singleton_type() is None, + self.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 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 + """ + 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 TypesEqual(TypeConstraint): + """ + Constraint specifying that two derived type vars must have the same runtime + type. + """ + def __init__(self, tv1, tv2): + # type: (TypeVar, TypeVar) -> None + (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 + """ See TypeConstraint.is_trivial() """ + return self.tv1 == self.tv2 or self.is_concrete() + + def eval(self): + # type: () -> bool + """ See TypeConstraint.eval() """ + assert self.is_concrete() + return self.tv1.singleton_type() == self.tv2.singleton_type() + + +class InTypeset(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 _args(self): + # type: () -> Tuple[Any,...] + """ See TypeConstraint._args() """ + return (self.tv, self.ts) + + def is_trivial(self): + # type: () -> bool + """ See TypeConstraint.is_trivial() """ + 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 self.is_concrete() + + def eval(self): + # type: () -> bool + """ 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 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. + :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. + '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_SINGLETON = 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, 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: List[TypeConstraint] + 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)): + assert arg in self.vars + 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, constr): + # type: (TypeConstraint) -> None + """ + Add a new constraint + """ + 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 + 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). 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_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): + # 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 = TypeEnv.RANK_INPUT + elif v.is_intermediate(): + r = TypeEnv.RANK_INTERMEDIATE + elif v.is_output(): + r = TypeEnv.RANK_OUTPUT + else: + assert(v.is_temp()) + r = TypeEnv.RANK_TEMP + + self.ranks[v.get_typevar()] = r + + def free_typevars(self): + # 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()]) + 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) + + 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 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 = [] # type: List[TypeConstraint] + for constr in self.constraints: + constr = constr.translate(self) + + if constr.is_trivial() or constr in new_constraints: + continue + + # Sanity: translated constraints should refer to only real vars + 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) + + # 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 + + 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[VarTyping] + """ + 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 constr in self.constraints: + concrete_constr = constr.translate(m) + if not concrete_constr.eval(): + failed = concrete_constr + break + + if (failed is not None): + continue + + 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 + """ + 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() # type: Set[TypeVar] + edges = set() # type: Set[Tuple[TypeVar, TypeVar, str, str, Optional[str]]] # noqa + + 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 + add_nodes(tv1, tv2) + edges.add((tv1, tv2, "dotted", "forward", None)) + + for constr in self.constraints: + if isinstance(constr, TypesEqual): + add_nodes(constr.tv1, constr.tv2) + edges.add((constr.tv1, constr.tv2, "dashed", "none", "equal")) + elif isinstance(constr, WiderOrEq): + add_nodes(constr.tv1, constr.tv2) + edges.add((constr.tv1, constr.tv2, "dashed", "forward", ">=")) + elif isinstance(constr, SameWidth): + add_nodes(constr.tv1, constr.tv2) + edges.add((constr.tv1, constr.tv2, "dashed", "none", + "same_width")) + 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]) + + 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, 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 += "];\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), \ + "Unexpected error: {}".format(typing_or_err) + + 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: + - 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 + + 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. 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), + (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 + + 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(TypesEqual(tv1, tv2)) + 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 + """ + 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 dict m mapping each free typevar in the signature of definition + # to a fresh copy of itself. + 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 + 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 = \ + [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()) + + # 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) + 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) + + # Add any instruction specific constraints + for constr in inst.constraints: + typ.add_constraint(constr.translate(m)) + + 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/types.py b/lib/cretonne/meta/cdsl/types.py new file mode 100644 index 000000000000..8ae66ee3597c --- /dev/null +++ b/lib/cretonne/meta/cdsl/types.py @@ -0,0 +1,286 @@ +"""Cretonne ValueType hierarchy""" +from __future__ import absolute_import +import math + +try: + from typing import Dict, List, cast, TYPE_CHECKING # noqa +except ImportError: + TYPE_CHECKING = False + pass + + +# 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[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 + ValueType._registry[name] = self + + def __str__(self): + # type: () -> str + return self.name + + def rust_name(self): + # type: () -> str + return 'ir::types::' + self.name.upper() + + @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)) + + 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 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 + """ + 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): + """ + 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 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 + + def lane_count(self): + # type: () -> int + """Return the number of lanes.""" + return 1 + + +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)) + + 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.""" + + 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) + + @staticmethod + 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 + + 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.""" + + 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) + + @staticmethod + 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 + + 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.""" + + 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) + + @staticmethod + 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 + + def lane_bits(self): + # type: () -> int + """Return the number of bits in a lane.""" + return self.bits + + +class BVType(ValueType): + """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 + 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: + return typ + + def lane_bits(self): + # 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 new file mode 100644 index 000000000000..988d34aaf86a --- /dev/null +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -0,0 +1,853 @@ +""" +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 +import math +from . import types, is_power_of_two +from copy import deepcopy + +try: + from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa + 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: + pass + +MAX_LANES = 256 +MAX_BITS = 64 +MAX_BITVEC = MAX_BITS * MAX_LANES + + +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) + + +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 + """ + 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 + 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) + + +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)]) + + +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. + + 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. + + A typeset representing scalar integer types `i8` through `i32`: + + >>> TypeSet(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}, ints={8, 16, 32, 64}) + >>> TypeSet(floats=True) + TypeSet(lanes={1}, floats={32, 64}) + >>> TypeSet(bools=True) + 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, 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 + widths. + :param floats: `(min, max)` inclusive range of permitted scalar floating + 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, + 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 + """ + 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, 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.bitvecs)))) + + def __hash__(self): + # type: () -> int + h = hash(self.typeset_key()) + assert h == getattr(self, 'prev_hash', h), "TypeSet changed!" + self.prev_hash = h + return h + + def __eq__(self, other): + # type: (object) -> bool + if isinstance(other, TypeSet): + return self.typeset_key() == other.typeset_key() + 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)) + 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)) + 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), + ('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 + """ + Intersect self with other type set. + + >>> a = TypeSet(lanes=True, ints=(16, 32)) + >>> a + 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, 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, 2, 4, 8, 16, 32, 64, 128, 256}) + """ + self.lanes.intersection_update(other.lanes) + 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 + + 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) and \ + self.bitvecs.issubset(other.bitvecs) + + def lane_of(self): + # type: () -> TypeSet + """ + Return a TypeSet describing the image of self across lane_of + """ + new = self.copy() + new.lanes = set([1]) + new.bitvecs = set() + 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.bitvecs = set() + + 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) + 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]) + new.bitvecs = set([x//2 for x in self.bitvecs if x > 1]) + + 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]))) + new.bitvecs = set([x*2 for x in self.bitvecs if x < MAX_BITVEC]) + + return new + + def half_vector(self): + # type: () -> TypeSet + """ + 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 + + def double_vector(self): + # type: () -> TypeSet + """ + 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 + """ + Return the image of self across the derived function func + """ + if (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() + elif (func == TypeVar.TOBITVEC): + return self.to_bitvec() + else: + assert False, "Unknown derived function: " + func + + def preimage(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.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])) + 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]) + + 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() + 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 + + 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) + len(self.bitvecs)) + + 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) + for bits in self.bitvecs: + assert nlanes == 1 + yield types.BVType.with_bits(bits) + + def get_singleton(self): + # type: () -> types.ValueType + """ + Return the singleton type represented by self. Can only call on + typesets containing 1 type. + """ + types = list(self.concrete_types()) + 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): + """ + 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 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, 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, bitvecs=False, + base=None, derived_func=None): + # 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) + 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 + lanes = decode_interval(simd, (min_lanes, MAX_LANES), 1) + self.type_set = TypeSet( + lanes=lanes, + ints=ints, + floats=floats, + bools=bools, + bitvecs=bitvecs) + + @staticmethod + 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 + bools = None + bitvecs = 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) + elif isinstance(scalar, types.BVType): + bitvecs = (scalar.bits, scalar.bits) + + tv = TypeVar( + typ.name, typ.__doc__, + ints=ints, floats=floats, bools=bools, + bitvecs=bitvecs, simd=lanes) + return tv + + def __str__(self): + # 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 __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): + return False + 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 + + 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 + # `instructions.rs`. + LANEOF = 'lane_of' + ASBOOL = 'as_bool' + HALFWIDTH = 'half_width' + DOUBLEWIDTH = 'double_width' + HALFVECTOR = 'half_vector' + DOUBLEVECTOR = 'double_vector' + TOBITVEC = 'to_bitvec' + + @staticmethod + def is_bijection(func): + # type: (str) -> bool + return func in [ + TypeVar.HALFWIDTH, + TypeVar.DOUBLEWIDTH, + TypeVar.HALFVECTOR, + TypeVar.DOUBLEVECTOR] + + @staticmethod + def inverse_func(func): + # type: (str) -> str + return { + 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 + """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 + 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 lane_of(self): + # type: () -> TypeVar + """ + 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.derived(self, self.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`. + """ + return TypeVar.derived(self, self.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. + """ + return TypeVar.derived(self, self.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. + """ + 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. + """ + 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. + """ + 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 + """ + 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 + """ + Get the free type variable controlling this one. + """ + if self.is_derived: + return self.base.free_typevar() + elif self.singleton_type() is not None: + # A singleton type variable is not a proper free variable. + return None + 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_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.preimage(self.derived_func)) + + 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 self is other: + return + + self.constrain_types_by_ts(other.get_typeset()) + + 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: + 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 new file mode 100644 index 000000000000..991e429b186a --- /dev/null +++ b/lib/cretonne/meta/cdsl/xform.py @@ -0,0 +1,395 @@ +""" +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 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] +except ImportError: + pass + + +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. + + An RTL list can represent both a source pattern to be matched, or a + destination pattern to be inserted. + """ + + def __init__(self, *args): + # type: (*DefApply) -> None + self.rtl = tuple(map(canonicalize_defapply, args)) + + def copy(self, m): + # 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 in self that correspond to SSA values""" + 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 free_vars(self): + # type: () -> Set[Var] + """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)) + 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([])) + + 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 + + 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 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 + 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: + assert typing[v].singleton_type() is not None + v.set_typevar(typing[v]) + + +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 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. + + >>> from base.instructions 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, src), Var(a, src, dst)], + c << iconst(v) + a << iadd(x, c) + => + a << iadd_imm(x, v) + ) + """ + + 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. + self.inputs = list() # type: List[Var] + # Variables defined in either src or dst. + self.defs = list() # type: List[Var] + + # 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, 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: + if not i.is_input(): + 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:])) + + # Perform type inference and cleanup + raw_ti = get_type_env(ti_xform(self, TypeEnv())) + 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()) + 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 free_typevars.issubset(src_tvs)): + raise AssertionError( + "Some free vars don't appear in src - {}" + .format(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 + s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs) + s += '\n '.join(str(n) for n in self.src.rtl) + s += '\n=>\n ' + s += '\n '.join(str(n) for n in self.dst.rtl) + s += '\n)' + return s + + def _rewrite_rtl(self, rtl, symtab, context): + # type: (Rtl, Dict[str, Var], int) -> None + for line in rtl.rtl: + if isinstance(line, Def): + line.defs = tuple( + self._rewrite_defs(line, symtab, context)) + expr = line.expr + else: + expr = line + 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. + """ + + # 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, 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 line.defs: + name = str(sym) + if name in symtab: + var = symtab[name] + if var.get_def(context): + raise AssertionError("'{}' multiply defined".format(name)) + else: + var = Var(name) + symtab[name] = var + self.defs.append(var) + var.set_def(context, line) + 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. + + 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.append(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 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)) + else: + # First time use of variable. + var = Var(name) + symtab[name] = var + 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." + for d in self.src.rtl[0].defs: + if not d.is_output(): + 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): + """ + 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, 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 + 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 + """ + Add a legalization pattern to this group. + + :param src: Single `Def` or `Apply` to be legalized. + :param dst: `Rtl` list of replacement instructions. + """ + xform = XForm(Rtl(src), dst) + xform.verify_legalize() + self.xforms.append(xform) diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh new file mode 100755 index 000000000000..655092e6cb09 --- /dev/null +++ b/lib/cretonne/meta/check.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -e +cd $(dirname "$0") + +runif() { + if command -v "$1" > /dev/null; then + echo " === $1 ===" + "$@" + else + echo "$1 not found" + fi +} + +# Style linting. +runif flake8 . + +# Type checking. +runif mypy --py2 build.py + +# 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/lib/cretonne/meta/constant_hash.py b/lib/cretonne/meta/constant_hash.py new file mode 100644 index 000000000000..47f5eebe33f6 --- /dev/null +++ b/lib/cretonne/meta/constant_hash.py @@ -0,0 +1,63 @@ +""" +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. +""" +from __future__ import absolute_import +from cdsl import next_power_of_two + +try: + from typing import Any, List, Iterable, Callable # noqa +except ImportError: + pass + + +def simple_hash(s): + # type: (str) -> int + """ + 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 compute_quadratic(items, hash_function): + # 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 + 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 # type: List[Any] + + 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 diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py new file mode 100644 index 000000000000..fddadf64e513 --- /dev/null +++ b/lib/cretonne/meta/gen_binemit.py @@ -0,0 +1,157 @@ +""" +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, 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) + + # Regmove instructions get special treatment. + is_regmove = (recipe.format.name == 'RegMove') + + # 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 and not is_regmove: + 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) and not is_regmove: + v = 'in_reg{}'.format(i) + args += ', ' + v + fmt.line( + 'let {} = divert.reg(args[{}], &func.locations);' + .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)) + + # 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. + 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 + """ + 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, ' + '_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, ' + 'divert: &mut RegDiversions, sink: &mut CS) {', '}'): + 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('_ => {}') + # 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): + # 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/meta/gen_build_deps.py b/lib/cretonne/meta/gen_build_deps.py new file mode 100644 index 000000000000..5e1419284c57 --- /dev/null +++ b/lib/cretonne/meta/gen_build_deps.py @@ -0,0 +1,43 @@ +""" +Generate build dependencies for Cargo. + +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. + +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. +""" +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. + """ + for (dirpath, dirnames, filenames) in os.walk(top): + yield dirpath + for f in filenames: + if f.endswith('.py'): + yield join(dirpath, f) + + +def generate(): + # type: () -> None + 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/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py new file mode 100644 index 000000000000..0516b5c4c821 --- /dev/null +++ b/lib/cretonne/meta/gen_encoding.py @@ -0,0 +1,889 @@ +""" +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 `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 + +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. 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. +""" +from __future__ import absolute_import +import srcgen +from constant_hash import compute_quadratic +from unique_table import UniqueSeqTable +from collections import OrderedDict, defaultdict +import math +from itertools import groupby +from cdsl.registers import RegClass, Register, Stack +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 + if TYPE_CHECKING: + 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 + from cdsl.xform import XFormGroup # noqa +except ImportError: + pass + + +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`. + + 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() + + # 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 or TypePredicate instances. Here we just + # care about the field names. + fnames = set() # type: Set[str] + for p in leafs: + 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 ir::InstructionData::{} {{ {}, .. }} = *inst {{' + .format(iform.name, fields), '}'): + 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): + # 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, number in instps.items(): + name = 'inst_predicate_{}'.format(number) + with fmt.indented( + 'fn {}(dfg: &ir::DataFlowGraph, inst: &ir::InstructionData)' + '-> bool {{'.format(name), '}'): + emit_instp(instp, fmt, has_dfg=True) + + # Generate the static table. + with fmt.indented( + 'pub static INST_PREDICATES: [InstPredicate; {}] = [' + .format(len(instps)), '];'): + for instp, number in instps.items(): + fmt.format('inst_predicate_{},', number) + + +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. + + 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 isa.all_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: &ir::InstructionData) -> bool {{' + .format( + name, + 'isap' if isap else '_'), '}'): + if isap: + 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(isa.all_recipes)), '];'): + for rcp in isa.all_recipes: + p = rcp.recipe_pred() + if p is None: + fmt.line('None,') + else: + fmt.format('Some({}),', pname[p]) + + +# The u16 values in an encoding list entry are interpreted as follows: +# +# 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. + + +class Encoder: + """ + Encoder for the list format above. + + Two parameters are needed: + + :param NR: Number of recipes. + :param NI: Number of instruction predicates. + """ + + def __init__(self, isa): + # type: (TargetISA) -> None + self.isa = isa + self.NR = len(isa.all_recipes) + self.NI = len(isa.instp_number) + # 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) + 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.""" + number = self.isa.instp_number[pred] + self._pred(pred, skip, 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): + """ + 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): + # type: (Instruction, ValueType) -> None + self.inst = inst + self.ty = ty + # List of applicable Encoding instances. + # These will have different predicates. + self.encodings = [] # type: List[Encoding] + + def name(self): + # type: () -> str + name = self.inst.name + if self.ty: + name = '{}.{}'.format(name, self.ty.name) + if self.encodings: + name += ' ({})'.format(self.encodings[0].cpumode) + return name + + def encoder_tree(self): + # type: () -> EncNode + """ + 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) + + return EncPred(None, forest).optimize() + + 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. + + 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. + """ + # Use an encoder object to hold the parameters. + encoder = Encoder(isa) + tree = self.encoder_tree() + tree.encode(encoder, True) + + 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 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): + """ + 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, legalize): + # type: (ValueType, XFormGroup) -> None + self.ty = ty + self.legalize = legalize + # Maps inst -> EncList + 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 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()) + + def layout_hashtable(self, level2_hashtables, level2_doc): + # type: (List[EncList], DefaultDict[int, List[str]]) -> None + """ + Compute the hash table mapping opcode -> enclist. + + Append the hash table to `level2_hashtables` and record the offset. + """ + 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) + + level2_doc[self.hash_table_offset].append( + '{:06x}: {}, {} entries'.format( + self.hash_table_offset, + self.ty, + self.hash_table_len)) + level2_hashtables.extend(hash_table) + + +class Level1Table(object): + """ + Level 1 table mapping types to `Level2` objects. + """ + + 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: + 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 (l2 for l2 in self.tables.values() if not l2.is_empty()) + + +def make_tables(cpumode): + # type: (CPUMode) -> Level1Table + """ + Generate tables for `cpumode` as described above. + """ + 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 + + +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.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)), + '];'): + 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 encode_level2_hashtables(level1, level2_hashtables, level2_doc): + # 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. + """ + with fmt.indented( + 'pub static LEVEL2: [Level2Entry<{}>; {}] = [' + .format(offt, len(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( + 'Level2Entry ' + + '{{ opcode: Some(ir::Opcode::{}), offset: {:#08x} }},' + .format(entry.inst.camel_name, entry.offset)) + else: + fmt.line( + 'Level2Entry ' + + '{ opcode: None, offset: 0 },') + + +def emit_level1_hashtable(cpumode, level1, offt, fmt): + # type: (CPUMode, Level1Table, str, srcgen.Formatter) -> None # noqa + """ + Emit a level 1 hash table for `cpumode`. + """ + def hash_func(level2): + # type: (Level2Table) -> int + return level2.ty.number if level2.ty is not None else 0 + hash_table = compute_quadratic(level1.tables.values(), hash_func) + + with fmt.indented( + 'pub static LEVEL1_{}: [Level1Entry<{}>; {}] = [' + .format(cpumode.name.upper(), offt, len(hash_table)), '];'): + for level2 in hash_table: + # Empty hash table entry. Include the default legalization action. + if not level2: + fmt.format( + 'Level1Entry {{ ty: ir::types::VOID, log2len: !0, ' + 'offset: 0, legalize: {} }},', + level1.legalize_code) + continue + + if level2.ty is not None: + tyname = level2.ty.rust_name() + else: + tyname = 'ir::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): + # type: (int) -> str + """ + 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 emit_recipe_names(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Emit a table of encoding recipe names keyed by recipe number. + + This is used for pretty-printing encodings. + """ + with fmt.indented( + 'static RECIPE_NAMES: [&str; {}] = [' + .format(len(isa.all_recipes)), '];'): + for r in isa.all_recipes: + 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( + 'static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = [' + .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, 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( + 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. + """ + if len(seq) == 0: + fmt.line('{}: &[],'.format(field)) + return + with fmt.indented('{}: &['.format(field), '],'): + for n, cons in enumerate(seq): + with fmt.indented('OperandConstraint {', '},'): + if isinstance(cons, RegClass): + 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): + 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]) + 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)) + + +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 + + # Make the `RECIPE_PREDICATES` table. + emit_recipe_predicates(isa, fmt) + + # Make the `INST_PREDICATES` table. + emit_inst_predicates(isa.instp_number, fmt) + + # Level1 tables, one per CPU mode + level1_tables = dict() + + # Tables for enclists with comments. + seq_table = UniqueSeqTable() + doc_table = defaultdict(list) # type: DefaultDict[int, List[str]] + + # Single table containing all the level2 hash tables. + 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) + level1 = make_tables(cpumode) + level1_tables[cpumode] = level1 + 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. + 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_offt, level2_doc, fmt) + for cpumode in isa.cpumodes: + emit_level1_hashtable( + cpumode, level1_tables[cpumode], level1_offt, 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: isa::EncInfo = isa::EncInfo {', '};'): + fmt.line('constraints: &RECIPE_CONSTRAINTS,') + fmt.line('sizing: &RECIPE_SIZING,') + fmt.line('names: &RECIPE_NAMES,') + + +def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None + for isa in isas: + fmt = srcgen.Formatter() + gen_isa(isa, fmt) + fmt.update_file('encoding-{}.rs'.format(isa.name), out_dir) diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py new file mode 100644 index 000000000000..ade66e643c8d --- /dev/null +++ b/lib/cretonne/meta/gen_instr.py @@ -0,0 +1,675 @@ +""" +Generate sources with instruction info. +""" +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 +from cdsl.formats import InstructionFormat +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, 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 + + +def gen_formats(fmt): + # type: (srcgen.Formatter) -> None + """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 InstructionFormat.all_formats: + fmt.doc_comment(str(f)) + 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 InstructionFormat.all_formats: + fmt.line(('InstructionData::{} {{ .. }} => ' + + 'InstructionFormat::{},') + .format(f.name, f.name)) + fmt.line() + + +def gen_arguments_method(fmt, is_mut): + # type: (srcgen.Formatter, bool) -> None + 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}<\'a>(&\'a {m}self, pool: &\'a {m}ValueListPool) -> ' + '&{m}[Value] {{' + .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, as_slice)) + continue + + # Fixed args. + if f.num_value_operands == 0: + arg = '&{}[]'.format(mut) + capture = '' + elif f.num_value_operands == 1: + capture = 'ref {}arg, '.format(mut) + arg = '{}(arg)'.format(rslice) + else: + capture = 'ref {}args, '.format(mut) + arg = 'args' + fmt.line( + '{} {{ {} .. }} => {},' + .format(n, capture, arg)) + + +def gen_instruction_data_impl(fmt): + # type: (srcgen.Formatter) -> None + """ + 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 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` 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 {', '}'): + with fmt.indented('match *self {', '}'): + for f in InstructionFormat.all_formats: + fmt.line( + 'InstructionData::{} {{ opcode, .. }} => opcode,' + .format(f.name)) + + fmt.doc_comment('Get the controlling type variable operand.') + with fmt.indented( + '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.typevar_operand + fmt.line( + '{} {{ ref args, .. }} => ' + 'args.get({}, pool),'.format(n, i)) + elif f.num_value_operands == 1: + # We have a single value operand called '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 + fmt.line( + n + + ' {{ ref args, .. }} => Some(args[{}]),' + .format(i)) + + fmt.doc_comment( + """ + Get the value arguments to this instruction. + """) + gen_arguments_method(fmt, False) + fmt.doc_comment( + """ + Get mutable references to the value arguments to this + instruction. + """) + 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): + # type: (Sequence[TargetISA]) -> List[InstructionGroup] + seen = set() # type: Set[InstructionGroup] + groups = [] + for isa in isas: + for g in isa.instruction_groups: + if g not in seen: + groups.append(g) + seen.add(g) + return groups + + +def gen_opcodes(groups, fmt): + # type: (Sequence[InstructionGroup], srcgen.Formatter) -> Sequence[Instruction] # noqa + """ + Generate opcode enumerations. + + Return a list of all instructions. + """ + + 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, Hash)]') + 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 {', '}'): + for g in groups: + for i in g.instructions: + instrs.append(i) + i.number = len(instrs) + fmt.doc_comment('`{}`. ({})'.format(i, i.format.name)) + # 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[opnum])) + # Enum variant itself. + if is_first_opcode: + fmt.line(i.camel_name + ' = 1,') + is_first_opcode = False + else: + fmt.line(i.camel_name + ',') + fmt.line() + + with fmt.indented('impl Opcode {', '}'): + for attr in sorted(Instruction.ATTRIBS.keys()): + fmt.doc_comment(Instruction.ATTRIBS[attr]) + with fmt.indented('pub 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; {}] = [' + .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 {', '}'): + with fmt.indented('match opc {', '}'): + 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( + instrs, + lambda i: constant_hash.simple_hash(i.name)) + with fmt.indented( + 'const OPCODE_HASH_TABLE: [Option; {}] = [' + .format(len(hash_table)), '];'): + for i in hash_table: + if i is None: + fmt.line('None,') + else: + fmt.format('Some(Opcode::{}),', i.camel_name) + fmt.line() + return instrs + + +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. + + 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. + """ + assert op.is_value() + tv = op.typevar + + # A concrete value type. + 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 + return 'Free({})'.format(type_sets.add(tv.type_set)) + + if tv.is_derived: + assert tv.base is ctrl_typevar, "Not derived from ctrl_typevar" + return camel_case(tv.derived_func) + + assert tv is ctrl_typevar + 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. + """ + 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( + 'const TYPE_SETS : [ir::instructions::ValueTypeSet; {}] = [' + .format(len(type_sets.table)), '];'): + for ts in type_sets.table: + with fmt.indented('ir::instructions::ValueTypeSet {', '},'): + ts.emit_fields(fmt) + + +def gen_type_constraints(fmt, instrs): + # type: (srcgen.Formatter, Sequence[Instruction]) -> None + """ + 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) + + 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 opnum in i.value_opnums: + constraints.append( + 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 + # 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={}, ' + 'requires_typevar_operand={}, fixed_values={}' + .format(i.camel_name, fixed_results, use_typevar_operand, + requires_typevar_operand, fixed_values)) + 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" + flags = fixed_results + if use_typevar_operand: + 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)) + fmt.line('typeset_offset: {},'.format(ctrl_typeset)) + fmt.line('constraint_offset: {},'.format(offset)) + + gen_typesets_table(fmt, type_sets) + + 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 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. + + All instruction formats take an `opcode` argument and a `ctrl_typevar` + argument for deducing the result types. + """ + + # Construct method arguments. + args = ['self', 'opcode: Opcode', 'ctrl_typevar: Type'] + + # Normal operand arguments. Start with the immediate operands. + 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 + # 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)" + + fmt.doc_comment(str(iform)) + fmt.line('#[allow(non_snake_case)]') + with fmt.indented('fn {} {{'.format(proto), '}'): + # Generate the instruction data. + with fmt.indented( + 'let data = InstructionData::{} {{'.format(iform.name), '};'): + fmt.line('opcode,') + gen_member_inits(iform, fmt) + + fmt.line('self.build(data, ctrl_typevar)') + + +def gen_member_inits(iform, fmt): + # type: (InstructionFormat, srcgen.Formatter) -> None + """ + Emit member initializers for an `iform` instruction. + """ + + # Immediate operands. + # We have local variables with the same names as the members. + for f in iform.imm_fields: + fmt.line('{}: {},'.format(f.member, f.member)) + + # Value operands. + if iform.has_value_list: + fmt.line('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): + # type: (Instruction, srcgen.Formatter) -> None + """ + 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. + 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. + if inst.is_polymorphic and not inst.use_typevar_operand: + args.append('{}: Type'.format(inst.ctrl_typevar.name)) + + tmpl_types = list() # type: List[str] + into_args = list() # type: List[str] + for op in inst.ins: + 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) + 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) + + if len(tmpl_types) > 0: + tmpl = '<{}>'.format(', '.join(tmpl_types)) + else: + tmpl = '' + proto = '{}{}({}) -> {}'.format( + inst.snake_name(), tmpl, ', '.join(args), rtype) + + fmt.doc_comment('`{}`\n\n{}'.format(inst, inst.blurb())) + fmt.line('#[allow(non_snake_case)]') + with fmt.indented('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 or not inst.is_polymorphic: + # No controlling type variable needed. + args.append('types::VOID') + 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)) + # 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: + 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)) + + if len(inst.value_results) == 0: + fmt.line(fcall + '.0') + return + + fmt.line('let (inst, dfg) = {};'.format(fcall)) + + if len(inst.value_results) == 1: + fmt.line('dfg.first_result(inst)') + return + + 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): + # type: (Sequence[Instruction], srcgen.Formatter) -> None + """ + Generate a Builder trait with methods for all instructions. + """ + fmt.doc_comment(""" + Convenience methods for building instructions. + + 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 `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<'f>: InstBuilderBase<'f> {", '}'): + for inst in insts: + gen_inst_builder(inst, fmt) + for f in InstructionFormat.all_formats: + gen_format_constructor(f, fmt) + + +def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None + groups = collect_instr_groups(isas) + + # opcodes.rs + fmt = srcgen.Formatter() + gen_formats(fmt) + gen_instruction_data_impl(fmt) + 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/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py new file mode 100644 index 000000000000..8a76d15e982c --- /dev/null +++ b/lib/cretonne/meta/gen_legalizer.py @@ -0,0 +1,427 @@ +""" +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 +from collections import defaultdict +from base import instructions +from cdsl.ast import Var +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 + +try: + 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 + 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(InTypeset(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. + + The emitted code is a statement redefining the `predicate` variable like + this: + + let predicate = predicate && ... + """ + 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) + + 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, InTypeset)): + assert not check.tv.is_derived + 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)) + fmt.format( + 'let predicate = predicate && TYPE_SETS[{}].contains({});', + ts, tv) + elif (isinstance(check, TypesEqual)): + 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( + '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) + + +def unwrap_inst(iref, node, fmt): + # type: (str, Def, Formatter) -> bool + """ + 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`. + + 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 + replacement instruction to overwrite the original. + """ + fmt.comment('Unwrap {}'.format(node)) + expr = node.expr + iform = expr.inst.format + nvops = iform.num_value_operands + + # The tuple of locals we're extracting is `expr.args`. + with fmt.indented( + 'let ({}, predicate) = if let ir::InstructionData::{} {{' + .format(', '.join(map(str, expr.args)), iform.name), '};'): + # Fields are encoded directly. + 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: + 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);') + elif nvops == 1: + fmt.line('let args = [arg];') + # Generate the values for the tuple. + 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) + 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")') + + # Get the types of any variables where it is needed. + 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)) + + # 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( + 'Results handled by {}.' + .format(node.defs[0].dst_def)) + replace_inst = True + else: + # Boring case: Detach the result values, capture them in locals. + 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)) + for d in node.defs: + if d.has_free_typevar(): + fmt.line( + 'let typeof_{0} = dfg.value_type({0});' + .format(d)) + + return replace_inst + + +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 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 + replaced_inst = None # type: str + + 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, cfg, 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 == 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' + else: + # Insert a new instruction. + builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs)) + # 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))) + + # If we just replaced an instruction, we need to bump the cursor so + # following instructions are inserted *after* the replaced instruction. + if replaced_inst: + with fmt.indented( + 'if pos.current_inst() == Some({}) {{' + .format(replaced_inst), '}'): + fmt.line('pos.next_inst();') + + +def gen_xform(xform, fmt, type_sets): + # type: (XForm, Formatter, UniqueTable) -> None + """ + 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: + Cursor`. + `dfg: DataFlowGraph` is available and mutable. + """ + # Unwrap the source instruction, create local variables for the input + # variables. + replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt) + + # 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) + + # 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);') + + # 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): + # 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( + 'pub fn {}(dfg: &mut ir::DataFlowGraph, ' + 'cfg: &mut ::flowgraph::ControlFlowGraph, ' + 'pos: &mut ir::Cursor) -> ' + 'bool {{'.format(xgrp.name), '}'): + fmt.line('use ir::{InstBuilder, CursorBase};') + + # 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");') + + # 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 camel_name in sorted(xforms.keys()): + with fmt.indented( + 'ir::Opcode::{} => {{'.format(camel_name), '}'): + for xform in xforms[camel_name]: + gen_xform(xform, fmt, type_sets) + # We'll assume there are uncovered opcodes. + 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): + # 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() + type_sets = UniqueTable() + 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/meta/gen_registers.py b/lib/cretonne/meta/gen_registers.py new file mode 100644 index 000000000000..6d41a403303c --- /dev/null +++ b/lib/cretonne/meta/gen_registers.py @@ -0,0 +1,106 @@ +""" +Generate register bank descriptions for each ISA. +""" + +from __future__ import absolute_import +import srcgen + +try: + from typing import Sequence, List # noqa + from cdsl.isa import TargetISA # noqa + from cdsl.registers import RegBank, RegClass # 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 {', '},'): + 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_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 + """ + Emit a static data definition for a register class. + """ + with fmt.indented('RegClassData {', '},'): + fmt.format('name: "{}",', rc.name) + 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()) + fmt.format('mask: [{}],', mask) + + +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) + fmt.line('classes: &CLASSES,') + + # Register class descriptors. + with fmt.indented( + '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 isa.regclasses: + fmt.line('#[allow(dead_code)]') + fmt.line( + '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 + 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/gen_settings.py b/lib/cretonne/meta/gen_settings.py new file mode 100644 index 000000000000..223e33f47856 --- /dev/null +++ b/lib/cretonne/meta/gen_settings.py @@ -0,0 +1,311 @@ +""" +Generate sources with settings. +""" +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 base import settings + +try: + 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, Preset, 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. + """ + for setting in sgrp.settings: + if not isinstance(setting, EnumSetting): + continue + ty = camel_case(setting.name) + fmt.doc_comment('Values for {}.'.format(setting)) + fmt.line('#[derive(Debug, PartialEq, Eq)]') + 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): + # type: (Setting, SettingGroup, srcgen.Formatter) -> None + """ + 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.numbered_predicate({})' + .format(sgrp.predicate_number[setting])) + elif isinstance(setting, NumSetting): + 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") + + +def gen_pred_getter(name, pred, sgrp, fmt): + # type: (str, Predicate, SettingGroup, srcgen.Formatter) -> None + """ + 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(name) + with fmt.indented(proto + ' {', '}'): + fmt.line( + 'self.numbered_predicate({})' + .format(sgrp.predicate_number[pred])) + + +def gen_getters(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None + """ + Emit getter functions for all the settings in fmt. + """ + fmt.doc_comment("User-defined settings.") + with fmt.indented('impl Flags {', '}'): + fmt.doc_comment('Get a view of the boolean predicates.') + with fmt.indented( + '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 name, pred in sgrp.named_predicates.items(): + gen_pred_getter(name, pred, sgrp, fmt) + + +def gen_descriptors(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None + """ + Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables. + """ + + enums = UniqueSeqTable() + + with fmt.indented( + 'static DESCRIPTORS: [detail::Descriptor; {}] = [' + .format(len(sgrp.settings) + len(sgrp.presets)), + '];'): + for idx, setting in enumerate(sgrp.settings): + setting.descriptor_index = idx + 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::Detail::Bool {{ bit: {} }},' + .format(setting.bit_offset)) + elif isinstance(setting, NumSetting): + fmt.line('detail: detail::Detail::Num,') + elif isinstance(setting, EnumSetting): + offs = enums.add(setting.values) + fmt.line( + 'detail: detail::Detail::Enum ' + + '{{ last: {}, enumerators: {} }},' + .format(len(setting.values)-1, offs)) + 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)), + '];'): + for txt in enums.table: + fmt.line('"{}",'.format(txt)) + + def hash_setting(s): + # type: (Union[Setting, Preset]) -> int + return constant_hash.simple_hash(s.name) + + 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)), + '];'): + for h in hash_table: + if h is None: + fmt.line('0xffff,') + 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 + """ + Emit a Template constant. + """ + v = [0] * sgrp.settings_size + for setting in sgrp.settings: + v[setting.byte_offset] |= setting.default_byte() + + 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)) + fmt.line('presets: &PRESETS,') + + 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)') + + +def gen_display(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None + """ + Generate the Display impl for Flags. + """ + with fmt.indented('impl fmt::Display for Flags {', '}'): + with fmt.indented( + 'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {', + '}'): + fmt.line('writeln!(f, "[{}]")?;'.format(sgrp.name)) + with fmt.indented('for d in &DESCRIPTORS {', '}'): + fmt.line('write!(f, "{} = ", d.name)?;') + fmt.line( + 'TEMPLATE.format_toml_value(d.detail,' + + 'self.bytes[d.offset as usize], f)?;') + fmt.line('writeln!(f, "")?;') + fmt.line('Ok(())') + + +def gen_constructor(sgrp, parent, fmt): + # type: (SettingGroup, PredContext, srcgen.Formatter) -> None + """ + Generate a Flags constructor. + """ + + with fmt.indented('impl Flags {', '}'): + args = 'builder: &Builder' + 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)) + fmt.line('#[allow(unused_variables)]') + with fmt.indented( + 'pub fn new({}) -> Flags {{'.format(args), '}'): + 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.iter().enumerate() {', '}'): + fmt.line('bytes[i] = *b;') + + # Stop here without predicates. + if len(sgrp.predicate_number) == sgrp.boolean_settings: + fmt.line('Flags { bytes: bytes }') + return + + # Now compute the predicates. + fmt.line( + 'let mut {} = Flags {{ bytes: bytes }};' + .format(sgrp.name)) + + for pred, number in sgrp.predicate_number.items(): + # Don't compute our own settings. + if number < sgrp.boolean_settings: + continue + fmt.comment('Precompute #{}.'.format(number)) + with fmt.indented( + 'if {} {{'.format(pred.rust_predicate(0)), + '}'): + fmt.line( + '{}.bytes[{}] |= 1 << {};' + .format( + sgrp.name, + sgrp.boolean_offset + number // 8, + number % 8)) + + fmt.line(sgrp.name) + + +def gen_group(sgrp, fmt): + # type: (SettingGroup, srcgen.Formatter) -> None + """ + Generate a Flags struct representing `sgrp`. + """ + fmt.line('#[derive(Clone)]') + fmt.doc_comment('Flags group `{}`.'.format(sgrp.name)) + with fmt.indented('pub struct Flags {', '}'): + fmt.line('bytes: [u8; {}],'.format(sgrp.byte_size())) + + gen_constructor(sgrp, None, fmt) + gen_enum_types(sgrp, fmt) + gen_getters(sgrp, fmt) + gen_descriptors(sgrp, fmt) + gen_template(sgrp, fmt) + gen_display(sgrp, fmt) + + +def generate(isas, out_dir): + # type: (Sequence[TargetISA], str) -> None + # 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: + 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/gen_types.py b/lib/cretonne/meta/gen_types.py new file mode 100644 index 000000000000..fa033c5bdba2 --- /dev/null +++ b/lib/cretonne/meta/gen_types.py @@ -0,0 +1,61 @@ +""" +Generate sources with type info. + +This generates a `types.rs` file which is included in +`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. +""" +from __future__ import absolute_import +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. + """ + 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): + # type: (int, srcgen.Formatter) -> None + """ + 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): + # type: (srcgen.Formatter) -> None + 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): + # type: (str) -> None + fmt = srcgen.Formatter() + emit_types(fmt) + fmt.update_file('types.rs', out_dir) diff --git a/lib/cretonne/meta/isa/__init__.py b/lib/cretonne/meta/isa/__init__.py new file mode 100644 index 000000000000..bad91c5c90d7 --- /dev/null +++ b/lib/cretonne/meta/isa/__init__.py @@ -0,0 +1,24 @@ +""" +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 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] + """ + Get a list of all the supported target ISAs. Each target ISA is represented + as a :py:class:`cretonne.TargetISA` instance. + """ + 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 000000000000..9e0ae5a7e10a --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/__init__.py @@ -0,0 +1,14 @@ +""" +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 +from . import settings, registers # noqa + +# 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 000000000000..6bba598d2790 --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/defs.py @@ -0,0 +1,19 @@ +""" +ARM 32-bit definitions. + +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/arm32/registers.py b/lib/cretonne/meta/isa/arm32/registers.py new file mode 100644 index 000000000000..9522057e3424 --- /dev/null +++ b/lib/cretonne/meta/isa/arm32/registers.py @@ -0,0 +1,37 @@ +""" +ARM32 register banks. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank, RegClass +from .defs import ISA + + +# Define the larger float bank first to avoid the alignment gap. +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') + +# 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(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/arm32/settings.py b/lib/cretonne/meta/isa/arm32/settings.py new file mode 100644 index 000000000000..5cc948cf2d3f --- /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 new file mode 100644 index 000000000000..3dd69feb4b79 --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/__init__.py @@ -0,0 +1,13 @@ +""" +ARM 64-bit Architecture +----------------------- + +ARMv8 CPUs running the Aarch64 architecture. +""" + +from __future__ import absolute_import +from . import defs +from . import settings, registers # noqa + +# 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 000000000000..b1ed79b5d62d --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/defs.py @@ -0,0 +1,15 @@ +""" +ARM64 definitions. + +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/arm64/registers.py b/lib/cretonne/meta/isa/arm64/registers.py new file mode 100644 index 000000000000..b39bc917a159 --- /dev/null +++ b/lib/cretonne/meta/isa/arm64/registers.py @@ -0,0 +1,24 @@ +""" +Aarch64 register banks. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank, RegClass +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') + +GPR = RegClass(IntRegs) +FPR = RegClass(FloatRegs) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/meta/isa/arm64/settings.py b/lib/cretonne/meta/isa/arm64/settings.py new file mode 100644 index 000000000000..9a2fc13dc730 --- /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 new file mode 100644 index 000000000000..0828d790aaa6 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/__init__.py @@ -0,0 +1,23 @@ +""" +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 +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/defs.py b/lib/cretonne/meta/isa/intel/defs.py new file mode 100644 index 000000000000..ad13741ebcb8 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/defs.py @@ -0,0 +1,15 @@ +""" +Intel definitions. + +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, x86.GROUP]) + +# CPU modes for 32-bit and 64-bit operation. +I64 = CPUMode('I64', ISA) +I32 = CPUMode('I32', ISA) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py new file mode 100644 index 000000000000..39b681205827 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -0,0 +1,314 @@ +""" +Intel Encodings. +""" +from __future__ import absolute_import +from cdsl.predicates import IsUnsignedInt +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 +from . import instructions as x86 +from .legalize import intel_expand +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=intel_expand, + f32=expand, + f64=expand) + +I64.legalize_type( + default=narrow, + i32=intel_expand, + i64=intel_expand, + 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_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 + """ + 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)]: + enc_i32_i64(inst, 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) + +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 [ + (base.iadd_imm, 0), + (base.band_imm, 4), + (base.bor_imm, 1), + (base.bxor_imm, 6)]: + 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. + +# 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)) + +# 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. +for inst, rrr in [ + (base.rotl, 0), + (base.rotr, 1), + (base.ishl, 4), + (base.ushr, 5), + (base.sshr, 7)]: + 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) +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) + +# 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. +# +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) + +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)) + +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) + +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)) + +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)) + +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) + +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 +# +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)) + +enc_i32_i64(base.brz, r.tjccb, 0x74) +enc_i32_i64(base.brnz, r.tjccb, 0x75) + +# +# Trap as ud2 +# +I32.enc(base.trap, *r.noop(0x0f, 0x0b)) +I64.enc(base.trap, *r.noop(0x0f, 0x0b)) + +# +# Comparisons +# +enc_i32_i64(base.icmp, 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)) + +# Numerical conversions. + +# 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)) + + +# +# 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 +enc_i32_i64(base.fcvt_from_sint.f32, r.frurm, 0xf3, 0x0f, 0x2a) + +# cvtsi2sd +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 [ + (base.fadd, 0x58), + (base.fsub, 0x5c), + (base.fmul, 0x59), + (base.fdiv, 0x5e)]: + 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 [ + (base.band, 0x54), + (base.band_not, 0x55), + (base.bor, 0x56), + (base.bxor, 0x57)]: + enc_flt(inst.f32, r.frm, 0x0f, opc) + enc_flt(inst.f64, r.frm, 0x0f, opc) diff --git a/lib/cretonne/meta/isa/intel/instructions.py b/lib/cretonne/meta/isa/intel/instructions.py new file mode 100644 index 000000000000..7921b2f43158 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/instructions.py @@ -0,0 +1,49 @@ +""" +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.typevar import TypeVar +from cdsl.instructions import Instruction, InstructionGroup + + +GROUP = InstructionGroup("x86", "Intel-specific instruction set") + +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""" + 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) + +GROUP.close() diff --git a/lib/cretonne/meta/isa/intel/legalize.py b/lib/cretonne/meta/isa/intel/legalize.py new file mode 100644 index 000000000000..cc46846d81a3 --- /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/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py new file mode 100644 index 000000000000..3e038e120870 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -0,0 +1,557 @@ +""" +Intel Encoding recipes. +""" +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 Nullary, Call, IndirectCall, Store, Load +from base.formats import IntCompare +from base.formats import RegMove, Ternary, Jump, Branch +from .registers import GPR, ABCD, FPR + +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: + +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) + } + +# 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: +# +# 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 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 + name, mmpp = OPCODE_PREFIX[ops[:-1]] + op = ops[-1] + assert op <= 256 + 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. + + Intel encodings are somewhat orthogonal with the opcode representation on + one side and the ModR/M, SIB and immediate fields on the other side. + + 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. + + 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__( + 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 + emit=None # type: str + ): + # 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 + self.emit = emit + + # 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, + 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) + + +# 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 opcode, no ModR/M. +noop = TailRecipe( + 'noop', Nullary, size=0, ins=(), outs=(), + emit='PUT_OP(bits, BASE_REX, sink);') + +# XX /r +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 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 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( + '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); + ''') + +# 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( + '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. 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, RM form, FPR -> FPR. +furm = TailRecipe( + '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); + ''') + +# 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, + emit=''' + PUT_OP(bits, rex1(in_reg0), sink); + 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, + 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), + 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 /n id with 32-bit immediate sign-extended. UnaryImm version. +uid = TailRecipe( + '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); + let imm: i64 = imm.into(); + 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. +# + +# XX /r register-indirect store with no offset. +st = TailRecipe( + 'st', Store, size=1, ins=(GPR, GPR), outs=(), + 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), + 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), + 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), + 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=(), + 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=(), + 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 +# + +# XX /r load with no offset. +ld = TailRecipe( + 'ld', Load, size=1, ins=(GPR), outs=(GPR), + 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), + 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), + 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=(), + 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); + ''') + +# +# 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); + ''') + +# 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); + ''') diff --git a/lib/cretonne/meta/isa/intel/registers.py b/lib/cretonne/meta/isa/intel/registers.py new file mode 100644 index 000000000000..92e76191fac8 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/registers.py @@ -0,0 +1,45 @@ +""" +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, RegClass +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') + +GPR = RegClass(IntRegs) +ABCD = GPR[0:4] +FPR = RegClass(FloatRegs) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/meta/isa/intel/settings.py b/lib/cretonne/meta/isa/intel/settings.py new file mode 100644 index 000000000000..a16303a3b598 --- /dev/null +++ b/lib/cretonne/meta/isa/intel/settings.py @@ -0,0 +1,47 @@ +""" +Intel settings. +""" +from __future__ import absolute_import +from cdsl.settings import SettingGroup, BoolSetting, Preset +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) + +# 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/meta/isa/riscv/__init__.py b/lib/cretonne/meta/isa/riscv/__init__.py new file mode 100644 index 000000000000..cf61cbdcf576 --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/__init__.py @@ -0,0 +1,32 @@ +""" +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 __future__ import absolute_import +from . import defs +from . import encodings, settings, registers # noqa + +# Re-export the primary target ISA definition. +ISA = defs.ISA.finish() diff --git a/lib/cretonne/meta/isa/riscv/defs.py b/lib/cretonne/meta/isa/riscv/defs.py new file mode 100644 index 000000000000..485dbd7ef0b9 --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/defs.py @@ -0,0 +1,14 @@ +""" +RISC-V definitions. + +Commonly used definitions. +""" +from __future__ import absolute_import +from cdsl.isa import TargetISA, CPUMode +import base.instructions + +ISA = TargetISA('riscv', [base.instructions.GROUP]) + +# CPU modes for 32-bit and 64-bit operation. +RV32 = CPUMode('RV32', ISA) +RV64 = CPUMode('RV64', ISA) diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py new file mode 100644 index 000000000000..b9f1f8245d28 --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -0,0 +1,155 @@ +""" +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, 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, 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') +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 [ + (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)) + + # 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)) + +# 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)) + +# 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), + (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)) + +# 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)) + +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)) + +# 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) +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. + +# 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 [ + (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)) + +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 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()) +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)) +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)) + +# 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)) + +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 new file mode 100644 index 000000000000..afffb2c0aa82 --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -0,0 +1,219 @@ +""" +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 __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 Unary, UnaryImm, BranchIcmp, Branch, Jump +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 +# 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): + # 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 JALR(funct3=0): + # type: (int) -> int + assert funct3 <= 0b111 + return 0b11001 | (funct3 << 5) + + +def JAL(): + # type: () -> int + return 0b11011 + + +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) + + +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, 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, + 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, + 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), + 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), + 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), + 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=(), + 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=(), + 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, + 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, + 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), + 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. +SB = EncRecipe( + 'SB', BranchIcmp, size=4, + ins=(GPR, GPR), outs=(), + 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), + 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), + emit='unimplemented!();') + +# Fill of a GPR. +GPfi = EncRecipe( + 'GPfi', Unary, size=4, + ins=Stack(GPR), outs=GPR, + emit='unimplemented!();') diff --git a/lib/cretonne/meta/isa/riscv/registers.py b/lib/cretonne/meta/isa/riscv/registers.py new file mode 100644 index 000000000000..d9d43f0432e6 --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/registers.py @@ -0,0 +1,23 @@ +""" +RISC-V register banks. +""" +from __future__ import absolute_import +from cdsl.registers import RegBank, RegClass +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') + +GPR = RegClass(IntRegs) +FPR = RegClass(FloatRegs) + +RegClass.extract_names(globals()) diff --git a/lib/cretonne/meta/isa/riscv/settings.py b/lib/cretonne/meta/isa/riscv/settings.py new file mode 100644 index 000000000000..c8b88db55e89 --- /dev/null +++ b/lib/cretonne/meta/isa/riscv/settings.py @@ -0,0 +1,31 @@ +""" +RISC-V settings. +""" +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 + +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)") + +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) +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/lib/cretonne/meta/mypy.ini b/lib/cretonne/meta/mypy.ini new file mode 100644 index 000000000000..7046100b4ca3 --- /dev/null +++ b/lib/cretonne/meta/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +disallow_untyped_defs = True +warn_unused_ignores = True +warn_return_any = True diff --git a/lib/cretonne/meta/semantics/__init__.py b/lib/cretonne/meta/semantics/__init__.py new file mode 100644 index 000000000000..1c1fee9b9fc1 --- /dev/null +++ b/lib/cretonne/meta/semantics/__init__.py @@ -0,0 +1,49 @@ +"""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, Rtl # noqa + from cdsl.ti import VarTyping # noqa + from cdsl.instructions import Instruction, InstructionSemantics # noqa +except ImportError: + pass + + +def verify_semantics(inst, src, xforms): + # type: (Instruction, Rtl, InstructionSemantics) -> None + """ + 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. + """ + # 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 + src = src.copy({}) + typenv = get_type_env(ti_rtl(src, TypeEnv())) + typenv.normalize() + typenv = typenv.extract() + + for t in typenv.concrete_typings(): + 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 new file mode 100644 index 000000000000..fc0ca98cc4c6 --- /dev/null +++ b/lib/cretonne/meta/semantics/elaborate.py @@ -0,0 +1,140 @@ +""" +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.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 Def, VarMap # noqa + from cdsl.ti import VarTyping # noqa +except ImportError: + TYPE_CHECKING = False + + +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], {}) + + # 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) + + assert len(res) == 1, "Couldn't find semantic transform for {}".format(d) + return res[0] + + +def cleanup_semantics(r, outputs): + # type: (Rtl, Set[Var]) -> Rtl + """ + 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) <-- redundant, replace by bva.0 + ... + + 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 + 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): + 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[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: + 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) + + +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 + + res = Rtl(*r.rtl) + outputs = res.definitions() + + while not fp: + assert res.is_concrete() + new_defs = [] # type: List[Def] + fp = True + + for d in res.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) + + res.rtl = tuple(new_defs) + + return cleanup_semantics(res, outputs) diff --git a/lib/cretonne/meta/semantics/primitives.py b/lib/cretonne/meta/semantics/primitives.py new file mode 100644 index 000000000000..0a727c1cf951 --- /dev/null +++ b/lib/cretonne/meta/semantics/primitives.py @@ -0,0 +1,89 @@ +""" +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 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) + +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=(fromReal), outs=(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) + +# 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)) + +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 new file mode 100644 index 000000000000..c1b252683291 --- /dev/null +++ b/lib/cretonne/meta/semantics/smtlib.py @@ -0,0 +1,221 @@ +""" +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, 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, 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 + + +# 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[List[ExprRef], Z3VarMap] + """ + Encode a concrete primitive Rtl r sa z3 query. + Returns a tuple (query, var_m) where: + - 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 = [] # 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 + + 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]] = var_to_bv[d.defs[0]] + continue + + if inst == prim_from_bv: + assert isinstance(d.expr.args[0], Var) + m[d.defs[0]] = var_to_bv[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 == 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 = 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] + 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], 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) + arg_typ = arg.get_typevar().singleton_type() + width = arg_typ.width() + assert (width % 2 == 0) + + lo = d.defs[0] + hi = d.defs[1] + + 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) + lo = d.expr.args[0] + hi = d.expr.args[1] + df = d.defs[0] + + # Z3 Concat expects hi bits first, then lo bits + 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.append(exp) + + return (q, m) + + +def equivalent(r1, r2, inp_m, out_m): + # type: (Rtl, Rtl, VarMap, VarMap) -> List[ExprRef] + """ + 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. + """ + # 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()} + r1 = r1.copy(src_m) + r2 = r2.copy(dst_m) + + # 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 of r1 and r2 + args_eq_exp = [] # type: List[ExprRef] + + for v in r1.free_vars(): + 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 = [] # type: List[ExprRef] + for (v1, v2) in out_m.items(): + results_eq_exp.append(mk_eq(m1[v1], m2[v2])) + + # Put the whole query toghether + return q1 + q2 + args_eq_exp + [Not(And(*results_eq_exp))] + + +def xform_correct(x, typing): + # type: (XForm, VarTyping) -> bool + """ + 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) + + # 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) + 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] + + # Get the primitive semantic Rtls for src and dst + prim_src = elaborate(src) + prim_dst = elaborate(dst) + 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/semantics/test_elaborate.py b/lib/cretonne/meta/semantics/test_elaborate.py new file mode 100644 index 000000000000..cb798295b9ef --- /dev/null +++ b/lib/cretonne/meta/semantics/test_elaborate.py @@ -0,0 +1,387 @@ +from __future__ import absolute_import +from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint +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 elaborate +from .primitives import prim_to_bv, bvsplit, prim_from_bv, bvconcat, bvadd, \ + bvult +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 r1.is_concrete() + assert r2.is_concrete() + + 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') + + r = Rtl( + (lo, hi) << vsplit(x), + ) + 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) + 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): + r.cleanup_concrete_rtl() + + def test_cleanup_concrete_rtl_ireduce(self): + # type: () -> None + x = Var('x') + y = Var('y') + r = Rtl( + y << ireduce(x), + ) + 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) + + 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): + r.cleanup_concrete_rtl() + + 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") + + r = Rtl( + zeroes << iconst(imm0), + y << icmp(intcc.eq, x, zeroes), + v << vselect(y, z, w), + ) + 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) + 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') + + r = Rtl( + z << iadd(x, y), + w << bint(v), + u << iadd(z, w) + ) + 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) + 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), + ) + r.cleanup_concrete_rtl() + sem = elaborate(r) + bvx = Var('bvx') + bvlo = Var('bvlo') + bvhi = Var('bvhi') + x = Var('x') + lo = Var('lo') + hi = Var('hi') + + 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) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) + + 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), + ) + r.cleanup_concrete_rtl() + sem = elaborate(r) + bvx = Var('bvx') + bvlo = Var('bvlo') + bvhi = Var('bvhi') + x = Var('x') + lo = Var('lo') + hi = Var('hi') + + 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) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) + + def test_elaborate_iadd_simple(self): + # type: () -> None + i32.by(2) # Make sure i32x2 exists. + 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), + ) + 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) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) + + 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), + ) + r.cleanup_concrete_rtl() + sem = elaborate(r) + 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') + + exp = 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(bvx_5) + ) + exp.cleanup_concrete_rtl() + + assert concrete_rtls_eq(sem, exp) + + 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), + ) + r.cleanup_concrete_rtl() + + sem = elaborate(r) + 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') + + exp = 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(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) diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py new file mode 100644 index 000000000000..f0f7931ea1c1 --- /dev/null +++ b/lib/cretonne/meta/srcgen.py @@ -0,0 +1,179 @@ +""" +Source code generator. + +The `srcgen` module contains generic helper routines and classes for generating +source code. + +""" +from __future__ import absolute_import +import sys +import os + +try: + from typing import Any, List # noqa +except ImportError: + pass + + +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.format('Back {} again', 'home') + >>> f.writelines() + Hello line 1 + // Nested comment + Back home again + + """ + + shiftwidth = 4 + + def __init__(self): + # type: () -> None + self.indent = '' + 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)) + else: + self.lines.append('\n') + + def outdented_line(self, s): + # type: (str) -> None + """ + 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): + # 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: + self.writelines(f) + + 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): + # type: (object, object, object) -> None + self.fmt.indent_pop() + if self.after: + 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: + + >>> 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 Formatter._IndentedScope(self, after) + + def format(self, fmt, *args): + # 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.""" + self.line('// ' + s) + + def doc_comment(self, s): + # type: (str) -> None + """Add a (multi-line) documentation comment.""" + 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) diff --git a/lib/cretonne/meta/stubs/z3/__init__.pyi b/lib/cretonne/meta/stubs/z3/__init__.pyi new file mode 100644 index 000000000000..2fd6c8341fe8 --- /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 000000000000..36f1f8879239 --- /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 000000000000..fa8fc446d15a --- /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: + ... diff --git a/lib/cretonne/meta/test_constant_hash.py b/lib/cretonne/meta/test_constant_hash.py new file mode 100644 index 000000000000..e76f09aed9c8 --- /dev/null +++ b/lib/cretonne/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/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py new file mode 100644 index 000000000000..793555a42cd7 --- /dev/null +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -0,0 +1,196 @@ +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, 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 +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, + 'let predicate = predicate && TYPE_SETS[{}].contains(typeof_{});\n', + ts, v) + + +def equiv_check(tv1, tv2): + # type: (str, str) -> CheckProducer + return lambda typesets: format_check( + typesets, + 'let predicate = predicate && match ({}, {}) {{\n' + ' (Some(a), Some(b)) => a == b,\n' + ' _ => false,\n' + '}};\n', tv1, tv2) + + +def wider_check(tv1, tv2): + # type: (str, str) -> CheckProducer + return lambda typesets: format_check( + typesets, + 'let predicate = predicate && match ({}, {}) {{\n' + ' (Some(a), Some(b)) => a.wider_or_equal(b),\n' + ' _ => 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=True, floats=True, 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) + 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(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/meta/test_srcgen.py b/lib/cretonne/meta/test_srcgen.py new file mode 100644 index 000000000000..2fb5e0fb61fe --- /dev/null +++ b/lib/cretonne/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 diff --git a/lib/cretonne/meta/unique_table.py b/lib/cretonne/meta/unique_table.py new file mode 100644 index 000000000000..0e18ae3afd3f --- /dev/null +++ b/lib/cretonne/meta/unique_table.py @@ -0,0 +1,77 @@ +""" +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. +""" + +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() # type: List[Any] + # Map item -> index. + 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. + + 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): + # type: () -> None + self.table = list() # type: List[Any] + # Map seq -> index. + 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. + + 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/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs new file mode 100644 index 000000000000..32210fecfb9d --- /dev/null +++ b/lib/cretonne/src/abi.rs @@ -0,0 +1,213 @@ +//! 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, ArgumentExtension, Type}; +use std::cmp::Ordering; + +/// 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), + + /// 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. + 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 { + /// Split an integer types into low and high parts, using `isplit`. + IntSplit, + + /// Split a vector type into halves with identical lane types, using `vsplit`. + 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. +/// +/// 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::Convert(conv) => { + 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); + } + } + } + } +} + +/// 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/binemit/memorysink.rs b/lib/cretonne/src/binemit/memorysink.rs new file mode 100644 index 000000000000..f3fd5cebbaf5 --- /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 new file mode 100644 index 000000000000..a4a62f869fa1 --- /dev/null +++ b/lib/cretonne/src/binemit/mod.rs @@ -0,0 +1,78 @@ +//! Binary machine code emission. +//! +//! The `binemit` module contains code for translating Cretonne's intermediate representation into +//! 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}; +use regalloc::RegDiversions; + +/// 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); + +/// 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 { + /// Get the current position. + fn offset(&self) -> CodeOffset; + + /// 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 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); + + /// 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, 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 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, &mut divert, sink); + } + } +} diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs new file mode 100644 index 000000000000..e49b53462418 --- /dev/null +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -0,0 +1,141 @@ +//! 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 cursor::{Cursor, FuncCursor}; +use ir::{Function, InstructionData, Opcode}; +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) -> Result { + 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()); + + // 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 cur = FuncCursor::new(func); + while let Some(ebb) = cur.next_ebb() { + // Record the offset for `ebb` and make sure we iterate until offsets are stable. + if cur.func.offsets[ebb] != offset { + assert!(cur.func.offsets[ebb] < offset, + "Code shrinking during relaxation"); + cur.func.offsets[ebb] = offset; + go_again = true; + } + + while let Some(inst) = cur.next_inst() { + let enc = cur.func.encodings.get_or_default(inst); + 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) = cur.func.dfg[inst].branch_destination() { + let dest_offset = cur.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) == cur.func.layout.entry_block() { + offset += relax_branch(&mut cur, offset, dest_offset, &encinfo); + continue; + } + } + } + } + + offset += size; + } + } + } + + Ok(offset) +} + +/// 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 +/// left. +fn relax_branch(cur: &mut FuncCursor, + offset: CodeOffset, + dest_offset: CodeOffset, + encinfo: &EncInfo) + -> CodeOffset { + let inst = cur.current_inst().unwrap(); + dbg!("Relaxing [{}] {} for {:#x}-{:#x} range", + encinfo.display(cur.func.encodings[inst]), + cur.func.dfg.display_inst(inst, None), + offset, + dest_offset); + unimplemented!(); +} diff --git a/lib/cretonne/src/bitset.rs b/lib/cretonne/src/bitset.rs new file mode 100644 index 000000000000..cfca8371fb9a --- /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/btree.rs b/lib/cretonne/src/btree.rs new file mode 100644 index 000000000000..b6732788f7db --- /dev/null +++ b/lib/cretonne/src/btree.rs @@ -0,0 +1,111 @@ +//! 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. +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!() + } +} diff --git a/lib/cretonne/src/constant_hash.rs b/lib/cretonne/src/constant_hash.rs new file mode 100644 index 000000000000..a165654dce00 --- /dev/null +++ b/lib/cretonne/src/constant_hash.rs @@ -0,0 +1,78 @@ +//! Runtime support for precomputed constant hash tables. +//! +//! 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. +//! +//! 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 `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; + + let mut idx = hash; + let mut step = 0; + + loop { + idx &= mask; + + match table.key(idx) { + None => return Err(idx), + Some(k) if k == key => return Ok(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 `lib/cretonne/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/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs new file mode 100644 index 000000000000..9967f1e11ea4 --- /dev/null +++ b/lib/cretonne/src/context.rs @@ -0,0 +1,152 @@ +//! 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. +//! +//! 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 binemit::{CodeOffset, relax_branches, MemoryCodeSink, RelocSink}; +use dominator_tree::DominatorTree; +use flowgraph::ControlFlowGraph; +use ir::Function; +use loop_analysis::LoopAnalysis; +use isa::TargetIsa; +use legalize_function; +use regalloc; +use result::{CtonError, CtonResult}; +use verifier; +use simple_gvn::do_simple_gvn; +use licm::do_licm; + +/// 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, + + /// Dominator tree for `func`. + pub domtree: DominatorTree, + + /// Register allocation context. + pub regalloc: regalloc::Context, + + /// Loop analysis of `func`. + pub loop_analysis: LoopAnalysis, +} + +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(), + cfg: ControlFlowGraph::new(), + domtree: DominatorTree::new(), + regalloc: regalloc::Context::new(), + loop_analysis: LoopAnalysis::new(), + } + } + + /// 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.prologue_epilogue(isa)?; + 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. + /// + /// The `isa` argument is currently unused, but the verifier will soon be able to also + /// check ISA-dependent constraints. + pub fn verify(&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(Some(isa)).map_err(Into::into) + } else { + Ok(()) + } + } + + /// Run the legalizer for `isa` on the function. + pub fn legalize(&mut self, isa: &TargetIsa) -> CtonResult { + legalize_function(&mut self.func, &mut self.cfg, &self.domtree, isa); + self.verify_if(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); + } + + /// 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) + } + + /// 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 + .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)?; + self.verify_if(isa)?; + + Ok(code_size) + } +} diff --git a/lib/cretonne/src/cursor.rs b/lib/cretonne/src/cursor.rs new file mode 100644 index 000000000000..3719aa1d348d --- /dev/null +++ b/lib/cretonne/src/cursor.rs @@ -0,0 +1,166 @@ +//! 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; + +/// 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. +/// 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/dbg.rs b/lib/cretonne/src/dbg.rs new file mode 100644 index 000000000000..bf1b92ef75f8 --- /dev/null +++ b/lib/cretonne/src/dbg.rs @@ -0,0 +1,119 @@ +//! 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::fmt; +use std::fs::File; +use std::io::{self, Write}; +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()); +} + +/// Write a line with the given format arguments. +/// +/// This is for use by the `dbg!` macro. +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() -> io::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"); + io::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::writeln_with_format_args(format_args!($($arg)+)).ok(); + } + } +} + +/// 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/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs new file mode 100644 index 000000000000..d88adbd4a04f --- /dev/null +++ b/lib/cretonne/src/dominator_tree.rs @@ -0,0 +1,570 @@ +//! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. + +use entity_map::EntityMap; +use flowgraph::{ControlFlowGraph, BasicBlock}; +use ir::{Ebb, Inst, Function, Layout, ProgramOrder, ExpandedProgramPoint}; +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, + + // 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 { + 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. +impl DominatorTree { + /// Is `ebb` reachable from the entry block? + pub fn is_reachable(&self, ebb: Ebb) -> bool { + 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 + /// 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 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. + /// + /// 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`. + /// + /// 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: 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 } + } + + /// 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_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?"); + 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 { + /// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a + /// function. + pub fn new() -> DominatorTree { + DominatorTree { + nodes: EntityMap::new(), + postorder: Vec::new(), + stack: Vec::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 + } + + /// 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()); + + // 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; + + 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, + }; + 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 * 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 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. + // + // 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 + 3) * STRIDE, + } + } + + // 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; + while changed { + changed = false; + for &ebb in postorder.iter().rev() { + let idom = self.compute_idom(ebb, cfg, &func.layout).into(); + if self.nodes[ebb].idom != idom { + self.nodes[ebb].idom = idom; + changed = true; + } + } + } + } + + // 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, 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(|&(pred, _)| self.nodes[pred].rpo_number > 1); + + // 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); + } + + idom.1 + } +} + +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 cursor::{Cursor, FuncCursor}; + use flowgraph::ControlFlowGraph; + use ir::{Function, InstBuilder, types}; + use super::*; + use ir::types::*; + use verifier::verify_context; + + #[test] + fn empty() { + let func = Function::new(); + 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] + 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(); + + let mut cur = FuncCursor::new(&mut func); + + cur.insert_ebb(ebb3); + let jmp_ebb3_ebb1 = cur.ins().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(ebb2); + cur.ins().jump(ebb0, &[]); + + cur.insert_ebb(ebb0); + + let cfg = ControlFlowGraph::with_function(cur.func); + let dt = DominatorTree::with_function(cur.func, &cfg); + + 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, &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, &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, &cur.func.layout), + Ordering::Less); + + 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 mut cur = FuncCursor::new(&mut func); + + cur.insert_ebb(ebb0); + let jmp02 = cur.ins().jump(ebb2, &[]); + + cur.insert_ebb(ebb1); + let trap = cur.ins().trap(); + + cur.insert_ebb(ebb2); + let jmp21 = cur.ins().jump(ebb1, &[]); + + let cfg = ControlFlowGraph::with_function(cur.func); + let dt = DominatorTree::with_function(cur.func, &cfg); + + 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, &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, &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, &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, &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, &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, &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] + fn renumbering() { + let mut func = Function::new(); + let ebb0 = func.dfg.make_ebb(); + let ebb100 = func.dfg.make_ebb(); + + let mut cur = FuncCursor::new(&mut func); + + 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, &[]); + + dt.recompute_split_ebb(ebb0, ebb1, middle_jump_inst); + + 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 = 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 = 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(cur.func); + verify_context(cur.func, &cfg, &dt, None).unwrap(); + } +} diff --git a/lib/cretonne/src/entity_list.rs b/lib/cretonne/src/entity_list.rs new file mode 100644 index 000000000000..4b535e3f56d0 --- /dev/null +++ b/lib/cretonne/src/entity_list.rs @@ -0,0 +1,680 @@ +//! 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, 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. +//! - 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 entity_ref::EntityRef; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::mem; + +/// 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. +/// +/// 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, + unused: PhantomData, +} + +/// Create an empty list. +impl Default for EntityList { + fn default() -> Self { + EntityList { + index: 0, + unused: PhantomData, + } + } +} + +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, Debug)] +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 + /// 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); + (&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 { + /// 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 + // 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) + } + + /// 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; + 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 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; + 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; + } + + /// 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. + /// 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 => { + // 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; + 0 + } + 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); + len + } + } + } + + /// 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 + { + // 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. Potentially linear complexity. + 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); + } + + /// 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. + /// 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)] +mod tests { + use super::*; + use super::{sclass_size, sclass_for_length}; + use ir::Inst; + use entity_ref::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), &[]); + assert_eq!(list.first(pool), None); + } + + #[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); + + 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.first(pool), Some(i1)); + assert_eq!(list.get(0, pool), Some(i1)); + assert_eq!(list.get(1, pool), None); + + 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]); + 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); + + 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]); + 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)); + assert_eq!(list.get(3, pool), None); + + // This triggers a reallocation. + 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]); + 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)); + 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()); + } + + #[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]); + } +} diff --git a/lib/cretonne/src/entity_map.rs b/lib/cretonne/src/entity_map.rs new file mode 100644 index 000000000000..be73b790e1d3 --- /dev/null +++ b/lib/cretonne/src/entity_map.rs @@ -0,0 +1,263 @@ +//! Densely numbered entity references as mapping keys. +//! +//! 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 +//! 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 entity_ref::EntityRef; +use std::marker::PhantomData; +use std::ops::{Index, IndexMut}; + +/// A mapping `K -> V` for densely indexed entity references. +#[derive(Debug, Clone)] +pub struct EntityMap + where K: EntityRef +{ + elems: Vec, + unused: PhantomData, +} + +/// Shared `EntityMap` implementation for all value types. +impl EntityMap + where K: EntityRef +{ + /// Create a new empty map. + pub fn new() -> Self { + EntityMap { + elems: Vec::new(), + unused: PhantomData, + } + } + + /// Check if `k` is a valid key in the map. + pub fn is_valid(&self, k: K) -> bool { + 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() + } + + /// 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 { + pos: 0, + rev_pos: self.elems.len(), + unused: PhantomData, + } + } +} + +/// 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 +{ + /// 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 = self.next_key(); + 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 +/// 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 +{ + /// 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 + } + + /// 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 necessary. + /// + /// Return a mutable reference to the corresponding entry. + pub fn ensure(&mut self, k: K) -> &mut V { + if !self.is_valid(k) { + self.resize(k.index() + 1) + } + &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`. +/// 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; + + fn index(&self, k: K) -> &V { + &self.elems[k.index()] + } +} + +/// Mutable indexing into an `EntityMap`. +/// Use `ensure` instead if the key is not known to be valid. +impl IndexMut for EntityMap + where K: EntityRef +{ + fn index_mut(&mut self, k: K) -> &mut V { + &mut self.elems[k.index()] + } +} + +/// Iterate over all keys in order. +pub struct Keys + where K: EntityRef +{ + pos: usize, + rev_pos: usize, + unused: PhantomData, +} + +impl Iterator for Keys + where K: EntityRef +{ + type Item = K; + + fn next(&mut self) -> Option { + if self.pos < self.rev_pos { + let k = K::new(self.pos); + self.pos += 1; + Some(k) + } else { + None + } + } +} + +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::*; + + // `EntityRef` impl for testing. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct E(u32); + + impl EntityRef for E { + fn new(i: usize) -> Self { + E(i as u32) + } + fn index(self) -> usize { + self.0 as usize + } + } + + impl PrimaryEntityData for isize {} + + #[test] + fn basic() { + let r0 = E(0); + let r1 = E(1); + 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; + assert!(m.is_valid(r1)); + m[r1] = 5; + + 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); + 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/lib/cretonne/src/entity_ref.rs b/lib/cretonne/src/entity_ref.rs new file mode 100644 index 000000000000..9990b521f069 --- /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/flowgraph.rs b/lib/cretonne/src/flowgraph.rs new file mode 100644 index 000000000000..f7cb0c97294e --- /dev/null +++ b/lib/cretonne/src/flowgraph.rs @@ -0,0 +1,259 @@ +//! 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. 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: +//! ... ; 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)` +//! and `(Ebb0, jmp Ebb2)` respectively. + +use ir::{Function, Inst, Ebb}; +use ir::instructions::BranchInfo; +use entity_map::EntityMap; +use std::mem; + +/// 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, 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, +} + +/// 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. +#[derive(Debug)] +pub struct ControlFlowGraph { + entry_block: Option, + data: EntityMap, +} + +impl ControlFlowGraph { + /// Allocate a new blank control flow graph. + pub fn new() -> ControlFlowGraph { + ControlFlowGraph { + entry_block: None, + data: EntityMap::new(), + } + } + + /// 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 { + 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::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); + } + + /// Get the CFG predecessor basic blocks to `ebb`. + 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) -> &[Ebb] { + &self.data[ebb].successors + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::{Function, InstBuilder, Cursor, CursorBase, types}; + + #[test] + fn empty() { + let func = Function::new(); + ControlFlowGraph::with_function(&func); + } + + #[test] + fn no_predecessors() { + let mut func = Function::new(); + 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); + + let cfg = ControlFlowGraph::with_function(&func); + + let mut fun_ebbs = func.layout.ebbs(); + 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); + } + } + + #[test] + 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(); + + let br_ebb0_ebb2; + let br_ebb1_ebb1; + let jmp_ebb0_ebb1; + let jmp_ebb1_ebb2; + + { + let dfg = &mut func.dfg; + let cur = &mut Cursor::new(&mut func.layout); + + cur.insert_ebb(ebb0); + 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, &[]); + jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, &[]); + + cur.insert_ebb(ebb2); + } + + 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_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!(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.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); + } + } +} diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs new file mode 100644 index 000000000000..697acfebd573 --- /dev/null +++ b/lib/cretonne/src/ir/builder.rs @@ -0,0 +1,266 @@ +//! 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; +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}; +use ir::condcodes::{IntCC, FloatCC}; +use isa::RegUnit; + +/// 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<'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. + /// + /// 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`. +// +// 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<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {} + +/// Base trait for instruction inserters. +/// +/// 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; +} + +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(inserter: IIB) -> InsertBuilder<'f, IIB> { + InsertBuilder { + inserter, + unused: PhantomData, + } + } + + /// 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<'f, IIB, Array> + where Array: AsRef<[Option]> + { + InsertReuseBuilder { + inserter: self.inserter, + reuse, + unused: PhantomData, + } + } + + /// 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<'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<'f, IIB: InstInserterBase<'f>> InstBuilderBase<'f> for InsertBuilder<'f, IIB> { + fn data_flow_graph(&self) -> &DataFlowGraph { + self.inserter.data_flow_graph() + } + + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + self.inserter.data_flow_graph_mut() + } + + 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<'f, IIB, Array> + where IIB: InstInserterBase<'f>, + Array: AsRef<[Option]> +{ + inserter: IIB, + reuse: Array, + unused: PhantomData<&'f u32>, +} + +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.inserter.data_flow_graph() + } + + fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { + self.inserter.data_flow_graph_mut() + } + + 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)) + } +} + +/// Instruction builder that replaces an existing instruction. +/// +/// The inserted instruction will have the same `Inst` number as the old one. +/// +/// 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, +} + +impl<'f> ReplaceBuilder<'f> { + /// Create a `ReplaceBuilder` that will overwrite `inst`. + pub fn new(dfg: &'f mut DataFlowGraph, inst: Inst) -> ReplaceBuilder { + ReplaceBuilder { dfg, inst } + } +} + +impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { + 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, &'f mut DataFlowGraph) { + // Splat the new instruction on top of the old one. + self.dfg[self.inst] = data; + + 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); + } + + (self.inst, self.dfg) + } +} + +#[cfg(test)] +mod tests { + use ir::{Function, Cursor, CursorBase, InstBuilder, ValueDef}; + 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); + } + + #[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)); + } +} diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs new file mode 100644 index 000000000000..f63bd6093889 --- /dev/null +++ b/lib/cretonne/src/ir/condcodes.rs @@ -0,0 +1,349 @@ +//! 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, Hash)] +pub enum IntCC { + /// `==`. + Equal, + /// `!=`. + NotEqual, + /// Signed `<`. + SignedLessThan, + /// Signed `>=`. + SignedGreaterThanOrEqual, + /// Signed `>`. + SignedGreaterThan, + /// Signed `<=`. + SignedLessThanOrEqual, + /// Unsigned `<`. + UnsignedLessThan, + /// Unsigned `>=`. + UnsignedGreaterThanOrEqual, + /// Unsigned `>`. + UnsignedGreaterThan, + /// Unsigned `<=`. + 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, Hash)] +pub enum FloatCC { + /// EQ | LT | GT + Ordered, + /// UN + Unordered, + + /// EQ + Equal, + /// The C '!=' operator is the inverse of '==': `NotEqual`. + /// UN | LT | GT + NotEqual, + /// LT | GT + OrderedNotEqual, + /// UN | EQ + UnorderedOrEqual, + + /// LT + LessThan, + /// LT | EQ + LessThanOrEqual, + /// GT + GreaterThan, + /// GT | EQ + GreaterThanOrEqual, + + /// UN | LT + UnorderedOrLessThan, + /// UN | LT | EQ + UnorderedOrLessThanOrEqual, + /// UN | GT + UnorderedOrGreaterThan, + /// UN | GT | EQ + UnorderedOrGreaterThanOrEqual, +} + +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/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs new file mode 100644 index 000000000000..959b29c8e89e --- /dev/null +++ b/lib/cretonne/src/ir/dfg.rs @@ -0,0 +1,1051 @@ +//! 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}; +use ir::layout::{Cursor, LayoutCursorInserter}; +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::mem; +use std::ops::{Index, IndexMut}; +use std::u16; + +/// 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. +/// +/// 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 + /// with the EBB containing each instruction. + insts: EntityMap, + + /// 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, + + /// 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. + 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 ValueData {} +impl PrimaryEntityData for Signature {} +impl PrimaryEntityData for ExtFuncData {} + +impl DataFlowGraph { + /// Create a new empty `DataFlowGraph`. + pub fn new() -> DataFlowGraph { + DataFlowGraph { + insts: EntityMap::new(), + results: EntityMap::new(), + ebbs: EntityMap::new(), + value_lists: ValueListPool::new(), + values: EntityMap::new(), + signatures: EntityMap::new(), + ext_funcs: EntityMap::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() + } + + /// 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. + /// + /// This is intended for use with `EntityMap::with_capacity`. + 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) + } + + /// Get the total number of values. + pub fn num_values(&self) -> usize { + self.values.len() + } +} + +/// 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. +impl DataFlowGraph { + /// Allocate an extended value entry. + fn make_value(&mut self, data: ValueData) -> Value { + self.values.push(data) + } + + /// Check if a value reference is valid. + pub fn value_is_valid(&self, v: Value) -> bool { + self.values.is_valid(v) + } + + /// Get the type of a value. + pub fn value_type(&self, v: Value) -> Type { + match self.values[v] { + ValueData::Inst { ty, .. } | + ValueData::Arg { ty, .. } | + ValueData::Alias { ty, .. } => ty, + } + } + + /// 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 { + 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, None)); + 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)) + } + } + } + + /// 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. + pub fn resolve_aliases(&self, value: Value) -> Value { + resolve_aliases(&self.values, 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 { + let mut v = value; + + for _ in 0..self.insts.len() { + v = self.resolve_aliases(v); + v = match self.value_def(v) { + ValueDef::Res(inst, 0) => { + 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); + } + + /// 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` + /// 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); + 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, + "Aliasing {} to {} would change its type {} to {}", + dest, + src, + self.value_type(dest), + ty); + + 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. + pub fn make_value_alias(&mut self, src: Value) -> Value { + let ty = self.value_type(src); + + let data = ValueData::Alias { ty, original: src }; + self.make_value(data) + } +} + +/// 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), +} + +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 { + // Value is defined by an instruction. + Inst { ty: Type, num: u16, inst: Inst }, + + // Value is an EBB argument. + 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 + // placeholder when the original instruction or EBB has been rewritten or modified. + Alias { ty: Type, original: Value }, +} + +/// 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 { + 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 + /// `make_inst`. + /// + /// This is only really useful to the parser. + pub fn next_inst(&self) -> Inst { + self.insts.next_key() + } + + /// Returns an object that displays `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. + pub fn inst_args(&self, inst: Inst) -> &[Value] { + self.insts[inst].arguments(&self.value_lists) + } + + /// 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 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 + /// 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. + 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; + + self.results[inst].clear(&mut self.value_lists); + + // The fixed results will appear at the front of the list. + for res_idx in 0..fixed_results { + 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. + 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 { + let ty = self.signatures[sig].return_types[res_idx].value_type; + 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); + } + } + } + + 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<'fd, LayoutCursorInserter<'c, 'fc, 'fd>> { + InsertBuilder::new(LayoutCursorInserter::new(at, self)) + } + + /// 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 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() + } + + /// 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. + /// + /// 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); + self.values[res] = ValueData::Inst { + ty, + num: num as u16, + inst, + }; + } + + /// 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, None)); + 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(); + 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, + inst, + num: num as u16, + }) + } + + /// 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. + pub fn first_result(&self, inst: Inst) -> Value { + self.results[inst] + .first(&self.value_lists) + .expect("Instruction has no results") + } + + /// 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) + } + + /// 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(&self.value_lists) { + CallInfo::NotACall => None, + CallInfo::Direct(f, _) => Some(self.ext_funcs[f].signature), + 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) + }) + } + + /// 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. +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 { + self.ebbs[ebb].args.len(&self.value_lists) + } + + /// 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, + num: num as u16, + ebb, + }) + } + + /// 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 + } + + /// 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. + /// + /// 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, + num: num as u16, + 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 + /// 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 { + // Create new value identical to the old one except for the type. + let (ebb, num) = if let ValueData::Arg { num, ebb, .. } = self.values[old_arg] { + (ebb, num) + } else { + panic!("{} must be an EBB argument", old_arg); + }; + let new_arg = self.make_value(ValueData::Arg { + ty: new_type, + num, + ebb, + }); + + self.ebbs[ebb].args.as_mut_slice(&mut self.value_lists)[num as usize] = new_arg; + new_arg + } + + /// 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) -> ValueList { + self.ebbs[ebb].args.take() + } +} + +// 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(Clone)] +struct EbbData { + // List of arguments to this EBB. + args: ValueList, +} + +impl EbbData { + fn new() -> EbbData { + EbbData { args: ValueList::new() } + } +} + +/// Object that can display an instruction. +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 isa = self.1; + let inst = self.2; + + if let Some((first, rest)) = dfg.inst_results(inst).split_first() { + write!(f, "{}", first)?; + for v in rest { + write!(f, ", {}", v)?; + } + write!(f, " = ")?; + } + + + let typevar = dfg.ctrl_typevar(inst); + if typevar.is_void() { + write!(f, "{}", dfg[inst].opcode())?; + } else { + write!(f, "{}.{}", dfg[inst].opcode(), typevar)?; + } + write_operands(f, dfg, isa, inst) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ir::types; + use ir::{Function, Cursor, CursorBase, Opcode, InstructionData}; + + #[test] + fn make_inst() { + let mut dfg = DataFlowGraph::new(); + + 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"); + assert_eq!(dfg.display_inst(inst, None).to_string(), "v0 = iconst.i32"); + + // Immutable reference resolution. + { + let immdfg = &dfg; + let ins = &immdfg[inst]; + assert_eq!(ins.opcode(), Opcode::Iconst); + } + + // 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); + + // 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] + fn no_results() { + let mut dfg = DataFlowGraph::new(); + + let idata = InstructionData::Nullary { opcode: Opcode::Trap }; + let inst = dfg.make_inst(idata); + assert_eq!(dfg.display_inst(inst, None).to_string(), "trap"); + + // Result slice should be empty. + assert_eq!(dfg.inst_results(inst), &[]); + } + + #[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), &[]); + assert!(dfg.detach_ebb_args(ebb).is_empty()); + assert_eq!(dfg.num_ebb_args(ebb), 0); + assert_eq!(dfg.ebb_args(ebb), &[]); + + let arg1 = dfg.append_ebb_arg(ebb, types::F32); + 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(), "v1"); + assert_eq!(dfg.num_ebb_args(ebb), 2); + 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)); + assert_eq!(dfg.value_type(arg1), types::F32); + assert_eq!(dfg.value_type(arg2), types::I16); + + // Swap the two EBB arguments. + let vlist = dfg.detach_ebb_args(ebb); + assert_eq!(dfg.num_ebb_args(ebb), 0); + assert_eq!(dfg.ebb_args(ebb), &[]); + 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, arg1); + assert_eq!(dfg.ebb_args(ebb), &[arg2, arg3, arg1]); + } + + #[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), &[new1]); + + dfg.attach_ebb_arg(ebb, 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), &[new1, new2]); + + dfg.attach_ebb_arg(ebb, 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), &[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; + use ir::condcodes::IntCC; + + let mut func = Function::new(); + let dfg = &mut func.dfg; + let ebb0 = dfg.make_ebb(); + 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 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 dfg.value_def(s) { + ValueDef::Res(i, 0) => i, + _ => 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); + 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); + } +} diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs new file mode 100644 index 000000000000..98a29c7e6104 --- /dev/null +++ b/lib/cretonne/src/ir/entities.rs @@ -0,0 +1,183 @@ +//! 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 `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 std::fmt; +use std::u32; + +/// An opaque reference to an extended basic block in a function. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] +pub struct Ebb(u32); +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 SSA value. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] +pub struct Value(u32); +entity_impl!(Value, "v"); + +impl Value { + /// 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 with_number(n: u32) -> Option { + if n < u32::MAX / 2 { + Some(Value(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"); + +/// An opaque reference to a stack slot. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct StackSlot(u32); +entity_impl!(StackSlot, "ss"); + +/// An opaque reference to a jump table. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct JumpTable(u32); +entity_impl!(JumpTable, "jt"); + +/// A reference to an external function. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct FuncRef(u32); +entity_impl!(FuncRef, "fn"); + +/// A reference to a function signature. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct SigRef(u32); +entity_impl!(SigRef, "sig"); + +/// 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, + /// 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), +} + +impl fmt::Display for AnyEntity { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + 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), + } + } +} + +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) + } +} + +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::*; + use std::u32; + + #[test] + fn value_with_number() { + assert_eq!(Value::with_number(0).unwrap().to_string(), "v0"); + assert_eq!(Value::with_number(1).unwrap().to_string(), "v1"); + + assert_eq!(Value::with_number(u32::MAX / 2), None); + assert!(Value::with_number(u32::MAX / 2 - 1).is_some()); + } + + #[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::>()); + } +} diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs new file mode 100644 index 000000000000..16211a4d83a3 --- /dev/null +++ b/lib/cretonne/src/ir/extfunc.rs @@ -0,0 +1,389 @@ +//! 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 ir::{Type, FunctionName, SigRef, ArgumentLoc}; +use isa::{RegInfo, RegUnit}; +use std::cmp; +use std::fmt; +use std::str::FromStr; + +/// 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. +/// +/// 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, + + /// 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`. + /// + /// 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 { + /// Create a new blank signature. + pub fn new(call_conv: CallConv) -> Signature { + Signature { + argument_types: Vec::new(), + return_types: Vec::new(), + call_conv, + 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) if offset >= 0 => { + Some(offset as u32 + arg.value_type.bytes()) + } + _ => None, + }) + .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()) + } +} + +/// 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: &[ArgumentType], + regs: Option<&RegInfo>) + -> fmt::Result { + match args.split_first() { + None => {} + Some((first, rest)) => { + write!(f, "{}", first.display(regs))?; + for arg in rest { + write!(f, ", {}", arg.display(regs))?; + } + } + } + Ok(()) +} + +impl<'a> fmt::Display for DisplaySignature<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "(")?; + write_list(f, &self.0.argument_types, self.1)?; + write!(f, ")")?; + if !self.0.return_types.is_empty() { + write!(f, " -> ")?; + write_list(f, &self.0.return_types, self.1)?; + } + write!(f, " {}", self.0.call_conv) + } +} + +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 +/// how the argument is passed. +#[derive(Copy, Clone, Debug)] +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, + + /// ABI-specific location of this argument, or `Unassigned` for arguments that have not yet + /// been legalized. + pub location: ArgumentLoc, +} + +impl ArgumentType { + /// Create an argument type with default flags. + pub fn new(vt: Type) -> ArgumentType { + ArgumentType { + value_type: vt, + extension: ArgumentExtension::None, + purpose: ArgumentPurpose::Normal, + location: Default::default(), + } + } + + /// 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, + 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()) + } +} + +/// 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.0.purpose != ArgumentPurpose::Normal { + write!(f, " {}", self.0.purpose)?; + } + + 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) + } +} + +/// 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, +} + +/// 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: [&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. +#[derive(Clone, Debug)] +pub struct ExtFuncData { + /// Name of the external function. + pub name: FunctionName, + /// Call signature of function. + pub signature: SigRef, +} + +impl fmt::Display for ExtFuncData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.signature, self.name) + } +} + +/// 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::*; + 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.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] + 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(CallConv::SpiderWASM); + assert_eq!(sig.to_string(), "() spiderwasm"); + sig.argument_types.push(ArgumentType::new(I32)); + assert_eq!(sig.to_string(), "(i32) spiderwasm"); + sig.return_types.push(ArgumentType::new(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 spiderwasm"); + sig.return_types.push(ArgumentType::new(B8)); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8 spiderwasm"); + + // 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 spiderwasm"); + } +} diff --git a/lib/cretonne/src/ir/funcname.rs b/lib/cretonne/src/ir/funcname.rs new file mode 100644 index 000000000000..4ab76d2001ae --- /dev/null +++ b/lib/cretonne/src/ir/funcname.rs @@ -0,0 +1,124 @@ +//! 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 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(NameRepr); + +impl FunctionName { + /// 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)) + } + } +} + +/// 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) +} + +const NAME_LENGTH_THRESHOLD: usize = 22; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum NameRepr { + Short { + length: u8, + bytes: [u8; NAME_LENGTH_THRESHOLD], + }, + Long(Vec), +} + +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 let Some(name) = try_as_name(self.0.as_ref()) { + write!(f, "%{}", name) + } else { + f.write_char('#')?; + for byte in self.0.as_ref() { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::FunctionName; + + #[test] + 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/ir/function.rs b/lib/cretonne/src/ir/function.rs new file mode 100644 index 000000000000..c2b280dc6c88 --- /dev/null +++ b/lib/cretonne/src/ir/function.rs @@ -0,0 +1,100 @@ +//! Intermediate representation of a function. +//! +//! The `Function` struct defined in this module owns all of its extended basic blocks and +//! instructions. + +use entity_map::{EntityMap, PrimaryEntityData}; +use ir::{FunctionName, CallConv, Signature, JumpTableData, DataFlowGraph, Layout}; +use ir::{JumpTables, InstEncodings, ValueLocations, StackSlots, EbbOffsets}; +use isa::TargetIsa; +use std::fmt; +use write::write_function; + +/// 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, + + /// Signature of this function. + pub signature: Signature, + + /// Stack slots allocated in this function. + pub stack_slots: StackSlots, + + /// Jump tables used in this function. + pub jump_tables: JumpTables, + + /// 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, + + /// Encoding recipe and bits for the legal instructions. + /// Illegal instructions have the `Encoding::default()` value. + pub encodings: InstEncodings, + + /// Location assigned to every value. + 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: EbbOffsets, +} + +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, + signature: sig, + stack_slots: StackSlots::new(), + jump_tables: EntityMap::new(), + dfg: DataFlowGraph::new(), + layout: Layout::new(), + encodings: EntityMap::new(), + locations: EntityMap::new(), + offsets: EntityMap::new(), + } + } + + /// Create a new empty, anonymous function with a native calling convention. + pub fn new() -> Function { + Self::with_name_signature(FunctionName::default(), Signature::new(CallConv::Native)) + } + + /// 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> fmt::Display for DisplayFunction<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write_function(fmt, self.0, self.1) + } +} + +impl fmt::Display for Function { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write_function(fmt, self, None) + } +} + +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/immediates.rs b/lib/cretonne/src/ir/immediates.rs new file mode 100644 index 000000000000..c525305a196d --- /dev/null +++ b/lib/cretonne/src/ir/immediates.rs @@ -0,0 +1,932 @@ + +//! 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}; +use std::{i32, u32}; +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 +/// sign-extending to `i64`. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct Imm64(i64); + +impl Imm64 { + /// Create a new `Imm64` representing the signed number `x`. + pub fn new(x: i64) -> Imm64 { + Imm64(x) + } +} + +impl Into for Imm64 { + fn into(self) -> i64 { + self.0 + } +} + +impl From for Imm64 { + fn from(x: i64) -> Self { + Imm64(x) + } +} + +// 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; + if -10_000 < x && x < 10_000 { + // Use decimal for small numbers. + write!(f, "{}", x) + } else { + 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 { + parse_i64(s).map(Imm64::new) + } +} + +/// 8-bit unsigned integer immediate operand. +/// +/// 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, Hash)] +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 Into for Offset32 { + fn into(self) -> i64 { + self.0 as i64 + } +} + +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") + }) + } +} + +/// 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, Hash)] +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, represented as a u32 +/// containing the bitpattern. +/// +/// All bit patterns are allowed. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Ieee32(u32); + +/// An IEEE binary64 immediate floating point value, represented as a u64 +/// containing the bitpattern. +/// +/// All bit patterns are allowed. +#[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 +// 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 { + 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. + 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 { + 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 { + // 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") + } else { + // NaN. + let payload = t_bits & ((1 << (t - 1)) - 1); + if t_bits & (1 << (t - 1)) != 0 { + // Quiet NaN. + if payload != 0 { + write!(f, "NaN:0x{:x}", payload) + } else { + write!(f, "NaN") + } + } 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) + } +} + +// 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 if s.starts_with('+') { + (0, &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 { + /// 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`. + #[cfg(test)] + pub fn with_float(x: f32) -> Ieee32 { + Ieee32(unsafe { mem::transmute(x) }) + } +} + +impl Display for Ieee32 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let bits: u32 = self.0; + format_float(bits as u64, 8, 23, f) + } +} + +impl FromStr for Ieee32 { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match parse_float(s, 8, 23) { + Ok(b) => Ok(Ieee32(b as u32)), + Err(s) => Err(s), + } + } +} + +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`. + #[cfg(test)] + pub fn with_float(x: f64) -> Ieee64 { + Ieee64(unsafe { mem::transmute(x) }) + } +} + +impl Display for Ieee64 { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let bits: u64 = self.0; + format_float(bits, 11, 52, f) + } +} + +impl FromStr for Ieee64 { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match parse_float(s, 11, 52) { + Ok(b) => Ok(Ieee64(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 format_imm64() { + 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`. + fn parse_ok(text: &str, want: &str) + where ::Err: Display + { + match text.parse::() { + Err(s) => panic!("\"{}\".parse() error: {}", text, s), + Ok(x) => assert_eq!(x.to_string(), 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!(s.to_string(), 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"); + 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 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"); + 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 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"); + } + + #[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] + 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::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::with_float(f32::MIN_POSITIVE).to_string(), + "0x1.000000p-126"); + // Subnormals. + assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE / 2.0).to_string(), + "0x0.800000p-126"); + assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE * f32::EPSILON).to_string(), + "0x0.000002p-126"); + 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"); + // Signaling NaNs. + assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee32(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"); + 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::("-Inf", "-Inf"); + parse_ok::("NaN", "+NaN"); + 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!(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::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::with_float(f64::MIN_POSITIVE).to_string(), + "0x1.0000000000000p-1022"); + // Subnormals. + assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE / 2.0).to_string(), + "0x0.8000000000000p-1022"); + assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE * f64::EPSILON).to_string(), + "0x0.0000000000001p-1022"); + 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(), + "+NaN:0x4000000000001"); + // Signaling NaNs. + assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1"); + assert_eq!(Ieee64(0x7ff4000000000001).to_string(), + "+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"); + } +} diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs new file mode 100644 index 000000000000..900dd56e88d6 --- /dev/null +++ b/lib/cretonne/src/ir/instructions.rs @@ -0,0 +1,769 @@ +//! 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 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::*; +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 +/// 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: +// +// - 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]`. +// +// 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 { + 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) -> InstructionFormat { + 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] + } +} + +// 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; + + /// Parse an Opcode name from a string. + fn from_str(s: &str) -> Result { + use constant_hash::{Table, simple_hash, probe}; + + impl<'a> Table<&'a str> for [Option] { + fn len(&self) -> usize { + self.len() + } + + fn key(&self, idx: usize) -> Option<&'a str> { + self[idx].map(opcode_name) + } + } + + match probe::<&str, [Option]>(&OPCODE_HASH_TABLE, s, simple_hash(s)) { + Err(_) => Err("Unknown opcode"), + // We unwrap here because probe() should have ensured that the entry + // at this index is not None. + Ok(i) => Ok(OPCODE_HASH_TABLE[i].unwrap()), + } + } +} + +/// 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(Clone, Debug, Hash, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum InstructionData { + Nullary { opcode: Opcode }, + Unary { opcode: Opcode, arg: Value }, + 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, + arg: Value, + imm: Imm64, + }, + Ternary { opcode: Opcode, args: [Value; 3] }, + MultiAry { opcode: Opcode, args: ValueList }, + InsertLane { + opcode: Opcode, + lane: Uimm8, + args: [Value; 2], + }, + ExtractLane { + opcode: Opcode, + lane: Uimm8, + arg: Value, + }, + IntCompare { + opcode: Opcode, + cond: IntCC, + args: [Value; 2], + }, + IntCompareImm { + opcode: Opcode, + cond: IntCC, + arg: Value, + imm: Imm64, + }, + FloatCompare { + opcode: Opcode, + cond: FloatCC, + args: [Value; 2], + }, + Jump { + opcode: Opcode, + destination: Ebb, + args: ValueList, + }, + Branch { + opcode: Opcode, + destination: Ebb, + args: ValueList, + }, + BranchIcmp { + opcode: Opcode, + cond: IntCC, + destination: Ebb, + args: ValueList, + }, + BranchTable { + opcode: Opcode, + arg: Value, + table: JumpTable, + }, + Call { + opcode: Opcode, + func_ref: FuncRef, + args: ValueList, + }, + IndirectCall { + opcode: Opcode, + sig_ref: SigRef, + args: ValueList, + }, + StackLoad { + opcode: Opcode, + stack_slot: StackSlot, + offset: Offset32, + }, + StackStore { + opcode: Opcode, + arg: Value, + stack_slot: StackSlot, + offset: Offset32, + }, + HeapLoad { + opcode: Opcode, + arg: Value, + offset: Uoffset32, + }, + HeapStore { + opcode: Opcode, + args: [Value; 2], + offset: Uoffset32, + }, + Load { + opcode: Opcode, + flags: MemFlags, + arg: Value, + offset: Offset32, + }, + Store { + opcode: Opcode, + flags: MemFlags, + 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 +/// basic blocks. +#[derive(Clone, Debug)] +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() + } + + /// 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 + } +} + +// 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 { + for (i, val) in self.0.iter().enumerate() { + if i == 0 { + write!(fmt, "{}", val)?; + } else { + write!(fmt, ", {}", val)?; + } + } + Ok(()) + } +} + +impl Default for VariableArgs { + fn default() -> VariableArgs { + VariableArgs::new() + } +} + +/// Analyzing an instruction. +/// +/// Avoid large matches on instruction formats by using the methods defined 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, 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::BranchTable { table, .. } => BranchInfo::Table(table), + _ => BranchInfo::NotABranch, + } + } + + /// 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, + } + } + + /// 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. + 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)) + } + InstructionData::IndirectCall { sig_ref, ref args, .. } => { + CallInfo::Indirect(sig_ref, &args.as_slice(pool)[1..]) + } + _ => CallInfo::NotACall, + } + } +} + +/// 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), +} + +/// 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 +/// results are not determined by the format. Every `Opcode` has an associated +/// `OpcodeConstraints` object that provides the missing details. +#[derive(Clone, Copy)] +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 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. + /// + /// 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`. + 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. (`fixed_value_arguments()` of them). + 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.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 { + (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 { + let offset = self.typeset_offset 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.constraint_offset 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"); + 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 + /// instruction. + 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() + } +} + +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 { + /// 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 { + /// 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 { + let l2b = scalar.log2_lane_bits(); + if scalar.is_int() { + self.ints.contains(l2b) + } else if scalar.is_float() { + self.floats.contains(l2b) + } else if scalar.is_bool() { + self.bools.contains(l2b) + } else { + false + } + } + + /// Does `typ` belong to this set? + pub fn contains(&self, typ: Type) -> bool { + let l2l = typ.log2_lane_count(); + 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.ints.max().unwrap_or(0) > 5 { + types::I32 + } else if self.floats.max().unwrap_or(0) > 5 { + types::F32 + } else if self.bools.max().unwrap_or(0) > 5 { + types::B32 + } else { + types::B1 + }; + t.by(1 << self.lanes.min().unwrap()).unwrap() + } +} + +/// 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()`. + LaneOf, + + /// This operand is `ctrlType.as_bool()`. + AsBool, + + /// This operand is `ctrlType.half_width()`. + HalfWidth, + + /// This operand is `ctrlType.double_width()`. + DoubleWidth, + + /// This operand is `ctrlType.half_vector()`. + HalfVector, + + /// This operand is `ctrlType.double_vector()`. + DoubleVector, +} + +impl OperandConstraint { + /// Resolve this operand constraint into a concrete value type, given the value of the + /// controlling type variable. + pub fn resolve(&self, ctrl_type: Type) -> ResolvedConstraint { + use self::OperandConstraint::*; + use self::ResolvedConstraint::Bound; + match *self { + 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 { + use super::*; + + #[test] + fn opcodes() { + use std::mem; + + let x = Opcode::Iadd; + let mut y = Opcode::Isub; + + assert!(x != y); + y = Opcode::Iadd; + assert_eq!(x, y); + assert_eq!(x.format(), 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")); + + // 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] + 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); + } + + #[test] + 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); + 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); + 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); + + 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] + fn value_set() { + use ir::types::*; + + let vts = ValueTypeSet { + 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)); + 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 { + 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 { + 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 { + 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)); + assert_eq!(vts.example().to_string(), "b32x4"); + + let vts = ValueTypeSet { + // TypeSet(lanes=(1, 256), ints=(8, 64)) + 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/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs new file mode 100644 index 000000000000..aaaa694a50eb --- /dev/null +++ b/lib/cretonne/src/ir/jumptable.rs @@ -0,0 +1,157 @@ +//! 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 packed_option::PackedOption; +use ir::entities::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. +#[derive(Clone)] +pub struct JumpTableData { + // Table entries, using `None` as a placeholder for missing entries. + table: Vec>, + + // How many `None` 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) { + // Resize table to fit `idx`. + if idx >= self.table.len() { + self.holes += idx - self.table.len(); + 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.into(); + } + + /// 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].is_some() { + self.holes += 1; + self.table[idx] = None.into(); + } + } + + /// Get the entry for `idx`, or `None`. + pub fn get_entry(&self, idx: usize) -> Option { + self.table.get(idx).and_then(|e| e.expand()) + } + + /// 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(&self) -> Entries { + 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)) + } + + /// Access the whole table as a mutable slice. + 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>>>); + +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 let Some(ebb) = dest.expand() { + return Some((idx, ebb)); + } + } else { + return None; + } + } + } +} + +impl Display for JumpTableData { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + match self.table.first().and_then(|e| e.expand()) { + 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 => write!(fmt, ", 0")?, + Some(ebb) => write!(fmt, ", {}", ebb)?, + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::JumpTableData; + use ir::Ebb; + use entity_ref::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/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs new file mode 100644 index 000000000000..3f31d34f234d --- /dev/null +++ b/lib/cretonne/src/ir/layout.rs @@ -0,0 +1,1478 @@ +//! 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 std::cmp; +use std::iter::{Iterator, IntoIterator}; +use entity_map::EntityMap; +use packed_option::PackedOption; +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 +/// 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. +/// +#[derive(Clone)] +pub struct Layout { + // Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in + // both ends by `None`. + ebbs: EntityMap, + + // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, + // terminated in both ends by `None`. + insts: EntityMap, + + // 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 `None` when no EBBs have been laid out. + last_ebb: Option, +} + +impl Layout { + /// Create a new empty `Layout`. + pub fn new() -> Layout { + Layout { + ebbs: EntityMap::new(), + insts: EntityMap::new(), + first_ebb: None, + last_ebb: None, + } + } +} + +// 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: 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) + } + + 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. +impl Layout { + /// Get the sequence number of a program point that must correspond to an entity in the layout. + 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, + } + } + + /// 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) + } + + /// 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 + .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() { + self.insts[inst].seq + } 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. + 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.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.expand() { + self.insts[next_inst].seq + } 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. + 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.expand() { + 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.expand() { + 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.expand() { + 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.expand() { + 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 +/// 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 do 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 { + 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. + 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 = self.ebbs.ensure(ebb); + assert!(node.first_inst.is_none() && node.last_inst.is_none()); + node.prev = self.last_ebb.into(); + node.next = None.into(); + } + if let Some(last) = self.last_ebb { + self.ebbs[last].next = ebb.into(); + } else { + 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`. + 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; + { + let node = self.ebbs.ensure(ebb); + node.next = before.into(); + node.prev = after; + } + 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); + } + + /// 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.into(); + } + 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); + } + + /// Return an iterator over all EBBs in layout order. + pub fn ebbs<'f>(&'f self) -> Ebbs<'f> { + Ebbs { + layout: self, + 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 + } + + /// 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)] +struct EbbNode { + prev: PackedOption, + next: PackedOption, + first_inst: PackedOption, + last_inst: PackedOption, + seq: SequenceNumber, +} + +/// Iterate over EBBs in layout order. See `Layout::ebbs()`. +pub struct Ebbs<'f> { + layout: &'f Layout, + next: Option, +} + +impl<'f> Iterator for Ebbs<'f> { + type Item = Ebb; + + fn next(&mut self) -> Option { + match self.next { + Some(ebb) => { + self.next = self.layout.ebbs[ebb].next.expand(); + Some(ebb) + } + None => None, + } + } +} + +/// Use a layout reference in a for loop. +impl<'f> IntoIterator for &'f Layout { + type Item = Ebb; + type IntoIter = Ebbs<'f>; + + fn into_iter(self) -> Ebbs<'f> { + self.ebbs() + } +} + +/// 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) { + self.insts[inst].ebb.into() + } else { + None + } + } + + /// 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); + 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.into(); + inst_node.prev = ebb_node.last_inst; + assert!(inst_node.next.is_none()); + } + if ebb_node.first_inst.is_none() { + ebb_node.first_inst = inst.into(); + } else { + self.insts[ebb_node.last_inst.unwrap()].next = inst.into(); + } + ebb_node.last_inst = inst.into(); + } + 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() + } + + /// 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 after = self.insts[before].prev; + { + let inst_node = self.insts.ensure(inst); + inst_node.ebb = ebb.into(); + inst_node.next = before.into(); + inst_node.prev = after; + } + 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); + } + + /// 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 { + layout: self, + head: self.ebbs[ebb].first_inst.into(), + tail: self.ebbs[ebb].last_inst.into(), + } + } + + /// 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.into(); + node.next = next_ebb; + node.first_inst = before.into(); + node.last_inst = last_inst; + } + 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.unwrap()].prev = new_ebb.into(); + } + + // Disconnect the instruction links. + let prev_inst = self.insts[before].prev; + self.insts[before].prev = None.into(); + self.ebbs[old_ebb].last_inst = prev_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 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(); + opt_i = self.insts[i].next.into(); + } + + self.assign_ebb_seq(new_ebb); + } +} + +#[derive(Clone, Debug, Default)] +struct InstNode { + // The Ebb containing this instruction, or `None` if the instruction is not yet inserted. + ebb: PackedOption, + prev: PackedOption, + next: PackedOption, + seq: SequenceNumber, +} + +/// Iterate over instructions in an EBB in layout order. See `Layout::ebb_insts()`. +pub struct Insts<'f> { + layout: &'f Layout, + head: Option, + tail: Option, +} + +impl<'f> Iterator for Insts<'f> { + type Item = Inst; + + fn next(&mut self) -> Option { + let rval = self.head; + if let Some(inst) = rval { + if self.head == self.tail { + self.head = None; + self.tail = None; + } else { + self.head = self.layout.insts[inst].next.into(); + } + } + 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 = self.layout.insts[inst].prev.into(); + } + } + rval + } +} + + +/// 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 +/// reference to the Layout. +/// +/// 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> { + /// Borrowed function layout. Public so it can be re-borrowed from this cursor. + pub layout: &'f 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()` 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. + After(Ebb), +} + +/// All cursor types implement the `CursorBase` which provides common navigation operations. +pub trait CursorBase { + /// Get the current cursor position. + fn position(&self) -> CursorPosition; + + /// Set the current position. + fn set_position(&mut self, pos: CursorPosition); + + /// 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; + + /// 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 + { + self.goto_first_inst(ebb); + self + } + + /// Get the EBB corresponding to the current position. + fn current_ebb(&self) -> Option { + use self::CursorPosition::*; + match self.position() { + Nowhere => None, + At(inst) => self.layout().inst_ebb(inst), + Before(ebb) | After(ebb) => Some(ebb), + } + } + + /// Get the instruction corresponding to the current position, if any. + fn current_inst(&self) -> Option { + use self::CursorPosition::*; + match self.position() { + At(inst) => Some(inst), + _ => None, + } + } + + /// Go to a specific instruction which must be inserted in the layout. + /// New instructions will be inserted before `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 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`. + 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`. + 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. + /// + /// - 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, CursorBase}; + /// fn edit_func(func: &mut Function) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// while let Some(ebb) = cursor.next_ebb() { + /// // Edit ebb. + /// } + /// } + /// ``` + fn next_ebb(&mut self) -> Option { + let next = if let Some(ebb) = self.current_ebb() { + self.layout().ebbs[ebb].next.expand() + } else { + self.layout().first_ebb + }; + self.set_position(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, CursorBase}; + /// fn edit_func(func: &mut Function) { + /// let mut cursor = Cursor::new(&mut func.layout); + /// while let Some(ebb) = cursor.prev_ebb() { + /// // Edit ebb. + /// } + /// } + /// ``` + fn prev_ebb(&mut self) -> Option { + let prev = if let Some(ebb) = self.current_ebb() { + self.layout().ebbs[ebb].prev.expand() + } else { + self.layout().last_ebb + }; + self.set_position(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, CursorBase}; + /// 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, CursorBase}; + /// 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... + /// } + /// } + /// } + /// ``` + fn next_inst(&mut self) -> Option { + use self::CursorPosition::*; + match self.position() { + Nowhere | After(..) => None, + At(inst) => { + if let Some(next) = self.layout().insts[inst].next.expand() { + self.set_position(At(next)); + Some(next) + } else { + 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.set_position(At(next)); + Some(next) + } else { + self.set_position(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, CursorBase}; + /// 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... + /// } + /// } + /// ``` + fn prev_inst(&mut self) -> Option { + use self::CursorPosition::*; + match self.position() { + Nowhere | Before(..) => None, + At(inst) => { + if let Some(prev) = self.layout().insts[inst].prev.expand() { + self.set_position(At(prev)); + Some(prev) + } else { + 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.set_position(At(prev)); + Some(prev) + } else { + self.set_position(Before(ebb)); + None + } + } + } + } + + /// 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 repeated calls to `insert_inst()` causes + /// instructions to appear in insertion order in the EBB. + fn insert_inst(&mut self, inst: Inst) { + use self::CursorPosition::*; + match self.position() { + Nowhere | Before(..) => panic!("Invalid insert_inst position"), + At(cur) => self.layout_mut().insert_inst(inst, cur), + After(ebb) => self.layout_mut().append_inst(inst, ebb), + } + } + + /// Remove the instruction under the cursor. + /// + /// The cursor is left pointing at the position following the current instruction. + /// + /// Return the instruction that was removed. + fn remove_inst(&mut self) -> Inst { + let inst = self.current_inst().expect("No instruction to remove"); + self.next_inst(); + self.layout_mut().remove_inst(inst); + 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. + fn remove_inst_and_step_back(&mut self) -> Inst { + let inst = self.current_inst().expect("No instruction to remove"); + self.prev_inst(); + self.layout_mut().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 + /// 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. + fn insert_ebb(&mut self, new_ebb: Ebb) { + use self::CursorPosition::*; + match self.position() { + At(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_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.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, + } + } +} + +/// 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 { + use super::{Layout, Cursor, CursorBase, CursorPosition}; + use entity_ref::EntityRef; + 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); + } + 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(); + 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)); + } + verify(&mut layout, &[]); + + 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]); + + { + let imm = &layout; + let mut v = Vec::new(); + for e in imm { + v.push(e); + } + 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] + 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)); + 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)); + 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)); + 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] + 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]); + + // 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])]); + + // 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 remove_inst. + cur.goto_inst(i2); + assert_eq!(cur.remove_inst(), i2); + verify(cur.layout, &[(e1, &[i1, i0])]); + assert_eq!(cur.layout.inst_ebb(i2), None); + 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)); + cur.layout.remove_inst(i1); + verify(cur.layout, &[(e1, &[])]); + assert_eq!(cur.layout.inst_ebb(i1), None); + } + + #[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); + verify(&mut layout, &[(e1, &[i2, i0, i1])]); + } + + #[test] + fn multiple_ebbs() { + let mut layout = Layout::new(); + + 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); + 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]); + } + + #[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); + } + + // 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); + + 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/memflags.rs b/lib/cretonne/src/ir/memflags.rs new file mode 100644 index 000000000000..8d0694e1a0b3 --- /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: [&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, Hash, PartialEq, Eq)] +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 new file mode 100644 index 000000000000..443d70bd1c09 --- /dev/null +++ b/lib/cretonne/src/ir/mod.rs @@ -0,0 +1,50 @@ +//! Representation of Cretonne IL functions. + +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; +pub mod function; +mod builder; +mod extfunc; +mod funcname; +mod memflags; +mod progpoint; +mod valueloc; + +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::funcname::FunctionName; +pub use ir::function::Function; +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::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; +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/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs new file mode 100644 index 000000000000..7adeb43c5c6c --- /dev/null +++ b/lib/cretonne/src/ir/progpoint.rs @@ -0,0 +1,152 @@ +//! Program points. + +use entity_ref::EntityRef; +use ir::{Ebb, Inst, ValueDef}; +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) + } +} + +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)] +pub enum ExpandedProgramPoint { + /// An instruction in the function. + Inst(Inst), + /// An EBB header. + Ebb(Ebb), +} + +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(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 { + ExpandedProgramPoint::Inst(Inst::new((pp.0 / 2) as usize)) + } else { + ExpandedProgramPoint::Ebb(Ebb::new((pp.0 / 2) as usize)) + } + } +} + +impl fmt::Display for ExpandedProgramPoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + 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) + } +} + +/// 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`. + /// + /// 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; + + /// 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)] +mod tests { + use super::*; + use entity_ref::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"); + } +} diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs new file mode 100644 index 000000000000..6f3de96cd279 --- /dev/null +++ b/lib/cretonne/src/ir/stackslot.rs @@ -0,0 +1,311 @@ +//! Stack slots. +//! +//! 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 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. +pub type StackSize = u32; + +/// A stack offset. +/// +/// The location of a stack offset relative to a stack pointer or frame pointer. +pub type StackOffset = i32; + +/// 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: StackSize, + + /// 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. + /// + /// For `OutgoingArg` stack slots, the offset is relative to the current function's stack + /// pointer immediately before the call. + pub offset: StackOffset, +} + +impl StackSlotData { + /// Create a stack slot with the specified byte size. + 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 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.kind, self.size)?; + if self.offset != 0 { + write!(f, ", offset {}", self.offset)?; + } + Ok(()) + } +} + +impl PrimaryEntityData for StackSlotData {} + +/// Stack frame manager. +/// +/// 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, + + /// 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. +impl StackSlots { + /// Create an empty stack slot manager. + pub fn new() -> StackSlots { + StackSlots { + slots: EntityMap::new(), + outgoing: Vec::new(), + frame_size: None, + } + } + + /// Clear out everything. + pub fn clear(&mut self) { + self.slots.clear(); + self.outgoing.clear(); + self.frame_size = None; + } + + /// 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) + } + + /// 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() + } + + /// 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() + } +} + +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`. + pub fn make_spill_slot(&mut self, ty: Type) -> StackSlot { + 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: StackOffset) -> StackSlot { + let mut data = StackSlotData::new(StackSlotKind::IncomingArg, ty.bytes()); + assert!(offset <= StackOffset::max_value() - data.size as StackOffset); + 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: StackOffset) -> 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 <= StackOffset::max_value() - size as StackOffset); + data.offset = offset; + let ss = self.slots.push(data); + self.outgoing.insert(inspos, ss); + ss + } +} + +#[cfg(test)] +mod tests { + use ir::Function; + use ir::types; + use super::*; + + #[test] + fn stack_slot() { + let mut func = Function::new(); + + 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"); + } + + #[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); + } + + #[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); + } +} diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs new file mode 100644 index 000000000000..000715402591 --- /dev/null +++ b/lib/cretonne/src/ir/types.rs @@ -0,0 +1,409 @@ +//! Common types for the Cretonne code generator. + +use std::default::Default; +use std::fmt::{self, Display, Debug, Formatter}; + +// ====--------------------------------------------------------------------------------------====// +// +// 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 +/// 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)] +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); + +// 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 { + /// 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 log_2 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() { + B1 => 1, + B8 | I8 => 8, + B16 | I16 => 16, + B32 | I32 | F32 => 32, + B64 | I64 | F64 => 64, + _ => 0, + } + } + + /// 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. + /// + /// 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, + B16 | I16 => B16, + B32 | I32 | F32 => B32, + B64 | I64 | F64 => B64, + _ => B1, + }; + 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 { + 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 + } + + /// 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 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. + /// + /// 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 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. + /// + /// 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) -> 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); + if new_type < 0x90 { + Some(Type(new_type as u8)) + } else { + None + } + } + + /// 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 + } else { + Some(Type(self.0 - 0x10)) + } + } + + /// Index of this type, for use with hash tables etc. + 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 { + 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) + } + } +} + +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 + } +} + +#[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 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(); + assert_eq!(big.lane_bits(), 64); + assert_eq!(big.lane_count(), 256); + 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!(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] + fn format_scalars() { + 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!(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); + } + + #[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); + } +} diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs new file mode 100644 index 000000000000..a6a358e14bf8 --- /dev/null +++ b/lib/cretonne/src/ir/valueloc.rs @@ -0,0 +1,157 @@ +//! 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::{RegInfo, RegUnit}; +use ir::StackSlot; +use std::fmt; + +/// Value location. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +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 + } +} + +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), + } + } + + /// 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> { + 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 +/// 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(i32), +} + +impl Default for ArgumentLoc { + fn default() -> Self { + ArgumentLoc::Unassigned + } +} + +impl ArgumentLoc { + /// Is this an assigned location? (That is, not `Unassigned`). + pub fn is_assigned(&self) -> bool { + match *self { + ArgumentLoc::Unassigned => false, + _ => true, + } + } + + /// Is this a register location? + pub fn is_reg(&self) -> bool { + match *self { + ArgumentLoc::Reg(_) => true, + _ => false, + } + } + + /// 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> { + 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/isa/arm32/abi.rs b/lib/cretonne/src/isa/arm32/abi.rs new file mode 100644 index 000000000000..93ae0e7e613f --- /dev/null +++ b/lib/cretonne/src/isa/arm32/abi.rs @@ -0,0 +1,33 @@ +//! ARM ABI implementation. + +use ir; +use isa::RegClass; +use regalloc::AllocatableSet; +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), + } + } +} + +/// 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/binemit.rs b/lib/cretonne/src/isa/arm32/binemit.rs new file mode 100644 index 000000000000..bbae03432cde --- /dev/null +++ b/lib/cretonne/src/isa/arm32/binemit.rs @@ -0,0 +1,9 @@ +//! Emitting binary ARM32 machine code. + +use binemit::{CodeSink, bad_encoding}; +use ir::{Function, Inst}; +use regalloc::RegDiversions; + +include!(concat!(env!("OUT_DIR"), "/binemit-arm32.rs")); + +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; 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 000000000000..f71dd33f87a0 --- /dev/null +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -0,0 +1,10 @@ +//! Encoding tables for ARM32 ISA. + +use ir; +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 new file mode 100644 index 000000000000..fafd5d987142 --- /dev/null +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -0,0 +1,108 @@ +//! ARM 32-bit Instruction Set Architecture. + +pub mod settings; +mod abi; +mod binemit; +mod enc_tables; +mod registers; + +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}; +use ir; +use regalloc; + +#[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, + 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 { + registers::INFO.clone() + } + + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + + fn legal_encodings<'a>(&'a self, + dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Encodings<'a> { + lookup_enclist(ctrl_typevar, + inst, + dfg, + 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()) + } + + 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 allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func) + } + + 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) { + emit_function(func, binemit::emit_inst, sink) + } + + fn reloc_names(&self) -> &'static [&'static str] { + &binemit::RELOC_NAMES + } +} diff --git a/lib/cretonne/src/isa/arm32/registers.rs b/lib/cretonne/src/isa/arm32/registers.rs new file mode 100644 index 000000000000..283f113d1e79 --- /dev/null +++ b/lib/cretonne/src/isa/arm32/registers.rs @@ -0,0 +1,67 @@ +//! ARM32 register descriptions. + +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); + +#[cfg(test)] +mod tests { + use super::{INFO, GPR, S, D}; + 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"); + } + + #[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/arm32/settings.rs b/lib/cretonne/src/isa/arm32/settings.rs new file mode 100644 index 000000000000..e857716a645e --- /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/abi.rs b/lib/cretonne/src/isa/arm64/abi.rs new file mode 100644 index 000000000000..f5a2dc91c751 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/abi.rs @@ -0,0 +1,24 @@ +//! ARM 64 ABI implementation. + +use ir; +use isa::RegClass; +use regalloc::AllocatableSet; +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 } +} + +/// 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/binemit.rs b/lib/cretonne/src/isa/arm64/binemit.rs new file mode 100644 index 000000000000..ecff1662bc18 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/binemit.rs @@ -0,0 +1,9 @@ +//! Emitting binary ARM64 machine code. + +use binemit::{CodeSink, bad_encoding}; +use ir::{Function, Inst}; +use regalloc::RegDiversions; + +include!(concat!(env!("OUT_DIR"), "/binemit-arm64.rs")); + +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; 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 000000000000..6007450cb558 --- /dev/null +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -0,0 +1,10 @@ +//! Encoding tables for ARM64 ISA. + +use ir; +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 new file mode 100644 index 000000000000..f4b21bf42dad --- /dev/null +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -0,0 +1,101 @@ +//! ARM 64-bit Instruction Set Architecture. + +pub mod settings; +mod abi; +mod binemit; +mod enc_tables; +mod registers; + +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}; +use ir; +use regalloc; + +#[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, + }) +} + +impl TargetIsa for Isa { + fn name(&self) -> &'static str { + "arm64" + } + + fn flags(&self) -> &shared_settings::Flags { + &self.shared_flags + } + + fn register_info(&self) -> RegInfo { + registers::INFO.clone() + } + + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + + fn legal_encodings<'a>(&'a self, + dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Encodings<'a> { + lookup_enclist(ctrl_typevar, + inst, + dfg, + &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()) + } + + 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 allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func) + } + + 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) { + 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/registers.rs b/lib/cretonne/src/isa/arm64/registers.rs new file mode 100644 index 000000000000..0a0e043a209b --- /dev/null +++ b/lib/cretonne/src/isa/arm64/registers.rs @@ -0,0 +1,37 @@ +//! ARM64 register descriptions. + +use isa::registers::{RegBank, RegClass, RegClassData, 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 000000000000..6427d7be99ba --- /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/constraints.rs b/lib/cretonne/src/isa/constraints.rs new file mode 100644 index 000000000000..e38b04453526 --- /dev/null +++ b/lib/cretonne/src/isa/constraints.rs @@ -0,0 +1,136 @@ +//! 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 binemit::CodeOffset; +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. +#[derive(Clone, Copy, PartialEq, Eq)] +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. + /// + /// 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. + /// + /// 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. + /// + /// The constraint's `regclass` field is the register class that would normally be used to load + /// and store values of this type. + Stack, +} + +/// Value operand constraints for an encoding recipe. +#[derive(Clone)] +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], + + /// 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. +/// +/// 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/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs new file mode 100644 index 000000000000..b9fa09dd658e --- /dev/null +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -0,0 +1,287 @@ +//! Support types for generated encoding tables. +//! +//! 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, DataFlowGraph, InstructionData}; +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>; + +/// 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(&DataFlowGraph, &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. +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 +/// 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. +/// +/// 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, +} + +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() + } + + 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 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` +/// 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: Option, + pub offset: OffT, +} + +impl + Copy> Table for [Level2Entry] { + fn len(&self) -> usize { + self.len() + } + + fn key(&self, idx: usize) -> Option { + self[idx].opcode + } +} + +/// Two-level hash table lookup and iterator construction. +/// +/// Given the controlling type variable and instruction opcode, find the corresponding encoding +/// list. +/// +/// 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], + legalize_actions: &'static [Legalize], + recipe_preds: &'static [RecipePredicate], + inst_preds: &'static [InstPredicate], + isa_preds: PredicateView<'a>) + -> Encodings<'a> + where OffT1: Into + Copy, + OffT2: Into + Copy +{ + 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. + (!0, level1_table[l1idx].legalize) + } + Ok(l1idx) => { + // We have a valid level 1 entry for this type. + let l1ent = &level1_table[l1idx]; + let offset = match level2_table.get(l1ent.range()) { + Some(l2tab) => { + let opcode = inst.opcode(); + match probe(l2tab, opcode, opcode as usize) { + Ok(l2idx) => l2tab[l2idx].offset.into() as usize, + Err(_) => !0, + } + } + // 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, + dfg, + enclist, + legalize_actions, + recipe_preds, + inst_preds, + isa_preds) +} + +/// 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: 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, + // Legalization code to use of no encoding is found. + legalize: LegalizeCode, + 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>, +} + +impl<'a> Encodings<'a> { + /// Creates a new instance of `Encodings`. + /// + /// 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, + legalize: LegalizeCode, + 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>) + -> Self { + Encodings { + offset, + inst, + dfg, + legalize, + isa_preds, + recipe_preds, + inst_preds, + enclist, + legalize_actions, + } + } + + /// 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) -> Legalize { + debug_assert_eq!(self.offset, !0, "Premature Encodings::legalize()"); + self.legalize_actions[self.legalize as usize] + } + + /// Check if the `rpred` recipe predicate s satisfied. + fn check_recipe(&self, rpred: RecipePredicate) -> bool { + match rpred { + 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_preds.get(pred) { + p(self.dfg, self.inst) + } else { + let pred = pred - self.inst_preds.len(); + self.isa_preds.test(pred) + } + } +} + +impl<'a> Iterator for Encodings<'a> { + type Item = Encoding; + + fn next(&mut self) -> Option { + 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_preds.get(recipe) { + let bits = self.offset + 1; + if entry & 1 == 0 { + self.offset += 2; // Next entry. + } else { + self.offset = !0; // Stop. + } + 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 { + self.legalize = (entry - 2 * self.recipe_preds.len()) as LegalizeCode; + self.offset = !0; // Stop. + return None; + } + + // 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; + } else if skip == 0 { + self.offset = !0; // Stop. + return None; + } else { + self.offset += 1 + skip; + } + } + None + } +} diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs new file mode 100644 index 000000000000..9bc78d9ef0e4 --- /dev/null +++ b/lib/cretonne/src/isa/encoding.rs @@ -0,0 +1,135 @@ +//! The `Encoding` struct. + +use binemit::CodeOffset; +use isa::constraints::{RecipeConstraints, BranchRange}; +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 +/// 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, 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 + } + + /// 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 `EncInfo::display()`. +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, "-") + } + } +} + +/// 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], +} + +impl EncInfo { + /// Get the value operand constraints for `enc` if it is a legal encoding. + pub fn operand_constraints(&self, enc: Encoding) -> Option<&'static 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, + } + } + + /// 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/abi.rs b/lib/cretonne/src/isa/intel/abi.rs new file mode 100644 index 000000000000..240e9d327d23 --- /dev/null +++ b/lib/cretonne/src/isa/intel/abi.rs @@ -0,0 +1,124 @@ +//! Intel ABI implementation. + +use ir; +use isa::{RegClass, RegUnit}; +use regalloc::AllocatableSet; +use settings as shared_settings; +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, + _current: bool) { + 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. +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, + 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/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs new file mode 100644 index 000000000000..0eb6199e6666 --- /dev/null +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -0,0 +1,192 @@ +//! Emitting binary Intel machine code. + +use binemit::{CodeSink, Reloc, bad_encoding}; +use ir::{Function, Inst, Ebb, InstructionData}; +use isa::RegUnit; +use regalloc::RegDiversions; + +include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); + +/// 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) + } +} + +// 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 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*"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Op1 encoding"); + 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*"); + debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Op2 encoding"); + sink.put1(0x0f); + 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*"); + let pp = (bits >> 8) & 3; + sink.put1(PREFIX[(pp - 1) as usize]); + 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); +} + +// 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; + let rm = rm as u8 & 7; + let mut b = 0b11000000; + b |= reg << 3; + b |= rm; + 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); +} + +/// 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); +} + +/// 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); +} 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 000000000000..35ca9a9d7652 --- /dev/null +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -0,0 +1,13 @@ +//! Encoding tables for Intel ISAs. + +use bitset::BitSet; +use ir; +use isa::constraints::*; +use isa::enc_tables::*; +use isa::encoding::RecipeSizing; +use isa; +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 new file mode 100644 index 000000000000..1cce45b12e67 --- /dev/null +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -0,0 +1,108 @@ +//! Intel Instruction Set Architectures. + +pub mod settings; +mod abi; +mod binemit; +mod enc_tables; +mod registers; + +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}; +use ir; +use regalloc; + +#[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, + 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 { + registers::INFO.clone() + } + + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + + fn legal_encodings<'a>(&'a self, + dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Encodings<'a> { + lookup_enclist(ctrl_typevar, + inst, + dfg, + 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()) + } + + 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 allocatable_registers(&self, func: &ir::Function) -> regalloc::AllocatableSet { + abi::allocatable_registers(func, &self.shared_flags) + } + + 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) { + 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/registers.rs b/lib/cretonne/src/isa/intel/registers.rs new file mode 100644 index 000000000000..14b4050d9436 --- /dev/null +++ b/lib/cretonne/src/isa/intel/registers.rs @@ -0,0 +1,62 @@ +//! Intel register descriptions. + +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-intel.rs")); + +#[cfg(test)] +mod tests { + use super::*; + 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"); + } + + #[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/intel/settings.rs b/lib/cretonne/src/isa/intel/settings.rs new file mode 100644 index 000000000000..7ca48865884f --- /dev/null +++ b/lib/cretonne/src/isa/intel/settings.rs @@ -0,0 +1,33 @@ +//! 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")); + +#[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 new file mode 100644 index 000000000000..08c45dd8b824 --- /dev/null +++ b/lib/cretonne/src/isa/mod.rs @@ -0,0 +1,263 @@ +//! Instruction Set Architectures. +//! +//! 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`. +//! +//! # Constructing a `TargetIsa` instance +//! +//! 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 +//! 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: +//! +//! ``` +//! 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") { +//! Err(_) => { +//! // The RISC-V target ISA is not available. +//! } +//! Ok(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 use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, BranchRange}; +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; +use result; +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) -> Result { + match name { + "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), + } +} + +/// Describes reason for target lookup failure +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LookupError { + /// Unknown Target + Unknown, + + /// Target known but not built and thus not supported + Unsupported, +} + +/// 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 enable(&mut self, name: &str) -> settings::Result<()> { + self.setup.enable(name) + } +} + +/// After determining that an instruction doesn't have an encoding, how should we proceed to +/// legalize it? +/// +/// 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 { + /// 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; + + /// 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>(&'a self, + dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Encodings<'a>; + + /// Encode an instruction after determining it is legal. + /// + /// If `inst` can legally be encoded in this ISA, produce the corresponding `Encoding` object. + /// 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 { + let mut iter = self.legal_encodings(dfg, inst, ctrl_typevar); + iter.next().ok_or_else(|| iter.legalize().into()) + } + + /// Get a data structure describing the instruction encodings in this ISA. + fn encoding_info(&self) -> EncInfo; + + /// 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. + /// + /// 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 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 + /// 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: 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; + + /// 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 + /// fastest way of emitting code. + fn emit_inst(&self, + func: &ir::Function, + inst: ir::Inst, + divert: &mut regalloc::RegDiversions, + 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. + /// + /// This array can be indexed by the contents of `binemit::Reloc` objects passed to a + /// `CodeSink`. + fn reloc_names(&self) -> &'static [&'static str]; +} diff --git a/lib/cretonne/src/isa/registers.rs b/lib/cretonne/src/isa/registers.rs new file mode 100644 index 000000000000..2823fbea3737 --- /dev/null +++ b/lib/cretonne/src/isa/registers.rs @@ -0,0 +1,295 @@ +//! Data structures describing the registers in an ISA. + +use entity_ref::EntityRef; +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; + +/// 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]; + +/// 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. +/// +/// 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: RegUnit, + + /// 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, + + /// 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 { + /// 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) + } + } +} + +/// 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 name of the register class. + pub name: &'static str, + + /// 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, + + /// 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, + + /// Bit-mask of sub-classes of this register class, including itself. + /// + /// Bits correspond to RC indexes. + 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. + 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)) + } + } + + /// 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 + } + + /// 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 + } + + /// 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 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name) + } +} + +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 +/// 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) + } +} + +impl fmt::Display for RegClassIndex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "rci{}", self.0) + } +} + +/// 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 +/// 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. + pub banks: &'static [RegBank], + + /// All register classes ordered topologically so a sub-class always follows its parent. + pub classes: &'static [RegClassData], +} + +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, + 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. +pub struct DisplayRegUnit<'a> { + regunit: RegUnit, + 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/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs new file mode 100644 index 000000000000..66d4c2e6b749 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -0,0 +1,137 @@ +//! RISC-V ABI implementation. +//! +//! 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 abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; +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; + +struct Args { + pointer_bits: u16, + pointer_bytes: u32, + pointer_type: Type, + regs: u32, + reg_limit: u32, + offset: u32, +} + +impl 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, + } + } +} + +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 ValueConversion::VectorSplit.into(); + } + + // 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 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(), + } + } + + if self.regs < self.reg_limit { + // 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; + ArgumentLoc::Reg(reg).into() + } else { + // 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` for RISC-V. +pub fn legalize_signature(sig: &mut ir::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, isa_flags.enable_e()); + legalize_args(&mut sig.argument_types, &mut args); + + let mut rets = Args::new(bits, isa_flags.enable_e()); + 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); + } +} + +/// 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 } +} + +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/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs new file mode 100644 index 000000000000..86cbb6b82c41 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -0,0 +1,197 @@ +//! Emitting binary RISC-V machine code. + +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")); + +/// RISC-V relocation kinds. +pub enum RelocKind { + /// A jal call to a function. + Call, +} + +pub static RELOC_NAMES: [&'static str; 1] = ["Call"]; + +impl Into for RelocKind { + fn into(self) -> Reloc { + Reloc(self as u16) + } +} + +/// 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 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); +} + +/// 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); +} + +/// 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); +} + +/// 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); +} + +/// SB-type branch instructions. +/// +/// 31 24 19 14 11 6 +/// imm rs2 rs1 funct3 imm opcode +/// 25 20 15 12 7 0 +/// +/// Encoding bits: `opcode[6:2] | (funct3 << 5)` +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; + i |= funct3 << 12; + 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); +} + +/// 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); +} diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs new file mode 100644 index 000000000000..f095e6724387 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -0,0 +1,19 @@ +//! Encoding tables for RISC-V. + +use ir::condcodes::IntCC; +use ir; +use isa; +use isa::constraints::*; +use isa::enc_tables::*; +use isa::encoding::RecipeSizing; +use predicates; +use super::registers::*; + +// Include the generated encoding tables: +// - `LEVEL1_RV32` +// - `LEVEL1_RV64` +// - `LEVEL2` +// - `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 new file mode 100644 index 000000000000..03482b920ca6 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -0,0 +1,243 @@ +//! RISC-V Instruction Set Architecture. + +pub mod settings; +mod abi; +mod binemit; +mod enc_tables; +mod registers; + +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}; +use ir; +use regalloc; + +#[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 RISC-V 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_RV64[..] + } else { + &enc_tables::LEVEL1_RV32[..] + }; + Box::new(Isa { + isa_flags: settings::Flags::new(&shared_flags, builder), + shared_flags, + cpumode: level1, + }) +} + +impl TargetIsa for Isa { + fn name(&self) -> &'static str { + "riscv" + } + + fn flags(&self) -> &shared_settings::Flags { + &self.shared_flags + } + + fn register_info(&self) -> RegInfo { + registers::INFO.clone() + } + + fn encoding_info(&self) -> EncInfo { + enc_tables::INFO.clone() + } + + fn legal_encodings<'a>(&'a self, + dfg: &'a ir::DataFlowGraph, + inst: &'a ir::InstructionData, + ctrl_typevar: ir::Type) + -> Encodings<'a> { + lookup_enclist(ctrl_typevar, + inst, + dfg, + 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()) + } + + 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: ir::Type) -> RegClass { + abi::regclass_for_abi_type(ty) + } + + 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, + divert: &mut regalloc::RegDiversions, + sink: &mut CodeSink) { + binemit::emit_inst(func, inst, divert, 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 + } +} + +#[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: Result) -> String { + match enc { + Ok(e) => isa.encoding_info().display(e).to_string(), + Err(_) => "no encoding".to_string(), + } + } + + #[test] + fn test_64bitenc() { + let mut shared_builder = settings::builder(); + shared_builder.enable("is_64bit").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 v1, -10. + let inst64 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + arg: arg64, + imm: immediates::Imm64::new(-10), + }; + + // ADDI is I/0b00100 + 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 { + opcode: Opcode::IaddImm, + arg: arg64, + imm: immediates::Imm64::new(-10000), + }; + + // Immediate is out of range for ADDI. + assert!(isa.encode(&dfg, &inst64_large, types::I64).is_err()); + + // Create an iadd_imm.i32 which is encodable in RV64. + let inst32 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + arg: arg32, + imm: immediates::Imm64::new(10), + }; + + // ADDIW is I/0b00110 + assert_eq!(encstr(&*isa, isa.encode(&dfg, &inst32, types::I32)), "I#06"); + } + + // Same as above, but for RV32. + #[test] + fn test_32bitenc() { + let mut shared_builder = settings::builder(); + shared_builder.set("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 v1, -10. + let inst64 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + arg: arg64, + imm: immediates::Imm64::new(-10), + }; + + // In 32-bit mode, an i64 bit add should be narrowed. + assert!(isa.encode(&dfg, &inst64, types::I64).is_err()); + + // Try to encode iadd_imm.i64 v1, -10000. + let inst64_large = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + arg: arg64, + imm: immediates::Imm64::new(-10000), + }; + + // In 32-bit mode, an i64 bit add should be narrowed. + assert!(isa.encode(&dfg, &inst64_large, types::I64).is_err()); + + // Create an iadd_imm.i32 which is encodable in RV32. + let inst32 = InstructionData::BinaryImm { + opcode: Opcode::IaddImm, + arg: arg32, + imm: immediates::Imm64::new(10), + }; + + // ADDI is I/0b00100 + 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 { + opcode: Opcode::Imul, + args: [arg32, arg32], + }; + + assert!(isa.encode(&dfg, &mul32, types::I32).is_err()); + } + + #[test] + fn test_rv32m() { + let mut shared_builder = settings::builder(); + 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.enable("supports_m").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, + args: [arg32, arg32], + }; + assert_eq!(encstr(&*isa, isa.encode(&dfg, &mul32, types::I32)), "R#10c"); + } +} diff --git a/lib/cretonne/src/isa/riscv/registers.py b/lib/cretonne/src/isa/riscv/registers.py new file mode 100644 index 000000000000..8b137891791f --- /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 000000000000..9447e5ea293c --- /dev/null +++ b/lib/cretonne/src/isa/riscv/registers.rs @@ -0,0 +1,49 @@ +//! RISC-V register descriptions. + +use isa::registers::{RegBank, RegClass, RegClassData, RegInfo}; + +include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs")); + +#[cfg(test)] +mod tests { + use super::{INFO, GPR, FPR}; + 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"); + } + + #[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/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs new file mode 100644 index 000000000000..69a47f8717d3 --- /dev/null +++ b/lib/cretonne/src/isa/riscv/settings.rs @@ -0,0 +1,51 @@ +//! RISC-V 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-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(&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\ + enable_m = true\n\ + enable_e = 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.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("enable_simd", "false").unwrap(); + let shared = settings::Flags::new(&sb); + let mut b = builder(); + 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/iterators.rs b/lib/cretonne/src/iterators.rs new file mode 100644 index 000000000000..70d1165ed77b --- /dev/null +++ b/lib/cretonne/src/iterators.rs @@ -0,0 +1,79 @@ +//! 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 } + } +} + +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/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs new file mode 100644 index 000000000000..7d248c0cfac9 --- /dev/null +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -0,0 +1,651 @@ +//! Legalize ABI boundaries. +//! +//! This legalizer sub-module contains code for dealing with ABI boundaries: +//! +//! - Function arguments passed to the entry block. +//! - Function arguments passed to call instructions. +//! - Return values from call instructions. +//! - Return values passed to return instructions. +//! +//! The ABI boundary legalization happens in two phases: +//! +//! 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 flowgraph::ControlFlowGraph; +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}; + +/// 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. +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); + } +} + +/// 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) { + 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. + 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 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 { + // 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)) + } else { + Err(abi_type) + } + }; + 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. + 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. +/// +/// 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. +/// +/// 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 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 results = dfg.detach_results(call); + let mut next_res = 0; + let mut abi_res = 0; + + // Point immediately after the call. + pos.next_inst(); + + 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_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 { + let last_res = dfg.append_result(call, ty); + abi_res += 1; + Ok(last_res) + } else { + Err(abi_type) + } + }; + let v = convert_from_abi(dfg, pos, res_type, Some(res), &mut get_res); + assert_eq!(dfg.resolve_aliases(res), v); + } + } + + call +} + +/// 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 +/// 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`. +/// +/// 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 +{ + // Terminate the recursion when we get the desired type. + 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, + }; + + // 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. + ValueConversion::IntSplit => { + let abi_ty = ty.half_width().expect("Invalid type for conversion"); + 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).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, 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, 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, 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).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) + } + } +} + +/// 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 `Ok(())`. +/// 2. If the suggested argument doesn't have the right value type, don't change anything, but +/// 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) + 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) { + Ok(_) => return, + Err(t) => t, + }; + + let ty = dfg.value_type(value); + match legalize_abi_value(ty, &arg_type) { + ValueConversion::IntSplit => { + 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, 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, cfg, pos, arg, put_arg); + } + ValueConversion::Sext(abi_ty) => { + let arg = dfg.ins(pos).sextend(abi_ty, value); + 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, cfg, pos, arg, put_arg); + } + } +} + +/// 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 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. +/// +/// 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) -> 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), + 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, &sig.argument_types[..]) && + check_arg_types(dfg, dfg.inst_results(inst), &sig.return_types[..]) { + // All types check out. + Ok(()) + } else { + // Call types need fixing. + 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 { + 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`. +/// +/// - `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, + cfg: &ControlFlowGraph, + 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(); + 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; + Ok(()) + } else { + Err(abi_type) + } + }; + convert_to_abi(dfg, cfg, pos, old_value, &mut put_arg); + } + + // 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. +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 spill_call_arguments(dfg, locations, stack_slots, pos), + Err(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, + cfg, + pos, + abi_args, + |dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]); + + if !dfg.signatures[sig_ref].return_types.is_empty() { + 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, None), + 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 +} + +/// 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, + 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. + if check_return_signature(dfg, inst, sig) { + return false; + } + + // 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, None)); + 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: {} / signature {}", + dfg.display_inst(inst, None), + sig); + + // 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 new file mode 100644 index 000000000000..9cc37dd896f4 --- /dev/null +++ b/lib/cretonne/src/legalizer/mod.rs @@ -0,0 +1,104 @@ +//! 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 dominator_tree::DominatorTree; +use flowgraph::ControlFlowGraph; +use ir::{self, Function, Cursor, CursorBase}; +use ir::condcodes::IntCC; +use isa::TargetIsa; +use bitset::BitSet; + +mod boundary; +mod split; + +/// 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, + cfg: &mut ControlFlowGraph, + domtree: &DominatorTree, + isa: &TargetIsa) { + boundary::legalize_signatures(func, isa); + + func.encodings.resize(func.dfg.num_insts()); + + let mut pos = Cursor::new(&mut func.layout); + + // 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 + // 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 func.locations, + &mut func.stack_slots, + 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, cfg, &mut pos, &func.signature) { + // Go back and legalize the inserted return value conversion instructions. + pos.set_position(prev_pos); + continue; + } + + if opcode.is_branch() { + split::simplify_branch_arguments(&mut 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. + 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. + // 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); + continue; + } + } + } + + // Remember this position in case we need to double back. + 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 +// `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/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs new file mode 100644 index 000000000000..da61303e058f --- /dev/null +++ b/lib/cretonne/src/legalizer/split.rs @@ -0,0 +1,329 @@ +//! 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 flowgraph::ControlFlowGraph; +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 +/// if possible. +pub fn isplit(dfg: &mut DataFlowGraph, + cfg: &ControlFlowGraph, + pos: &mut Cursor, + value: Value) + -> (Value, Value) { + 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, + pos: &mut Cursor, + value: Value) + -> (Value, Value) { + 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, None)); + 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. +/// +/// 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, + repairs: &mut Vec) + -> (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) => { + // 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(); + 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. + 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`. 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), + _ => panic!("Unhandled concat opcode: {}", concat), + } + } +} + +// 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, + split_type, + ebb, + 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_args(inst)[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_args(inst)[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.inst_args(branch) { + let new_arg = resolve_splits(dfg, arg); + new_args.push(new_arg); + } + + dfg.inst_args_mut(branch).copy_from_slice(&new_args); +} diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs new file mode 100644 index 000000000000..b021bf55b215 --- /dev/null +++ b/lib/cretonne/src/lib.rs @@ -0,0 +1,47 @@ +//! Cretonne code generation library. + +#![deny(missing_docs)] + +pub use context::Context; +pub use legalizer::legalize_function; +pub use verifier::verify_function; +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; +#[macro_use] +pub mod entity_ref; + +pub mod binemit; +pub mod bitset; +pub mod dominator_tree; +pub mod entity_list; +pub mod entity_map; +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; +pub mod sparse_map; +pub mod verifier; + +mod abi; +mod constant_hash; +mod context; +mod cursor; +mod iterators; +mod legalizer; +mod licm; +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/licm.rs b/lib/cretonne/src/licm.rs new file mode 100644 index 000000000000..8ed88d47dd19 --- /dev/null +++ b/lib/cretonne/src/licm.rs @@ -0,0 +1,195 @@ +//! A Loop Invariant Code Motion optimization pass + +use ir::{Function, Ebb, Inst, Value, Cursor, CursorBase, 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.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)) { + None => { + 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(); + } + // 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 { + pos.insert_inst(inst); + } + } + } + // 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().cloned().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.dominates(header, 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.dominates(header, 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 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, + 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); + } + 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_and_step_back(); + } 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); + } + } + } + } + invariant_inst +} + +/// Return ebbs from a loop in post-order, starting from an entry point in the block. +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)]; + 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, lp) && !grey.contains(child) { + stack.push(*child); + } + } + } else if !black.contains(&node) { + postorder.push(node); + black.insert(node); + } + } + postorder +} diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs new file mode 100644 index 000000000000..5a4607ba79b0 --- /dev/null +++ b/lib/cretonne/src/loop_analysis.rs @@ -0,0 +1,318 @@ +//! A loop analysis represented as mappings of loops to their header Ebb +//! and parent in the loop tree. + +use dominator_tree::DominatorTree; +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); +entity_impl!(Loop, "loop"); + +/// 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 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.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(); + 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.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, CursorBase, 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); + } +} diff --git a/lib/cretonne/src/packed_option.rs b/lib/cretonne/src/packed_option.rs new file mode 100644 index 000000000000..9082a4846f3e --- /dev/null +++ b/lib/cretonne/src/packed_option.rs @@ -0,0 +1,149 @@ +//! 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; +use std::mem; + +/// 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() + } + + /// 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) } + } + + /// 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() + } + + /// 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() + } +} + +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()); + } +} diff --git a/lib/cretonne/src/partition_slice.rs b/lib/cretonne/src/partition_slice.rs new file mode 100644 index 000000000000..9626b5fd3705 --- /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]); + } +} diff --git a/lib/cretonne/src/predicates.rs b/lib/cretonne/src/predicates.rs new file mode 100644 index 000000000000..6ce3e0a799d2 --- /dev/null +++ b/lib/cretonne/src/predicates.rs @@ -0,0 +1,72 @@ +//! Predicate functions for testing instruction fields. +//! +//! This module defines functions that are used by the instruction predicates defined by +//! `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`. +//! +//! 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 + 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. +#[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; + // Bit-mask 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)); + } +} diff --git a/lib/cretonne/src/ref_slice.rs b/lib/cretonne/src/ref_slice.rs new file mode 100644 index 000000000000..b9dbd55dd1f0 --- /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) } +} diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs new file mode 100644 index 000000000000..a7c95df99f65 --- /dev/null +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -0,0 +1,130 @@ +//! 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 std::fmt; +use ir::{ArgumentType, ArgumentLoc}; +use isa::{TargetIsa, RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; + +/// Preferred register allocation for an SSA value. +#[derive(Clone, Copy)] +pub enum Affinity { + /// 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, + + /// This value prefers a register from the given register class. + Reg(RegClassIndex), +} + +impl Default for Affinity { + fn default() -> Self { + Affinity::None + } +} + +impl Affinity { + /// Create an affinity that satisfies a single constraint. + /// + /// This will never create an `Affinity::None`. + /// 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()) + } + } + + /// Create an affinity that matches an ABI argument for `isa`. + pub fn abi(arg: &ArgumentType, isa: &TargetIsa) -> Affinity { + match arg.location { + 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, + } + } + + /// 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 { + 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 + /// satisfies the constraint. + pub fn merge(&mut self, constraint: &OperandConstraint, reg_info: &RegInfo) { + match *self { + 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. + 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 => {} + } + } + + /// 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), + } + } + } + } +} diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs new file mode 100644 index 000000000000..063c8e63ac30 --- /dev/null +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -0,0 +1,274 @@ +//! 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 isa::registers::{RegInfo, RegUnit, RegUnitMask, RegClass}; +use std::fmt; +use std::iter::ExactSizeIterator; +use std::mem::size_of_val; + +/// 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 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 + } + + /// 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_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_eq!(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 + } + + /// 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; + } + } + + /// 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. +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 - 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 + } + + 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 {} + +/// 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::*; + use isa::registers::{RegClass, RegClassData}; + + // Register classes for testing. + const GPR: RegClass = &RegClassData { + name: "GPR", + index: 0, + width: 1, + bank: 0, + toprc: 0, + first: 28, + subclasses: 0, + mask: [0xf0000000, 0x0000000f, 0], + }; + const DPR: RegClass = &RegClassData { + name: "DPR", + index: 0, + width: 2, + bank: 0, + toprc: 0, + first: 28, + subclasses: 0, + mask: [0x50000000, 0x0000000a, 0], + }; + + #[test] + fn put_and_take() { + 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]); + + 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]); + } + + #[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)); + } +} diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs new file mode 100644 index 000000000000..ac11df349851 --- /dev/null +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -0,0 +1,522 @@ +//! 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 cursor::{Cursor, EncCursor}; +use dbg::DisplayList; +use dominator_tree::DominatorTree; +use flowgraph::{ControlFlowGraph, BasicBlock}; +use ir::{DataFlowGraph, Layout, InstBuilder, ValueDef}; +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, 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 + // `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 + // 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 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 {}: {}", + pos.display_inst(inst), + pred_ebb, + pos.display_inst(pred_inst)); + + // Create a live range for the new value. + let affinity = Affinity::new(&self.encinfo + .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, &pos.func.layout); + + pos.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 = 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 {}({}: {})", + pos.display_inst(inst), + ebb, + new_val, + ty); + + // Create a live range for the new value. + let affinity = Affinity::new(&self.encinfo + .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, &pos.func.layout); + + self.split_values.push(new_val); + new_val + } +} diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs new file mode 100644 index 000000000000..02e4d47f81d8 --- /dev/null +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -0,0 +1,747 @@ +//! 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. 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. +//! +//! 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 +//! 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 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 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}; +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::liverange::LiveRange; +use regalloc::solver::Solver; + + +/// Data structures for the coloring pass. +/// +/// These are scratch space data structures that can be reused between invocations. +pub struct Coloring { + divert: RegDiversions, + solver: Solver, +} + +/// 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> { + isa: &'a TargetIsa, + // Cached ISA information. + // We save it here to avoid frequent virtual function calls on the `TargetIsa` trait object. + reginfo: RegInfo, + encinfo: EncInfo, + + // References to contextual data structures we need. + 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. + 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, +} + +impl Coloring { + /// Allocate scratch space data structures for the coloring pass. + pub fn new() -> Coloring { + Coloring { + divert: RegDiversions::new(), + solver: Solver::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) { + dbg!("Coloring for:\n{}", func.display(isa)); + let mut ctx = Context { + isa, + reginfo: isa.register_info(), + encinfo: isa.encoding_info(), + domtree, + liveness, + divert: &mut self.divert, + solver: &mut self.solver, + usable_regs: isa.allocatable_registers(func), + }; + ctx.run(func, tracker) + } +} + +impl<'a> Context<'a> { + /// Run the coloring algorithm. + fn run(&mut self, func: &mut Function, tracker: &mut LiveValueTracker) { + 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); + } + } + + /// 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); + tracker.drop_dead_args(); + self.divert.clear(); + + // 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() { + 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, + &mut func.encodings, + &func.signature); + } else { + let (_throughs, kills) = tracker.process_ghost(inst); + self.process_ghost_kills(kills, &mut regs, &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. + tracker.ebb_top(ebb, &func.dfg, self.liveness, &func.layout, self.domtree); + + if func.layout.entry_block() == Some(ebb) { + // Arguments to the entry block have ABI constraints. + self.color_entry_args(&func.signature, tracker.live(), &mut func.locations) + } else { + // 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. + /// + /// 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 live.iter().filter(|lv| !lv.is_dead) { + let value = lv.value; + let affinity = self.liveness + .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]; + 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) + } + } + } + } + + 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 after filtering out the dead arguments. + fn color_entry_args(&self, + sig: &Signature, + args: &[LiveValue], + locations: &mut ValueLocations) + -> 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(rci) => { + let rc = self.reginfo.rc(rci); + if let ArgumentLoc::Reg(reg) = abi.location { + if !lv.is_dead { + regs.take(rc, reg); + } + locations[lv.value] = ValueLoc::Reg(reg); + } 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)); + } + + } + // 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 => {} + } + } + + regs + } + + /// 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(&mut self, + inst: Inst, + constraints: &RecipeConstraints, + pos: &mut Cursor, + dfg: &mut DataFlowGraph, + tracker: &mut LiveValueTracker, + regs: &mut AllocatableSet, + locations: &mut ValueLocations, + encodings: &mut InstEncodings, + func_signature: &Signature) { + dbg!("Coloring {}\n {}", + dfg.display_inst(inst, self.isa), + regs.display(&self.reginfo)); + + // 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); + 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); + } 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.isa)); + self.undivert_regs(|lr| !lr.is_local()); + } + } + + 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(rci) = lv.affinity { + self.solver + .add_kill(lv.value, + self.reginfo.rc(rci), + self.divert.reg(lv.value, locations)); + } + } + + // 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_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); + + // 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()); + + + // The solution and/or fixed input constraints may require us to shuffle the set of live + // registers around. + 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. + 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[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]; + let reg = self.divert.reg(arg, locations); + locations[lv.value] = ValueLoc::Reg(reg); + } + } + } + + // Update `regs` for the next instruction, remove the dead defs. + for lv in defs { + if lv.endpoint == inst { + 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); + } + } + } + + self.forget_diverted(kills); + + *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: &DataFlowGraph, + locations: &ValueLocations) { + 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!(), + } + } + } + + /// 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: &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) = + 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); + } + } + } + } + + /// 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. + // + // It is possible for `dest_arg` to have no affinity, and then it should simply + // be ignored. + 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`. + 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); + } 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 => { + if self.liveness[dest_arg].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. + 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, + live: &[LiveValue], + locations: &mut ValueLocations) { + 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_outputs(&mut self, + constraints: &[OperandConstraint], + defs: &[LiveValue], + throughs: &[LiveValue], + 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); + } + } + } + + /// 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 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. + 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, + rc: RegClass, + reg: RegUnit, + throughs: &[LiveValue], + 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 { + 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[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 ValueLocations) { + 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(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)); + } + } + } + } + + /// 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!(); + } + + /// 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, + 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); + 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)), + } + } + } + + /// 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/context.rs b/lib/cretonne/src/regalloc/context.rs new file mode 100644 index 000000000000..052398460469 --- /dev/null +++ b/lib/cretonne/src/regalloc/context.rs @@ -0,0 +1,134 @@ +//! 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 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; +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, verify_cssa}; + +/// Persistent memory allocations for register allocation. +pub struct Context { + liveness: Liveness, + virtregs: VirtRegs, + coalescing: Coalescing, + topo: TopoOrder, + tracker: LiveValueTracker, + spilling: Spilling, + reload: Reload, + 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(), + virtregs: VirtRegs::new(), + coalescing: Coalescing::new(), + topo: TopoOrder::new(), + tracker: LiveValueTracker::new(), + spilling: Spilling::new(), + reload: Reload::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) + -> 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(); + + // Pass: Liveness analysis. + self.liveness.compute(isa, func, cfg); + + if isa.flags().enable_verifier() { + verify_liveness(isa, func, cfg, &self.liveness)?; + } + + // Pass: 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)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; + } + + + // Pass: Spilling. + self.spilling + .run(isa, + func, + domtree, + &mut self.liveness, + &self.virtregs, + &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)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; + } + + // 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)?; + verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; + } + + // Pass: Coloring. + self.coloring + .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + + 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/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs new file mode 100644 index 000000000000..4f17d3685da1 --- /dev/null +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -0,0 +1,129 @@ +//! 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() + } + + /// 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) + } + + /// Get all current diversions. + 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)); + } + } + + /// 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)] +mod tests { + use super::*; + use ir::Value; + use entity_ref::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/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs new file mode 100644 index 000000000000..bee25aa287d1 --- /dev/null +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -0,0 +1,335 @@ +//! 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, Layout, ExpandedProgramPoint}; +use partition_slice::partition_slice; +use regalloc::affinity::Affinity; +use regalloc::liveness::Liveness; +use regalloc::liverange::LiveRange; +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, + + /// 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, + + /// 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 { + /// 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`. Copy some properties from `lr`. + fn push(&mut self, value: Value, endpoint: Inst, lr: &LiveRange) { + self.values + .push(LiveValue { + value, + endpoint, + affinity: lr.affinity, + is_local: lr.is_local(), + is_dead: lr.is_dead(), + }); + } + + /// 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); + } + + /// Remove any dead values. + fn remove_dead_values(&mut self) { + self.values.retain(|v| !v.is_dead); + self.live_prefix = None; + } +} + +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. + /// + /// 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 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 + // 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, layout) { + self.live.push(value, endpoint, lr); + } + } + } + + // 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()); + match lr.def_local_end().into() { + ExpandedProgramPoint::Inst(endpoint) => { + self.live.push(value, endpoint, lr); + } + ExpandedProgramPoint::Ebb(local_ebb) => { + // This is a dead EBB argument which is not even live into the first + // 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); + } + } + } + + self.live.values.split_at(first_arg) + } + + /// 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 `(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. + pub fn process_inst(&mut self, + inst: Inst, + dfg: &DataFlowGraph, + liveness: &Liveness) + -> (&[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) { + 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[value]; + assert_eq!(lr.def(), inst.into()); + match lr.def_local_end().into() { + ExpandedProgramPoint::Inst(endpoint) => { + self.live.push(value, endpoint, lr); + } + ExpandedProgramPoint::Ebb(ebb) => { + panic!("Instruction result live range can't end at {}", ebb); + } + } + } + + (&self.live.values[0..first_kill], + &self.live.values[first_kill..first_def], + &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. + /// + /// 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); + } + + /// 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(); + } + + /// 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); + 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 new file mode 100644 index 000000000000..973e278f626d --- /dev/null +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -0,0 +1,465 @@ +//! 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 +//! 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 +//! 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 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. +//! +//! 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. +//! +//! ## 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 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: +//! +//! - 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 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. +//! +//! 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. +//! +//! ## 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 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 +//! 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 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 bit-vectors. +//! +//! 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 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. +//! +//! +//! # 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 to be found in L2 cache at least. +//! +//! There is some room for improvement. + +use flowgraph::ControlFlowGraph; +use ir::dfg::ValueDef; +use ir::{Function, Value, Inst, Ebb, Layout, ProgramPoint}; +use isa::{TargetIsa, EncInfo}; +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; + +/// 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, + isa: &TargetIsa, + func: &Function, + 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. + // 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 = enc_info + .operand_constraints(func.encodings[inst]) + .and_then(|rc| rc.outs.get(rnum)) + .map(Affinity::new) + .unwrap_or_default(); + } + ValueDef::Arg(ebb, num) => { + def = ebb.into(); + 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)); + } + 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); + } + + // 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); + } + } + } +} + +/// 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, + + /// Working space for the `propagate_ebb_arguments` algorithm. + ebb_args: 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(), + ebb_args: Vec::new(), + } + } + + /// Get the live range for `value`, if it exists. + pub fn get(&self, value: Value) -> Option<&LiveRange> { + 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 + } + + /// 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) { + self.ranges.clear(); + + // Get ISA data structures used for computing live range affinities. + 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. + // 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() { + // 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 + // an unused value. + for &def in func.dfg.inst_results(inst) { + get_or_create(&mut self.ranges, def, isa, func, &enc_info); + } + + // Iterator of constraints, one per value operand. + let encoding = func.encodings[inst]; + let mut operand_constraints = enc_info + .operand_constraints(encoding) + .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, isa, func, &enc_info); + + // Extend the live range to reach this use. + extend_to_use(lr, ebb, inst, &mut self.worklist, func, cfg); + + // 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. + 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()); + } + } + } + } + + 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); + } + } + } + } + } + } +} + +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/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs new file mode 100644 index 000000000000..9174e0cdbaf8 --- /dev/null +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -0,0 +1,676 @@ +//! 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 opportunities, but all of them would +//! do a decent job of compressing a long live range. The numerical order might be preferable +//! because: +//! +//! - 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 preceding +//! `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, Value, ProgramPoint, ExpandedProgramPoint, ProgramOrder}; +use regalloc::affinity::Affinity; +use sparse_map::SparseMapValue; + +/// 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 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 preferred register allocation for this value. + pub affinity: Affinity, + + /// 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 interval covers the whole EBB. +/// +#[derive(Copy, Clone)] +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. + pub 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. + pub 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 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: ProgramPoint, affinity: Affinity) -> LiveRange { + LiveRange { + value, + affinity, + 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 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. + // + // 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); + + // 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`, 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 + } + } + } + + /// 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 + } + + /// 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. + /// 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) + } + + /// Get all the live-in intervals. + 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 `user` 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, + } + } + + /// 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. +impl SparseMapValue for LiveRange { + fn key(&self) -> Value { + self.value + } +} + +#[cfg(test)] +mod tests { + use super::LiveRange; + use ir::{Inst, Ebb, Value}; + use entity_ref::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. `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 { + 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) + } + + fn is_ebb_gap(&self, inst: Inst, ebb: Ebb) -> bool { + inst.index() % 10 == 1 && ebb.index() / 10 == inst.index() / 10 + 1 + } + } + + 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 v0 = Value::new(0); + let i1 = Inst::new(1); + let e2 = Ebb::new(2); + let lr = LiveRange::new(v0, i1.into(), Default::default()); + 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 v0 = Value::new(0); + let e2 = Ebb::new(2); + let lr = LiveRange::new(v0, e2.into(), Default::default()); + 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 argument does not count as live-in. + assert_eq!(lr.livein_local_end(e2, PO), None); + PO.validate(&lr); + } + + #[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(v0, i11.into(), Default::default()); + + 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 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(v0, e10.into(), Default::default()); + + // 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()); + 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 v0 = Value::new(0); + 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(v0, i11.into(), Default::default()); + + 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)); + } + + #[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. +} diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs new file mode 100644 index 000000000000..6ae93c67b662 --- /dev/null +++ b/lib/cretonne/src/regalloc/mod.rs @@ -0,0 +1,23 @@ +//! Register allocation. +//! +//! This module contains data structures and algorithms used for register allocation. + +pub mod liverange; +pub mod liveness; +pub mod allocatable_set; +pub mod live_value_tracker; +pub mod coloring; +pub mod virtregs; + +mod affinity; +mod coalescing; +mod context; +mod diversion; +mod pressure; +mod reload; +mod solver; +mod spilling; + +pub use self::allocatable_set::AllocatableSet; +pub use self::context::Context; +pub use self::diversion::RegDiversions; diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs new file mode 100644 index 000000000000..a9ab6bbbcb52 --- /dev/null +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -0,0 +1,356 @@ +//! 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. +//! +//! # 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)] + +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. +/// +/// 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. + base_count: u32, + transient_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, +} + +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. + 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. + 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.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) + } + } + + /// 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.total_count() * width, rc.limit * rcw) + } else { + rc.total_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 does not check if there are enough registers available. + pub fn take(&mut self, rc: RegClass) { + 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].base_count -= 1 + } + + /// Reset all counts to 0, both base and transient. + pub fn reset(&mut self) { + for e in &mut self.toprc { + 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 &mut self.toprc { + 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 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Pressure[")?; + for rc in &self.toprc { + if rc.limit > 0 { + write!(f, " {}+{}/{}", rc.base_count, rc.transient_count, rc.limit)?; + } + } + write!(f, " ]") + } +} + +#[cfg(test)] +#[cfg(build_arm32)] +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").ok().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); + } +} diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs new file mode 100644 index 000000000000..837dbd40d02d --- /dev/null +++ b/lib/cretonne/src/regalloc/reload.rs @@ -0,0 +1,324 @@ +//! 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 cursor::{Cursor, EncCursor}; +use dominator_tree::DominatorTree; +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; +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> { + cur: EncCursor<'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) { + dbg!("Reload for:\n{}", func.display(isa)); + let mut ctx = Context { + cur: EncCursor::new(func, isa), + encinfo: isa.encoding_info(), + domtree, + liveness, + topo, + candidates: &mut self.candidates, + reloads: &mut self.reloads, + }; + ctx.run(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, 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, tracker: &mut LiveValueTracker) { + dbg!("Reloading {}:", ebb); + self.visit_ebb_header(ebb, tracker); + tracker.drop_dead_args(); + + // 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, tracker); + tracker.drop_dead(inst); + } else { + self.cur.next_inst(); + } + } + } + + /// 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 self.cur.func.layout.entry_block() == Some(ebb) { + assert_eq!(liveins.len(), 0); + self.visit_entry_args(ebb, args); + } else { + 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, args: &[LiveValue]) { + assert_eq!(self.cur.func.signature.argument_types.len(), args.len()); + self.cur.goto_first_inst(ebb); + + 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 = 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); + } + } + ArgumentLoc::Stack(_) => { + assert!(arg.affinity.is_stack()); + } + ArgumentLoc::Unassigned => panic!("Unexpected ABI location"), + } + } + } + + 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 + /// that needs processing. + fn visit_inst(&mut self, + ebb: Ebb, + inst: Inst, + encoding: Encoding, + 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"); + + // Identify reload candidates. + assert!(self.candidates.is_empty()); + self.find_candidates(inst, constraints); + + // 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 = self.cur.ins().fill(cand.value); + let fill = self.cur.built_inst(); + + 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, fill, affinity); + self.liveness + .extend_locally(reg, ebb, inst, &self.cur.func.layout); + } + + // Rewrite arguments. + for arg in self.cur.func.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, &self.cur.func.dfg, self.liveness); + + // Advance to the next instruction so we can insert any spills after the instruction. + self.cur.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 = 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); + } + } + } + + // 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) { + let args = self.cur.func.dfg.inst_args(inst); + + for (op, &arg) in constraints.ins.iter().zip(args) { + if op.kind != ConstraintKind::Stack { + if self.liveness[arg].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) = 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); + } + } + + /// 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) { + 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, &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), + }); + } + } + } +} diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs new file mode 100644 index 000000000000..ea028f551b03 --- /dev/null +++ b/lib/cretonne/src/regalloc/solver.rs @@ -0,0 +1,840 @@ +//! 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, + + /// 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, + + /// Avoid interference on the output side. + is_output: bool, + + /// Avoid interference with the global registers. + is_global: 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 { + 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 { + 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, ")") + } +} + +#[derive(Clone, Debug)] +pub struct Assignment { + pub value: Value, + pub from: RegUnit, + pub to: RegUnit, + pub rc: RegClass, +} + +impl SparseMapValue for Assignment { + fn key(&self) -> Value { + self.value + } +} + +#[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. +/// +/// 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, + + /// 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. +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(), + moves: Vec::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); + if from != 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::new_live(value, constraint, from)); + } + + /// 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); + } + + /// 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 + /// 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::new_def(value, constraint)); + } +} + +/// 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 `Ok(regs)` if 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 + } +} + +/// 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)] +#[cfg(build_arm32)] +mod tests { + use entity_ref::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").ok().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)]); + } +} diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs new file mode 100644 index 000000000000..12a938c81c1d --- /dev/null +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -0,0 +1,541 @@ +//! 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. +//! +//! 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 cursor::{Cursor, EncCursor}; +use dominator_tree::DominatorTree; +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; +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. +pub struct Spilling { + spills: Vec, + reg_uses: Vec, +} + +/// Context data structure that gets instantiated once per pass. +struct Context<'a> { + // Current instruction as well as reference to function and ISA. + cur: EncCursor<'a>, + + // Cached ISA information. + reginfo: RegInfo, + encinfo: EncInfo, + + // References to contextual data structures we need. + domtree: &'a DominatorTree, + liveness: &'a mut Liveness, + virtregs: &'a VirtRegs, + 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, + + // 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(), + reg_uses: Vec::new(), + } + } + + /// Run the spilling algorithm over `func`. + pub fn run(&mut self, + isa: &TargetIsa, + func: &mut Function, + domtree: &DominatorTree, + liveness: &mut Liveness, + virtregs: &VirtRegs, + 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 { + cur: EncCursor::new(func, isa), + reginfo: isa.register_info(), + encinfo: isa.encoding_info(), + domtree, + liveness, + virtregs, + topo, + pressure: Pressure::new(®info, &usable_regs), + spills: &mut self.spills, + reg_uses: &mut self.reg_uses, + }; + ctx.run(tracker) + } +} + +impl<'a> Context<'a> { + 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, tracker: &mut LiveValueTracker) { + dbg!("Spilling {}:", ebb); + self.cur.goto_top(ebb); + self.visit_ebb_header(ebb, tracker); + tracker.drop_dead_args(); + + 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); + } + 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, 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. + self.pressure.reset(); + self.take_live_regs(liveins); + + // 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) { + Some(cand) => { + dbg!("Spilling live-in {} to make room for {} EBB argument {}", + cand, + rc, + lv.value); + self.spill_reg(cand); + } + 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); + 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, + inst: Inst, + ebb: Ebb, + constraints: &RecipeConstraints, + tracker: &mut LiveValueTracker) { + 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); + + // Calls usually have fixed register uses. + let call_sig = self.cur.func.dfg.call_signature(inst); + if let Some(sig) = call_sig { + self.collect_abi_reg_uses(inst, sig); + } + + if !self.reg_uses.is_empty() { + self.process_reg_uses(inst, tracker); + } + + // Update the live value tracker with this instruction. + let (throughs, kills, defs) = tracker.process_inst(inst, &self.cur.func.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. + 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()); + match self.spill_candidate(mask, throughs) { + Some(cand) => self.spill_reg(cand), + None => { + panic!("Ran out of {} registers for {}", + op.regclass, + self.cur.display_inst(inst)) + } + } + } + } + } + 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); + } + + // 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 + // operands are always compatible. + 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]; + match op.kind { + ConstraintKind::Stack => continue, + ConstraintKind::FixedReg(_) => reguse.fixed = true, + ConstraintKind::Tied(_) => { + // A tied operand must kill the used value. + reguse.tied = !lr.killed_at(inst, ebb, &self.cur.func.layout); + } + ConstraintKind::Reg => {} + } + if lr.affinity.is_stack() { + reguse.spilled = true; + } + + // Only collect the interesting register uses. + if reguse.fixed || reguse.tied || reguse.spilled { + dbg!(" reguse: {}", reguse); + self.reg_uses.push(reguse); + } + } + } + + // Collect register uses from the ABI input constraints. + 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 + self.cur.func.dfg.signatures[sig] + .argument_types + .iter() + .zip(args) + .enumerate() { + if abi.location.is_reg() { + let (rci, spilled) = match self.liveness[arg].affinity { + Affinity::Reg(rci) => (rci, false), + Affinity::Stack => { + (self.cur.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); + } + } + } + + // 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, 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)); + + for i in 0..self.reg_uses.len() { + let ru = self.reg_uses[i]; + + // 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); + 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 + // 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. + 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, + self.cur.display_inst(inst)) + } + } + } + } + } + self.pressure.reset_transient(); + self.reg_uses.clear() + } + + // Find a spill candidate from `candidates` whose top-level register class is in `mask`. + fn spill_candidate<'ii, II>(&self, mask: RegClassMask, candidates: II) -> Option + 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. + 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(self.cur.func.dfg.value_def(a), + self.cur.func.dfg.value_def(b), + &self.cur.func.layout) + }) + } + + /// 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); + } + + // Assign a spill slot for the whole virtual register. + 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.cur.func.locations.ensure(v) = ValueLoc::Stack(ss); + } + } + + /// Process any pending spills in the `self.spills` vector. + /// + /// It is assumed that spills are removed from the pressure tracker immediately, see + /// `spill_reg` 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() + } + } + + /// 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) -> 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, + self.cur.func.layout.pp_ebb(inst), + self.cur.current_inst().expect("must be at an instruction"), + &self.cur.func.layout); + + copy + } +} + +// 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, + + // 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, rci: RegClassIndex) -> RegUse { + RegUse { + value, + opidx: idx as u16, + rci, + fixed: false, + spilled: false, + tied: false, + } + } +} + +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(()) + } +} diff --git a/lib/cretonne/src/regalloc/virtregs.rs b/lib/cretonne/src/regalloc/virtregs.rs new file mode 100644 index 000000000000..17fa641fcdc6 --- /dev/null +++ b/lib/cretonne/src/regalloc/virtregs.rs @@ -0,0 +1,141 @@ +//! 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, 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"); + +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 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 + /// 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 + } +} diff --git a/lib/cretonne/src/result.rs b/lib/cretonne/src/result.rs new file mode 100644 index 000000000000..f4d7316fb5ac --- /dev/null +++ b/lib/cretonne/src/result.rs @@ -0,0 +1,67 @@ +//! Result and error types representing the outcome of compiling a function. + +use verifier; +use std::error::Error as StdError; +use std::fmt; + +/// A compilation error. +/// +/// When Cretonne fails to compile a function, it will return one of these error codes. +#[derive(Debug, PartialEq, Eq)] +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<(), 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/settings.rs b/lib/cretonne/src/settings.rs new file mode 100644 index 000000000000..660c833a4a00 --- /dev/null +++ b/lib/cretonne/src/settings.rs @@ -0,0 +1,353 @@ +//! Shared settings module. +//! +//! This module defines data structures to access the settings defined in the meta language. +//! +//! 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 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; + +use constant_hash::{probe, simple_hash}; + +/// A string-based configurator for settings groups. +/// +/// 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<()>; + + /// Enable a boolean setting or apply a preset. + /// + /// 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. +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 state_for(&self, name: &str) -> &[u8] { + 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; + } + } + + /// 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)) { + 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)) + } + } + } +} + +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 enable(&mut self, name: &str) -> Result<()> { + use self::detail::Detail; + let (offset, detail) = self.lookup(name)?; + 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), + } + } + + fn set(&mut self, name: &str, value: &str) -> Result<()> { + use self::detail::Detail; + let (offset, detail) = self.lookup(name)?; + match detail { + Detail::Bool { bit } => { + // 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)?; + } + Detail::Enum { last, enumerators } => { + self.bytes[offset] = parse_enum_value(value, + self.template.enums(last, enumerators))?; + } + Detail::Preset => return Err(Error::BadName), + } + Ok(()) + } +} + +/// An error produced when changing a setting. +#[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, +} + +/// 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 +/// code in other modules. +pub mod detail { + use std::fmt; + use constant_hash; + + /// 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], + /// Pairs of (mask, value) for presets. + pub presets: &'static [(u8, u8)], + } + + 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] + } + + /// 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) + } + } + // Presets aren't printed. They are reflected in the other settings. + Detail::Preset { .. } => Ok(()), + } + } + } + + /// 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. + 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 { + /// 0-7. + 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, + }, + + /// 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, + } +} + +// 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::{builder, Flags}; + use super::Error::*; + use super::Configurable; + + #[test] + fn display_default() { + let b = builder(); + let f = Flags::new(&b); + 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\ + enable_simd = true\n\ + enable_atomics = true\n"); + assert_eq!(f.opt_level(), super::OptLevel::Default); + assert_eq!(f.enable_simd(), true); + } + + #[test] + fn modify_bool() { + let mut b = builder(); + 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); + } + + #[test] + fn modify_string() { + 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(())); + + let f = Flags::new(&b); + assert_eq!(f.enable_simd(), false); + assert_eq!(f.opt_level(), super::OptLevel::Best); + } +} diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs new file mode 100644 index 000000000000..078bca2857ec --- /dev/null +++ b/lib/cretonne/src/simple_gvn.rs @@ -0,0 +1,67 @@ +//! A simple GVN pass. + +use flowgraph::ControlFlowGraph; +use dominator_tree::DominatorTree; +use ir::{Cursor, CursorBase, 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 pos = Cursor::new(&mut func.layout); + + for &ebb in domtree.cfg_postorder().iter().rev() { + pos.goto_top(ebb); + + 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; + } + + // 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_and_step_back(); + } 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/lib/cretonne/src/sparse_map.rs b/lib/cretonne/src/sparse_map.rs new file mode 100644 index 000000000000..b1e844b6188d --- /dev/null +++ b/lib/cretonne/src/sparse_map.rs @@ -0,0 +1,343 @@ +//! 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 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 +//! 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::EntityMap; +use entity_ref::EntityRef; +use std::mem; +use std::slice; +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 allowed 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() + } + + /// 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() { + 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 + } + + /// 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. + /// + /// 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 + } + + /// 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. +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_ref::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); + assert_eq!(map.values().count(), 0); + } + + #[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); + + // 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"))); + 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); + + // 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] + 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)); + } +} diff --git a/lib/cretonne/src/stack_layout.rs b/lib/cretonne/src/stack_layout.rs new file mode 100644 index 000000000000..f968e56a0765 --- /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); + } +} diff --git a/lib/cretonne/src/topo_order.rs b/lib/cretonne/src/topo_order.rs new file mode 100644 index 000000000000..938e4a876a72 --- /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, + } + } + } + } + } + self.stack.pop() + } +} + +#[cfg(test)] +mod test { + use flowgraph::ControlFlowGraph; + use dominator_tree::DominatorTree; + use ir::{Function, InstBuilder, Cursor, CursorBase}; + 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); + } +} diff --git a/lib/cretonne/src/verifier/cssa.rs b/lib/cretonne/src/verifier/cssa.rs new file mode 100644 index 000000000000..fe9a2fe18e70 --- /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/liveness.rs b/lib/cretonne/src/verifier/liveness.rs new file mode 100644 index 000000000000..2dff4a6e3e0e --- /dev/null +++ b/lib/cretonne/src/verifier/liveness.rs @@ -0,0 +1,240 @@ +//! 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 { + isa, + func, + cfg, + liveness, + }; + verifier.check_ebbs()?; + verifier.check_insts()?; + Ok(()) +} + +struct LivenessVerifier<'a> { + isa: &'a TargetIsa, + 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) { + 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) { + Some(lr) => lr, + 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. + 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), + }; + 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. + // + // 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, + self.isa.encoding_info().display(encoding)); + } + } + } + } + } + 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, + } + } + + /// 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; + + 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 new file mode 100644 index 000000000000..d3f05ef4c8c7 --- /dev/null +++ b/lib/cretonne/src/verifier/mod.rs @@ -0,0 +1,889 @@ +//! 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. +//! - All result values must be created for multi-valued instructions. +//! - 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 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 +//! function. +//! - 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. +//! - 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 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, StackSlot, + StackSlotKind, Value, Type, Opcode, ValueLoc, ArgumentLoc}; +use isa::TargetIsa; +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; + +// 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 cssa; +mod liveness; + +/// A verifier error. +#[derive(Debug, PartialEq, Eq)] +pub struct Error { + /// The entity causing the verifier error. + pub location: AnyEntity, + /// Error message. + pub message: String, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}: {}", self.location, self.message) + } +} + +impl std_error::Error for Error { + fn description(&self) -> &str { + &self.message + } +} + +/// Verifier result. +pub type Result = result::Result<(), Error>; + +/// Verify `func`. +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, + isa: Option<&TargetIsa>) + -> Result { + let verifier = Verifier::new(func, isa); + verifier.cfg_integrity(cfg)?; + verifier.domtree_integrity(domtree)?; + verifier.run() +} + +struct Verifier<'a> { + func: &'a Function, + cfg: ControlFlowGraph, + domtree: DominatorTree, + isa: Option<&'a TargetIsa>, +} + +impl<'a> Verifier<'a> { + 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, + cfg, + domtree, + isa, + } + } + + 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); + + if is_terminator && !is_last_inst { + // Terminating instructions only occur at the end of blocks. + return err!(inst, + "a terminator instruction was encountered before the end of {}", + ebb); + } + if is_last_inst && !is_terminator { + 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!(inst, "should belong to {} not {:?}", 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!(arg, "does not belong to {}", ebb); + } + } + _ => { + return err!(arg, "expected an argument, found a result"); + } + } + } + + Ok(()) + } + + 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; + + // 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) + } + + fn verify_entity_references(&self, inst: Inst) -> Result { + use ir::instructions::InstructionData::*; + + 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) { + self.verify_value(inst, res)?; + } + + match self.func.dfg[inst] { + MultiAry { ref args, .. } => { + self.verify_value_list(inst, args)?; + } + Jump { + destination, + ref args, + .. + } | + Branch { + destination, + ref args, + .. + } | + BranchIcmp { + 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)?; + } + 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 { .. } | + UnaryBool { .. } | + Binary { .. } | + BinaryImm { .. } | + Ternary { .. } | + InsertLane { .. } | + ExtractLane { .. } | + IntCompare { .. } | + IntCompareImm { .. } | + FloatCompare { .. } | + HeapLoad { .. } | + HeapStore { .. } | + Load { .. } | + Store { .. } | + RegMove { .. } => {} + } + + 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_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) + } 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, 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) { + ValueDef::Res(def_inst, _) => { + // Value is defined by an instruction that exists. + if !dfg.inst_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.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); + } + } + 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.is_reachable(ebb) && + !self.domtree.dominates(ebb, loc_inst, &self.func.layout) { + return err!(loc_inst, "uses value arg from non-dominating {}", 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 + // 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); + } + } + // 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(()) + } + + 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).iter().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 = self.func.dfg.ctrl_typevar(inst); + + 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) + .iter() + .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)?; + self.check_outgoing_args(inst, sig_ref)?; + } + 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)?; + self.check_outgoing_args(inst, sig_ref)?; + } + 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(()) + } + + /// 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); + 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(()) + } + + 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.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.is_empty() { + 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.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.is_empty() { + return err!(ebb, "cfg had unexpected predecessor(s) {:?}", excess_preds); + } + + expected_succs.clear(); + got_succs.clear(); + expected_preds.clear(); + got_preds.clear(); + } + 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 { + // 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 re-encoding {} doesn't match {}", + isa.encoding_info().display(verify_encoding), + isa.encoding_info().display(encoding)); + } + } + Err(_) => { + return err!(inst, + "Instruction failed to re-encode {}", + isa.encoding_info().display(encoding)) + } + } + 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(); + + // 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"); + } + + 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(()) + } + + 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.verify_encoding(inst)?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::{Verifier, Error}; + use ir::Function; + use ir::instructions::{InstructionData, Opcode}; + + macro_rules! assert_err_with_msg { + ($e:expr, $msg:expr) => ( + match $e { + Ok(_) => { panic!("Expected an error!") }, + Err(Error { message, .. } ) => { + if !message.contains($msg) { + panic!(format!("'{}' did not contain the substring '{}'", message, $msg)); + } + } + } + ) + } + + #[test] + fn empty() { + let func = Function::new(); + let verifier = Verifier::new(&func, None); + 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 }); + func.layout.append_inst(nullary_with_bad_opcode, ebb0); + let verifier = Verifier::new(&func, None); + assert_err_with_msg!(verifier.run(), "instruction format"); + } +} diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs new file mode 100644 index 000000000000..c342c4f27300 --- /dev/null +++ b/lib/cretonne/src/write.rs @@ -0,0 +1,392 @@ +//! 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 +//! `cretonne-reader` crate. + +use ir::{Function, DataFlowGraph, Ebb, Inst, Value, ValueDef, Type}; +use isa::{TargetIsa, RegInfo}; +use std::fmt::{self, 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 { + 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, regs)?; + for ebb in &func.layout { + if any { + writeln!(w, "")?; + } + write_ebb(w, func, isa, ebb)?; + any = true; + } + writeln!(w, "}}") +} + +// ====--------------------------------------------------------------------------------------====// +// +// Function spec. +// +// ====--------------------------------------------------------------------------------------====// + +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, + regs: Option<&RegInfo>) + -> result::Result { + let mut any = false; + + for ss in func.stack_slots.keys() { + any = true; + 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; + writeln!(w, + " {} = {}", + sig, + func.dfg.signatures[sig].display(regs))?; + } + + for fnref in func.dfg.ext_funcs.keys() { + any = true; + writeln!(w, " {} = {}", fnref, func.dfg.ext_funcs[fnref])?; + } + + for jt in func.jump_tables.keys() { + any = true; + writeln!(w, " {} = {}", jt, func.jump_tables[jt])?; + } + + Ok(any) +} + +// ====--------------------------------------------------------------------------------------====// +// +// Basic blocks +// +// ====--------------------------------------------------------------------------------------====// + +pub fn write_arg(w: &mut Write, func: &Function, arg: Value) -> Result { + write!(w, "{}: {}", arg, func.dfg.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(v1: i32): + // ebb10(v4: f64, v5: b1): + // + + // If we're writing encoding annotations, shift by 20. + if !func.encodings.is_empty() { + write!(w, " ")?; + } + + let mut args = func.dfg.ebb_args(ebb).iter().cloned(); + match args.next() { + None => return writeln!(w, "{}:", ebb), + Some(arg) => { + write!(w, "{}(", ebb)?; + write_arg(w, func, arg)?; + } + } + // Remaining arguments. + for arg in args { + write!(w, ", ")?; + write_arg(w, func, arg)?; + } + writeln!(w, "):") +} + +pub fn write_ebb(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, ebb: Ebb) -> Result { + write_ebb_header(w, func, ebb)?; + for inst in func.layout.ebb_insts(ebb) { + write_instruction(w, func, isa, inst)?; + } + Ok(()) +} + + +// ====--------------------------------------------------------------------------------------====// +// +// Instructions +// +// ====--------------------------------------------------------------------------------------====// + +// 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 inst_data = &func.dfg[inst]; + let constraints = inst_data.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. + if constraints.use_typevar_operand() { + 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; + } + } + + let rtype = func.dfg.ctrl_typevar(inst); + assert!(!rtype.is_void(), + "Polymorphic instruction must produce a result"); + 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_args(inst) { + let resolved = func.dfg.resolve_aliases(arg); + if resolved != arg { + 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. + 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 { + write!(s, "[{}", isa.encoding_info().display(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) { + write!(s, ",{}", func.locations.get_or_default(r).display(®s))? + } + } + write!(s, "]")?; + } else { + write!(s, "[{}]", enc)?; + } + // Align instruction following ISA annotation to col 24. + write!(w, "{:23} ", s)?; + } else { + // No annotations, simply indent. + write!(w, "{1:0$}", indent, "")?; + } + + // Write out the result values, if any. + let mut has_results = false; + for r in func.dfg.inst_results(inst) { + if !has_results { + has_results = true; + write!(w, "{}", r)?; + } else { + write!(w, ", {}", r)?; + } + } + if has_results { + write!(w, " = ")?; + } + + // Then the opcode, possibly with a '.type' suffix. + let opcode = func.dfg[inst].opcode(); + + match type_suffix(func, inst) { + Some(suf) => write!(w, "{}.{}", opcode, suf)?, + None => write!(w, "{}", opcode)?, + } + + 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, + isa: Option<&TargetIsa>, + inst: Inst) + -> Result { + let pool = &dfg.value_lists; + use ir::instructions::InstructionData::*; + 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), + 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]), + MultiAry { ref args, .. } => { + if args.is_empty() { + write!(w, "") + } else { + write!(w, " {}", DisplayValues(args.as_slice(pool))) + } + } + 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() { + write!(w, " {}", destination) + } else { + write!(w, + " {}({})", + destination, + DisplayValues(args.as_slice(pool))) + } + } + Branch { + destination, + ref args, + .. + } => { + let args = args.as_slice(pool); + 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, .. } => { + write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool))) + } + IndirectCall { sig_ref, ref args, .. } => { + let args = args.as_slice(pool); + write!(w, + " {}, {}({})", + sig_ref, + 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), + 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), + 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) + } + } + + } +} + +/// 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, StackSlotKind}; + use ir::types; + + #[test] + fn basic() { + let mut f = Function::new(); + assert_eq!(f.to_string(), "function %() native {\n}\n"); + + f.name = FunctionName::new("foo"); + 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() 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() native {\n ss0 = local 4\n\nebb0:\n}\n"); + + f.dfg.append_ebb_arg(ebb, types::I8); + assert_eq!(f.to_string(), + "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() native {\n ss0 = local 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"); + } +} diff --git a/lib/filecheck/Cargo.toml b/lib/filecheck/Cargo.toml new file mode 100644 index 000000000000..41effb059d4a --- /dev/null +++ b/lib/filecheck/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["The Cretonne Project Developers"] +name = "filecheck" +version = "0.0.1" +description = "Library for matching test outputs against filecheck directives" +license = "Apache-2.0" +repository = "https://github.com/stoklund/cretonne" +documentation = "https://docs.rs/filecheck" + +[lib] +name = "filecheck" + +[dependencies] +regex = "0.2.2" diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs new file mode 100644 index 000000000000..21d9049fee83 --- /dev/null +++ b/lib/filecheck/src/checker.rs @@ -0,0 +1,432 @@ +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}; +use MatchRange; +use explain::{Recorder, Explainer}; + +// 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: &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.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); + } + + // All other commands are followed by a pattern. + let pat = 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))); + } + // Ignore trailing white space in the regex, including CR. + Ok(Directive::Regex(var, rest[varlen + 1..].trim_right().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(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(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 { + 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 = 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_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()), + 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((dct_idx, state.unordered_begin(pat), pat.resolve(&state)?)); + continue; + } + Directive::Regex(ref var, ref rx) => { + state + .vars + .insert(var.clone(), + VarDef { + value: Value::Regex(Cow::Borrowed(rx)), + offset: 0, + }); + continue; + } + }; + // 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 { + // 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_idx, not_begin, rx) in nots.drain(..) { + state.recorder.directive(not_idx); + if let Some(mat) = rx.find(&text[not_begin..match_begin]) { + // Matched `not:` pattern. + state + .recorder + .matched_not(rx.as_str(), + (not_begin + mat.start(), not_begin + mat.end())); + return Ok(false); + } else { + state + .recorder + .missed_not(rx.as_str(), (not_begin, match_begin)); + } + } + } + } else { + // No match! + return Ok(false); + } + } + + // 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 rx.find(&text[not_begin..]).is_some() { + // 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<'a> { + /// The value given to the variable. + value: Value<'a>, + /// Offset in input text from where the variable is available. + offset: usize, +} + +struct State<'a> { + 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, + // 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, recorder: &'a mut Recorder) -> State<'a> { + State { + text, + env_vars, + recorder, + 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) -> MatchRange { + (self.max_match, self.text.len()) + } + + // Get the range in text to be matched by a `sameln:`. + 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) -> MatchRange { + 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 { + 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. + 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: MatchRange) -> Result> { + let rx = 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.get(0).expect("whole expression must match"); + for var in defs { + 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.end(), + }; + self.vars.insert(var.clone(), vardef); + } + matched_range + }) + }; + 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 { + self.recorder.missed_check(rx.as_str(), range); + None + }) + } +} + +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() { + 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)); + // 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#3 regex: Y=foo\n"); + } +} diff --git a/lib/filecheck/src/error.rs b/lib/filecheck/src/error.rs new file mode 100644 index 000000000000..1cc17001fc05 --- /dev/null +++ b/lib/filecheck/src/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/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs new file mode 100644 index 000000000000..f268620d11af --- /dev/null +++ b/lib/filecheck/src/explain.rs @@ -0,0 +1,202 @@ +//! 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, + 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); + writeln!(f, "> {}", &self.text[nextln..newln - 1])?; + curln = nextln; + nextln = newln; + } + + // Emit ~~~ under the part of the match in curln. + if m.is_match { + write!(f, " ")?; + let mend = min(m.range.1, nextln - 1); + for pos in curln..mend { + if pos < m.range.0 { + write!(f, " ") + } else if pos == m.range.0 { + write!(f, "^") + } else { + write!(f, "~") + }?; + } + writeln!(f, "")?; + } + + // Emit the match message itself. + 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; + } + writeln!(f, "Define {}={}", d.varname, d.value)?; + } + } + } + + // Emit trailing lines. + for line in self.text[nextln..].lines() { + 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/lib/filecheck/src/lib.rs b/lib/filecheck/src/lib.rs new file mode 100644 index 000000000000..8cca73736b4b --- /dev/null +++ b/lib/filecheck/src/lib.rs @@ -0,0 +1,252 @@ +//! 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. + +#![deny(missing_docs)] + +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; +mod explain; + +/// The range of a match in the input text. +pub type MatchRange = (usize, usize); diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs new file mode 100644 index 000000000000..7eb3c8fa3040 --- /dev/null +++ b/lib/filecheck/src/pattern.rs @@ -0,0 +1,518 @@ +//! 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, escape}; + +/// 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(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) = 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(&escape(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(&escape(&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]).unwrap(); + match vmap.lookup(var) { + None => { + return Err(Error::UndefVariable(format!("undefined variable ${}", var))) + } + Some(Value::Text(s)) => write!(out, "{})", escape(&s[..])).unwrap(), + Some(Value::Regex(rx)) => write!(out, "{})", 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(RegexBuilder::new(&out).multi_line(true).build()?) + } +} + +impl Display for Pattern { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for part in &self.parts { + use self::Part::*; + 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/lib/filecheck/src/variable.rs b/lib/filecheck/src/variable.rs new file mode 100644 index 000000000000..1a43f1428a12 --- /dev/null +++ b/lib/filecheck/src/variable.rs @@ -0,0 +1,62 @@ +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. +/// +/// 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<'a> { + /// Verbatim text. + Text(Cow<'a, str>), + /// Regular expression. + Regex(Cow<'a, str>), +} + +/// 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); + } +} diff --git a/lib/filecheck/tests/basic.rs b/lib/filecheck/tests/basic.rs new file mode 100644 index 000000000000..15e74788d7c2 --- /dev/null +++ b/lib/filecheck/tests/basic.rs @@ -0,0 +1,340 @@ +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/lib/frontend/Cargo.toml b/lib/frontend/Cargo.toml new file mode 100644 index 000000000000..7ff44a1f6f93 --- /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 000000000000..d81cee4bc7c8 --- /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, CallConv, 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(CallConv::Native); + 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 000000000000..10a06b23ccc2 --- /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, CallConv, 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(CallConv::Native); +//! 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 000000000000..fc5c85f1dd5f --- /dev/null +++ b/lib/frontend/src/ssa.rs @@ -0,0 +1,1277 @@ +//! 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, CursorBase, + 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::with_bits(0)) + } else if ty == F64 { + dfg.ins(&mut cur).f64const(Ieee64::with_bits(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, CursorBase, 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/lib/reader/Cargo.toml b/lib/reader/Cargo.toml new file mode 100644 index 000000000000..030d20ff9f6b --- /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/lib/reader/src/error.rs b/lib/reader/src/error.rs new file mode 100644 index 000000000000..c49e523b728b --- /dev/null +++ b/lib/reader/src/error.rs @@ -0,0 +1,48 @@ +//! 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, 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, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.location.line_number, self.message) + } +} + +/// 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. +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/lib/reader/src/isaspec.rs b/lib/reader/src/isaspec.rs new file mode 100644 index 000000000000..51748e284a97 --- /dev/null +++ b/lib/reader/src/isaspec.rs @@ -0,0 +1,61 @@ +//! 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>), +} + +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 +{ + for opt in iter.map(TestOption::new) { + match opt { + TestOption::Flag(name) => { + match config.enable(name) { + 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/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs new file mode 100644 index 000000000000..d4392a5bd9a4 --- /dev/null +++ b/lib/reader/src/lexer.rs @@ -0,0 +1,571 @@ + +// ====--------------------------------------------------------------------------------------====// +// +// Lexical analysis for .cton files. +// +// ====--------------------------------------------------------------------------------------====// + +use std::str::CharIndices; +use std::u16; +use std::ascii::AsciiExt; +use cretonne::ir::types; +use cretonne::ir::{Value, Ebb}; +use error::Location; + +/// 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, // '}' + LBracket, // '[' + RBracket, // ']' + Minus, // '-' + Comma, // ',' + Dot, // '.' + Colon, // ':' + Equal, // '=' + Arrow, // '->' + Float(&'a str), // Floating point immediate + Integer(&'a str), // Integer immediate + Type(types::Type), // i32, f32, b32x4, ... + Value(Value), // v12, v7 + Ebb(Ebb), // ebb3 + StackSlot(u32), // ss3 + 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, ...) +} + +/// 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, + 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, + 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() + .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. +/// +/// 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_ne!(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_ne!(self.lookahead, None); + self.next_ch(); + } + token(tok, loc) + } + + /// 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; + loop { + match self.next_ch() { + 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(); + 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 in 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. + 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); + } + } + } + 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(); + + assert!(self.lookahead == Some('_') || self.lookahead.unwrap().is_alphabetic()); + loop { + match self.next_ch() { + Some('_') => {} + Some(ch) if ch.is_alphanumeric() => {} + _ => break, + } + } + let text = &self.source[begin..self.pos]; + + // Look for numbered well-known entities like ebb15, v45, ... + 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, number: u32) -> Option> { + match prefix { + "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)), + "sig" => Some(Token::SigRef(number)), + _ => None, + } + } + + // Recognize a scalar or vector type. + 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] + } 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 { + if number <= u16::MAX as u32 { + base_type.by(number as u16).map(Token::Type) + } else { + None + } + } else { + Some(Token::Type(base_type)) + } + } + + fn scan_name(&mut self) -> Result, LocatedError> { + let loc = self.loc(); + let begin = self.pos + 1; + + assert_eq!(self.lookahead, Some('%')); + + while let Some(c) = self.next_ch() { + if !(c.is_ascii() && 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 + 1; + + assert_eq!(self.lookahead, Some('#')); + + while let Some(c) = self.next_ch() { + if !char::is_digit(c, 16) { + 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. + 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::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('+') => Some(self.scan_number()), + 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)) + } + }; + } + } +} + +#[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 })) + } + + 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 +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)); + 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(), token(Token::Integer("+5"), 1)); + assert_eq!(lex.next(), None); + } + + #[test] + fn lex_identifiers() { + 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::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::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)); + 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); + } + + #[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)); + } +} diff --git a/lib/reader/src/lib.rs b/lib/reader/src/lib.rs new file mode 100644 index 000000000000..61473afe3873 --- /dev/null +++ b/lib/reader/src/lib.rs @@ -0,0 +1,23 @@ +//! 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. + +#![deny(missing_docs)] + +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, Comment}; +pub use isaspec::IsaSpec; +pub use sourcemap::SourceMap; + +mod error; +mod lexer; +mod parser; +mod testcommand; +mod isaspec; +mod testfile; +mod sourcemap; diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs new file mode 100644 index 000000000000..fe2aec3e3859 --- /dev/null +++ b/lib/reader/src/parser.rs @@ -0,0 +1,2027 @@ + +// ====--------------------------------------------------------------------------------------====// +// +// Parser for .cton files. +// +// ====--------------------------------------------------------------------------------------====// + +use std::collections::HashMap; +use std::str::FromStr; +use std::{u16, u32}; +use std::mem; +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; +use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; +use cretonne::isa::{self, TargetIsa, Encoding, RegUnit}; +use cretonne::settings::{self, Configurable}; +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. +/// +/// 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 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> { + 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 = parser.parse_function_list(isa_spec.unique_isa())?; + + Ok(TestFile { + commands, + isa_spec, + preamble_comments, + functions, + }) +} + +pub struct Parser<'a> { + lex: Lexer<'a>, + + lex_error: Option, + + // Current lookahead token. + lookahead: Option>, + + // 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. +// +// 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<'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 + // 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<'a> Context<'a> { + fn new(f: Function, unique_isa: Option<&'a TargetIsa>) -> Context<'a> { + Context { + function: f, + map: SourceMap::new(), + aliases: HashMap::new(), + 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 + .encoding_info() + .names + .iter() + .position(|&name| name == recipe_name) + .map(|idx| idx as u16) + } else { + None + } + } + + // 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) + } + + // 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 + .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) + } + + // Resolve a reference to a jump table. + fn get_jt(&self, number: u32, loc: &Location) -> Result { + match self.map.get_jt(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(); + self.function.layout.append_ebb(ebb); + 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. + 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(); + 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)?; + } + } + } + + // Rewrite EBB references in jump tables. + for jt in self.function.jump_tables.keys() { + let loc = jt.into(); + for ebb_ref in self.function.jump_tables[jt].as_mut_slice() { + if let Some(mut ebb) = ebb_ref.expand() { + self.map.rewrite_ebb(&mut ebb, loc)?; + // Convert back to a packed option. + *ebb_ref = ebb.into(); + } + } + } + + 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 { + Parser { + lex: Lexer::new(text), + lex_error: None, + lookahead: None, + loc: Location { line_number: 0 }, + comment_entity: None, + comments: Vec::new(), + } + } + + // Consume the current lookahead token and return it. + fn consume(&mut self) -> Token<'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 { + match self.lex.next() { + Some(Ok(lexer::LocatedToken { token, location })) => { + match token { + Token::Comment(text) => { + // Gather comments, associate them with `comment_entity`. + if let Some(entity) = self.comment_entity { + self.comments.push(Comment { entity, text }); + } + } + _ => self.lookahead = Some(token), + } + self.loc = location; + } + Some(Err(lexer::LocatedError { error, location })) => { + self.lex_error = Some(error); + self.loc = location; + break; + } + None => break, + } + } + self.lookahead + } + + // Begin gathering comments associated with `entity`. + fn gather_comments>(&mut self, entity: E) { + 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()) + } + + // 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.loc, err_msg) + } + } + + // If the next token is a `want`, consume it, otherwise do nothing. + fn optional(&mut self, want: Token<'a>) -> bool { + if self.token() == Some(want) { + self.consume(); + true + } else { + false + } + } + + // 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.loc, err_msg) + } + } + + // 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.loc, 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.loc, err_msg) + } + } + + // 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() { + self.consume(); + Ok(jt) + } else { + err!(self.loc, "expected jump table number: jt«n»") + } + } + + // 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.loc, 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 { + if let Some(Token::Value(v)) = self.token() { + self.consume(); + Ok(v) + } else { + err!(self.loc, err_msg) + } + } + + fn error(&self, message: &str) -> Error { + Error { + location: self.loc, + message: message.to_string(), + } + } + + // 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.loc, err_msg) + } + } + + // Match and consume a u8 immediate. + // This is used for lane numbers in SIMD vectors. + 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. + // 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 i32 immediate. + // This is used for stack argument byte offsets. + 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 i32 to check for overflow and other issues. + text.parse() + .map_err(|_| self.error("expected i32 decimal immediate")) + } else { + err!(self.loc, err_msg) + } + } + + // 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 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() { + 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.loc, 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.loc, err_msg) + } + } + + // 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() { + self.consume(); + text.parse().map_err(|_| self.error(err_msg)) + } else { + err!(self.loc, err_msg) + } + } + + // 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() { + 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 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(); + while self.token() == Some(Token::Identifier("test")) { + list.push(TestCommand::new(self.consume_line())); + } + 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(); + + // 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 + .enable("enable_verifier") + .expect("Missing enable_verifier setting"); + + while let Some(Token::Identifier(command)) = self.token() { + match command { + "set" => { + last_set_loc = Some(self.loc); + isaspec::parse_options(self.consume_line().trim().split_whitespace(), + &mut flag_builder, + &self.loc)?; + } + "isa" => { + 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) { + 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)?; + + // Construct a trait object with the aggregate 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. + 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)?); + } + Ok(list) + } + + // Parse a whole function definition. + // + // function ::= * function-spec "{" preamble function-body "}" + // + 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(); + self.comments.clear(); + self.gather_comments(AnyEntity::Function); + + 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 "}" + self.match_token(Token::LBrace, "expected '{' before function body")?; + // function ::= function-spec "{" * preamble function-body "}" + self.parse_preamble(&mut ctx)?; + // function ::= function-spec "{" preamble * function-body "}" + self.parse_function_body(&mut ctx)?; + // function ::= function-spec "{" preamble 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); + self.token(); + self.comment_entity = None; + + // Rewrite references to values and EBBs after parsing everything to allow forward + // references. + ctx.rewrite_references()?; + + let details = Details { + location, + comments: self.take_comments(), + map: ctx.map, + }; + + Ok((ctx.function, details)) + } + + // Parse a function spec. + // + // function-spec ::= * "function" name 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; + + // function-spec ::= "function" * name signature + let name = self.parse_function_name()?; + + // function-spec ::= "function" name * signature + let sig = self.parse_signature(unique_isa)?; + + Ok((location, name, sig)) + } + + // Parse a function name. + // + // function ::= "function" * name signature { ... } + // + fn parse_function_name(&mut self) -> Result { + match self.token() { + 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"), + } + } + + // Parse a function signature. + // + // signature ::= * "(" [arglist] ")" ["->" retlist] [callconv] + // + fn parse_signature(&mut self, unique_isa: Option<&TargetIsa>) -> Result { + // 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] [callconv] + if self.token() != Some(Token::RPar) { + 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(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(); + } + + Ok(sig) + } + + // Parse list of function argument / return value types. + // + // arglist ::= * arg { "," arg } + // + 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(unique_isa)?); + + // arglist ::= arg * { "," arg } + while self.optional(Token::Comma) { + // arglist ::= arg { "," * arg } + list.push(self.parse_argument_type(unique_isa)?); + } + + Ok(list) + } + + // Parse a single argument type with flags. + 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 } [ argumentloc ] + while let Some(Token::Identifier(s)) = self.token() { + match s { + "uext" => arg.extension = ArgumentExtension::Uext, + "sext" => arg.extension = ArgumentExtension::Sext, + _ => { + if let Ok(purpose) = s.parse() { + arg.purpose = purpose; + } else { + break; + } + } + } + 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_imm32("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 } + // 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<()> { + loop { + 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, &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)) + } + 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() + .and_then(|(num, dat)| ctx.add_jt(num, dat, &self.loc)) + } + // More to come.. + _ => return Ok(()), + }?; + } + } + + // Parse a stack slot decl. + // + // 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 declaration")?; + let kind = self.match_enum("expected stack slot kind")?; + + // 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 { + return err!(self.loc, "negative stack slot size"); + } + if bytes > u32::MAX as i64 { + return err!(self.loc, "stack slot too large"); + } + 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)) + } + + // Parse a signature decl. + // + // 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")?; + let data = self.parse_signature(unique_isa)?; + 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 = 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) = 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 { + name, + signature: sigref, + } + } + Some(Token::SigRef(sig_src)) => { + let sig = ctx.get_sig(sig_src, &self.loc)?; + self.consume(); + let name = self.parse_function_name()?; + ExtFuncData { + 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} + fn parse_jump_table_decl(&mut self) -> Result<(u32, JumpTableData)> { + 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) = 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 } + // + fn parse_function_body(&mut self, ctx: &mut Context) -> Result<()> { + while self.token() != Some(Token::RBrace) { + self.parse_extended_basic_block(ctx)?; + } + Ok(()) + } + + // Parse an extended basic block, add contents to `ctx`. + // + // extended-basic-block ::= * ebb-header { instruction } + // ebb-header ::= Ebb(ebb) [ebb-args] ":" + // + fn parse_extended_basic_block(&mut self, ctx: &mut Context) -> Result<()> { + 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 ] ":" + self.parse_ebb_args(ctx, ebb)?; + self.match_token(Token::Colon, "expected ':' after EBB arguments")?; + } + + // extended-basic-block ::= ebb-header * { instruction } + while match self.token() { + Some(Token::Value(_)) => true, + Some(Token::Identifier(_)) => true, + Some(Token::LBracket) => true, + _ => false, + } { + 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(v) } + 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.is_empty() => return err!(self.loc, "expected -> or ="), + _ => self.parse_instruction(results, encoding, result_locations, ctx, ebb)?, + } + } + + Ok(()) + } + + // Parse parenthesized list of EBB arguments. Returns a vector of (u32, Type) pairs with the + // 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<()> { + // ebb-args ::= * "(" ebb-arg { "," ebb-arg } ")" + self.match_token(Token::LPar, "expected '(' before EBB arguments")?; + + // ebb-args ::= "(" * ebb-arg { "," ebb-arg } ")" + self.parse_ebb_arg(ctx, ebb)?; + + // ebb-args ::= "(" ebb-arg * { "," ebb-arg } ")" + while self.optional(Token::Comma) { + // ebb-args ::= "(" ebb-arg { "," * ebb-arg } ")" + self.parse_ebb_arg(ctx, ebb)?; + } + + // ebb-args ::= "(" ebb-arg { "," ebb-arg } * ")" + self.match_token(Token::RPar, "expected ')' after EBB arguments")?; + + Ok(()) + } + + // Parse a single EBB argument declaration, and append it to `ebb`. + // + // ebb-arg ::= * Value(v) ":" Type(t) + // + fn parse_ebb_arg(&mut self, ctx: &mut Context, ebb: Ebb) -> Result<()> { + // 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(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(v, value, &v_location) + } + + fn parse_value_location(&mut self, ctx: &Context) -> Result { + 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(); + 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"), + } + } + + fn parse_instruction_encoding(&mut self, + ctx: &Context) + -> Result<(Option, Option>)> { + let (mut encoding, mut result_locations) = (None, None); + + // encoding ::= "[" encoding_literal result_locations "]" + 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)); + } else if ctx.unique_isa.is_some() { + return err!(self.loc, "invalid instruction recipe"); + } else { + // We allow encodings to be specified when there's no unique ISA purely + // for convenience, eg when copy-pasting code for a test. + } + } + + // result_locations ::= ("," ( "-" | names ) )? + // names ::= Name { "," Name } + if self.optional(Token::Comma) { + let mut results = Vec::new(); + + results.push(self.parse_value_location(ctx)?); + while self.optional(Token::Comma) { + results.push(self.parse_value_location(ctx)?); + } + + result_locations = Some(results); + } + + self.match_token(Token::RBracket, + "expected ']' to terminate instruction encoding")?; + } + + Ok((encoding, result_locations)) + } + + // Parse instruction results and return them. + // + // 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(v) } + if let Some(Token::Value(v)) = self.token() { + self.consume(); + results.push(v); + + // inst-results ::= Value(v) * { "," Value(v) } + while self.optional(Token::Comma) { + // inst-results ::= Value(v) { "," * Value(v) } + results.push(self.match_value("expected result value")?); + } + } + + Ok(results) + } + + // Parse a value alias, and append it to `ebb`. + // + // value_alias ::= [inst-results] "->" Value(v) + // + fn parse_value_alias(&mut self, results: Vec, ctx: &mut Context) -> Result<()> { + if results.len() != 1 { + return err!(self.loc, "wrong number of aliases"); + } + let dest = self.match_value("expected value alias")?; + ctx.add_alias(results[0], dest, self.loc) + } + + // 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() { + Ok(opc) => opc, + Err(msg) => return err!(self.loc, "{}: '{}'", msg, text), + } + } else { + return err!(self.loc, "expected instruction opcode"); + }; + let opcode_loc = self.loc; + self.consume(); + + // Look for a controlling type variable annotation. + // instruction ::= [inst-results "="] Opcode(opc) * ["." Type] ... + let explicit_ctrl_type = if self.optional(Token::Dot) { + Some(self.match_type("expected type after 'opcode.'")?) + } else { + None + }; + + // instruction ::= [inst-results "="] Opcode(opc) ["." Type] * ... + 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 = 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); + 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", + num_results, + 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.into_iter(), + ctx.function.dfg.inst_results(inst).iter().cloned())?; + + if let Some(result_locations) = result_locations { + for (&value, loc) in ctx.function + .dfg + .inst_results(inst) + .iter() + .zip(result_locations) { + *ctx.function.locations.ensure(value) = loc; + } + } + + 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(&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. + // 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) { + 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.loc, "{} does not take a typevar", opcode); + } + } + + Ok(ctrl_type) + } + + // Add mappings for a list of source values to their corresponding new values. + 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) { + map.def_value(src, val, &self.loc)?; + } + 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(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 = self.parse_value_list()?; + + 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. + fn parse_inst_operands(&mut self, + ctx: &mut Context, + opcode: Opcode) + -> Result { + let idata = match opcode.format() { + InstructionFormat::Nullary => InstructionData::Nullary { opcode }, + InstructionFormat::Unary => { + InstructionData::Unary { + opcode, + arg: self.match_value("expected SSA value operand")?, + } + } + InstructionFormat::UnaryImm => { + InstructionData::UnaryImm { + opcode, + imm: self.match_imm64("expected immediate integer operand")?, + } + } + InstructionFormat::UnaryIeee32 => { + InstructionData::UnaryIeee32 { + opcode, + imm: self.match_ieee32("expected immediate 32-bit float operand")?, + } + } + InstructionFormat::UnaryIeee64 => { + InstructionData::UnaryIeee64 { + opcode, + 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")?; + let rhs = self.match_value("expected SSA value second operand")?; + InstructionData::Binary { + opcode, + args: [lhs, rhs], + } + } + InstructionFormat::BinaryImm => { + 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, + arg: lhs, + imm: rhs, + } + } + InstructionFormat::Ternary => { + // Names here refer to the `select` instruction. + // This format is also use by `fma`. + 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, + args: [ctrl_arg, true_arg, false_arg], + } + } + InstructionFormat::MultiAry => { + let args = self.parse_value_list()?; + InstructionData::MultiAry { + opcode, + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), + } + } + InstructionFormat::Jump => { + // Parse the destination EBB number. Don't translate source to local numbers yet. + let ebb_num = self.match_ebb("expected jump destination EBB")?; + let args = self.parse_opt_value_list()?; + InstructionData::Jump { + opcode, + destination: ebb_num, + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), + } + } + InstructionFormat::Branch => { + 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, + destination: ebb_num, + args: args.into_value_list(&[ctrl_arg], &mut ctx.function.dfg.value_lists), + } + } + InstructionFormat::BranchIcmp => { + let cond = self.match_enum("expected intcc condition code")?; + 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, + 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")?; + 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, + lane, + args: [lhs, rhs], + } + } + InstructionFormat::ExtractLane => { + 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, lane, arg } + } + InstructionFormat::IntCompare => { + let cond = self.match_enum("expected intcc condition code")?; + 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, + cond, + args: [lhs, rhs], + } + } + InstructionFormat::IntCompareImm => { + let cond = self.match_enum("expected intcc condition code")?; + 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, + cond, + arg: lhs, + imm: rhs, + } + } + InstructionFormat::FloatCompare => { + let cond = self.match_enum("expected floatcc condition code")?; + 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, + cond, + args: [lhs, rhs], + } + } + InstructionFormat::Call => { + 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, + func_ref, + args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), + } + } + InstructionFormat::IndirectCall => { + 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, + sig_ref, + args: args.into_value_list(&[callee], &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")?; + let table = self.match_jt().and_then(|num| ctx.get_jt(num, &self.loc))?; + 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, + stack_slot: ss, + 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, + arg, + stack_slot: ss, + offset, + } + } + InstructionFormat::HeapLoad => { + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_uoffset32()?; + InstructionData::HeapLoad { + opcode, + arg: addr, + 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, + args: [arg, addr], + 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, + flags, + arg: addr, + 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, + flags, + args: [arg, addr], + 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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cretonne::ir::{CallConv, ArgumentExtension, ArgumentPurpose}; + use cretonne::ir::types; + use cretonne::ir::StackSlotKind; + use cretonne::ir::entities::AnyEntity; + use testfile::{Details, Comment}; + use isaspec::IsaSpec; + use error::Error; + + #[test] + fn argument_type() { + let mut p = Parser::new("i32 sext"); + let arg = p.parse_argument_type(None).unwrap(); + assert_eq!(arg.value_type, types::I32); + assert_eq!(arg.extension, ArgumentExtension::Sext); + 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"); + } + + #[test] + fn aliases() { + let (func, details) = Parser::new("function %qux() native { + ebb0: + v4 = iconst.i8 6 + 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(), "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] + fn signature() { + 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 spiderwasm") + .parse_signature(None) + .unwrap(); + assert_eq!(sig2.to_string(), + "(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") + .parse_signature(None) + .unwrap_err() + .to_string(), + "1: expected argument type"); + 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(), + "1: expected ')' after function arguments"); + } + + #[test] + fn stack_slot_decl() { + let (func, _) = Parser::new("function %foo() native { + ss3 = incoming_arg 13 + ss1 = spill_slot 1 + }") + .parse_function(None) + .unwrap(); + 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"); + 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() native { + ss1 = spill_slot 13 + ss1 = spill_slot 1 + }") + .parse_function(None) + .unwrap_err() + .to_string(), + "3: duplicate stack slot: ss1"); + } + + #[test] + fn ebb_header() { + let (func, _) = Parser::new("function %ebbs() native { + ebb0: + ebb4(v3: i32): + }") + .parse_function(None) + .unwrap(); + assert_eq!(func.name.to_string(), "%ebbs"); + + let mut ebbs = func.layout.ebbs(); + + let ebb0 = ebbs.next().unwrap(); + assert_eq!(func.dfg.ebb_args(ebb0), &[]); + + let ebb4 = ebbs.next().unwrap(); + 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] + fn comments() { + let (func, Details { comments, .. }) = Parser::new("; before + function %comment() native { ; decl + ss10 = outgoing_arg 13 ; stackslot. + ; Still stackslot. + jt10 = jump_table ebb0 + ; Jumptable + ebb0: ; Basic block + trap ; Instruction + } ; Trailing. + ; More trailing.") + .parse_function(None) + .unwrap(); + assert_eq!(func.name.to_string(), "%comment"); + assert_eq!(comments.len(), 8); // no 'before' comment. + assert_eq!(comments[0], + Comment { + entity: AnyEntity::Function, + text: "; decl", + }); + 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!(comments[5].entity.to_string(), "inst0"); + assert_eq!(comments[5].text, "; Instruction"); + + assert_eq!(comments[6].entity, AnyEntity::Function); + assert_eq!(comments[7].entity, AnyEntity::Function); + } + + #[test] + fn test_file() { + let tf = parse_test("; before + test cfg option=5 + test verify + set enable_float=false + ; still preamble + function %comment() native {}") + .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_verifier()); + 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"); + } + + #[test] + #[cfg(build_riscv)] + fn isa_spec() { + assert!(parse_test("isa + function %foo() native {}") + .is_err()); + + assert!(parse_test("isa riscv + set enable_float=false + function %foo() native {}") + .is_err()); + + match parse_test("set enable_float=false + isa riscv + function %foo() native {}") + .unwrap() + .isa_spec { + IsaSpec::None(_) => panic!("Expected some ISA"), + IsaSpec::Some(v) => { + assert_eq!(v.len(), 1); + assert_eq!(v[0].name(), "riscv"); + } + } + } + + #[test] + fn binary_function_name() { + // Valid characters in the name. + let func = Parser::new("function #1234567890AbCdEf() native { + 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() 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() native { + ebb0: + trap + }"); + assert!(parser.parse_function(None).is_err()); + + // Empty binary function name should be valid. + let func = Parser::new("function #() native { + 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 new file mode 100644 index 000000000000..d590a5220f2a --- /dev/null +++ b/lib/reader/src/sourcemap.rs @@ -0,0 +1,243 @@ +//! 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, SigRef, FuncRef}; +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)] +pub struct SourceMap { + values: HashMap, // vNN + ebbs: HashMap, // ebbNN + stack_slots: HashMap, // ssNN + signatures: HashMap, // sigNN + functions: HashMap, // fnNN + jump_tables: HashMap, // jtNN + + // Store locations for entities, including instructions. + locations: HashMap, +} + +/// 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 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() + } + + /// 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::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. + /// 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: AnyEntity) -> Result<()> { + match self.get_ebb(*ebb) { + Some(new) => { + *ebb = new; + Ok(()) + } + None => { + err!(self.location(loc).unwrap_or_default(), + "undefined reference: {}", + ebb) + } + } + } + + /// Rewrite a value reference. + pub fn rewrite_value(&self, val: &mut Value, loc: AnyEntity) -> Result<()> { + match self.get_value(*val) { + Some(new) => { + *val = new; + Ok(()) + } + 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: AnyEntity) -> Result<()> { + for val in vals { + self.rewrite_value(val, loc)?; + } + Ok(()) + } +} + + +/// 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_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 + /// whose numbers never appear in source, or implicitly defined signatures. + fn def_entity(&mut self, entity: AnyEntity, loc: &Location) -> Result<()>; +} + +impl MutableSourceMap for SourceMap { + fn new() -> SourceMap { + SourceMap { + values: HashMap::new(), + ebbs: HashMap::new(), + stack_slots: HashMap::new(), + signatures: HashMap::new(), + functions: 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 { + 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 { + 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 { + self.def_entity(entity.into(), loc) + } + } + + 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) + } else { + self.def_entity(entity.into(), loc) + } + } + + fn def_entity(&mut self, entity: AnyEntity, loc: &Location) -> Result<()> { + if self.locations.insert(entity, *loc).is_some() { + err!(loc, "duplicate entity: {}", entity) + } else { + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use parse_test; + + #[test] + fn details() { + let tf = parse_test("function %detail() { + ss10 = incoming_arg 13 + jt10 = jump_table ebb0 + ebb0(v4: i32, v7: i32): + v10 = iadd v4, v7 + }") + .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(), "v0"); + assert_eq!(map.lookup_str("v7").unwrap().to_string(), "v1"); + assert_eq!(map.lookup_str("v10").unwrap().to_string(), "v2"); + } +} diff --git a/lib/reader/src/testcommand.rs b/lib/reader/src/testcommand.rs new file mode 100644 index 000000000000..a147487c77b8 --- /dev/null +++ b/lib/reader/src/testcommand.rs @@ -0,0 +1,101 @@ +//! 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}; + +/// 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(""); + 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 { + write!(f, "{}", self.command)?; + for opt in &self.options { + write!(f, " {}", opt)?; + } + writeln!(f, "") + } +} + +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), + 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"); + } +} diff --git a/lib/reader/src/testfile.rs b/lib/reader/src/testfile.rs new file mode 100644 index 000000000000..fcb11590e873 --- /dev/null +++ b/lib/reader/src/testfile.rs @@ -0,0 +1,58 @@ +//! 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; +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 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, + /// 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>)>, +} + +/// 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 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, +} + +/// 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> { + /// 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, +} diff --git a/lib/wasm2cretonne-util/Cargo.toml b/lib/wasm2cretonne-util/Cargo.toml new file mode 100644 index 000000000000..f709e4afacfd --- /dev/null +++ b/lib/wasm2cretonne-util/Cargo.toml @@ -0,0 +1,25 @@ +[package] +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" +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 000000000000..a3f63c05c6cf --- /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.wast b/lib/wasm2cretonne-util/filetests/arith.wast new file mode 100644 index 000000000000..fa7115696b61 --- /dev/null +++ b/lib/wasm2cretonne-util/filetests/arith.wast @@ -0,0 +1,13 @@ +(module + (memory 1) + (func $main (local i32) + (set_local 0 (i32.sub (i32.const 4) (i32.const 4))) + (if + (get_local 0) + (then unreachable) + (else (drop (i32.mul (i32.const 6) (get_local 0)))) + ) + ) + (start $main) + (data (i32.const 0) "abcdefgh") +) diff --git a/lib/wasm2cretonne-util/filetests/call.wast b/lib/wasm2cretonne-util/filetests/call.wast new file mode 100644 index 000000000000..e8640d2342a4 --- /dev/null +++ b/lib/wasm2cretonne-util/filetests/call.wast @@ -0,0 +1,10 @@ +(module + (func $main (local i32) + (set_local 0 (i32.const 0)) + (drop (call $inc)) + ) + (func $inc (result i32) + (i32.const 1) + ) + (start $main) +) diff --git a/lib/wasm2cretonne-util/filetests/fibonacci.wast b/lib/wasm2cretonne-util/filetests/fibonacci.wast new file mode 100644 index 000000000000..1788a467ca0f --- /dev/null +++ b/lib/wasm2cretonne-util/filetests/fibonacci.wast @@ -0,0 +1,22 @@ +(module + (memory 1) + (func $main (local i32 i32 i32 i32) + (set_local 0 (i32.const 0)) + (set_local 1 (i32.const 1)) + (set_local 2 (i32.const 1)) + (set_local 3 (i32.const 0)) + (block + (loop + (br_if 1 (i32.gt_s (get_local 0) (i32.const 5))) + (set_local 3 (get_local 2)) + (set_local 2 (i32.add (get_local 2) (get_local 1))) + (set_local 1 (get_local 3)) + (set_local 0 (i32.add (get_local 0) (i32.const 1))) + (br 0) + ) + ) + (i32.store (i32.const 0) (get_local 2)) + ) + (start $main) + (data (i32.const 0) "0000") +) diff --git a/lib/wasm2cretonne-util/filetests/globals.wast b/lib/wasm2cretonne-util/filetests/globals.wast new file mode 100644 index 000000000000..646e5f0f453d --- /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.wast b/lib/wasm2cretonne-util/filetests/memory.wast new file mode 100644 index 000000000000..0c81bad174de --- /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/src/main.rs b/lib/wasm2cretonne-util/src/main.rs new file mode 100644 index 000000000000..8a1c7c95d394 --- /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/.gitignore b/lib/wasm2cretonne/.gitignore new file mode 100644 index 000000000000..4308d822046d --- /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 000000000000..e954c7a7a919 --- /dev/null +++ b/lib/wasm2cretonne/Cargo.toml @@ -0,0 +1,13 @@ +[package] +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" +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 000000000000..b1647a9aee84 --- /dev/null +++ b/lib/wasm2cretonne/src/code_translator.rs @@ -0,0 +1,1385 @@ +//! 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 i = control_stack.len() - 1; + let (destination, return_values, branch_inst) = match &control_stack[i] { + &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 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()); + // 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 = { + 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(); + 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 000000000000..5006e5c9aa41 --- /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 000000000000..1c226553001e --- /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 000000000000..5969c0ee8f45 --- /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 000000000000..9f2a41d4f922 --- /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 000000000000..24e98b0d5834 --- /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 000000000000..c9954e00c623 --- /dev/null +++ b/lib/wasm2cretonne/src/sections_translator.rs @@ -0,0 +1,372 @@ +//! 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 000000000000..840e568787e4 --- /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/wasm2cretonne/tests/testsuite.rs b/lib/wasm2cretonne/tests/testsuite.rs new file mode 100644 index 000000000000..bbad41d56b3d --- /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/testsuite/address.wast.0.wasm b/lib/wasm2cretonne/testsuite/address.wast.0.wasm new file mode 100644 index 000000000000..bf81db7cc8f1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/address.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/binary.wast.0.wasm b/lib/wasm2cretonne/testsuite/binary.wast.0.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/binary.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/binary.wast.1.wasm b/lib/wasm2cretonne/testsuite/binary.wast.1.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/binary.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/binary.wast.2.wasm b/lib/wasm2cretonne/testsuite/binary.wast.2.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/binary.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/binary.wast.3.wasm b/lib/wasm2cretonne/testsuite/binary.wast.3.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/binary.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/block.wast.0.wasm b/lib/wasm2cretonne/testsuite/block.wast.0.wasm new file mode 100644 index 000000000000..a5f96555a0c1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/block.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/br.wast.0.wasm b/lib/wasm2cretonne/testsuite/br.wast.0.wasm new file mode 100644 index 000000000000..f4131e83b634 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/br.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/br_if.wast.0.wasm b/lib/wasm2cretonne/testsuite/br_if.wast.0.wasm new file mode 100644 index 000000000000..3c91d8350b51 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/br_if.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/br_table.wast.0.wasm b/lib/wasm2cretonne/testsuite/br_table.wast.0.wasm new file mode 100644 index 000000000000..09b895d2b020 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/br_table.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/break-drop.wast.0.wasm b/lib/wasm2cretonne/testsuite/break-drop.wast.0.wasm new file mode 100644 index 000000000000..57a108d47861 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/break-drop.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/call.wast.0.wasm b/lib/wasm2cretonne/testsuite/call.wast.0.wasm new file mode 100644 index 000000000000..83a7f7921767 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/call.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/call_indirect.wast.0.wasm b/lib/wasm2cretonne/testsuite/call_indirect.wast.0.wasm new file mode 100644 index 000000000000..974170480d6e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/call_indirect.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/comments.wast.0.wasm b/lib/wasm2cretonne/testsuite/comments.wast.0.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/comments.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/comments.wast.1.wasm b/lib/wasm2cretonne/testsuite/comments.wast.1.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/comments.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/comments.wast.2.wasm b/lib/wasm2cretonne/testsuite/comments.wast.2.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/comments.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/comments.wast.3.wasm b/lib/wasm2cretonne/testsuite/comments.wast.3.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/comments.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/conversions.wast.0.wasm b/lib/wasm2cretonne/testsuite/conversions.wast.0.wasm new file mode 100644 index 000000000000..ddebe20d3da3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/conversions.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/custom_section.wast.0.wasm b/lib/wasm2cretonne/testsuite/custom_section.wast.0.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/custom_section.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/custom_section.wast.1.wasm b/lib/wasm2cretonne/testsuite/custom_section.wast.1.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/custom_section.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/custom_section.wast.2.wasm b/lib/wasm2cretonne/testsuite/custom_section.wast.2.wasm new file mode 100644 index 000000000000..8b19588df228 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/custom_section.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/endianness.wast.0.wasm b/lib/wasm2cretonne/testsuite/endianness.wast.0.wasm new file mode 100644 index 000000000000..d26c99d06ca8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/endianness.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.0.wasm b/lib/wasm2cretonne/testsuite/exports.wast.0.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.1.wasm b/lib/wasm2cretonne/testsuite/exports.wast.1.wasm new file mode 100644 index 000000000000..505f4fe7a4c9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.10.wasm b/lib/wasm2cretonne/testsuite/exports.wast.10.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.10.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.11.wasm b/lib/wasm2cretonne/testsuite/exports.wast.11.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.11.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.18.wasm b/lib/wasm2cretonne/testsuite/exports.wast.18.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.18.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.19.wasm b/lib/wasm2cretonne/testsuite/exports.wast.19.wasm new file mode 100644 index 000000000000..f1bae6aa2461 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.19.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.2.wasm b/lib/wasm2cretonne/testsuite/exports.wast.2.wasm new file mode 100644 index 000000000000..0ec4916edc57 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.20.wasm b/lib/wasm2cretonne/testsuite/exports.wast.20.wasm new file mode 100644 index 000000000000..3c2410be16b0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.20.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.21.wasm b/lib/wasm2cretonne/testsuite/exports.wast.21.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.21.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.22.wasm b/lib/wasm2cretonne/testsuite/exports.wast.22.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.22.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.23.wasm b/lib/wasm2cretonne/testsuite/exports.wast.23.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.23.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.24.wasm b/lib/wasm2cretonne/testsuite/exports.wast.24.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.24.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.25.wasm b/lib/wasm2cretonne/testsuite/exports.wast.25.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.25.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.26.wasm b/lib/wasm2cretonne/testsuite/exports.wast.26.wasm new file mode 100644 index 000000000000..a2f90fe1130c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.26.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.27.wasm b/lib/wasm2cretonne/testsuite/exports.wast.27.wasm new file mode 100644 index 000000000000..a9893a2a7e14 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.27.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.28.wasm b/lib/wasm2cretonne/testsuite/exports.wast.28.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.28.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.29.wasm b/lib/wasm2cretonne/testsuite/exports.wast.29.wasm new file mode 100644 index 000000000000..d8fc92d022fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.29.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.3.wasm b/lib/wasm2cretonne/testsuite/exports.wast.3.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.36.wasm b/lib/wasm2cretonne/testsuite/exports.wast.36.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.36.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.37.wasm b/lib/wasm2cretonne/testsuite/exports.wast.37.wasm new file mode 100644 index 000000000000..1f2cfccf74d6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.37.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.38.wasm b/lib/wasm2cretonne/testsuite/exports.wast.38.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.38.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.39.wasm b/lib/wasm2cretonne/testsuite/exports.wast.39.wasm new file mode 100644 index 000000000000..48fb7293d128 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.39.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.4.wasm b/lib/wasm2cretonne/testsuite/exports.wast.4.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.40.wasm b/lib/wasm2cretonne/testsuite/exports.wast.40.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.40.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.41.wasm b/lib/wasm2cretonne/testsuite/exports.wast.41.wasm new file mode 100644 index 000000000000..48fb7293d128 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.41.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.42.wasm b/lib/wasm2cretonne/testsuite/exports.wast.42.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.42.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.43.wasm b/lib/wasm2cretonne/testsuite/exports.wast.43.wasm new file mode 100644 index 000000000000..48fb7293d128 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.43.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.44.wasm b/lib/wasm2cretonne/testsuite/exports.wast.44.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.44.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.45.wasm b/lib/wasm2cretonne/testsuite/exports.wast.45.wasm new file mode 100644 index 000000000000..48fb7293d128 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.45.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.46.wasm b/lib/wasm2cretonne/testsuite/exports.wast.46.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.46.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.47.wasm b/lib/wasm2cretonne/testsuite/exports.wast.47.wasm new file mode 100644 index 000000000000..48fb7293d128 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.47.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.48.wasm b/lib/wasm2cretonne/testsuite/exports.wast.48.wasm new file mode 100644 index 000000000000..539a89bf190a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.48.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.49.wasm b/lib/wasm2cretonne/testsuite/exports.wast.49.wasm new file mode 100644 index 000000000000..48fb7293d128 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.49.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.5.wasm b/lib/wasm2cretonne/testsuite/exports.wast.5.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.55.wasm b/lib/wasm2cretonne/testsuite/exports.wast.55.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.55.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.56.wasm b/lib/wasm2cretonne/testsuite/exports.wast.56.wasm new file mode 100644 index 000000000000..6696dc1f5fc9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.56.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.57.wasm b/lib/wasm2cretonne/testsuite/exports.wast.57.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.57.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.58.wasm b/lib/wasm2cretonne/testsuite/exports.wast.58.wasm new file mode 100644 index 000000000000..f6bda9d18c5c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.58.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.59.wasm b/lib/wasm2cretonne/testsuite/exports.wast.59.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.59.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.6.wasm b/lib/wasm2cretonne/testsuite/exports.wast.6.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.60.wasm b/lib/wasm2cretonne/testsuite/exports.wast.60.wasm new file mode 100644 index 000000000000..f6bda9d18c5c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.60.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.61.wasm b/lib/wasm2cretonne/testsuite/exports.wast.61.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.61.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.62.wasm b/lib/wasm2cretonne/testsuite/exports.wast.62.wasm new file mode 100644 index 000000000000..f6bda9d18c5c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.62.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.63.wasm b/lib/wasm2cretonne/testsuite/exports.wast.63.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.63.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.64.wasm b/lib/wasm2cretonne/testsuite/exports.wast.64.wasm new file mode 100644 index 000000000000..f6bda9d18c5c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.64.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.65.wasm b/lib/wasm2cretonne/testsuite/exports.wast.65.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.65.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.66.wasm b/lib/wasm2cretonne/testsuite/exports.wast.66.wasm new file mode 100644 index 000000000000..f6bda9d18c5c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.66.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.67.wasm b/lib/wasm2cretonne/testsuite/exports.wast.67.wasm new file mode 100644 index 000000000000..2981c2461cd6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.67.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.68.wasm b/lib/wasm2cretonne/testsuite/exports.wast.68.wasm new file mode 100644 index 000000000000..f6bda9d18c5c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.68.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.7.wasm b/lib/wasm2cretonne/testsuite/exports.wast.7.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.7.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.8.wasm b/lib/wasm2cretonne/testsuite/exports.wast.8.wasm new file mode 100644 index 000000000000..fff82363ca62 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/exports.wast.9.wasm b/lib/wasm2cretonne/testsuite/exports.wast.9.wasm new file mode 100644 index 000000000000..615bb04c6f31 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/exports.wast.9.wasm differ diff --git a/lib/wasm2cretonne/testsuite/f32.wast.0.wasm b/lib/wasm2cretonne/testsuite/f32.wast.0.wasm new file mode 100644 index 000000000000..9bde5ff26087 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/f32.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/f32_bitwise.wast.0.wasm b/lib/wasm2cretonne/testsuite/f32_bitwise.wast.0.wasm new file mode 100644 index 000000000000..76817cfe134b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/f32_bitwise.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/f32_cmp.wast.0.wasm b/lib/wasm2cretonne/testsuite/f32_cmp.wast.0.wasm new file mode 100644 index 000000000000..54042ae83968 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/f32_cmp.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/f64.wast.0.wasm b/lib/wasm2cretonne/testsuite/f64.wast.0.wasm new file mode 100644 index 000000000000..69c3e69ca451 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/f64.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/f64_bitwise.wast.0.wasm b/lib/wasm2cretonne/testsuite/f64_bitwise.wast.0.wasm new file mode 100644 index 000000000000..8c71ce01a4ac Binary files /dev/null and b/lib/wasm2cretonne/testsuite/f64_bitwise.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/f64_cmp.wast.0.wasm b/lib/wasm2cretonne/testsuite/f64_cmp.wast.0.wasm new file mode 100644 index 000000000000..db5c2f565fb6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/f64_cmp.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/fac.wast.0.wasm b/lib/wasm2cretonne/testsuite/fac.wast.0.wasm new file mode 100644 index 000000000000..9c3af8127a32 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/fac.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.0.wasm new file mode 100644 index 000000000000..93fc9ddbc27a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.1.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.1.wasm new file mode 100644 index 000000000000..638a2124f4b5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.10.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.10.wasm new file mode 100644 index 000000000000..63bf9ab6eaa0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.10.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.11.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.11.wasm new file mode 100644 index 000000000000..f459847b969c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.11.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.12.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.12.wasm new file mode 100644 index 000000000000..b3480b8159af Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.12.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.13.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.13.wasm new file mode 100644 index 000000000000..109d98ede939 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.13.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.14.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.14.wasm new file mode 100644 index 000000000000..6ec49ca9fa77 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.14.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.15.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.15.wasm new file mode 100644 index 000000000000..c01271abfa65 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.15.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.16.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.16.wasm new file mode 100644 index 000000000000..ce28899f480c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.16.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.17.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.17.wasm new file mode 100644 index 000000000000..40f19d071088 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.17.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.18.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.18.wasm new file mode 100644 index 000000000000..ed67b7aff309 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.18.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.19.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.19.wasm new file mode 100644 index 000000000000..3cb6e639805a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.19.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.2.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.2.wasm new file mode 100644 index 000000000000..a33c750123d8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.20.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.20.wasm new file mode 100644 index 000000000000..054222cdcfcd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.20.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.21.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.21.wasm new file mode 100644 index 000000000000..b0bf7a0a4dea Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.21.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.22.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.22.wasm new file mode 100644 index 000000000000..4d5a0ac68eba Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.22.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.23.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.23.wasm new file mode 100644 index 000000000000..fe96aa795bf5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.23.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.24.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.24.wasm new file mode 100644 index 000000000000..37810664073f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.24.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.25.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.25.wasm new file mode 100644 index 000000000000..57ab86234e67 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.25.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.26.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.26.wasm new file mode 100644 index 000000000000..8d41cd60ff9e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.26.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.27.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.27.wasm new file mode 100644 index 000000000000..a5b348cb7dd9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.27.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.28.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.28.wasm new file mode 100644 index 000000000000..72c035d96fae Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.28.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.29.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.29.wasm new file mode 100644 index 000000000000..9e81c03093af Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.29.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.3.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.3.wasm new file mode 100644 index 000000000000..58fb0986884a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.30.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.30.wasm new file mode 100644 index 000000000000..093e0789a177 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.30.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.31.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.31.wasm new file mode 100644 index 000000000000..f03455aa23f6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.31.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.32.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.32.wasm new file mode 100644 index 000000000000..710445bc00a9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.32.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.33.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.33.wasm new file mode 100644 index 000000000000..54a29b9df641 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.33.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.34.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.34.wasm new file mode 100644 index 000000000000..a1a99c443ead Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.34.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.35.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.35.wasm new file mode 100644 index 000000000000..c3abad956d3e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.35.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.36.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.36.wasm new file mode 100644 index 000000000000..59ffbb5d8765 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.36.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.37.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.37.wasm new file mode 100644 index 000000000000..d1cd0f57efd0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.37.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.38.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.38.wasm new file mode 100644 index 000000000000..ca28d5e4d1e1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.38.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.39.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.39.wasm new file mode 100644 index 000000000000..20d1715bd5bd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.39.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.4.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.4.wasm new file mode 100644 index 000000000000..8c4561e20503 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.40.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.40.wasm new file mode 100644 index 000000000000..d363f76fb0a4 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.40.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.41.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.41.wasm new file mode 100644 index 000000000000..c6b9e24fb7b9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.41.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.42.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.42.wasm new file mode 100644 index 000000000000..f194fc1498db Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.42.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.43.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.43.wasm new file mode 100644 index 000000000000..ce2b73f7abac Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.43.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.44.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.44.wasm new file mode 100644 index 000000000000..9650b94d6ef6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.44.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.45.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.45.wasm new file mode 100644 index 000000000000..d8d69bbb208f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.45.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.46.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.46.wasm new file mode 100644 index 000000000000..b01765562240 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.46.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.47.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.47.wasm new file mode 100644 index 000000000000..33dc257ff272 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.47.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.48.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.48.wasm new file mode 100644 index 000000000000..5be5f22c3aff Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.48.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.49.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.49.wasm new file mode 100644 index 000000000000..b754cc9d95f8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.49.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.5.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.5.wasm new file mode 100644 index 000000000000..20ec463aa516 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.50.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.50.wasm new file mode 100644 index 000000000000..3fd1baca4966 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.50.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.51.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.51.wasm new file mode 100644 index 000000000000..f39c98d057f1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.51.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.52.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.52.wasm new file mode 100644 index 000000000000..61021fcf69b5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.52.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.53.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.53.wasm new file mode 100644 index 000000000000..4e3bde4866ae Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.53.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.54.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.54.wasm new file mode 100644 index 000000000000..1572cc4298e3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.54.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.55.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.55.wasm new file mode 100644 index 000000000000..fa832af0e493 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.55.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.56.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.56.wasm new file mode 100644 index 000000000000..ef8bec6e156a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.56.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.57.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.57.wasm new file mode 100644 index 000000000000..15959e632928 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.57.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.58.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.58.wasm new file mode 100644 index 000000000000..b8870a873c7c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.58.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.59.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.59.wasm new file mode 100644 index 000000000000..1184279628c3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.59.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.6.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.6.wasm new file mode 100644 index 000000000000..9ba8b8660cfb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.60.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.60.wasm new file mode 100644 index 000000000000..2387d2d98225 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.60.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.61.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.61.wasm new file mode 100644 index 000000000000..c17811c135b9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.61.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.62.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.62.wasm new file mode 100644 index 000000000000..3e6561f5f4e9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.62.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.63.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.63.wasm new file mode 100644 index 000000000000..fba3bcf97217 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.63.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.64.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.64.wasm new file mode 100644 index 000000000000..ca400ce4cbd3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.64.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.65.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.65.wasm new file mode 100644 index 000000000000..f5ddcf462e2d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.65.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.66.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.66.wasm new file mode 100644 index 000000000000..13ba4d5a5493 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.66.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.67.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.67.wasm new file mode 100644 index 000000000000..5b0cb31b8002 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.67.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.68.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.68.wasm new file mode 100644 index 000000000000..39d64d72d6e8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.68.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.69.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.69.wasm new file mode 100644 index 000000000000..410dfbc269d1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.69.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.7.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.7.wasm new file mode 100644 index 000000000000..76b83081891d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.7.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.70.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.70.wasm new file mode 100644 index 000000000000..05c684522cd5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.70.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.71.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.71.wasm new file mode 100644 index 000000000000..fe18d15da7ae Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.71.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.72.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.72.wasm new file mode 100644 index 000000000000..eef3768648d8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.72.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.73.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.73.wasm new file mode 100644 index 000000000000..abbf750b1841 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.73.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.74.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.74.wasm new file mode 100644 index 000000000000..add2c0ab20d3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.74.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.75.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.75.wasm new file mode 100644 index 000000000000..315d6494e24c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.75.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.76.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.76.wasm new file mode 100644 index 000000000000..10b819568083 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.76.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.77.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.77.wasm new file mode 100644 index 000000000000..18103680c671 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.77.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.78.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.78.wasm new file mode 100644 index 000000000000..c25f9c43af0b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.78.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.79.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.79.wasm new file mode 100644 index 000000000000..687aad0de19a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.79.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.8.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.8.wasm new file mode 100644 index 000000000000..bac9a68a7761 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.80.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.80.wasm new file mode 100644 index 000000000000..226f08ce9b18 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.80.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.81.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.81.wasm new file mode 100644 index 000000000000..e6489b12ee5f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.81.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.82.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.82.wasm new file mode 100644 index 000000000000..b0ba33f992bc Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.82.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.83.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.83.wasm new file mode 100644 index 000000000000..b34801139900 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.83.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.84.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.84.wasm new file mode 100644 index 000000000000..413a38416431 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.84.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.85.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.85.wasm new file mode 100644 index 000000000000..7f542c9297aa Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.85.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.86.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.86.wasm new file mode 100644 index 000000000000..f8608e0c5977 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.86.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.87.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.87.wasm new file mode 100644 index 000000000000..8bc5cd88ff1b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.87.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.88.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.88.wasm new file mode 100644 index 000000000000..66f2bfa69966 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.88.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.89.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.89.wasm new file mode 100644 index 000000000000..7fefd64cbeba Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.89.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.9.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.9.wasm new file mode 100644 index 000000000000..1f070302c4d6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.9.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.90.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.90.wasm new file mode 100644 index 000000000000..7e53886a4c20 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.90.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.91.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.91.wasm new file mode 100644 index 000000000000..97da0e6c0445 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.91.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.92.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.92.wasm new file mode 100644 index 000000000000..be60329b8321 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.92.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.93.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.93.wasm new file mode 100644 index 000000000000..da5cc6ed34e9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.93.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.94.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.94.wasm new file mode 100644 index 000000000000..7403a43dc747 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.94.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_exprs.wast.95.wasm b/lib/wasm2cretonne/testsuite/float_exprs.wast.95.wasm new file mode 100644 index 000000000000..5933e6544e26 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_exprs.wast.95.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_literals.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_literals.wast.0.wasm new file mode 100644 index 000000000000..7ce5d39c8355 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_literals.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_memory.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.0.wasm new file mode 100644 index 000000000000..05258ff16bc8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_memory.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_memory.wast.1.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.1.wasm new file mode 100644 index 000000000000..c46b327da3b2 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_memory.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_memory.wast.2.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.2.wasm new file mode 100644 index 000000000000..9174f1c7b776 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_memory.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_memory.wast.3.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.3.wasm new file mode 100644 index 000000000000..3e56542a049e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_memory.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_memory.wast.4.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.4.wasm new file mode 100644 index 000000000000..587914ae1244 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_memory.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_memory.wast.5.wasm b/lib/wasm2cretonne/testsuite/float_memory.wast.5.wasm new file mode 100644 index 000000000000..d384d39e3007 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_memory.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/float_misc.wast.0.wasm b/lib/wasm2cretonne/testsuite/float_misc.wast.0.wasm new file mode 100644 index 000000000000..351164b0a8ff Binary files /dev/null and b/lib/wasm2cretonne/testsuite/float_misc.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/forward.wast.0.wasm b/lib/wasm2cretonne/testsuite/forward.wast.0.wasm new file mode 100644 index 000000000000..7ac7c552c444 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/forward.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/func.wast.0.wasm b/lib/wasm2cretonne/testsuite/func.wast.0.wasm new file mode 100644 index 000000000000..cda073522633 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/func.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/func_ptrs.wast.0.wasm b/lib/wasm2cretonne/testsuite/func_ptrs.wast.0.wasm new file mode 100644 index 000000000000..8916eb257909 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/func_ptrs.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/func_ptrs.wast.8.wasm b/lib/wasm2cretonne/testsuite/func_ptrs.wast.8.wasm new file mode 100644 index 000000000000..6826a72c9a9b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/func_ptrs.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/func_ptrs.wast.9.wasm b/lib/wasm2cretonne/testsuite/func_ptrs.wast.9.wasm new file mode 100644 index 000000000000..f77e78a8a96d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/func_ptrs.wast.9.wasm differ diff --git a/lib/wasm2cretonne/testsuite/get_local.wast.0.wasm b/lib/wasm2cretonne/testsuite/get_local.wast.0.wasm new file mode 100644 index 000000000000..cb39060eec6a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/get_local.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/globals.wast.0.wasm b/lib/wasm2cretonne/testsuite/globals.wast.0.wasm new file mode 100644 index 000000000000..653d8dfbd4bf Binary files /dev/null and b/lib/wasm2cretonne/testsuite/globals.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/globals.wast.16.wasm b/lib/wasm2cretonne/testsuite/globals.wast.16.wasm new file mode 100644 index 000000000000..4418d5180438 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/globals.wast.16.wasm differ diff --git a/lib/wasm2cretonne/testsuite/globals.wast.19.wasm b/lib/wasm2cretonne/testsuite/globals.wast.19.wasm new file mode 100644 index 000000000000..f634698e6855 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/globals.wast.19.wasm differ diff --git a/lib/wasm2cretonne/testsuite/i32.wast.0.wasm b/lib/wasm2cretonne/testsuite/i32.wast.0.wasm new file mode 100644 index 000000000000..b516c4dfd026 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/i32.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/i64.wast.0.wasm b/lib/wasm2cretonne/testsuite/i64.wast.0.wasm new file mode 100644 index 000000000000..1eeba31df4f1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/i64.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/if.wast.0.wasm b/lib/wasm2cretonne/testsuite/if.wast.0.wasm new file mode 100644 index 000000000000..49e2b1ad8fcd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/if.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.0.wasm b/lib/wasm2cretonne/testsuite/imports.wast.0.wasm new file mode 100644 index 000000000000..57fd691a022b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.1.wasm b/lib/wasm2cretonne/testsuite/imports.wast.1.wasm new file mode 100644 index 000000000000..44cafbbedaba Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.10.wasm b/lib/wasm2cretonne/testsuite/imports.wast.10.wasm new file mode 100644 index 000000000000..2f5d2a6a7471 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.10.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.11.wasm b/lib/wasm2cretonne/testsuite/imports.wast.11.wasm new file mode 100644 index 000000000000..fd7a0a796545 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.11.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.12.wasm b/lib/wasm2cretonne/testsuite/imports.wast.12.wasm new file mode 100644 index 000000000000..a67a230aa167 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.12.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.13.wasm b/lib/wasm2cretonne/testsuite/imports.wast.13.wasm new file mode 100644 index 000000000000..fc0333420aac Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.13.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.14.wasm b/lib/wasm2cretonne/testsuite/imports.wast.14.wasm new file mode 100644 index 000000000000..1b10aa7138f0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.14.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.15.wasm b/lib/wasm2cretonne/testsuite/imports.wast.15.wasm new file mode 100644 index 000000000000..1d576d9bd1ad Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.15.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.16.wasm b/lib/wasm2cretonne/testsuite/imports.wast.16.wasm new file mode 100644 index 000000000000..01e697f21c78 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.16.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.17.wasm b/lib/wasm2cretonne/testsuite/imports.wast.17.wasm new file mode 100644 index 000000000000..885d5bd9b212 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.17.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.18.wasm b/lib/wasm2cretonne/testsuite/imports.wast.18.wasm new file mode 100644 index 000000000000..d6647f6b58fb Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.18.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.19.wasm b/lib/wasm2cretonne/testsuite/imports.wast.19.wasm new file mode 100644 index 000000000000..55f8407e748c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.19.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.2.wasm b/lib/wasm2cretonne/testsuite/imports.wast.2.wasm new file mode 100644 index 000000000000..767dd4cab0f2 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.20.wasm b/lib/wasm2cretonne/testsuite/imports.wast.20.wasm new file mode 100644 index 000000000000..83d154c9d5f4 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.20.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.21.wasm b/lib/wasm2cretonne/testsuite/imports.wast.21.wasm new file mode 100644 index 000000000000..6072362cc608 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.21.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.22.wasm b/lib/wasm2cretonne/testsuite/imports.wast.22.wasm new file mode 100644 index 000000000000..dd6531f10bf2 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.22.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.23.wasm b/lib/wasm2cretonne/testsuite/imports.wast.23.wasm new file mode 100644 index 000000000000..1104039dad71 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.23.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.24.wasm b/lib/wasm2cretonne/testsuite/imports.wast.24.wasm new file mode 100644 index 000000000000..211ade86ecc2 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.24.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.25.wasm b/lib/wasm2cretonne/testsuite/imports.wast.25.wasm new file mode 100644 index 000000000000..76bf13a6c0d6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.25.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.26.wasm b/lib/wasm2cretonne/testsuite/imports.wast.26.wasm new file mode 100644 index 000000000000..dd4e01d42dc6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.26.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.27.wasm b/lib/wasm2cretonne/testsuite/imports.wast.27.wasm new file mode 100644 index 000000000000..23488f40a877 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.27.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.28.wasm b/lib/wasm2cretonne/testsuite/imports.wast.28.wasm new file mode 100644 index 000000000000..7b046aaa672c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.28.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.29.wasm b/lib/wasm2cretonne/testsuite/imports.wast.29.wasm new file mode 100644 index 000000000000..552abb20d175 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.29.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.3.wasm b/lib/wasm2cretonne/testsuite/imports.wast.3.wasm new file mode 100644 index 000000000000..8d7f4e4af60e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.30.wasm b/lib/wasm2cretonne/testsuite/imports.wast.30.wasm new file mode 100644 index 000000000000..c2c821468ffd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.30.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.31.wasm b/lib/wasm2cretonne/testsuite/imports.wast.31.wasm new file mode 100644 index 000000000000..638e16846124 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.31.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.32.wasm b/lib/wasm2cretonne/testsuite/imports.wast.32.wasm new file mode 100644 index 000000000000..89bfb0a9bf05 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.32.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.33.wasm b/lib/wasm2cretonne/testsuite/imports.wast.33.wasm new file mode 100644 index 000000000000..ab0911f6fab0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.33.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.34.wasm b/lib/wasm2cretonne/testsuite/imports.wast.34.wasm new file mode 100644 index 000000000000..e382fa9b0f27 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.34.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.35.wasm b/lib/wasm2cretonne/testsuite/imports.wast.35.wasm new file mode 100644 index 000000000000..bf0e2b152842 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.35.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.36.wasm b/lib/wasm2cretonne/testsuite/imports.wast.36.wasm new file mode 100644 index 000000000000..0bd2ff71c436 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.36.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.37.wasm b/lib/wasm2cretonne/testsuite/imports.wast.37.wasm new file mode 100644 index 000000000000..82f7f25d0a18 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.37.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.38.wasm b/lib/wasm2cretonne/testsuite/imports.wast.38.wasm new file mode 100644 index 000000000000..cd5a9c959fed Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.38.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.39.wasm b/lib/wasm2cretonne/testsuite/imports.wast.39.wasm new file mode 100644 index 000000000000..4760358307c0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.39.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.4.wasm b/lib/wasm2cretonne/testsuite/imports.wast.4.wasm new file mode 100644 index 000000000000..5c1ae26e52f8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.40.wasm b/lib/wasm2cretonne/testsuite/imports.wast.40.wasm new file mode 100644 index 000000000000..826872e84a01 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.40.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.41.wasm b/lib/wasm2cretonne/testsuite/imports.wast.41.wasm new file mode 100644 index 000000000000..ea0cd6bbf237 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.41.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.42.wasm b/lib/wasm2cretonne/testsuite/imports.wast.42.wasm new file mode 100644 index 000000000000..29af0f908e0b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.42.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.43.wasm b/lib/wasm2cretonne/testsuite/imports.wast.43.wasm new file mode 100644 index 000000000000..ee499e9268a7 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.43.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.44.wasm b/lib/wasm2cretonne/testsuite/imports.wast.44.wasm new file mode 100644 index 000000000000..27aa8136efee Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.44.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.45.wasm b/lib/wasm2cretonne/testsuite/imports.wast.45.wasm new file mode 100644 index 000000000000..27aa8136efee Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.45.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.49.wasm b/lib/wasm2cretonne/testsuite/imports.wast.49.wasm new file mode 100644 index 000000000000..3cf0083692a2 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.49.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.5.wasm b/lib/wasm2cretonne/testsuite/imports.wast.5.wasm new file mode 100644 index 000000000000..07b93fb1a414 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.50.wasm b/lib/wasm2cretonne/testsuite/imports.wast.50.wasm new file mode 100644 index 000000000000..d4cfcd9b5066 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.50.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.51.wasm b/lib/wasm2cretonne/testsuite/imports.wast.51.wasm new file mode 100644 index 000000000000..e0ce1decbd0d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.51.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.52.wasm b/lib/wasm2cretonne/testsuite/imports.wast.52.wasm new file mode 100644 index 000000000000..27d3f4d906dc Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.52.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.53.wasm b/lib/wasm2cretonne/testsuite/imports.wast.53.wasm new file mode 100644 index 000000000000..431bc7e67dcd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.53.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.54.wasm b/lib/wasm2cretonne/testsuite/imports.wast.54.wasm new file mode 100644 index 000000000000..1e25eb25498d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.54.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.55.wasm b/lib/wasm2cretonne/testsuite/imports.wast.55.wasm new file mode 100644 index 000000000000..8c61b5bd7da3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.55.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.56.wasm b/lib/wasm2cretonne/testsuite/imports.wast.56.wasm new file mode 100644 index 000000000000..2a6b158a1aa9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.56.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.57.wasm b/lib/wasm2cretonne/testsuite/imports.wast.57.wasm new file mode 100644 index 000000000000..a098343f396b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.57.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.58.wasm b/lib/wasm2cretonne/testsuite/imports.wast.58.wasm new file mode 100644 index 000000000000..bf2cd933c840 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.58.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.59.wasm b/lib/wasm2cretonne/testsuite/imports.wast.59.wasm new file mode 100644 index 000000000000..60cf1991dd29 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.59.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.6.wasm b/lib/wasm2cretonne/testsuite/imports.wast.6.wasm new file mode 100644 index 000000000000..10e75e56a39e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.60.wasm b/lib/wasm2cretonne/testsuite/imports.wast.60.wasm new file mode 100644 index 000000000000..0d52289f71b3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.60.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.61.wasm b/lib/wasm2cretonne/testsuite/imports.wast.61.wasm new file mode 100644 index 000000000000..c3e0390f5380 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.61.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.62.wasm b/lib/wasm2cretonne/testsuite/imports.wast.62.wasm new file mode 100644 index 000000000000..9f17b38fc32d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.62.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.63.wasm b/lib/wasm2cretonne/testsuite/imports.wast.63.wasm new file mode 100644 index 000000000000..62d775f65df1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.63.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.64.wasm b/lib/wasm2cretonne/testsuite/imports.wast.64.wasm new file mode 100644 index 000000000000..8130164ea59c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.64.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.65.wasm b/lib/wasm2cretonne/testsuite/imports.wast.65.wasm new file mode 100644 index 000000000000..68ee3ca26ff0 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.65.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.66.wasm b/lib/wasm2cretonne/testsuite/imports.wast.66.wasm new file mode 100644 index 000000000000..ac4bc9f248e8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.66.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.67.wasm b/lib/wasm2cretonne/testsuite/imports.wast.67.wasm new file mode 100644 index 000000000000..86d66cffd967 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.67.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.68.wasm b/lib/wasm2cretonne/testsuite/imports.wast.68.wasm new file mode 100644 index 000000000000..5101c52d66b7 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.68.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.69.wasm b/lib/wasm2cretonne/testsuite/imports.wast.69.wasm new file mode 100644 index 000000000000..ad4cf5e00699 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.69.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.7.wasm b/lib/wasm2cretonne/testsuite/imports.wast.7.wasm new file mode 100644 index 000000000000..0cca06035971 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.7.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.70.wasm b/lib/wasm2cretonne/testsuite/imports.wast.70.wasm new file mode 100644 index 000000000000..551315ad8233 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.70.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.71.wasm b/lib/wasm2cretonne/testsuite/imports.wast.71.wasm new file mode 100644 index 000000000000..551315ad8233 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.71.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.75.wasm b/lib/wasm2cretonne/testsuite/imports.wast.75.wasm new file mode 100644 index 000000000000..01e7c8deb2f9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.75.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.76.wasm b/lib/wasm2cretonne/testsuite/imports.wast.76.wasm new file mode 100644 index 000000000000..a1146c50ab67 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.76.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.77.wasm b/lib/wasm2cretonne/testsuite/imports.wast.77.wasm new file mode 100644 index 000000000000..b966a19092e8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.77.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.78.wasm b/lib/wasm2cretonne/testsuite/imports.wast.78.wasm new file mode 100644 index 000000000000..a7db97093ea5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.78.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.79.wasm b/lib/wasm2cretonne/testsuite/imports.wast.79.wasm new file mode 100644 index 000000000000..19592e8b9dd8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.79.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.8.wasm b/lib/wasm2cretonne/testsuite/imports.wast.8.wasm new file mode 100644 index 000000000000..843504ae3d46 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.80.wasm b/lib/wasm2cretonne/testsuite/imports.wast.80.wasm new file mode 100644 index 000000000000..9c81a86595df Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.80.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.81.wasm b/lib/wasm2cretonne/testsuite/imports.wast.81.wasm new file mode 100644 index 000000000000..4d0cb1a9d4f6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.81.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.82.wasm b/lib/wasm2cretonne/testsuite/imports.wast.82.wasm new file mode 100644 index 000000000000..0fd00827c512 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.82.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.83.wasm b/lib/wasm2cretonne/testsuite/imports.wast.83.wasm new file mode 100644 index 000000000000..1d55b9bcff67 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.83.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.84.wasm b/lib/wasm2cretonne/testsuite/imports.wast.84.wasm new file mode 100644 index 000000000000..6f794ae33525 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.84.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.85.wasm b/lib/wasm2cretonne/testsuite/imports.wast.85.wasm new file mode 100644 index 000000000000..20ed7a1956b9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.85.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.86.wasm b/lib/wasm2cretonne/testsuite/imports.wast.86.wasm new file mode 100644 index 000000000000..969740f29a7a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.86.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.87.wasm b/lib/wasm2cretonne/testsuite/imports.wast.87.wasm new file mode 100644 index 000000000000..bd3ae5ba7108 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.87.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.88.wasm b/lib/wasm2cretonne/testsuite/imports.wast.88.wasm new file mode 100644 index 000000000000..877a2e563d46 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.88.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.89.wasm b/lib/wasm2cretonne/testsuite/imports.wast.89.wasm new file mode 100644 index 000000000000..1acf047dd57f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.89.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.9.wasm b/lib/wasm2cretonne/testsuite/imports.wast.9.wasm new file mode 100644 index 000000000000..2a48ca29583c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.9.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.90.wasm b/lib/wasm2cretonne/testsuite/imports.wast.90.wasm new file mode 100644 index 000000000000..3c7cc625f820 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.90.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.91.wasm b/lib/wasm2cretonne/testsuite/imports.wast.91.wasm new file mode 100644 index 000000000000..788006a40848 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.91.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.92.wasm b/lib/wasm2cretonne/testsuite/imports.wast.92.wasm new file mode 100644 index 000000000000..de0a76f947b6 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.92.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.93.wasm b/lib/wasm2cretonne/testsuite/imports.wast.93.wasm new file mode 100644 index 000000000000..e9bd718944ea Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.93.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.94.wasm b/lib/wasm2cretonne/testsuite/imports.wast.94.wasm new file mode 100644 index 000000000000..4dd394f853b5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.94.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.95.wasm b/lib/wasm2cretonne/testsuite/imports.wast.95.wasm new file mode 100644 index 000000000000..7cc4a3cba000 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.95.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.96.wasm b/lib/wasm2cretonne/testsuite/imports.wast.96.wasm new file mode 100644 index 000000000000..877a2e563d46 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.96.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.97.wasm b/lib/wasm2cretonne/testsuite/imports.wast.97.wasm new file mode 100644 index 000000000000..1acf047dd57f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.97.wasm differ diff --git a/lib/wasm2cretonne/testsuite/imports.wast.98.wasm b/lib/wasm2cretonne/testsuite/imports.wast.98.wasm new file mode 100644 index 000000000000..2b92957d1027 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/imports.wast.98.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.0.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.0.wasm new file mode 100644 index 000000000000..363ee730eb2b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.1.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.1.wasm new file mode 100644 index 000000000000..b764f351817e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.10.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.10.wasm new file mode 100644 index 000000000000..f83f6b8d3dc8 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.10.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.11.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.11.wasm new file mode 100644 index 000000000000..ceaf7098597f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.11.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.12.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.12.wasm new file mode 100644 index 000000000000..64199159e199 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.12.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.13.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.13.wasm new file mode 100644 index 000000000000..d07ab17528f5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.13.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.14.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.14.wasm new file mode 100644 index 000000000000..0df895aa4634 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.14.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.15.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.15.wasm new file mode 100644 index 000000000000..ef7d53fd6c88 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.15.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.16.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.16.wasm new file mode 100644 index 000000000000..498f0b557fdf Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.16.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.17.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.17.wasm new file mode 100644 index 000000000000..b0e10e0028c4 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.17.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.18.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.18.wasm new file mode 100644 index 000000000000..5efa19fa749a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.18.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.2.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.2.wasm new file mode 100644 index 000000000000..4e4b5e48775c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.3.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.3.wasm new file mode 100644 index 000000000000..df82315c1fab Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.4.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.4.wasm new file mode 100644 index 000000000000..0ac751201949 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.5.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.5.wasm new file mode 100644 index 000000000000..8321359ba15d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.6.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.6.wasm new file mode 100644 index 000000000000..d29c13d51577 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.7.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.7.wasm new file mode 100644 index 000000000000..c988103c7fc5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.7.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.8.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.8.wasm new file mode 100644 index 000000000000..16204e74da7b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_exprs.wast.9.wasm b/lib/wasm2cretonne/testsuite/int_exprs.wast.9.wasm new file mode 100644 index 000000000000..4b9734ec295c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_exprs.wast.9.wasm differ diff --git a/lib/wasm2cretonne/testsuite/int_literals.wast.0.wasm b/lib/wasm2cretonne/testsuite/int_literals.wast.0.wasm new file mode 100644 index 000000000000..ff5a26f1ed4d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/int_literals.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/labels.wast.0.wasm b/lib/wasm2cretonne/testsuite/labels.wast.0.wasm new file mode 100644 index 000000000000..6e893c43b875 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/labels.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/left-to-right.wast.0.wasm b/lib/wasm2cretonne/testsuite/left-to-right.wast.0.wasm new file mode 100644 index 000000000000..29a2475c866e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/left-to-right.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.0.wasm b/lib/wasm2cretonne/testsuite/linking.wast.0.wasm new file mode 100644 index 000000000000..f9137a286a8e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.1.wasm b/lib/wasm2cretonne/testsuite/linking.wast.1.wasm new file mode 100644 index 000000000000..8d2a6f57629d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.15.wasm b/lib/wasm2cretonne/testsuite/linking.wast.15.wasm new file mode 100644 index 000000000000..4616241323dd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.15.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.16.wasm b/lib/wasm2cretonne/testsuite/linking.wast.16.wasm new file mode 100644 index 000000000000..5d1cf1eb973c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.16.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.17.wasm b/lib/wasm2cretonne/testsuite/linking.wast.17.wasm new file mode 100644 index 000000000000..f61d36722bbc Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.17.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.2.wasm b/lib/wasm2cretonne/testsuite/linking.wast.2.wasm new file mode 100644 index 000000000000..2aba3e42d79a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.3.wasm b/lib/wasm2cretonne/testsuite/linking.wast.3.wasm new file mode 100644 index 000000000000..d5e336c80da5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.4.wasm b/lib/wasm2cretonne/testsuite/linking.wast.4.wasm new file mode 100644 index 000000000000..8a7332cd59f1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.5.wasm b/lib/wasm2cretonne/testsuite/linking.wast.5.wasm new file mode 100644 index 000000000000..0b76a6338a2f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.6.wasm b/lib/wasm2cretonne/testsuite/linking.wast.6.wasm new file mode 100644 index 000000000000..7b2e142a3bf4 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.7.wasm b/lib/wasm2cretonne/testsuite/linking.wast.7.wasm new file mode 100644 index 000000000000..39ea09809a19 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.7.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.8.wasm b/lib/wasm2cretonne/testsuite/linking.wast.8.wasm new file mode 100644 index 000000000000..231032f16f0c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/linking.wast.9.wasm b/lib/wasm2cretonne/testsuite/linking.wast.9.wasm new file mode 100644 index 000000000000..c881d376680b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/linking.wast.9.wasm differ diff --git a/lib/wasm2cretonne/testsuite/loop.wast.0.wasm b/lib/wasm2cretonne/testsuite/loop.wast.0.wasm new file mode 100644 index 000000000000..3e69bf667d73 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/loop.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.0.wasm b/lib/wasm2cretonne/testsuite/memory.wast.0.wasm new file mode 100644 index 000000000000..93b6da766285 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.1.wasm b/lib/wasm2cretonne/testsuite/memory.wast.1.wasm new file mode 100644 index 000000000000..e47e0cf8d471 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.15.wasm b/lib/wasm2cretonne/testsuite/memory.wast.15.wasm new file mode 100644 index 000000000000..6591570f72fe Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.15.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.2.wasm b/lib/wasm2cretonne/testsuite/memory.wast.2.wasm new file mode 100644 index 000000000000..64ffe0388be9 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.3.wasm b/lib/wasm2cretonne/testsuite/memory.wast.3.wasm new file mode 100644 index 000000000000..d3e641614685 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.39.wasm b/lib/wasm2cretonne/testsuite/memory.wast.39.wasm new file mode 100644 index 000000000000..77ba1f041e07 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.39.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.40.wasm b/lib/wasm2cretonne/testsuite/memory.wast.40.wasm new file mode 100644 index 000000000000..64197c7ffb5b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.40.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.41.wasm b/lib/wasm2cretonne/testsuite/memory.wast.41.wasm new file mode 100644 index 000000000000..8b535ce73c7d Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.41.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.49.wasm b/lib/wasm2cretonne/testsuite/memory.wast.49.wasm new file mode 100644 index 000000000000..60ae51fd8b28 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.49.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.50.wasm b/lib/wasm2cretonne/testsuite/memory.wast.50.wasm new file mode 100644 index 000000000000..ebf4341625bc Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.50.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.51.wasm b/lib/wasm2cretonne/testsuite/memory.wast.51.wasm new file mode 100644 index 000000000000..2da5f3408824 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.51.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.52.wasm b/lib/wasm2cretonne/testsuite/memory.wast.52.wasm new file mode 100644 index 000000000000..0d6f3fe08a55 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.52.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.6.wasm b/lib/wasm2cretonne/testsuite/memory.wast.6.wasm new file mode 100644 index 000000000000..f9528a2f711b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.62.wasm b/lib/wasm2cretonne/testsuite/memory.wast.62.wasm new file mode 100644 index 000000000000..b59b22057569 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.62.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory.wast.8.wasm b/lib/wasm2cretonne/testsuite/memory.wast.8.wasm new file mode 100644 index 000000000000..1d5ecb7b107e Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory_redundancy.wast.0.wasm b/lib/wasm2cretonne/testsuite/memory_redundancy.wast.0.wasm new file mode 100644 index 000000000000..ac41ccc6b046 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory_redundancy.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory_trap.wast.0.wasm b/lib/wasm2cretonne/testsuite/memory_trap.wast.0.wasm new file mode 100644 index 000000000000..7171e8856684 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory_trap.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/memory_trap.wast.1.wasm b/lib/wasm2cretonne/testsuite/memory_trap.wast.1.wasm new file mode 100644 index 000000000000..c4e7acd77eec Binary files /dev/null and b/lib/wasm2cretonne/testsuite/memory_trap.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/names.wast.0.wasm b/lib/wasm2cretonne/testsuite/names.wast.0.wasm new file mode 100644 index 000000000000..6e34bb08ade7 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/names.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/names.wast.1.wasm b/lib/wasm2cretonne/testsuite/names.wast.1.wasm new file mode 100644 index 000000000000..c6ac4b4d2613 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/names.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/names.wast.2.wasm b/lib/wasm2cretonne/testsuite/names.wast.2.wasm new file mode 100644 index 000000000000..cadc3c568dff Binary files /dev/null and b/lib/wasm2cretonne/testsuite/names.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/names.wast.3.wasm b/lib/wasm2cretonne/testsuite/names.wast.3.wasm new file mode 100644 index 000000000000..bb835113839a Binary files /dev/null and b/lib/wasm2cretonne/testsuite/names.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/nop.wast.0.wasm b/lib/wasm2cretonne/testsuite/nop.wast.0.wasm new file mode 100644 index 000000000000..c59a8cd83c39 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/nop.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/reloc.wasm b/lib/wasm2cretonne/testsuite/reloc.wasm new file mode 100644 index 000000000000..4f18ef6c98dc Binary files /dev/null and b/lib/wasm2cretonne/testsuite/reloc.wasm differ diff --git a/lib/wasm2cretonne/testsuite/resizing.wast.0.wasm b/lib/wasm2cretonne/testsuite/resizing.wast.0.wasm new file mode 100644 index 000000000000..44fec0b34207 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/resizing.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/resizing.wast.1.wasm b/lib/wasm2cretonne/testsuite/resizing.wast.1.wasm new file mode 100644 index 000000000000..45e0f4e5fa2b Binary files /dev/null and b/lib/wasm2cretonne/testsuite/resizing.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/resizing.wast.2.wasm b/lib/wasm2cretonne/testsuite/resizing.wast.2.wasm new file mode 100644 index 000000000000..01cee6f1eea3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/resizing.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/return.wast.0.wasm b/lib/wasm2cretonne/testsuite/return.wast.0.wasm new file mode 100644 index 000000000000..72ef8c5b7cc5 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/return.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/select.wast.0.wasm b/lib/wasm2cretonne/testsuite/select.wast.0.wasm new file mode 100644 index 000000000000..fceb0289bcbc Binary files /dev/null and b/lib/wasm2cretonne/testsuite/select.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/set_local.wast.0.wasm b/lib/wasm2cretonne/testsuite/set_local.wast.0.wasm new file mode 100644 index 000000000000..6f60f4507b55 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/set_local.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/simple.wasm b/lib/wasm2cretonne/testsuite/simple.wasm new file mode 100644 index 000000000000..4e7c773ae2c4 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/simple.wasm differ diff --git a/lib/wasm2cretonne/testsuite/skip-stack-guard-page.wast.0.wasm b/lib/wasm2cretonne/testsuite/skip-stack-guard-page.wast.0.wasm new file mode 100644 index 000000000000..79d487b41b60 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/skip-stack-guard-page.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/stack.wast.0.wasm b/lib/wasm2cretonne/testsuite/stack.wast.0.wasm new file mode 100644 index 000000000000..1b639cfdcf54 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/stack.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/start.wast.3.wasm b/lib/wasm2cretonne/testsuite/start.wast.3.wasm new file mode 100644 index 000000000000..21b795c5e456 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/start.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/start.wast.4.wasm b/lib/wasm2cretonne/testsuite/start.wast.4.wasm new file mode 100644 index 000000000000..21b795c5e456 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/start.wast.4.wasm differ diff --git a/lib/wasm2cretonne/testsuite/start.wast.5.wasm b/lib/wasm2cretonne/testsuite/start.wast.5.wasm new file mode 100644 index 000000000000..7a4b81947e28 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/start.wast.5.wasm differ diff --git a/lib/wasm2cretonne/testsuite/start.wast.6.wasm b/lib/wasm2cretonne/testsuite/start.wast.6.wasm new file mode 100644 index 000000000000..fccdf74aba15 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/start.wast.6.wasm differ diff --git a/lib/wasm2cretonne/testsuite/start.wast.7.wasm b/lib/wasm2cretonne/testsuite/start.wast.7.wasm new file mode 100644 index 000000000000..55403c53b4cf Binary files /dev/null and b/lib/wasm2cretonne/testsuite/start.wast.7.wasm differ diff --git a/lib/wasm2cretonne/testsuite/start.wast.8.wasm b/lib/wasm2cretonne/testsuite/start.wast.8.wasm new file mode 100644 index 000000000000..c9c64ba2c372 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/start.wast.8.wasm differ diff --git a/lib/wasm2cretonne/testsuite/switch.wast.0.wasm b/lib/wasm2cretonne/testsuite/switch.wast.0.wasm new file mode 100644 index 000000000000..b20ce9f154b3 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/switch.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/tee_local.wast.0.wasm b/lib/wasm2cretonne/testsuite/tee_local.wast.0.wasm new file mode 100644 index 000000000000..c7490e67c6e1 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/tee_local.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/traps.wast.0.wasm b/lib/wasm2cretonne/testsuite/traps.wast.0.wasm new file mode 100644 index 000000000000..6b36d5c04320 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/traps.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/traps.wast.1.wasm b/lib/wasm2cretonne/testsuite/traps.wast.1.wasm new file mode 100644 index 000000000000..31d416dfdffd Binary files /dev/null and b/lib/wasm2cretonne/testsuite/traps.wast.1.wasm differ diff --git a/lib/wasm2cretonne/testsuite/traps.wast.2.wasm b/lib/wasm2cretonne/testsuite/traps.wast.2.wasm new file mode 100644 index 000000000000..74e1fe5bf30c Binary files /dev/null and b/lib/wasm2cretonne/testsuite/traps.wast.2.wasm differ diff --git a/lib/wasm2cretonne/testsuite/traps.wast.3.wasm b/lib/wasm2cretonne/testsuite/traps.wast.3.wasm new file mode 100644 index 000000000000..6d98e644425f Binary files /dev/null and b/lib/wasm2cretonne/testsuite/traps.wast.3.wasm differ diff --git a/lib/wasm2cretonne/testsuite/unreachable.wast.0.wasm b/lib/wasm2cretonne/testsuite/unreachable.wast.0.wasm new file mode 100644 index 000000000000..1615bb904352 Binary files /dev/null and b/lib/wasm2cretonne/testsuite/unreachable.wast.0.wasm differ diff --git a/lib/wasm2cretonne/testsuite/unwind.wast.0.wasm b/lib/wasm2cretonne/testsuite/unwind.wast.0.wasm new file mode 100644 index 000000000000..6057472111db Binary files /dev/null and b/lib/wasm2cretonne/testsuite/unwind.wast.0.wasm differ diff --git a/lib/wasmstandalone/.gitignore b/lib/wasmstandalone/.gitignore new file mode 100644 index 000000000000..4308d822046d --- /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 000000000000..a8e3cc283bdb --- /dev/null +++ b/lib/wasmstandalone/Cargo.toml @@ -0,0 +1,14 @@ +[package] +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" } +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 000000000000..9d8a3f28396d --- /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 000000000000..75479a33a51d --- /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 000000000000..1c3fd79bf8fa --- /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] + } +} diff --git a/misc/vim/ftdetect/cton.vim b/misc/vim/ftdetect/cton.vim new file mode 100644 index 000000000000..9d7754c47284 --- /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 000000000000..d34574215404 --- /dev/null +++ b/misc/vim/syntax/cton.vim @@ -0,0 +1,42 @@ +" Vim syntax file +" Language: Cretonne +" Maintainer: Jakob Stoklund Olesen / +syn match ctonEntity /\<\(v\|ss\|jt\|fn\|sig\)\d\+\>/ +syn match ctonLabel /\/ +syn match ctonName /%\w\+\>/ + +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 + +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 ctonName String +hi def link ctonNumber Number +hi def link ctonHexSeq Number +hi def link ctonCommentLine Comment +hi def link ctonFilecheck SpecialComment + +let b:current_syntax = "cton" diff --git a/src/cat.rs b/src/cat.rs new file mode 100644 index 000000000000..a53d3b4294ca --- /dev/null +++ b/src/cat.rs @@ -0,0 +1,68 @@ +//! 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 std::borrow::Cow; +use cretonne::ir::Function; +use cton_reader::{parse_functions, TestCommand}; +use CommandResult; +use utils::read_to_string; +use filetest::subtest::{self, SubTest, Context, Result as STResult}; + +pub fn run(files: Vec) -> CommandResult { + for (i, f) in files.into_iter().enumerate() { + if i != 0 { + println!(""); + } + cat_one(f)? + } + Ok(()) +} + +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))?; + + for (idx, func) in items.into_iter().enumerate() { + if idx != 0 { + println!(""); + } + print!("{}", func); + } + + 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.display(context.isa).to_string(), context) + } +} diff --git a/src/cton-util.rs b/src/cton-util.rs new file mode 100644 index 000000000000..28b4b90de02c --- /dev/null +++ b/src/cton-util.rs @@ -0,0 +1,86 @@ +#[macro_use(dbg)] +extern crate cretonne; +extern crate cton_reader; +extern crate docopt; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate filecheck; +extern crate num_cpus; + +use cretonne::VERSION; +use docopt::Docopt; +use std::io::{self, Write}; +use std::process; + +mod utils; +mod filetest; +mod cat; +mod print_cfg; +mod rsfilecheck; + +const USAGE: &str = " +Cretonne code generator utility + +Usage: + cton-util test [-v] ... + cton-util cat ... + cton-util filecheck [-v] + cton-util print-cfg ... + cton-util --help | --version + +Options: + -v, --verbose be more verbose + -h, --help print this help message + --version print the Cretonne version + +"; + +#[derive(Deserialize, Debug)] +struct Args { + cmd_test: bool, + 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. +pub type CommandResult = Result<(), String>; + +/// Parse the command line arguments and run the requested command. +fn cton_util() -> CommandResult { + // Parse command line arguments. + let args: Args = Docopt::new(USAGE) + .and_then(|d| { + d.help(true) + .version(Some(format!("Cretonne {}", VERSION))) + .deserialize() + }) + .unwrap_or_else(|e| e.exit()); + + // Find the sub-command to execute. + if args.cmd_test { + filetest::run(args.flag_verbose, 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) + } 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)) + } +} + +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); + } +} diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs new file mode 100644 index 000000000000..09a6955b5e3c --- /dev/null +++ b/src/filetest/binemit.rs @@ -0,0 +1,201 @@ +//! 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::Cow; +use std::collections::HashMap; +use std::fmt::Write; +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}; + +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 { + 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) { + write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], ebb).unwrap(); + } + + 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(); + } +} + +impl SubTest for TestBinEmit { + fn name(&self) -> Cow { + Cow::from("binemit") + } + + 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("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(); + + // 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_or_default(inst).is_legal() { + if let Ok(enc) = isa.encode(&func.dfg, + &func.dfg[inst], + func.dfg.ctrl_typevar(inst)) { + *func.encodings.ensure(inst) = enc; + } + } + } + } + + // Relax branches and compute EBB offsets based on the encodings. + 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(); + 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, isa), + 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); + 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], + "Inconsistent {} header offset", + ebb); + for inst in func.layout.ebb_insts(ebb) { + sink.text.clear(); + let enc = func.encodings.get_or_default(inst); + + // Send legal encodings into the emitter. + if enc.is_legal() { + let before = sink.offset; + 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, + encinfo.bytes(enc), + "Inconsistent size for [{}] {}", + encinfo.display(enc), + func.dfg.display_inst(inst, isa)); + } + + // 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, isa))); + } + let have = sink.text.trim(); + if have != want { + return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}", + inst, + func.dfg.display_inst(inst, isa), + want, + have)); + } + } + } + } + + 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 000000000000..5d00ee63c24b --- /dev/null +++ b/src/filetest/compile.rs @@ -0,0 +1,95 @@ +//! 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 }; + binemit::emit_function(&comp_ctx.func, + |func, inst, div, sink| isa.emit_inst(func, inst, div, sink), + &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/concurrent.rs b/src/filetest/concurrent.rs new file mode 100644 index 000000000000..7dcbe1b4fc69 --- /dev/null +++ b/src/filetest/concurrent.rs @@ -0,0 +1,144 @@ +//! 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, + 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, thread_num }).unwrap(); + + 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)) + } + }); + + if let &Err(ref msg) = &result { + dbg!("FAIL: {}", msg); + } + + replies.send(Reply::Done { jobid, result }).unwrap(); + } + }) + .unwrap() +} diff --git a/src/filetest/domtree.rs b/src/filetest/domtree.rs new file mode 100644 index 000000000000..29bde6d13f7a --- /dev/null +++ b/src/filetest/domtree.rs @@ -0,0 +1,106 @@ + +//! 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 cretonne::dominator_tree::DominatorTree; +use cretonne::flowgraph::ControlFlowGraph; +use cretonne::ir::Function; +use cretonne::ir::entities::AnyEntity; +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; + +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::with_function(func); + let domtree = DominatorTree::with_function(func, &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/filetest/legalizer.rs b/src/filetest/legalizer.rs new file mode 100644 index 000000000000..0c5ea04b771e --- /dev/null +++ b/src/filetest/legalizer.rs @@ -0,0 +1,53 @@ +//! 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; +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; + +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 comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + let isa = context.isa.expect("legalizer needs an ISA"); + + comp_ctx.flowgraph(); + comp_ctx + .legalize(isa) + .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))) + .map_err(|e| e.to_string())?; + run_filecheck(&text, context) + } +} diff --git a/src/filetest/licm.rs b/src/filetest/licm.rs new file mode 100644 index 000000000000..548818e75835 --- /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, context.isa, 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 new file mode 100644 index 000000000000..2d2a9c6cac52 --- /dev/null +++ b/src/filetest/mod.rs @@ -0,0 +1,73 @@ +//! 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 std::time; +use cton_reader::TestCommand; +use CommandResult; +use cat; +use print_cfg; +use filetest::runner::TestRunner; + +pub mod subtest; + +mod binemit; +mod compile; +mod concurrent; +mod domtree; +mod legalizer; +mod licm; +mod regalloc; +mod runner; +mod runone; +mod simple_gvn; +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. +/// +/// 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(verbose: bool, files: Vec) -> CommandResult { + let mut runner = TestRunner::new(verbose); + + for path in files.iter().map(Path::new) { + if path.is_file() { + runner.push_test(path); + } else { + runner.push_dir(path); + } + } + + runner.start_threads(); + 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 { + "binemit" => binemit::subtest(parsed), + "cat" => cat::subtest(parsed), + "compile" => compile::subtest(parsed), + "domtree" => domtree::subtest(parsed), + "legalizer" => legalizer::subtest(parsed), + "licm" => licm::subtest(parsed), + "print-cfg" => print_cfg::subtest(parsed), + "regalloc" => regalloc::subtest(parsed), + "simple-gvn" => simple_gvn::subtest(parsed), + "verifier" => verifier::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 000000000000..e90012973b50 --- /dev/null +++ b/src/filetest/regalloc.rs @@ -0,0 +1,61 @@ +//! 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 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 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(); + + comp_ctx.flowgraph(); + // TODO: Should we have an option to skip legalization? + comp_ctx + .legalize(isa) + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; + comp_ctx + .regalloc(isa) + .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))) + .map_err(|e| e.to_string())?; + run_filecheck(&text, context) + } +} diff --git a/src/filetest/runner.rs b/src/filetest/runner.rs new file mode 100644 index 000000000000..9188ee00ad11 --- /dev/null +++ b/src/filetest/runner.rs @@ -0,0 +1,331 @@ +//! Test runner. +//! +//! This module implements the `TestRunner` struct which manages executing tests as well as +//! 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}; +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, +} + +#[derive(PartialEq, Eq, Debug)] +enum State { + New, + Queued, + Running, + Done(TestResult), +} + +impl QueueEntry { + pub fn path(&self) -> &Path { + self.path.as_path() + } +} + +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, + + // Filenames of tests to run. + tests: Vec, + + // Pointer into `tests` where the `New` entries begin. + new_tests: usize, + + // Number of contiguous reported tests at the front of `tests`. + reported_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 { + /// Create a new blank TrstRunner. + pub fn new(verbose: bool) -> TestRunner { + TestRunner { + verbose, + dir_stack: Vec::new(), + tests: Vec::new(), + new_tests: 0, + reported_tests: 0, + errors: 0, + ticks_since_progress: 0, + threads: None, + } + } + + /// 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.tests + .push(QueueEntry { + path: file.into(), + state: State::New, + }); + } + + /// 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. + /// 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), + } + } + } + } + } + } + // 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.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. + fn schedule_jobs(&mut self) { + 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 result.is_err() { + self.errors += 1; + } + self.tests[jobid].state = State::Done(result); + + // Rports jobs in order. + while self.report_job() { + self.reported_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.reported_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]); + } + } + } + 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.reported_tests < self.tests.len() { + match conc.get() { + Some(reply) => self.handle_reply(reply), + None => break, + } + } + conc.join(); + } + } + + /// 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(()), + 1 => Err("1 failure".to_string()), + n => Err(format!("{} failures", n)), + } + } +} diff --git a/src/filetest/runone.rs b/src/filetest/runone.rs new file mode 100644 index 000000000000..3b23463fef90 --- /dev/null +++ b/src/filetest/runone.rs @@ -0,0 +1,127 @@ +//! Run the tests in a single test file. + +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, pretty_verifier_error}; +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 { + 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())?; + if testfile.functions.is_empty() { + return Err("no functions found".to_string()); + } + + // Parse the test commands. + 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. + 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 = 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_tuple = match tuples.pop() { + None => return Err("no test commands found".to_string()), + Some(t) => t, + }; + + for (func, details) in testfile.functions { + let mut context = Context { + preamble_comments: &testfile.preamble_comments, + details, + verified: false, + flags, + isa: None, + }; + + for tuple in &tuples { + 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. + run_one_test(last_tuple, Cow::Owned(func), &mut context)?; + } + + + // TODO: Actually run the tests. + Ok(started.elapsed()) +} + +// 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 { + // 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) +} + +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); + dbg!("Test: {} {}", name, isa.map(TargetIsa::name).unwrap_or("-")); + + context.flags = flags; + context.isa = isa; + + // 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, isa, e))?; + context.verified = true; + } + + test.run(func, context) + .map_err(|e| format!("{}: {}", name, e)) +} diff --git a/src/filetest/simple_gvn.rs b/src/filetest/simple_gvn.rs new file mode 100644 index 000000000000..a522c17bad95 --- /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, context.isa, 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/subtest.rs b/src/filetest/subtest.rs new file mode 100644 index 000000000000..9c7159dbb5a8 --- /dev/null +++ b/src/filetest/subtest.rs @@ -0,0 +1,112 @@ +//! SubTest trait. + +use std::result; +use std::borrow::Cow; +use cretonne::ir::Function; +use cretonne::isa::TargetIsa; +use cretonne::settings::Flags; +use cton_reader::{Details, Comment}; +use filecheck::{self, CheckerBuilder, Checker, Value as FCValue}; + +pub type Result = result::Result; + +/// 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>], + + /// Additional details about the function from the parser. + pub details: Details<'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 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>, +} + +/// 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 + } + + /// 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<()>; +} + +/// 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 = 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) = checker + .explain(&text, context) + .map_err(|e| format!("explain: {}", e))?; + Err(format!("filecheck failed:\n{}{}", checker, explain)) + } +} + +/// 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(); + // Preamble comments apply to all functions. + for comment in context.preamble_comments { + 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))?; + } + let checker = builder.finish(); + if checker.is_empty() { + Err("no filecheck directives in function".to_string()) + } else { + Ok(checker) + } +} diff --git a/src/filetest/verifier.rs b/src/filetest/verifier.rs new file mode 100644 index 000000000000..1dd5cc88b440 --- /dev/null +++ b/src/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, context.isa) { + 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/print_cfg.rs b/src/print_cfg.rs new file mode 100644 index 000000000000..ec479e3c9250 --- /dev/null +++ b/src/print_cfg.rs @@ -0,0 +1,133 @@ +//! The `print-cfg` sub-command. +//! +//! 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 cretonne::flowgraph::ControlFlowGraph; +use cretonne::ir::Function; +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() { + if i != 0 { + println!(""); + } + print_cfg(f)? + } + Ok(()) +} + +struct CFGPrinter<'a> { + func: &'a Function, + cfg: ControlFlowGraph, +} + +impl<'a> CFGPrinter<'a> { + pub fn new(func: &'a Function) -> CFGPrinter<'a> { + CFGPrinter { + func, + cfg: ControlFlowGraph::with_function(func), + } + } + + /// Write the CFG for this function to `w`. + pub fn write(&self, w: &mut Write) -> Result { + self.header(w)?; + self.ebb_nodes(w)?; + self.cfg_connections(w)?; + writeln!(w, "}}") + } + + fn header(&self, w: &mut Write) -> Result { + writeln!(w, "digraph {} {{", self.func.name)?; + if let Some(entry) = self.func.layout.entry_block() { + writeln!(w, " {{rank=min; {}}}", entry)?; + } + Ok(()) + } + + fn ebb_nodes(&self, w: &mut Write) -> Result { + for ebb in &self.func.layout { + 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(&self.func.dfg.value_lists) { + BranchInfo::SingleDest(dest, _) => { + write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)? + } + BranchInfo::Table(table) => { + write!(w, " | <{}>{} {}", inst, idata.opcode(), table)? + } + BranchInfo::NotABranch => {} + } + } + writeln!(w, "}}\"]")? + } + Ok(()) + } + + fn cfg_connections(&self, w: &mut Write) -> Result { + for ebb in &self.func.layout { + for &(parent, inst) in self.cfg.get_predecessors(ebb) { + writeln!(w, " {}:{} -> {}", parent, inst, ebb)?; + } + } + Ok(()) + } +} + +impl<'a> Display for CFGPrinter<'a> { + fn fmt(&self, f: &mut Formatter) -> Result { + self.write(f) + } +} + +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))?; + + for (idx, func) in items.into_iter().enumerate() { + if idx != 0 { + println!(""); + } + print!("{}", CFGPrinter::new(&func)); + } + + 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/src/rsfilecheck.rs b/src/rsfilecheck.rs new file mode 100644 index 000000000000..5ee464d20ab8 --- /dev/null +++ b/src/rsfilecheck.rs @@ -0,0 +1,57 @@ +use CommandResult; +use utils::read_to_string; +use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; +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 = 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(); + 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())?; + print!("{}", explain); + if success { + println!("OK"); + Ok(()) + } else { + Err("Check failed".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())?; + print!("{}", explain); + Err("Check failed".to_string()) + } +} + +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))?; + Ok(builder.finish()) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 000000000000..3bc8e7b1dfd4 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,69 @@ +//! Utility functions. + +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}; +use std::path::Path; + +/// Read an entire file into a string. +pub fn read_to_string>(path: P) -> Result { + let mut file = File::open(path)?; + let mut buffer = String::new(); + 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 + } +} + +/// Pretty-print a verifier error. +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) => { + 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() + } +} + +#[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")); +} diff --git a/test-all.sh b/test-all.sh new file mode 100755 index 000000000000..3e25c546a9e0 --- /dev/null +++ b/test-all.sh @@ -0,0 +1,66 @@ +#!/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) + +function banner() { + echo "====== $@ ======" +} + +# Run rustfmt if we have it. +if $topdir/check-rustfmt.sh; then + banner "Rust formatting" + $topdir/format-all.sh --write-mode=diff +fi + +# 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), $(python3 --version 2>&1)" + $topdir/lib/cretonne/meta/check.sh + touch $tsfile || echo no target directory +fi + +PKGS="cretonne cretonne-reader cretonne-tools cretonne-frontend filecheck \ + wasm2cretonne" +cd "$topdir" +for PKG in $PKGS +do + banner "Rust $PKG unit tests" + cargo test -p $PKG +done + +# Build cton-util for parser testing. +cd "$topdir" +banner "Rust documentation" +echo "open $topdir/target/doc/cretonne/index.html" +cargo doc +banner "Rust release build" +cargo build --release + +export CTONUTIL="$topdir/target/release/cton-util" + +cd "$topdir" +banner "File tests" +"$CTONUTIL" test filetests docs + +banner "OK" diff --git a/tests/cfg_traversal.rs b/tests/cfg_traversal.rs new file mode 100644 index 000000000000..37d568e0e4f4 --- /dev/null +++ b/tests/cfg_traversal.rs @@ -0,0 +1,150 @@ +extern crate cretonne; +extern crate cton_reader; + +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 domtree = DominatorTree::with_function(&func, &cfg); + + let got = domtree + .cfg_postorder() + .iter() + .rev() + .cloned() + .collect::>(); + let want = ebb_order + .iter() + .map(|&n| Ebb::with_number(n).unwrap()) + .collect::>(); + assert_eq!(got, want); +} + +#[test] +fn simple_traversal() { + test_reverse_postorder_traversal(" + function %test(i32) native { + 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, 1, 3, 2, 4, 5]); +} + +#[test] +fn loops_one() { + test_reverse_postorder_traversal(" + function %test(i32) native { + ebb0(v0: i32): + jump ebb1 + ebb1: + brnz v0, ebb3 + jump ebb2 + ebb2: + jump ebb3 + ebb3: + return + } + ", + vec![0, 1, 3, 2]); +} + +#[test] +fn loops_two() { + test_reverse_postorder_traversal(" + function %test(i32) native { + 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, 1, 2, 4, 3, 5]); +} + +#[test] +fn loops_three() { + test_reverse_postorder_traversal(" + function %test(i32) native { + ebb0(v0: i32): + brz v0, ebb1 + jump ebb2 + ebb1: + jump ebb3 + ebb2: + brz v0, ebb4 + jump ebb5 + ebb3: + jump ebb4 + ebb4: + brz v0, ebb3 + brnz v0, ebb5 + jump ebb6 + ebb5: + brz v0, ebb4 + trap + ebb6: + jump ebb7 + ebb7: + return + } + ", + vec![0, 1, 2, 4, 3, 6, 7, 5]); +} + +#[test] +fn back_edge_one() { + test_reverse_postorder_traversal(" + function %test(i32) native { + 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]); +}