Check in the wasmstandalone code.
This is based on the code in https://github.com/denismerigoux/cretonne/commits/wasm2cretonne before wasmstandalone was removed, with minor updates for the new library structure. It is not yet updated for the latest cretonne API changes.
This commit is contained in:
24
Cargo.toml
24
Cargo.toml
@@ -1,26 +1,30 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cretonne-tools"
|
name = "wasmstandalone-tools"
|
||||||
authors = ["The Cretonne Project Developers"]
|
authors = ["The Cretonne Project Developers"]
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = "Binaries for testing the Cretonne library"
|
description = "Command-line interface for the wasmstandalone crate"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
documentation = "https://cretonne.readthedocs.io/"
|
documentation = "https://cretonne.readthedocs.io/"
|
||||||
repository = "https://github.com/stoklund/cretonne"
|
repository = "https://github.com/sunfishcode/wasmstandalone"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cton-util"
|
name = "wasmstandalone"
|
||||||
path = "src/cton-util.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cretonne = { path = "lib/cretonne" }
|
cretonne = { git = "https://github.com/stoklund/cretonne.git" }
|
||||||
cretonne-reader = { path = "lib/reader" }
|
cretonne-reader = { git = "https://github.com/stoklund/cretonne.git" }
|
||||||
cretonne-frontend = { path = "lib/frontend" }
|
cretonne-wasm = { git = "https://github.com/stoklund/cretonne.git" }
|
||||||
wasm2cretonne-util = { path = "lib/wasm2cretonne-util" }
|
wasmstandalone = { path = "lib/wasmstandalone" }
|
||||||
filecheck = { path = "lib/filecheck" }
|
wasmparser = "0.8.2"
|
||||||
|
wasmtext = { git = "https://github.com/yurydelendik/wasmtext" }
|
||||||
|
filecheck = "0.0.1"
|
||||||
docopt = "0.8.0"
|
docopt = "0.8.0"
|
||||||
serde = "1.0.8"
|
serde = "1.0.8"
|
||||||
serde_derive = "1.0.8"
|
serde_derive = "1.0.8"
|
||||||
num_cpus = "1.5.1"
|
num_cpus = "1.5.1"
|
||||||
|
term = "0.4.6"
|
||||||
|
tempdir="*"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
74
README.rst
74
README.rst
@@ -1,74 +0,0 @@
|
|||||||
=======================
|
|
||||||
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 <http://www.catb.org/jargon/html/N/nasal-demons.html>`_, 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 <http://www.sphinx-doc.org/>`_::
|
|
||||||
|
|
||||||
$ 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.
|
|
||||||
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
_build
|
|
||||||
196
docs/Makefile
196
docs/Makefile
@@ -1,196 +0,0 @@
|
|||||||
# 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 <target>' where <target> 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."
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
*************************
|
|
||||||
Cretonne compared to LLVM
|
|
||||||
*************************
|
|
||||||
|
|
||||||
`LLVM <http://llvm.org>`_ 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 <http://clang.llvm.org>`_, and it is deservedly very
|
|
||||||
popular. `Chris Lattner's chapter about LLVM
|
|
||||||
<http://www.aosabook.org/en/llvm.html>`_ in the `Architecture of Open Source
|
|
||||||
Applications <http://aosabook.org/en/index.html>`_ 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 <http://llvm.org/docs/LangRef.html>`_
|
|
||||||
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 <http://llvm.org/docs/CodeGenerator.html#instruction-selection-section>`_
|
|
||||||
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 <http://llvm.org/docs/CodeGenerator.html#machine-code-representation>`_
|
|
||||||
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 <http://llvm.org/docs/CodeGenerator.html#the-mc-layer>`_
|
|
||||||
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 <langref>` 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
|
|
||||||
<http://llvm.org/docs/LangRef.html#phi-instruction>`_ 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 <value-types>` 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.
|
|
||||||
137
docs/conf.py
137
docs/conf.py
@@ -1,137 +0,0 @@
|
|||||||
# -*- 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')
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
# -*- 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'}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# -*- 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<nn> = value
|
|
||||||
# ss<nn> = stack slot
|
|
||||||
# jt<nn> = jump table
|
|
||||||
(r'(v|ss|jt)\d+', Name.Variable),
|
|
||||||
# ebb<nn> = 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'}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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::
|
|
||||||
924
docs/langref.rst
924
docs/langref.rst
@@ -1,924 +0,0 @@
|
|||||||
***************************
|
|
||||||
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.
|
|
||||||
263
docs/make.bat
263
docs/make.bat
@@ -1,263 +0,0 @@
|
|||||||
@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 ^<target^>` where ^<target^> 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
|
|
||||||
483
docs/metaref.rst
483
docs/metaref.rst
@@ -1,483 +0,0 @@
|
|||||||
********************************
|
|
||||||
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.
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
*******************************
|
|
||||||
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.
|
|
||||||
354
docs/testing.rst
354
docs/testing.rst
@@ -1,354 +0,0 @@
|
|||||||
****************
|
|
||||||
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
|
|
||||||
<langref>`:
|
|
||||||
|
|
||||||
.. 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 <https://docs.rs/filecheck/>`_ 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.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
; 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 | <inst0>brnz ebb2}"]
|
|
||||||
; check: ebb1 [shape=record, label="{ebb1 | <inst4>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
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,368 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,848 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
; 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
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
; 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)
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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: }
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
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: }
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
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: }
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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: }
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
; 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: }
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
; 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: }
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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: }
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
test cat
|
|
||||||
|
|
||||||
; 'function' is not a keyword, and can be used as the name of a function too.
|
|
||||||
function %function() {}
|
|
||||||
; check: function %function() native
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
; 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: }
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
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: }
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
; 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
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
; 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
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
; 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
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
[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`.
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
// 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<Self> {
|
|
||||||
Isa::all()
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.filter(|isa| isa.name() == name)
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates isa target from arch.
|
|
||||||
fn from_arch(arch: &str) -> Option<Isa> {
|
|
||||||
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<Vec<Isa>, 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::<Vec<_>>();
|
|
||||||
let isa_targets = targets.split(',').flat_map(Isa::new).collect::<Vec<_>>();
|
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
"""Definitions for the base Cretonne language."""
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
"""
|
|
||||||
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')
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
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())
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
"""
|
|
||||||
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')
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,189 +0,0 @@
|
|||||||
"""
|
|
||||||
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)
|
|
||||||
))
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
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),
|
|
||||||
))
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
"""
|
|
||||||
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())
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"""
|
|
||||||
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.
|
|
||||||
""")
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# 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()
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
"""
|
|
||||||
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
|
|
||||||
@@ -1,501 +0,0 @@
|
|||||||
"""
|
|
||||||
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)
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,427 +0,0 @@
|
|||||||
"""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)
|
|
||||||
@@ -1,462 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,375 +0,0 @@
|
|||||||
"""
|
|
||||||
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())
|
|
||||||
@@ -1,354 +0,0 @@
|
|||||||
"""
|
|
||||||
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
|
|
||||||
@@ -1,407 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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'))")
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
import doctest
|
|
||||||
import cdsl
|
|
||||||
|
|
||||||
|
|
||||||
def load_tests(loader, tests, ignore):
|
|
||||||
tests.addTests(doctest.DocTestSuite(cdsl))
|
|
||||||
return tests
|
|
||||||
@@ -1,605 +0,0 @@
|
|||||||
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)
|
|
||||||
))
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,886 +0,0 @@
|
|||||||
"""
|
|
||||||
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)
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
"""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
|
|
||||||
@@ -1,853 +0,0 @@
|
|||||||
"""
|
|
||||||
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::<u{}>({}),'
|
|
||||||
.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
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user