diff --git a/.travis.yml b/.travis.yml index ce1a269955..301d7871d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: rust rust: - stable - beta + - nightly +matrix: + allow_failures: + - rust: nightly dist: trusty sudo: false addons: diff --git a/README.rst b/README.rst index 8368aed68f..445fbf3c06 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Cretonne Code Generator ======================= Cretonne is a low-level retargetable code generator. It translates a `target-independent -intermediate language `_ into executable +intermediate representation `_ into executable machine code. *This is a work in progress that is not yet functional.* @@ -16,6 +16,10 @@ machine code. :target: https://travis-ci.org/Cretonne/cretonne :alt: Build Status +.. image:: https://badges.gitter.im/Cretonne/cretonne.png + :target: https://gitter.im/Cretonne/Lobby/~chat + :alt: Gitter chat + For more information, see `the documentation `_. diff --git a/check-clippy.sh b/check-clippy.sh new file mode 100755 index 0000000000..20ac595851 --- /dev/null +++ b/check-clippy.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -euo pipefail + +# Usage: check-clippy.sh [--install] + +if cargo install --list | tee /dev/null | grep -q "^clippy v0"; then + exit 0 +else + exit 1 +fi diff --git a/check-rustfmt.sh b/check-rustfmt.sh index 1983493342..9a49e8bac5 100755 --- a/check-rustfmt.sh +++ b/check-rustfmt.sh @@ -22,11 +22,11 @@ set -euo pipefail # operation, however that doesn't appear to be possible through "cargo fmt"). VERS="0.9.0" -if cargo install --list | grep -q "^rustfmt v$VERS"; then +if cargo install --list | tee /dev/null | grep -q "^rustfmt v$VERS"; then exit 0 fi -if [ "$1" != "--install" ]; then +if [[ ${1:-""} != "--install" ]]; then echo "********************************************************************" echo "* Please install rustfmt v$VERS to verify formatting. *" echo "* If a newer version of rustfmt is available, update this script. *" diff --git a/clippy-all.sh b/clippy-all.sh new file mode 100755 index 0000000000..6ec2fd1411 --- /dev/null +++ b/clippy-all.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail + +# Check all sources with clippy. +# In the cton-util crate (root dir) clippy will only work with nightly cargo - +# there is a bug where it will reject the commands passed to it by cargo 0.25.0 +cargo +nightly clippy --all diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 297e8c31b3..947b50b44a 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cretonne-tools" authors = ["The Cretonne Project Developers"] -version = "0.3.4" +version = "0.4.1" description = "Binaries for testing the Cretonne library" license = "Apache-2.0" documentation = "https://cretonne.readthedocs.io/" @@ -13,18 +13,18 @@ name = "cton-util" path = "src/cton-util.rs" [dependencies] -cretonne = { path = "lib/cretonne", version = "0.3.4" } -cretonne-reader = { path = "lib/reader", version = "0.3.4" } -cretonne-frontend = { path = "lib/frontend", version = "0.3.4" } -cretonne-wasm = { path = "lib/wasm", version = "0.3.4" } -cretonne-native = { path = "lib/native", version = "0.3.4" } -filecheck = { path = "lib/filecheck" } +cretonne = { path = "lib/cretonne", version = "0.4.1" } +cretonne-reader = { path = "lib/reader", version = "0.4.1" } +cretonne-frontend = { path = "lib/frontend", version = "0.4.1" } +cretonne-wasm = { path = "lib/wasm", version = "0.4.1" } +cretonne-native = { path = "lib/native", version = "0.4.1" } +cretonne-filetests = { path = "lib/filetests", version = "0.4.1" } +filecheck = "0.2.1" docopt = "0.8.0" serde = "1.0.8" serde_derive = "1.0.8" -num_cpus = "1.5.1" -tempdir="0.3.5" -term = "0.5" +tempdir = "0.3.5" +term = "0.5.1" [workspace] diff --git a/cranelift/clippy.toml b/cranelift/clippy.toml new file mode 100644 index 0000000000..caabf12b77 --- /dev/null +++ b/cranelift/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = [ "WebAssembly", "NaN", "SetCC" ] diff --git a/cranelift/docs/callex.cton b/cranelift/docs/callex.cton index 6690e78f90..837f9ea6e7 100644 --- a/cranelift/docs/callex.cton +++ b/cranelift/docs/callex.cton @@ -1,6 +1,6 @@ test verifier -function %gcd(i32 uext, i32 uext) -> i32 uext native { +function %gcd(i32 uext, i32 uext) -> i32 uext system_v { fn1 = function %divmod(i32 uext, i32 uext) -> i32 uext, i32 uext ebb1(v1: i32, v2: i32): diff --git a/cranelift/docs/compare-llvm.rst b/cranelift/docs/compare-llvm.rst index 0180f6cc92..4a622dbeda 100644 --- a/cranelift/docs/compare-llvm.rst +++ b/cranelift/docs/compare-llvm.rst @@ -16,8 +16,8 @@ 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.) +- Have both textual and in-memory forms of their primary intermediate + representation. (LLVM also has a binary bitcode format; Cretonne doesn't.) - Can target multiple ISAs. - Can cross-compile by default without rebuilding the code generator. @@ -41,8 +41,8 @@ LLVM uses multiple intermediate representations as it translates a program to binary machine code: `LLVM IR `_ - This is the primary intermediate language which has textual, binary, and - in-memory representations. It serves two main purposes: + This is the primary intermediate representation which has textual, binary, and + in-memory forms. It serves two main purposes: - An ISA-agnostic, stable(ish) input language that front ends can generate easily. @@ -89,9 +89,9 @@ representation. Some target ISAs have a fast instruction selector that can translate simple code directly to MachineInstrs, bypassing SelectionDAG when possible. -:doc:`Cretonne ` uses a single intermediate language to cover these -levels of abstraction. This is possible in part because of Cretonne's smaller -scope. +:doc:`Cretonne ` uses a single intermediate representation 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 @@ -102,7 +102,7 @@ scope. - 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 +The Cretonne intermediate representation is similar to LLVM IR, but at a slightly lower level of abstraction. Program structure @@ -112,12 +112,12 @@ 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 +In Cretonne IR, 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 +An LLVM IR function is a graph of *basic blocks*. A Cretonne IR 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 diff --git a/cranelift/docs/cton_domain.py b/cranelift/docs/cton_domain.py index a03acf1160..2d6e45eeed 100644 --- a/cranelift/docs/cton_domain.py +++ b/cranelift/docs/cton_domain.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Sphinx domain for documenting compiler intermediate languages. +# Sphinx domain for documenting compiler intermediate representations. # # This defines a 'cton' Sphinx domain with the following directives and roles: # @@ -29,10 +29,10 @@ import sphinx.ext.autodoc class CtonObject(ObjectDescription): """ - Any kind of Cretonne IL object. + Any kind of Cretonne IR object. This is a shared base class for the different kinds of indexable objects - in the Cretonne IL reference. + in the Cretonne IR reference. """ option_spec = { 'noindex': directives.flag, @@ -98,7 +98,7 @@ def parse_type(name, signode): class CtonType(CtonObject): - """A Cretonne IL type description.""" + """A Cretonne IR type description.""" def handle_signature(self, sig, signode): """ @@ -112,7 +112,7 @@ class CtonType(CtonObject): return name def get_index_text(self, name): - return name + ' (IL type)' + return name + ' (IR type)' sep_equal = re.compile('\s*=\s*') @@ -127,7 +127,7 @@ def parse_params(s, signode): class CtonInst(CtonObject): - """A Cretonne IL instruction.""" + """A Cretonne IR instruction.""" doc_field_types = [ TypedField('argument', label=l_('Arguments'), @@ -176,11 +176,11 @@ class CtonInst(CtonObject): class CtonInstGroup(CtonObject): - """A Cretonne IL instruction group.""" + """A Cretonne IR instruction group.""" class CretonneDomain(Domain): - """Cretonne domain for intermediate language objects.""" + """Cretonne domain for IR objects.""" name = 'cton' label = 'Cretonne' diff --git a/cranelift/docs/example.cton b/cranelift/docs/example.cton index a36fef3238..09e0c9cb5f 100644 --- a/cranelift/docs/example.cton +++ b/cranelift/docs/example.cton @@ -1,6 +1,6 @@ test verifier -function %average(i32, i32) -> f32 native { +function %average(i32, i32) -> f32 system_v { ss1 = explicit_slot 8 ; Stack slot for ``sum``. ebb1(v1: i32, v2: i32): diff --git a/cranelift/docs/langref.rst b/cranelift/docs/langref.rst index 3a855c32b4..edcc039c86 100644 --- a/cranelift/docs/langref.rst +++ b/cranelift/docs/langref.rst @@ -5,19 +5,19 @@ 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. +The Cretonne intermediate representation (:term:`IR`) has two primary forms: +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 IR have the ``.cton`` filename extension. -This reference uses the text format to describe IL semantics but glosses over +This reference uses the text format to describe IR 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 +Cretonne compiles functions independently. A ``.cton`` IR 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. @@ -27,7 +27,7 @@ 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: +Here is the same function compiled into Cretonne IR: .. literalinclude:: example.cton :language: cton @@ -77,7 +77,7 @@ variable value for the next iteration. The `cton_frontend` crate contains utilities for translating from programs containing multiple assignments to the same variables into SSA form for -Cretonne :term:`IL`. +Cretonne :term:`IR`. Such variables can also be presented to Cretonne as :term:`stack slot`\s. Stack slots are accessed with the :inst:`stack_store` and :inst:`stack_load` @@ -303,7 +303,7 @@ indicate the different kinds of immediate operands on an instruction. A floating point condition code. See the :inst:`fcmp` instruction for details. The two IEEE floating point immediate types :type:`ieee32` and :type:`ieee64` -are displayed as hexadecimal floating point literals in the textual :term:`IL` +are displayed as hexadecimal floating point literals in the textual :term:`IR` 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 @@ -400,11 +400,11 @@ convention: param : type [paramext] [paramspecial] paramext : "uext" | "sext" paramspecial : "sret" | "link" | "fp" | "csr" | "vmctx" - callconv : "native" | "spiderwasm" + callconv : "system_v" | "spiderwasm" Parameters 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. +dependent. These flags support interfacing with code produced by other +compilers. Functions that are called directly must be declared in the :term:`function preamble`: @@ -563,7 +563,7 @@ runtime data structures. alignment for storing a pointer. Chains of ``deref`` global variables are possible, but cycles are not - allowed. They will be caught by the IL verifier. + allowed. They will be caught by the IR verifier. :arg BaseGV: Global variable containing the base pointer. :arg Offset: Byte offset from the loaded base pointer to the global @@ -654,6 +654,11 @@ trap when accessed. address space reserved for the heap, not including the guard pages. :arg GuardBytes: Size of the guard pages in bytes. +When the base is a global variable, it must be :term:`accessible` and naturally +aligned for a pointer value. + +The ``reserved_reg`` option is not yet implemented. + Dynamic heaps ~~~~~~~~~~~~~ @@ -672,6 +677,11 @@ is resized. The bound of a dynamic heap is stored in a global variable. :arg BoundGV: Global variable containing the current heap bound in bytes. :arg GuardBytes: Size of the guard pages in bytes. +When the base is a global variable, it must be :term:`accessible` and naturally +aligned for a pointer value. + +The ``reserved_reg`` option is not yet implemented. + Heap examples ~~~~~~~~~~~~~ @@ -1144,19 +1154,11 @@ Glossary The extended basic blocks which contain all the executable code in a function. The function body follows the function preamble. - 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. + The language used to describe functions to Cretonne. This reference + describes the syntax and semantics of Cretonne IR. The IR has two + forms: Textual, and an in-memory data structure. stack slot A fixed size memory allocation in the current function's activation diff --git a/cranelift/docs/testing.rst b/cranelift/docs/testing.rst index 2b84b1c19b..db7efb1da1 100644 --- a/cranelift/docs/testing.rst +++ b/cranelift/docs/testing.rst @@ -89,7 +89,7 @@ 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 +of input functions in the :doc:`Cretonne textual intermediate representation `: .. productionlist:: @@ -136,13 +136,15 @@ 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. +The filetests are run automatically as part of `cargo test`, and they can +also be run manually with the `cton-util test` command. + Filecheck --------- Many of the test commands described below use *filecheck* to verify their output. Filecheck is a Rust implementation of the LLVM tool of the same name. -See the :file:`lib/filecheck` `documentation `_ for -details of its syntax. +See the `documentation `_ for details of its syntax. Comments in :file:`.cton` files are associated with the entity they follow. This typically means an instruction or the whole function. Those tests that @@ -164,7 +166,7 @@ Cretonne's tests don't need this. ---------- 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 +from textual IR. 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. @@ -186,7 +188,7 @@ Example:: `test verifier` --------------- -Run each function through the IL verifier and check that it produces the +Run each function through the IR verifier and check that it produces the expected error messages. Expected error messages are indicated with an ``error:`` directive *on the @@ -324,6 +326,38 @@ Test the simple GVN pass. The simple GVN pass is run on each function, and then results are run through filecheck. +`test licm` +----------------- + +Test the LICM pass. + +The LICM pass is run on each function, and then results are run +through filecheck. + +`test dce` +----------------- + +Test the DCE pass. + +The DCE pass is run on each function, and then results are run +through filecheck. + +`test preopt` +----------------- + +Test the preopt pass. + +The preopt pass is run on each function, and then results are run +through filecheck. + +`test postopt` +----------------- + +Test the postopt pass. + +The postopt pass is run on each function, and then results are run +through filecheck. + `test compile` -------------- @@ -333,4 +367,4 @@ Each function is passed through the full ``Context::compile()`` function which is normally used to compile code. This type of test often depends on assertions or verifier errors, but it is also possible to use filecheck directives which will be matched against the final form of the -Cretonne IL right before binary machine code emission. +Cretonne IR right before binary machine code emission. diff --git a/cranelift/filetests/dce/basic.cton b/cranelift/filetests/dce/basic.cton new file mode 100644 index 0000000000..436b4aee19 --- /dev/null +++ b/cranelift/filetests/dce/basic.cton @@ -0,0 +1,46 @@ +test dce + +function %simple() -> i32 { +ebb0: + v2 = iconst.i32 2 + v3 = iconst.i32 3 + return v3 +} +; sameln: function %simple +; nextln: ebb0: +; nextln: v3 = iconst.i32 3 +; nextln: return v3 +; nextln: } + +function %some_branching(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v3 = iconst.i32 70 + v4 = iconst.i32 71 + v5 = iconst.i32 72 + v8 = iconst.i32 73 + brz v0, ebb1 + jump ebb2(v8) + +ebb1: + v2 = iadd v0, v3 + return v0 + +ebb2(v9: i32): + v6 = iadd v1, v4 + v7 = iadd v6, v9 + return v7 +} +; sameln: function %some_branching +; nextln: ebb0(v0: i32, v1: i32): +; nextln: v4 = iconst.i32 71 +; nextln: v8 = iconst.i32 73 +; nextln: brz v0, ebb1 +; nextln: jump ebb2(v8) +; nextln: +; nextln: ebb1: +; nextln: return v0 +; nextln: +; nextln: ebb2(v9: i32): +; nextln: v6 = iadd.i32 v1, v4 +; nextln: v7 = iadd v6, v9 +; nextln: return v7 diff --git a/cranelift/filetests/domtree/loops.cton b/cranelift/filetests/domtree/loops.cton index 70c62d4130..fda2984e94 100644 --- a/cranelift/filetests/domtree/loops.cton +++ b/cranelift/filetests/domtree/loops.cton @@ -59,7 +59,7 @@ function %test(i32) { ; nextln: ebb5: ; nextln: } -function %loop2(i32) native { +function %loop2(i32) system_v { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 jump ebb2 ; dominates: ebb2 diff --git a/cranelift/filetests/domtree/loops2.cton b/cranelift/filetests/domtree/loops2.cton index 029cea922a..80e00ca278 100644 --- a/cranelift/filetests/domtree/loops2.cton +++ b/cranelift/filetests/domtree/loops2.cton @@ -43,7 +43,7 @@ function %loop1(i32) { ; nextln: ebb9: ; nextln: } -function %loop2(i32) native { +function %loop2(i32) system_v { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 jump ebb2 ; dominates: ebb2 diff --git a/cranelift/filetests/isa/intel/abi-bool.cton b/cranelift/filetests/isa/intel/abi-bool.cton index bc1a1ba1d5..6a53cdbb3c 100644 --- a/cranelift/filetests/isa/intel/abi-bool.cton +++ b/cranelift/filetests/isa/intel/abi-bool.cton @@ -2,7 +2,7 @@ test compile set is_64bit=1 isa intel haswell -function %foo(i64, i64, i64, i32) -> b1 native { +function %foo(i64, i64, i64, i32) -> b1 system_v { ebb3(v0: i64, v1: i64, v2: i64, v3: i32): v5 = icmp ne v2, v2 v8 = iconst.i64 0 diff --git a/cranelift/filetests/isa/intel/abi32.cton b/cranelift/filetests/isa/intel/abi32.cton index c7e399960c..b949af16f7 100644 --- a/cranelift/filetests/isa/intel/abi32.cton +++ b/cranelift/filetests/isa/intel/abi32.cton @@ -5,14 +5,14 @@ isa intel ; regex: V=v\d+ function %f() { - sig0 = (i32) -> i32 native - ; check: sig0 = (i32 [0]) -> i32 [%rax] native + sig0 = (i32) -> i32 system_v + ; check: sig0 = (i32 [0]) -> i32 [%rax] system_v - sig1 = (i64) -> b1 native - ; check: sig1 = (i32 [0], i32 [4]) -> b1 [%rax] native + sig1 = (i64) -> b1 system_v + ; check: sig1 = (i32 [0], i32 [4]) -> b1 [%rax] system_v - sig2 = (f32, i64) -> f64 native - ; check: sig2 = (f32 [0], i32 [4], i32 [8]) -> f64 [%xmm0] native + sig2 = (f32, i64) -> f64 system_v + ; check: sig2 = (f32 [0], i32 [4], i32 [8]) -> f64 [%xmm0] system_v ebb0: return diff --git a/cranelift/filetests/isa/intel/abi64.cton b/cranelift/filetests/isa/intel/abi64.cton index d56a31d479..8aa6a49b0f 100644 --- a/cranelift/filetests/isa/intel/abi64.cton +++ b/cranelift/filetests/isa/intel/abi64.cton @@ -6,14 +6,14 @@ isa intel ; regex: V=v\d+ function %f() { - sig0 = (i32) -> i32 native - ; check: sig0 = (i32 [%rdi]) -> i32 [%rax] native + sig0 = (i32) -> i32 system_v + ; check: sig0 = (i32 [%rdi]) -> i32 [%rax] system_v - sig1 = (i64) -> b1 native - ; check: sig1 = (i64 [%rdi]) -> b1 [%rax] native + sig1 = (i64) -> b1 system_v + ; check: sig1 = (i64 [%rdi]) -> b1 [%rax] system_v - sig2 = (f32, i64) -> f64 native - ; check: sig2 = (f32 [%xmm0], i64 [%rdi]) -> f64 [%xmm0] native + sig2 = (f32, i64) -> f64 system_v + ; check: sig2 = (f32 [%xmm0], i64 [%rdi]) -> f64 [%xmm0] system_v ebb0: return diff --git a/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount.cton b/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount.cton index 62a793de60..b87815aa12 100644 --- a/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount.cton +++ b/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount.cton @@ -47,28 +47,23 @@ ebb1(v20: i32): function %i64_popcount(i64) -> i64 { ebb0(v30: i64): v31 = popcnt v30; - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: iconst.i64 ; check: band ; check: isub - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: band ; check: isub - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: band ; check: isub - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: iadd ; check: iconst.i64 ; check: band ; check: iconst.i64 ; check: imul - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm return v31; } @@ -78,27 +73,22 @@ ebb0(v30: i64): function %i32_popcount(i32) -> i32 { ebb0(v40: i32): v41 = popcnt v40; - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: iconst.i32 ; check: band ; check: isub - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: band ; check: isub - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: band ; check: isub - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm ; check: iadd ; check: iconst.i32 ; check: band ; check: iconst.i32 ; check: imul - ; check: iconst.i32 - ; check: ushr + ; check: ushr_imm return v41; } diff --git a/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount_encoding.cton b/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount_encoding.cton index 0b7003449d..2e552b7145 100644 --- a/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount_encoding.cton +++ b/cranelift/filetests/isa/intel/baseline_clz_ctz_popcount_encoding.cton @@ -15,56 +15,56 @@ ebb0: [-,%r11] v10 = iconst.i64 0x1234 ; asm: bsfq %r11, %rcx - [-,%rcx,%eflags] v11, v12 = x86_bsf v10 ; bin: 49 0f bc cb + [-,%rcx,%rflags] v11, v12 = x86_bsf v10 ; bin: 49 0f bc cb [-,%rdx] v14 = iconst.i64 0x5678 ; asm: bsfq %rdx, %r12 - [-,%r12,%eflags] v15, v16 = x86_bsf v14 ; bin: 4c 0f bc e2 + [-,%r12,%rflags] v15, v16 = x86_bsf v14 ; bin: 4c 0f bc e2 ; asm: bsfq %rdx, %rdi - [-,%rdi,%eflags] v17, v18 = x86_bsf v14 ; bin: 48 0f bc fa + [-,%rdi,%rflags] v17, v18 = x86_bsf v14 ; bin: 48 0f bc fa ; 32-bit wide bsf [-,%r11] v20 = iconst.i32 0x1234 ; asm: bsfl %r11d, %ecx - [-,%rcx,%eflags] v21, v22 = x86_bsf v20 ; bin: 41 0f bc cb + [-,%rcx,%rflags] v21, v22 = x86_bsf v20 ; bin: 41 0f bc cb [-,%rdx] v24 = iconst.i32 0x5678 ; asm: bsfl %edx, %r12d - [-,%r12,%eflags] v25, v26 = x86_bsf v24 ; bin: 44 0f bc e2 + [-,%r12,%rflags] v25, v26 = x86_bsf v24 ; bin: 44 0f bc e2 ; asm: bsfl %edx, %esi - [-,%rsi,%eflags] v27, v28 = x86_bsf v24 ; bin: 0f bc f2 + [-,%rsi,%rflags] v27, v28 = x86_bsf v24 ; bin: 0f bc f2 ; 64-bit wide bsr [-,%r11] v30 = iconst.i64 0x1234 ; asm: bsrq %r11, %rcx - [-,%rcx,%eflags] v31, v32 = x86_bsr v30 ; bin: 49 0f bd cb + [-,%rcx,%rflags] v31, v32 = x86_bsr v30 ; bin: 49 0f bd cb [-,%rdx] v34 = iconst.i64 0x5678 ; asm: bsrq %rdx, %r12 - [-,%r12,%eflags] v35, v36 = x86_bsr v34 ; bin: 4c 0f bd e2 + [-,%r12,%rflags] v35, v36 = x86_bsr v34 ; bin: 4c 0f bd e2 ; asm: bsrq %rdx, %rdi - [-,%rdi,%eflags] v37, v38 = x86_bsr v34 ; bin: 48 0f bd fa + [-,%rdi,%rflags] v37, v38 = x86_bsr v34 ; bin: 48 0f bd fa ; 32-bit wide bsr [-,%r11] v40 = iconst.i32 0x1234 ; asm: bsrl %r11d, %ecx - [-,%rcx,%eflags] v41, v42 = x86_bsr v40 ; bin: 41 0f bd cb + [-,%rcx,%rflags] v41, v42 = x86_bsr v40 ; bin: 41 0f bd cb [-,%rdx] v44 = iconst.i32 0x5678 ; asm: bsrl %edx, %r12d - [-,%r12,%eflags] v45, v46 = x86_bsr v44 ; bin: 44 0f bd e2 + [-,%r12,%rflags] v45, v46 = x86_bsr v44 ; bin: 44 0f bd e2 ; asm: bsrl %edx, %esi - [-,%rsi,%eflags] v47, v48 = x86_bsr v44 ; bin: 0f bd f2 + [-,%rsi,%rflags] v47, v48 = x86_bsr v44 ; bin: 0f bd f2 ; 64-bit wide cmov diff --git a/cranelift/filetests/isa/intel/binary32-float.cton b/cranelift/filetests/isa/intel/binary32-float.cton index 8f60cf20be..4477c4d8d3 100644 --- a/cranelift/filetests/isa/intel/binary32-float.cton +++ b/cranelift/filetests/isa/intel/binary32-float.cton @@ -147,48 +147,48 @@ ebb0: ; Load/Store - ; asm: movd (%ecx), %xmm5 - [-,%xmm5] v100 = load.f32 v0 ; bin: 66 0f 6e 29 - ; asm: movd (%esi), %xmm2 - [-,%xmm2] v101 = load.f32 v1 ; bin: 66 0f 6e 16 - ; asm: movd 50(%ecx), %xmm5 - [-,%xmm5] v110 = load.f32 v0+50 ; bin: 66 0f 6e 69 32 - ; asm: movd -50(%esi), %xmm2 - [-,%xmm2] v111 = load.f32 v1-50 ; bin: 66 0f 6e 56 ce - ; asm: movd 10000(%ecx), %xmm5 - [-,%xmm5] v120 = load.f32 v0+10000 ; bin: 66 0f 6e a9 00002710 - ; asm: movd -10000(%esi), %xmm2 - [-,%xmm2] v121 = load.f32 v1-10000 ; bin: 66 0f 6e 96 ffffd8f0 + ; asm: movss (%ecx), %xmm5 + [-,%xmm5] v100 = load.f32 v0 ; bin: heap_oob f3 0f 10 29 + ; asm: movss (%esi), %xmm2 + [-,%xmm2] v101 = load.f32 v1 ; bin: heap_oob f3 0f 10 16 + ; asm: movss 50(%ecx), %xmm5 + [-,%xmm5] v110 = load.f32 v0+50 ; bin: heap_oob f3 0f 10 69 32 + ; asm: movss -50(%esi), %xmm2 + [-,%xmm2] v111 = load.f32 v1-50 ; bin: heap_oob f3 0f 10 56 ce + ; asm: movss 10000(%ecx), %xmm5 + [-,%xmm5] v120 = load.f32 v0+10000 ; bin: heap_oob f3 0f 10 a9 00002710 + ; asm: movss -10000(%esi), %xmm2 + [-,%xmm2] v121 = load.f32 v1-10000 ; bin: heap_oob f3 0f 10 96 ffffd8f0 - ; asm: movd %xmm5, (%ecx) - [-] store.f32 v100, v0 ; bin: 66 0f 7e 29 - ; asm: movd %xmm2, (%esi) - [-] store.f32 v101, v1 ; bin: 66 0f 7e 16 - ; asm: movd %xmm5, 50(%ecx) - [-] store.f32 v100, v0+50 ; bin: 66 0f 7e 69 32 - ; asm: movd %xmm2, -50(%esi) - [-] store.f32 v101, v1-50 ; bin: 66 0f 7e 56 ce - ; asm: movd %xmm5, 10000(%ecx) - [-] store.f32 v100, v0+10000 ; bin: 66 0f 7e a9 00002710 - ; asm: movd %xmm2, -10000(%esi) - [-] store.f32 v101, v1-10000 ; bin: 66 0f 7e 96 ffffd8f0 + ; asm: movss %xmm5, (%ecx) + [-] store.f32 v100, v0 ; bin: heap_oob f3 0f 11 29 + ; asm: movss %xmm2, (%esi) + [-] store.f32 v101, v1 ; bin: heap_oob f3 0f 11 16 + ; asm: movss %xmm5, 50(%ecx) + [-] store.f32 v100, v0+50 ; bin: heap_oob f3 0f 11 69 32 + ; asm: movss %xmm2, -50(%esi) + [-] store.f32 v101, v1-50 ; bin: heap_oob f3 0f 11 56 ce + ; asm: movss %xmm5, 10000(%ecx) + [-] store.f32 v100, v0+10000 ; bin: heap_oob f3 0f 11 a9 00002710 + ; asm: movss %xmm2, -10000(%esi) + [-] store.f32 v101, v1-10000 ; bin: heap_oob f3 0f 11 96 ffffd8f0 ; Spill / Fill. - ; asm: movd %xmm5, 1032(%esp) - [-,ss1] v200 = spill v100 ; bin: 66 0f 7e ac 24 00000408 - ; asm: movd %xmm2, 1032(%esp) - [-,ss1] v201 = spill v101 ; bin: 66 0f 7e 94 24 00000408 + ; asm: movss %xmm5, 1032(%esp) + [-,ss1] v200 = spill v100 ; bin: f3 0f 11 ac 24 00000408 + ; asm: movss %xmm2, 1032(%esp) + [-,ss1] v201 = spill v101 ; bin: f3 0f 11 94 24 00000408 - ; asm: movd 1032(%esp), %xmm5 - [-,%xmm5] v210 = fill v200 ; bin: 66 0f 6e ac 24 00000408 - ; asm: movd 1032(%esp), %xmm2 - [-,%xmm2] v211 = fill v201 ; bin: 66 0f 6e 94 24 00000408 + ; asm: movss 1032(%esp), %xmm5 + [-,%xmm5] v210 = fill v200 ; bin: f3 0f 10 ac 24 00000408 + ; asm: movss 1032(%esp), %xmm2 + [-,%xmm2] v211 = fill v201 ; bin: f3 0f 10 94 24 00000408 - ; asm: movd %xmm5, 1032(%rsp) - regspill v100, %xmm5 -> ss1 ; bin: 66 0f 7e ac 24 00000408 - ; asm: movd 1032(%rsp), %xmm5 - regfill v100, ss1 -> %xmm5 ; bin: 66 0f 6e ac 24 00000408 + ; asm: movss %xmm5, 1032(%rsp) + regspill v100, %xmm5 -> ss1 ; bin: f3 0f 11 ac 24 00000408 + ; asm: movss 1032(%rsp), %xmm5 + regfill v100, ss1 -> %xmm5 ; bin: f3 0f 10 ac 24 00000408 ; Comparisons. ; @@ -221,11 +221,11 @@ ebb0: [-,%rdx] v307 = fcmp ule v11, v10 ; bin: 0f 2e d5 0f 96 c2 ; asm: ucomiss %xmm2, %xmm5 - [-,%eflags] v310 = ffcmp v10, v11 ; bin: 0f 2e ea + [-,%rflags] v310 = ffcmp v10, v11 ; bin: 0f 2e ea ; asm: ucomiss %xmm2, %xmm5 - [-,%eflags] v311 = ffcmp v11, v10 ; bin: 0f 2e d5 + [-,%rflags] v311 = ffcmp v11, v10 ; bin: 0f 2e d5 ; asm: ucomiss %xmm5, %xmm5 - [-,%eflags] v312 = ffcmp v10, v10 ; bin: 0f 2e ed + [-,%rflags] v312 = ffcmp v10, v10 ; bin: 0f 2e ed return } @@ -362,48 +362,48 @@ ebb0: ; Load/Store - ; asm: movq (%ecx), %xmm5 - [-,%xmm5] v100 = load.f64 v0 ; bin: f3 0f 7e 29 - ; asm: movq (%esi), %xmm2 - [-,%xmm2] v101 = load.f64 v1 ; bin: f3 0f 7e 16 - ; asm: movq 50(%ecx), %xmm5 - [-,%xmm5] v110 = load.f64 v0+50 ; bin: f3 0f 7e 69 32 - ; asm: movq -50(%esi), %xmm2 - [-,%xmm2] v111 = load.f64 v1-50 ; bin: f3 0f 7e 56 ce - ; asm: movq 10000(%ecx), %xmm5 - [-,%xmm5] v120 = load.f64 v0+10000 ; bin: f3 0f 7e a9 00002710 - ; asm: movq -10000(%esi), %xmm2 - [-,%xmm2] v121 = load.f64 v1-10000 ; bin: f3 0f 7e 96 ffffd8f0 + ; asm: movsd (%ecx), %xmm5 + [-,%xmm5] v100 = load.f64 v0 ; bin: heap_oob f2 0f 10 29 + ; asm: movsd (%esi), %xmm2 + [-,%xmm2] v101 = load.f64 v1 ; bin: heap_oob f2 0f 10 16 + ; asm: movsd 50(%ecx), %xmm5 + [-,%xmm5] v110 = load.f64 v0+50 ; bin: heap_oob f2 0f 10 69 32 + ; asm: movsd -50(%esi), %xmm2 + [-,%xmm2] v111 = load.f64 v1-50 ; bin: heap_oob f2 0f 10 56 ce + ; asm: movsd 10000(%ecx), %xmm5 + [-,%xmm5] v120 = load.f64 v0+10000 ; bin: heap_oob f2 0f 10 a9 00002710 + ; asm: movsd -10000(%esi), %xmm2 + [-,%xmm2] v121 = load.f64 v1-10000 ; bin: heap_oob f2 0f 10 96 ffffd8f0 - ; asm: movq %xmm5, (%ecx) - [-] store.f64 v100, v0 ; bin: 66 0f d6 29 - ; asm: movq %xmm2, (%esi) - [-] store.f64 v101, v1 ; bin: 66 0f d6 16 - ; asm: movq %xmm5, 50(%ecx) - [-] store.f64 v100, v0+50 ; bin: 66 0f d6 69 32 - ; asm: movq %xmm2, -50(%esi) - [-] store.f64 v101, v1-50 ; bin: 66 0f d6 56 ce - ; asm: movq %xmm5, 10000(%ecx) - [-] store.f64 v100, v0+10000 ; bin: 66 0f d6 a9 00002710 - ; asm: movq %xmm2, -10000(%esi) - [-] store.f64 v101, v1-10000 ; bin: 66 0f d6 96 ffffd8f0 + ; asm: movsd %xmm5, (%ecx) + [-] store.f64 v100, v0 ; bin: heap_oob f2 0f 11 29 + ; asm: movsd %xmm2, (%esi) + [-] store.f64 v101, v1 ; bin: heap_oob f2 0f 11 16 + ; asm: movsd %xmm5, 50(%ecx) + [-] store.f64 v100, v0+50 ; bin: heap_oob f2 0f 11 69 32 + ; asm: movsd %xmm2, -50(%esi) + [-] store.f64 v101, v1-50 ; bin: heap_oob f2 0f 11 56 ce + ; asm: movsd %xmm5, 10000(%ecx) + [-] store.f64 v100, v0+10000 ; bin: heap_oob f2 0f 11 a9 00002710 + ; asm: movsd %xmm2, -10000(%esi) + [-] store.f64 v101, v1-10000 ; bin: heap_oob f2 0f 11 96 ffffd8f0 ; Spill / Fill. - ; asm: movq %xmm5, 1032(%esp) - [-,ss1] v200 = spill v100 ; bin: 66 0f d6 ac 24 00000408 - ; asm: movq %xmm2, 1032(%esp) - [-,ss1] v201 = spill v101 ; bin: 66 0f d6 94 24 00000408 + ; asm: movsd %xmm5, 1032(%esp) + [-,ss1] v200 = spill v100 ; bin: f2 0f 11 ac 24 00000408 + ; asm: movsd %xmm2, 1032(%esp) + [-,ss1] v201 = spill v101 ; bin: f2 0f 11 94 24 00000408 - ; asm: movq 1032(%esp), %xmm5 - [-,%xmm5] v210 = fill v200 ; bin: f3 0f 7e ac 24 00000408 - ; asm: movq 1032(%esp), %xmm2 - [-,%xmm2] v211 = fill v201 ; bin: f3 0f 7e 94 24 00000408 + ; asm: movsd 1032(%esp), %xmm5 + [-,%xmm5] v210 = fill v200 ; bin: f2 0f 10 ac 24 00000408 + ; asm: movsd 1032(%esp), %xmm2 + [-,%xmm2] v211 = fill v201 ; bin: f2 0f 10 94 24 00000408 - ; asm: movq %xmm5, 1032(%rsp) - regspill v100, %xmm5 -> ss1 ; bin: 66 0f d6 ac 24 00000408 - ; asm: movq 1032(%rsp), %xmm5 - regfill v100, ss1 -> %xmm5 ; bin: f3 0f 7e ac 24 00000408 + ; asm: movsd %xmm5, 1032(%rsp) + regspill v100, %xmm5 -> ss1 ; bin: f2 0f 11 ac 24 00000408 + ; asm: movsd 1032(%rsp), %xmm5 + regfill v100, ss1 -> %xmm5 ; bin: f2 0f 10 ac 24 00000408 ; Comparisons. ; @@ -436,11 +436,11 @@ ebb0: [-,%rdx] v307 = fcmp ule v11, v10 ; bin: 66 0f 2e d5 0f 96 c2 ; asm: ucomisd %xmm2, %xmm5 - [-,%eflags] v310 = ffcmp v10, v11 ; bin: 66 0f 2e ea + [-,%rflags] v310 = ffcmp v10, v11 ; bin: 66 0f 2e ea ; asm: ucomisd %xmm2, %xmm5 - [-,%eflags] v311 = ffcmp v11, v10 ; bin: 66 0f 2e d5 + [-,%rflags] v311 = ffcmp v11, v10 ; bin: 66 0f 2e d5 ; asm: ucomisd %xmm5, %xmm5 - [-,%eflags] v312 = ffcmp v10, v10 ; bin: 66 0f 2e ed + [-,%rflags] v312 = ffcmp v10, v10 ; bin: 66 0f 2e ed return } @@ -448,7 +448,7 @@ ebb0: function %cpuflags_float(f32 [%xmm0]) { ebb0(v0: f32 [%xmm0]): ; asm: ucomiss %xmm0, %xmm0 - [-,%eflags] v1 = ffcmp v0, v0 ; bin: 0f 2e c0 + [-,%rflags] v1 = ffcmp v0, v0 ; bin: 0f 2e c0 jump ebb1 @@ -471,21 +471,21 @@ ebb1: brff ule v1, ebb1 ; bin: 76 f0 ; asm: jp .+4; ud2 - trapff ord v1, user0 ; bin: 7a 02 0f 0b + trapff ord v1, user0 ; bin: 7a 02 user0 0f 0b ; asm: jnp .+4; ud2 - trapff uno v1, user0 ; bin: 7b 02 0f 0b + trapff uno v1, user0 ; bin: 7b 02 user0 0f 0b ; asm: je .+4; ud2 - trapff one v1, user0 ; bin: 74 02 0f 0b + trapff one v1, user0 ; bin: 74 02 user0 0f 0b ; asm: jne .+4; ud2 - trapff ueq v1, user0 ; bin: 75 02 0f 0b + trapff ueq v1, user0 ; bin: 75 02 user0 0f 0b ; asm: jna .+4; ud2 - trapff gt v1, user0 ; bin: 76 02 0f 0b + trapff gt v1, user0 ; bin: 76 02 user0 0f 0b ; asm: jnae .+4; ud2 - trapff ge v1, user0 ; bin: 72 02 0f 0b + trapff ge v1, user0 ; bin: 72 02 user0 0f 0b ; asm: jnb .+4; ud2 - trapff ult v1, user0 ; bin: 73 02 0f 0b + trapff ult v1, user0 ; bin: 73 02 user0 0f 0b ; asm: jnbe .+4; ud2 - trapff ule v1, user0 ; bin: 77 02 0f 0b + trapff ule v1, user0 ; bin: 77 02 user0 0f 0b ; asm: setnp %bl [-,%rbx] v10 = trueff ord v1 ; bin: 0f 9b c3 diff --git a/cranelift/filetests/isa/intel/binary32.cton b/cranelift/filetests/isa/intel/binary32.cton index df98a86515..2567fbe88c 100644 --- a/cranelift/filetests/isa/intel/binary32.cton +++ b/cranelift/filetests/isa/intel/binary32.cton @@ -1,4 +1,4 @@ -; binary emission of 32-bit code. +; binary emission of x86-32 code. test binemit set is_compressed isa intel haswell @@ -25,6 +25,9 @@ ebb0: ; asm: movl $2, %esi [-,%rsi] v2 = iconst.i32 2 ; bin: be 00000002 + ; asm: movb $1, %cl + [-,%rcx] v9007 = bconst.b1 true ; bin: b9 00000001 + ; Integer Register-Register Operations. ; asm: addl %esi, %ecx @@ -125,13 +128,13 @@ ebb0: ; 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 + [-,%rax,%rdx] v54, v55 = x86_sdivmodx v52, v53, v1 ; bin: int_divz f7 f9 ; asm: idivl %esi - [-,%rax,%rdx] v56, v57 = x86_sdivmodx v52, v53, v2 ; bin: f7 fe + [-,%rax,%rdx] v56, v57 = x86_sdivmodx v52, v53, v2 ; bin: int_divz f7 fe ; asm: divl %ecx - [-,%rax,%rdx] v58, v59 = x86_udivmodx v52, v53, v1 ; bin: f7 f1 + [-,%rax,%rdx] v58, v59 = x86_udivmodx v52, v53, v1 ; bin: int_divz f7 f1 ; asm: divl %esi - [-,%rax,%rdx] v60, v61 = x86_udivmodx v52, v53, v2 ; bin: f7 f6 + [-,%rax,%rdx] v60, v61 = x86_udivmodx v52, v53, v2 ; bin: int_divz f7 f6 ; Register copies. @@ -152,105 +155,105 @@ ebb0: ; Register indirect addressing with no displacement. ; asm: movl %ecx, (%esi) - store v1, v2 ; bin: 89 0e + store v1, v2 ; bin: heap_oob 89 0e ; asm: movl %esi, (%ecx) - store v2, v1 ; bin: 89 31 + store v2, v1 ; bin: heap_oob 89 31 ; asm: movw %cx, (%esi) - istore16 v1, v2 ; bin: 66 89 0e + istore16 v1, v2 ; bin: heap_oob 66 89 0e ; asm: movw %si, (%ecx) - istore16 v2, v1 ; bin: 66 89 31 + istore16 v2, v1 ; bin: heap_oob 66 89 31 ; asm: movb %cl, (%esi) - istore8 v1, v2 ; bin: 88 0e + istore8 v1, v2 ; bin: heap_oob 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 + [-,%rdi] v100 = load.i32 v1 ; bin: heap_oob 8b 39 ; asm: movl (%esi), %edx - [-,%rdx] v101 = load.i32 v2 ; bin: 8b 16 + [-,%rdx] v101 = load.i32 v2 ; bin: heap_oob 8b 16 ; asm: movzwl (%ecx), %edi - [-,%rdi] v102 = uload16.i32 v1 ; bin: 0f b7 39 + [-,%rdi] v102 = uload16.i32 v1 ; bin: heap_oob 0f b7 39 ; asm: movzwl (%esi), %edx - [-,%rdx] v103 = uload16.i32 v2 ; bin: 0f b7 16 + [-,%rdx] v103 = uload16.i32 v2 ; bin: heap_oob 0f b7 16 ; asm: movswl (%ecx), %edi - [-,%rdi] v104 = sload16.i32 v1 ; bin: 0f bf 39 + [-,%rdi] v104 = sload16.i32 v1 ; bin: heap_oob 0f bf 39 ; asm: movswl (%esi), %edx - [-,%rdx] v105 = sload16.i32 v2 ; bin: 0f bf 16 + [-,%rdx] v105 = sload16.i32 v2 ; bin: heap_oob 0f bf 16 ; asm: movzbl (%ecx), %edi - [-,%rdi] v106 = uload8.i32 v1 ; bin: 0f b6 39 + [-,%rdi] v106 = uload8.i32 v1 ; bin: heap_oob 0f b6 39 ; asm: movzbl (%esi), %edx - [-,%rdx] v107 = uload8.i32 v2 ; bin: 0f b6 16 + [-,%rdx] v107 = uload8.i32 v2 ; bin: heap_oob 0f b6 16 ; asm: movsbl (%ecx), %edi - [-,%rdi] v108 = sload8.i32 v1 ; bin: 0f be 39 + [-,%rdi] v108 = sload8.i32 v1 ; bin: heap_oob 0f be 39 ; asm: movsbl (%esi), %edx - [-,%rdx] v109 = sload8.i32 v2 ; bin: 0f be 16 + [-,%rdx] v109 = sload8.i32 v2 ; bin: heap_oob 0f be 16 ; Register-indirect with 8-bit signed displacement. ; asm: movl %ecx, 100(%esi) - store v1, v2+100 ; bin: 89 4e 64 + store v1, v2+100 ; bin: heap_oob 89 4e 64 ; asm: movl %esi, -100(%ecx) - store v2, v1-100 ; bin: 89 71 9c + store v2, v1-100 ; bin: heap_oob 89 71 9c ; asm: movw %cx, 100(%esi) - istore16 v1, v2+100 ; bin: 66 89 4e 64 + istore16 v1, v2+100 ; bin: heap_oob 66 89 4e 64 ; asm: movw %si, -100(%ecx) - istore16 v2, v1-100 ; bin: 66 89 71 9c + istore16 v2, v1-100 ; bin: heap_oob 66 89 71 9c ; asm: movb %cl, 100(%esi) - istore8 v1, v2+100 ; bin: 88 4e 64 + istore8 v1, v2+100 ; bin: heap_oob 88 4e 64 ; asm: movl 50(%ecx), %edi - [-,%rdi] v110 = load.i32 v1+50 ; bin: 8b 79 32 + [-,%rdi] v110 = load.i32 v1+50 ; bin: heap_oob 8b 79 32 ; asm: movl -50(%esi), %edx - [-,%rdx] v111 = load.i32 v2-50 ; bin: 8b 56 ce + [-,%rdx] v111 = load.i32 v2-50 ; bin: heap_oob 8b 56 ce ; asm: movzwl 50(%ecx), %edi - [-,%rdi] v112 = uload16.i32 v1+50 ; bin: 0f b7 79 32 + [-,%rdi] v112 = uload16.i32 v1+50 ; bin: heap_oob 0f b7 79 32 ; asm: movzwl -50(%esi), %edx - [-,%rdx] v113 = uload16.i32 v2-50 ; bin: 0f b7 56 ce + [-,%rdx] v113 = uload16.i32 v2-50 ; bin: heap_oob 0f b7 56 ce ; asm: movswl 50(%ecx), %edi - [-,%rdi] v114 = sload16.i32 v1+50 ; bin: 0f bf 79 32 + [-,%rdi] v114 = sload16.i32 v1+50 ; bin: heap_oob 0f bf 79 32 ; asm: movswl -50(%esi), %edx - [-,%rdx] v115 = sload16.i32 v2-50 ; bin: 0f bf 56 ce + [-,%rdx] v115 = sload16.i32 v2-50 ; bin: heap_oob 0f bf 56 ce ; asm: movzbl 50(%ecx), %edi - [-,%rdi] v116 = uload8.i32 v1+50 ; bin: 0f b6 79 32 + [-,%rdi] v116 = uload8.i32 v1+50 ; bin: heap_oob 0f b6 79 32 ; asm: movzbl -50(%esi), %edx - [-,%rdx] v117 = uload8.i32 v2-50 ; bin: 0f b6 56 ce + [-,%rdx] v117 = uload8.i32 v2-50 ; bin: heap_oob 0f b6 56 ce ; asm: movsbl 50(%ecx), %edi - [-,%rdi] v118 = sload8.i32 v1+50 ; bin: 0f be 79 32 + [-,%rdi] v118 = sload8.i32 v1+50 ; bin: heap_oob 0f be 79 32 ; asm: movsbl -50(%esi), %edx - [-,%rdx] v119 = sload8.i32 v2-50 ; bin: 0f be 56 ce + [-,%rdx] v119 = sload8.i32 v2-50 ; bin: heap_oob 0f be 56 ce ; Register-indirect with 32-bit signed displacement. ; asm: movl %ecx, 10000(%esi) - store v1, v2+10000 ; bin: 89 8e 00002710 + store v1, v2+10000 ; bin: heap_oob 89 8e 00002710 ; asm: movl %esi, -10000(%ecx) - store v2, v1-10000 ; bin: 89 b1 ffffd8f0 + store v2, v1-10000 ; bin: heap_oob 89 b1 ffffd8f0 ; asm: movw %cx, 10000(%esi) - istore16 v1, v2+10000 ; bin: 66 89 8e 00002710 + istore16 v1, v2+10000 ; bin: heap_oob 66 89 8e 00002710 ; asm: movw %si, -10000(%ecx) - istore16 v2, v1-10000 ; bin: 66 89 b1 ffffd8f0 + istore16 v2, v1-10000 ; bin: heap_oob 66 89 b1 ffffd8f0 ; asm: movb %cl, 10000(%esi) - istore8 v1, v2+10000 ; bin: 88 8e 00002710 + istore8 v1, v2+10000 ; bin: heap_oob 88 8e 00002710 ; asm: movl 50000(%ecx), %edi - [-,%rdi] v120 = load.i32 v1+50000 ; bin: 8b b9 0000c350 + [-,%rdi] v120 = load.i32 v1+50000 ; bin: heap_oob 8b b9 0000c350 ; asm: movl -50000(%esi), %edx - [-,%rdx] v121 = load.i32 v2-50000 ; bin: 8b 96 ffff3cb0 + [-,%rdx] v121 = load.i32 v2-50000 ; bin: heap_oob 8b 96 ffff3cb0 ; asm: movzwl 50000(%ecx), %edi - [-,%rdi] v122 = uload16.i32 v1+50000 ; bin: 0f b7 b9 0000c350 + [-,%rdi] v122 = uload16.i32 v1+50000 ; bin: heap_oob 0f b7 b9 0000c350 ; asm: movzwl -50000(%esi), %edx - [-,%rdx] v123 = uload16.i32 v2-50000 ; bin: 0f b7 96 ffff3cb0 + [-,%rdx] v123 = uload16.i32 v2-50000 ; bin: heap_oob 0f b7 96 ffff3cb0 ; asm: movswl 50000(%ecx), %edi - [-,%rdi] v124 = sload16.i32 v1+50000 ; bin: 0f bf b9 0000c350 + [-,%rdi] v124 = sload16.i32 v1+50000 ; bin: heap_oob 0f bf b9 0000c350 ; asm: movswl -50000(%esi), %edx - [-,%rdx] v125 = sload16.i32 v2-50000 ; bin: 0f bf 96 ffff3cb0 + [-,%rdx] v125 = sload16.i32 v2-50000 ; bin: heap_oob 0f bf 96 ffff3cb0 ; asm: movzbl 50000(%ecx), %edi - [-,%rdi] v126 = uload8.i32 v1+50000 ; bin: 0f b6 b9 0000c350 + [-,%rdi] v126 = uload8.i32 v1+50000 ; bin: heap_oob 0f b6 b9 0000c350 ; asm: movzbl -50000(%esi), %edx - [-,%rdx] v127 = uload8.i32 v2-50000 ; bin: 0f b6 96 ffff3cb0 + [-,%rdx] v127 = uload8.i32 v2-50000 ; bin: heap_oob 0f b6 96 ffff3cb0 ; asm: movsbl 50000(%ecx), %edi - [-,%rdi] v128 = sload8.i32 v1+50000 ; bin: 0f be b9 0000c350 + [-,%rdi] v128 = sload8.i32 v1+50000 ; bin: heap_oob 0f be b9 0000c350 ; asm: movsbl -50000(%esi), %edx - [-,%rdx] v129 = sload8.i32 v2-50000 ; bin: 0f be 96 ffff3cb0 + [-,%rdx] v129 = sload8.i32 v2-50000 ; bin: heap_oob 0f be 96 ffff3cb0 ; Bit-counting instructions. @@ -403,6 +406,13 @@ ebb0: ; asm: addl $-2147483648, %esp adjust_sp_imm -2147483648 ; bin: 81 c4 80000000 + ; Shift immediates + ; asm: shll $2, %esi + [-,%rsi] v513 = ishl_imm v2, 2 ; bin: c1 e6 02 + ; asm: sarl $5, %esi + [-,%rsi] v514 = sshr_imm v2, 5 ; bin: c1 fe 05 + ; asm: shrl $8, %esi + [-,%rsi] v515 = ushr_imm v2, 8 ; bin: c1 ee 08 ; asm: testl %ecx, %ecx ; asm: je ebb1 @@ -427,7 +437,7 @@ ebb1: ; asm: ebb2: ebb2: - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } ; Special branch encodings only for I32 mode. @@ -466,9 +476,9 @@ ebb0: ebb1: ; asm: cmpl %esi, %ecx - [-,%eflags] v10 = ifcmp v1, v2 ; bin: 39 f1 + [-,%rflags] v10 = ifcmp v1, v2 ; bin: 39 f1 ; asm: cmpl %ecx, %esi - [-,%eflags] v11 = ifcmp v2, v1 ; bin: 39 ce + [-,%rflags] v11 = ifcmp v2, v1 ; bin: 39 ce ; asm: je ebb1 brif eq v11, ebb1 ; bin: 74 fa @@ -514,41 +524,41 @@ ebb1: ; The trapif instructions are encoded as macros: a conditional jump over a ud2. ; asm: jne .+4; ud2 - trapif eq v11, user0 ; bin: 75 02 0f 0b + trapif eq v11, user0 ; bin: 75 02 user0 0f 0b ; asm: je .+4; ud2 - trapif ne v11, user0 ; bin: 74 02 0f 0b + trapif ne v11, user0 ; bin: 74 02 user0 0f 0b ; asm: jnl .+4; ud2 - trapif slt v11, user0 ; bin: 7d 02 0f 0b + trapif slt v11, user0 ; bin: 7d 02 user0 0f 0b ; asm: jnge .+4; ud2 - trapif sge v11, user0 ; bin: 7c 02 0f 0b + trapif sge v11, user0 ; bin: 7c 02 user0 0f 0b ; asm: jng .+4; ud2 - trapif sgt v11, user0 ; bin: 7e 02 0f 0b + trapif sgt v11, user0 ; bin: 7e 02 user0 0f 0b ; asm: jnle .+4; ud2 - trapif sle v11, user0 ; bin: 7f 02 0f 0b + trapif sle v11, user0 ; bin: 7f 02 user0 0f 0b ; asm: jnb .+4; ud2 - trapif ult v11, user0 ; bin: 73 02 0f 0b + trapif ult v11, user0 ; bin: 73 02 user0 0f 0b ; asm: jnae .+4; ud2 - trapif uge v11, user0 ; bin: 72 02 0f 0b + trapif uge v11, user0 ; bin: 72 02 user0 0f 0b ; asm: jna .+4; ud2 - trapif ugt v11, user0 ; bin: 76 02 0f 0b + trapif ugt v11, user0 ; bin: 76 02 user0 0f 0b ; asm: jnbe .+4; ud2 - trapif ule v11, user0 ; bin: 77 02 0f 0b + trapif ule v11, user0 ; bin: 77 02 user0 0f 0b ; Stack check. ; asm: cmpl %esp, %ecx - [-,%eflags] v40 = ifcmp_sp v1 ; bin: 39 e1 + [-,%rflags] v40 = ifcmp_sp v1 ; bin: 39 e1 ; asm: cmpl %esp, %esi - [-,%eflags] v41 = ifcmp_sp v2 ; bin: 39 e6 + [-,%rflags] v41 = ifcmp_sp v2 ; bin: 39 e6 ; asm: cmpl $-100, %ecx - [-,%eflags] v42 = ifcmp_imm v1, -100 ; bin: 83 f9 9c + [-,%rflags] v42 = ifcmp_imm v1, -100 ; bin: 83 f9 9c ; asm: cmpl $100, %esi - [-,%eflags] v43 = ifcmp_imm v2, 100 ; bin: 83 fe 64 + [-,%rflags] v43 = ifcmp_imm v2, 100 ; bin: 83 fe 64 ; asm: cmpl $-10000, %ecx - [-,%eflags] v44 = ifcmp_imm v1, -10000 ; bin: 81 f9 ffffd8f0 + [-,%rflags] v44 = ifcmp_imm v1, -10000 ; bin: 81 f9 ffffd8f0 ; asm: cmpl $10000, %esi - [-,%eflags] v45 = ifcmp_imm v2, 10000 ; bin: 81 fe 00002710 + [-,%rflags] v45 = ifcmp_imm v2, 10000 ; bin: 81 fe 00002710 return } @@ -566,7 +576,7 @@ ebb0: ; asm: movzbl %cl, %esi [-,%rsi] v30 = uextend.i32 v11 ; bin: 0f b6 f1 - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } ; Tests for i32/i16 conversion instructions. @@ -582,5 +592,5 @@ ebb0: ; asm: movzwl %cx, %esi [-,%rsi] v30 = uextend.i32 v11 ; bin: 0f b7 f1 - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } diff --git a/cranelift/filetests/isa/intel/binary64-float.cton b/cranelift/filetests/isa/intel/binary64-float.cton index 69bfeb6c36..7053a1809e 100644 --- a/cranelift/filetests/isa/intel/binary64-float.cton +++ b/cranelift/filetests/isa/intel/binary64-float.cton @@ -157,52 +157,52 @@ ebb0: ; Load/Store - ; asm: movd (%r14), %xmm5 - [-,%xmm5] v100 = load.f32 v3 ; bin: 66 41 0f 6e 2e - ; asm: movd (%rax), %xmm10 - [-,%xmm10] v101 = load.f32 v2 ; bin: 66 44 0f 6e 10 - ; asm: movd 50(%r14), %xmm5 - [-,%xmm5] v110 = load.f32 v3+50 ; bin: 66 41 0f 6e 6e 32 - ; asm: movd -50(%rax), %xmm10 - [-,%xmm10] v111 = load.f32 v2-50 ; bin: 66 44 0f 6e 50 ce - ; asm: movd 10000(%r14), %xmm5 - [-,%xmm5] v120 = load.f32 v3+10000 ; bin: 66 41 0f 6e ae 00002710 - ; asm: movd -10000(%rax), %xmm10 - [-,%xmm10] v121 = load.f32 v2-10000 ; bin: 66 44 0f 6e 90 ffffd8f0 + ; asm: movss (%r14), %xmm5 + [-,%xmm5] v100 = load.f32 v3 ; bin: heap_oob f3 41 0f 10 2e + ; asm: movss (%rax), %xmm10 + [-,%xmm10] v101 = load.f32 v2 ; bin: heap_oob f3 44 0f 10 10 + ; asm: movss 50(%r14), %xmm5 + [-,%xmm5] v110 = load.f32 v3+50 ; bin: heap_oob f3 41 0f 10 6e 32 + ; asm: movss -50(%rax), %xmm10 + [-,%xmm10] v111 = load.f32 v2-50 ; bin: heap_oob f3 44 0f 10 50 ce + ; asm: movss 10000(%r14), %xmm5 + [-,%xmm5] v120 = load.f32 v3+10000 ; bin: heap_oob f3 41 0f 10 ae 00002710 + ; asm: movss -10000(%rax), %xmm10 + [-,%xmm10] v121 = load.f32 v2-10000 ; bin: heap_oob f3 44 0f 10 90 ffffd8f0 - ; asm: movd %xmm5, (%r14) - [-] store.f32 v100, v3 ; bin: 66 41 0f 7e 2e - ; asm: movd %xmm10, (%rax) - [-] store.f32 v101, v2 ; bin: 66 44 0f 7e 10 - ; asm: movd %xmm5, (%r13) - [-] store.f32 v100, v4 ; bin: 66 41 0f 7e 6d 00 - ; asm: movd %xmm10, (%r13) - [-] store.f32 v101, v4 ; bin: 66 45 0f 7e 55 00 - ; asm: movd %xmm5, 50(%r14) - [-] store.f32 v100, v3+50 ; bin: 66 41 0f 7e 6e 32 - ; asm: movd %xmm10, -50(%rax) - [-] store.f32 v101, v2-50 ; bin: 66 44 0f 7e 50 ce - ; asm: movd %xmm5, 10000(%r14) - [-] store.f32 v100, v3+10000 ; bin: 66 41 0f 7e ae 00002710 - ; asm: movd %xmm10, -10000(%rax) - [-] store.f32 v101, v2-10000 ; bin: 66 44 0f 7e 90 ffffd8f0 + ; asm: movss %xmm5, (%r14) + [-] store.f32 v100, v3 ; bin: heap_oob f3 41 0f 11 2e + ; asm: movss %xmm10, (%rax) + [-] store.f32 v101, v2 ; bin: heap_oob f3 44 0f 11 10 + ; asm: movss %xmm5, (%r13) + [-] store.f32 v100, v4 ; bin: heap_oob f3 41 0f 11 6d 00 + ; asm: movss %xmm10, (%r13) + [-] store.f32 v101, v4 ; bin: heap_oob f3 45 0f 11 55 00 + ; asm: movss %xmm5, 50(%r14) + [-] store.f32 v100, v3+50 ; bin: heap_oob f3 41 0f 11 6e 32 + ; asm: movss %xmm10, -50(%rax) + [-] store.f32 v101, v2-50 ; bin: heap_oob f3 44 0f 11 50 ce + ; asm: movss %xmm5, 10000(%r14) + [-] store.f32 v100, v3+10000 ; bin: heap_oob f3 41 0f 11 ae 00002710 + ; asm: movss %xmm10, -10000(%rax) + [-] store.f32 v101, v2-10000 ; bin: heap_oob f3 44 0f 11 90 ffffd8f0 ; Spill / Fill. - ; asm: movd %xmm5, 1032(%rsp) - [-,ss1] v200 = spill v100 ; bin: 66 0f 7e ac 24 00000408 - ; asm: movd %xmm10, 1032(%rsp) - [-,ss1] v201 = spill v101 ; bin: 66 44 0f 7e 94 24 00000408 + ; asm: movss %xmm5, 1032(%rsp) + [-,ss1] v200 = spill v100 ; bin: f3 0f 11 ac 24 00000408 + ; asm: movss %xmm10, 1032(%rsp) + [-,ss1] v201 = spill v101 ; bin: f3 44 0f 11 94 24 00000408 - ; asm: movd 1032(%rsp), %xmm5 - [-,%xmm5] v210 = fill v200 ; bin: 66 0f 6e ac 24 00000408 - ; asm: movd 1032(%rsp), %xmm10 - [-,%xmm10] v211 = fill v201 ; bin: 66 44 0f 6e 94 24 00000408 + ; asm: movss 1032(%rsp), %xmm5 + [-,%xmm5] v210 = fill v200 ; bin: f3 0f 10 ac 24 00000408 + ; asm: movss 1032(%rsp), %xmm10 + [-,%xmm10] v211 = fill v201 ; bin: f3 44 0f 10 94 24 00000408 - ; asm: movd %xmm5, 1032(%rsp) - regspill v100, %xmm5 -> ss1 ; bin: 66 0f 7e ac 24 00000408 - ; asm: movd 1032(%rsp), %xmm5 - regfill v100, ss1 -> %xmm5 ; bin: 66 0f 6e ac 24 00000408 + ; asm: movss %xmm5, 1032(%rsp) + regspill v100, %xmm5 -> ss1 ; bin: f3 0f 11 ac 24 00000408 + ; asm: movss 1032(%rsp), %xmm5 + regfill v100, ss1 -> %xmm5 ; bin: f3 0f 10 ac 24 00000408 ; Comparisons. ; @@ -235,11 +235,11 @@ ebb0: [-,%rdx] v307 = fcmp ule v11, v10 ; bin: 44 0f 2e d5 0f 96 c2 ; asm: ucomiss %xmm10, %xmm5 - [-,%eflags] v310 = ffcmp v10, v11 ; bin: 41 0f 2e ea + [-,%rflags] v310 = ffcmp v10, v11 ; bin: 41 0f 2e ea ; asm: ucomiss %xmm10, %xmm5 - [-,%eflags] v311 = ffcmp v11, v10 ; bin: 44 0f 2e d5 + [-,%rflags] v311 = ffcmp v11, v10 ; bin: 44 0f 2e d5 ; asm: ucomiss %xmm5, %xmm5 - [-,%eflags] v312 = ffcmp v10, v10 ; bin: 0f 2e ed + [-,%rflags] v312 = ffcmp v10, v10 ; bin: 0f 2e ed return } @@ -392,52 +392,52 @@ ebb0: ; Load/Store - ; asm: movq (%r14), %xmm5 - [-,%xmm5] v100 = load.f64 v3 ; bin: f3 41 0f 7e 2e - ; asm: movq (%rax), %xmm10 - [-,%xmm10] v101 = load.f64 v2 ; bin: f3 44 0f 7e 10 - ; asm: movq 50(%r14), %xmm5 - [-,%xmm5] v110 = load.f64 v3+50 ; bin: f3 41 0f 7e 6e 32 - ; asm: movq -50(%rax), %xmm10 - [-,%xmm10] v111 = load.f64 v2-50 ; bin: f3 44 0f 7e 50 ce - ; asm: movq 10000(%r14), %xmm5 - [-,%xmm5] v120 = load.f64 v3+10000 ; bin: f3 41 0f 7e ae 00002710 - ; asm: movq -10000(%rax), %xmm10 - [-,%xmm10] v121 = load.f64 v2-10000 ; bin: f3 44 0f 7e 90 ffffd8f0 + ; asm: movsd (%r14), %xmm5 + [-,%xmm5] v100 = load.f64 v3 ; bin: heap_oob f2 41 0f 10 2e + ; asm: movsd (%rax), %xmm10 + [-,%xmm10] v101 = load.f64 v2 ; bin: heap_oob f2 44 0f 10 10 + ; asm: movsd 50(%r14), %xmm5 + [-,%xmm5] v110 = load.f64 v3+50 ; bin: heap_oob f2 41 0f 10 6e 32 + ; asm: movsd -50(%rax), %xmm10 + [-,%xmm10] v111 = load.f64 v2-50 ; bin: heap_oob f2 44 0f 10 50 ce + ; asm: movsd 10000(%r14), %xmm5 + [-,%xmm5] v120 = load.f64 v3+10000 ; bin: heap_oob f2 41 0f 10 ae 00002710 + ; asm: movsd -10000(%rax), %xmm10 + [-,%xmm10] v121 = load.f64 v2-10000 ; bin: heap_oob f2 44 0f 10 90 ffffd8f0 - ; asm: movq %xmm5, (%r14) - [-] store.f64 v100, v3 ; bin: 66 41 0f d6 2e - ; asm: movq %xmm10, (%rax) - [-] store.f64 v101, v2 ; bin: 66 44 0f d6 10 - ; asm: movq %xmm5, (%r13) - [-] store.f64 v100, v4 ; bin: 66 41 0f d6 6d 00 - ; asm: movq %xmm10, (%r13) - [-] store.f64 v101, v4 ; bin: 66 45 0f d6 55 00 - ; asm: movq %xmm5, 50(%r14) - [-] store.f64 v100, v3+50 ; bin: 66 41 0f d6 6e 32 - ; asm: movq %xmm10, -50(%rax) - [-] store.f64 v101, v2-50 ; bin: 66 44 0f d6 50 ce - ; asm: movq %xmm5, 10000(%r14) - [-] store.f64 v100, v3+10000 ; bin: 66 41 0f d6 ae 00002710 - ; asm: movq %xmm10, -10000(%rax) - [-] store.f64 v101, v2-10000 ; bin: 66 44 0f d6 90 ffffd8f0 + ; asm: movsd %xmm5, (%r14) + [-] store.f64 v100, v3 ; bin: heap_oob f2 41 0f 11 2e + ; asm: movsd %xmm10, (%rax) + [-] store.f64 v101, v2 ; bin: heap_oob f2 44 0f 11 10 + ; asm: movsd %xmm5, (%r13) + [-] store.f64 v100, v4 ; bin: heap_oob f2 41 0f 11 6d 00 + ; asm: movsd %xmm10, (%r13) + [-] store.f64 v101, v4 ; bin: heap_oob f2 45 0f 11 55 00 + ; asm: movsd %xmm5, 50(%r14) + [-] store.f64 v100, v3+50 ; bin: heap_oob f2 41 0f 11 6e 32 + ; asm: movsd %xmm10, -50(%rax) + [-] store.f64 v101, v2-50 ; bin: heap_oob f2 44 0f 11 50 ce + ; asm: movsd %xmm5, 10000(%r14) + [-] store.f64 v100, v3+10000 ; bin: heap_oob f2 41 0f 11 ae 00002710 + ; asm: movsd %xmm10, -10000(%rax) + [-] store.f64 v101, v2-10000 ; bin: heap_oob f2 44 0f 11 90 ffffd8f0 ; Spill / Fill. - ; asm: movq %xmm5, 1032(%rsp) - [-,ss1] v200 = spill v100 ; bin: 66 0f d6 ac 24 00000408 - ; asm: movq %xmm10, 1032(%rsp) - [-,ss1] v201 = spill v101 ; bin: 66 44 0f d6 94 24 00000408 + ; asm: movsd %xmm5, 1032(%rsp) + [-,ss1] v200 = spill v100 ; bin: f2 0f 11 ac 24 00000408 + ; asm: movsd %xmm10, 1032(%rsp) + [-,ss1] v201 = spill v101 ; bin: f2 44 0f 11 94 24 00000408 - ; asm: movq 1032(%rsp), %xmm5 - [-,%xmm5] v210 = fill v200 ; bin: f3 0f 7e ac 24 00000408 - ; asm: movq 1032(%rsp), %xmm10 - [-,%xmm10] v211 = fill v201 ; bin: f3 44 0f 7e 94 24 00000408 + ; asm: movsd 1032(%rsp), %xmm5 + [-,%xmm5] v210 = fill v200 ; bin: f2 0f 10 ac 24 00000408 + ; asm: movsd 1032(%rsp), %xmm10 + [-,%xmm10] v211 = fill v201 ; bin: f2 44 0f 10 94 24 00000408 - ; asm: movq %xmm5, 1032(%rsp) - regspill v100, %xmm5 -> ss1 ; bin: 66 0f d6 ac 24 00000408 - ; asm: movq 1032(%rsp), %xmm5 - regfill v100, ss1 -> %xmm5 ; bin: f3 0f 7e ac 24 00000408 + ; asm: movsd %xmm5, 1032(%rsp) + regspill v100, %xmm5 -> ss1 ; bin: f2 0f 11 ac 24 00000408 + ; asm: movsd 1032(%rsp), %xmm5 + regfill v100, ss1 -> %xmm5 ; bin: f2 0f 10 ac 24 00000408 ; Comparisons. ; @@ -470,11 +470,11 @@ ebb0: [-,%rdx] v307 = fcmp ule v11, v10 ; bin: 66 44 0f 2e d5 0f 96 c2 ; asm: ucomisd %xmm10, %xmm5 - [-,%eflags] v310 = ffcmp v10, v11 ; bin: 66 41 0f 2e ea + [-,%rflags] v310 = ffcmp v10, v11 ; bin: 66 41 0f 2e ea ; asm: ucomisd %xmm10, %xmm5 - [-,%eflags] v311 = ffcmp v11, v10 ; bin: 66 44 0f 2e d5 + [-,%rflags] v311 = ffcmp v11, v10 ; bin: 66 44 0f 2e d5 ; asm: ucomisd %xmm5, %xmm5 - [-,%eflags] v312 = ffcmp v10, v10 ; bin: 66 0f 2e ed + [-,%rflags] v312 = ffcmp v10, v10 ; bin: 66 0f 2e ed return } @@ -482,7 +482,7 @@ ebb0: function %cpuflags_float(f32 [%xmm0]) { ebb0(v0: f32 [%xmm0]): ; asm: ucomiss %xmm0, %xmm0 - [-,%eflags] v1 = ffcmp v0, v0 ; bin: 0f 2e c0 + [-,%rflags] v1 = ffcmp v0, v0 ; bin: 0f 2e c0 jump ebb1 @@ -505,21 +505,21 @@ ebb1: brff ule v1, ebb1 ; bin: 76 f0 ; asm: jp .+4; ud2 - trapff ord v1, user0 ; bin: 7a 02 0f 0b + trapff ord v1, user0 ; bin: 7a 02 user0 0f 0b ; asm: jnp .+4; ud2 - trapff uno v1, user0 ; bin: 7b 02 0f 0b + trapff uno v1, user0 ; bin: 7b 02 user0 0f 0b ; asm: je .+4; ud2 - trapff one v1, user0 ; bin: 74 02 0f 0b + trapff one v1, user0 ; bin: 74 02 user0 0f 0b ; asm: jne .+4; ud2 - trapff ueq v1, user0 ; bin: 75 02 0f 0b + trapff ueq v1, user0 ; bin: 75 02 user0 0f 0b ; asm: jna .+4; ud2 - trapff gt v1, user0 ; bin: 76 02 0f 0b + trapff gt v1, user0 ; bin: 76 02 user0 0f 0b ; asm: jnae .+4; ud2 - trapff ge v1, user0 ; bin: 72 02 0f 0b + trapff ge v1, user0 ; bin: 72 02 user0 0f 0b ; asm: jnb .+4; ud2 - trapff ult v1, user0 ; bin: 73 02 0f 0b + trapff ult v1, user0 ; bin: 73 02 user0 0f 0b ; asm: jnbe .+4; ud2 - trapff ule v1, user0 ; bin: 77 02 0f 0b + trapff ule v1, user0 ; bin: 77 02 user0 0f 0b ; asm: setnp %bl [-,%rbx] v10 = trueff ord v1 ; bin: 0f 9b c3 diff --git a/cranelift/filetests/isa/intel/binary64.cton b/cranelift/filetests/isa/intel/binary64.cton index 9eca950ee6..1a47dc1cba 100644 --- a/cranelift/filetests/isa/intel/binary64.cton +++ b/cranelift/filetests/isa/intel/binary64.cton @@ -1,4 +1,4 @@ -; binary emission of 64-bit code. +; binary emission of x86-64 code. test binemit set is_64bit set is_compressed @@ -38,6 +38,11 @@ ebb0: ; asm: movq $0xffffffff88001122, %r14 # 32-bit sign-extended constant. [-,%r14] v5 = iconst.i64 0xffff_ffff_8800_1122 ; bin: 49 c7 c6 88001122 + ; asm: movb $1, %cl + [-,%rcx] v9007 = bconst.b1 true ; bin: b9 00000001 + ; asm: movb $1, %sil + [-,%r10] v9008 = bconst.b1 true ; bin: 41 ba 00000001 + ; Integer Register-Register Operations. ; asm: addq %rsi, %rcx @@ -170,146 +175,146 @@ ebb0: ; Register indirect addressing with no displacement. ; asm: movq %rcx, (%r10) - store v1, v3 ; bin: 49 89 0a + store v1, v3 ; bin: heap_oob 49 89 0a ; asm: movq %r10, (%rcx) - store v3, v1 ; bin: 4c 89 11 + store v3, v1 ; bin: heap_oob 4c 89 11 ; asm: movl %ecx, (%r10) - istore32 v1, v3 ; bin: 41 89 0a + istore32 v1, v3 ; bin: heap_oob 41 89 0a ; asm: movl %r10d, (%rcx) - istore32 v3, v1 ; bin: 44 89 11 + istore32 v3, v1 ; bin: heap_oob 44 89 11 ; asm: movw %cx, (%r10) - istore16 v1, v3 ; bin: 66 41 89 0a + istore16 v1, v3 ; bin: heap_oob 66 41 89 0a ; asm: movw %r10w, (%rcx) - istore16 v3, v1 ; bin: 66 44 89 11 + istore16 v3, v1 ; bin: heap_oob 66 44 89 11 ; asm: movb %cl, (%r10) - istore8 v1, v3 ; bin: 41 88 0a + istore8 v1, v3 ; bin: heap_oob 41 88 0a ; asm: movb %r10b, (%rcx) - istore8 v3, v1 ; bin: 44 88 11 + istore8 v3, v1 ; bin: heap_oob 44 88 11 ; asm: movq (%rcx), %r14 - [-,%r14] v120 = load.i64 v1 ; bin: 4c 8b 31 + [-,%r14] v120 = load.i64 v1 ; bin: heap_oob 4c 8b 31 ; asm: movq (%r10), %rdx - [-,%rdx] v121 = load.i64 v3 ; bin: 49 8b 12 + [-,%rdx] v121 = load.i64 v3 ; bin: heap_oob 49 8b 12 ; asm: movl (%rcx), %r14d - [-,%r14] v122 = uload32.i64 v1 ; bin: 44 8b 31 + [-,%r14] v122 = uload32.i64 v1 ; bin: heap_oob 44 8b 31 ; asm: movl (%r10), %edx - [-,%rdx] v123 = uload32.i64 v3 ; bin: 41 8b 12 + [-,%rdx] v123 = uload32.i64 v3 ; bin: heap_oob 41 8b 12 ; asm: movslq (%rcx), %r14 - [-,%r14] v124 = sload32.i64 v1 ; bin: 4c 63 31 + [-,%r14] v124 = sload32.i64 v1 ; bin: heap_oob 4c 63 31 ; asm: movslq (%r10), %rdx - [-,%rdx] v125 = sload32.i64 v3 ; bin: 49 63 12 + [-,%rdx] v125 = sload32.i64 v3 ; bin: heap_oob 49 63 12 ; asm: movzwq (%rcx), %r14 - [-,%r14] v126 = uload16.i64 v1 ; bin: 4c 0f b7 31 + [-,%r14] v126 = uload16.i64 v1 ; bin: heap_oob 4c 0f b7 31 ; asm: movzwq (%r10), %rdx - [-,%rdx] v127 = uload16.i64 v3 ; bin: 49 0f b7 12 + [-,%rdx] v127 = uload16.i64 v3 ; bin: heap_oob 49 0f b7 12 ; asm: movswq (%rcx), %r14 - [-,%r14] v128 = sload16.i64 v1 ; bin: 4c 0f bf 31 + [-,%r14] v128 = sload16.i64 v1 ; bin: heap_oob 4c 0f bf 31 ; asm: movswq (%r10), %rdx - [-,%rdx] v129 = sload16.i64 v3 ; bin: 49 0f bf 12 + [-,%rdx] v129 = sload16.i64 v3 ; bin: heap_oob 49 0f bf 12 ; asm: movzbq (%rcx), %r14 - [-,%r14] v130 = uload8.i64 v1 ; bin: 4c 0f b6 31 + [-,%r14] v130 = uload8.i64 v1 ; bin: heap_oob 4c 0f b6 31 ; asm: movzbq (%r10), %rdx - [-,%rdx] v131 = uload8.i64 v3 ; bin: 49 0f b6 12 + [-,%rdx] v131 = uload8.i64 v3 ; bin: heap_oob 49 0f b6 12 ; asm: movsbq (%rcx), %r14 - [-,%r14] v132 = sload8.i64 v1 ; bin: 4c 0f be 31 + [-,%r14] v132 = sload8.i64 v1 ; bin: heap_oob 4c 0f be 31 ; asm: movsbq (%r10), %rdx - [-,%rdx] v133 = sload8.i64 v3 ; bin: 49 0f be 12 + [-,%rdx] v133 = sload8.i64 v3 ; bin: heap_oob 49 0f be 12 ; Register-indirect with 8-bit signed displacement. ; asm: movq %rcx, 100(%r10) - store v1, v3+100 ; bin: 49 89 4a 64 + store v1, v3+100 ; bin: heap_oob 49 89 4a 64 ; asm: movq %r10, -100(%rcx) - store v3, v1-100 ; bin: 4c 89 51 9c + store v3, v1-100 ; bin: heap_oob 4c 89 51 9c ; asm: movl %ecx, 100(%r10) - istore32 v1, v3+100 ; bin: 41 89 4a 64 + istore32 v1, v3+100 ; bin: heap_oob 41 89 4a 64 ; asm: movl %r10d, -100(%rcx) - istore32 v3, v1-100 ; bin: 44 89 51 9c + istore32 v3, v1-100 ; bin: heap_oob 44 89 51 9c ; asm: movw %cx, 100(%r10) - istore16 v1, v3+100 ; bin: 66 41 89 4a 64 + istore16 v1, v3+100 ; bin: heap_oob 66 41 89 4a 64 ; asm: movw %r10w, -100(%rcx) - istore16 v3, v1-100 ; bin: 66 44 89 51 9c + istore16 v3, v1-100 ; bin: heap_oob 66 44 89 51 9c ; asm: movb %cl, 100(%r10) - istore8 v1, v3+100 ; bin: 41 88 4a 64 + istore8 v1, v3+100 ; bin: heap_oob 41 88 4a 64 ; asm: movb %r10b, 100(%rcx) - istore8 v3, v1+100 ; bin: 44 88 51 64 + istore8 v3, v1+100 ; bin: heap_oob 44 88 51 64 ; asm: movq 50(%rcx), %r10 - [-,%r10] v140 = load.i64 v1+50 ; bin: 4c 8b 51 32 + [-,%r10] v140 = load.i64 v1+50 ; bin: heap_oob 4c 8b 51 32 ; asm: movq -50(%r10), %rdx - [-,%rdx] v141 = load.i64 v3-50 ; bin: 49 8b 52 ce + [-,%rdx] v141 = load.i64 v3-50 ; bin: heap_oob 49 8b 52 ce ; asm: movl 50(%rcx), %edi - [-,%rdi] v142 = uload32.i64 v1+50 ; bin: 8b 79 32 + [-,%rdi] v142 = uload32.i64 v1+50 ; bin: heap_oob 8b 79 32 ; asm: movl -50(%rsi), %edx - [-,%rdx] v143 = uload32.i64 v2-50 ; bin: 8b 56 ce + [-,%rdx] v143 = uload32.i64 v2-50 ; bin: heap_oob 8b 56 ce ; asm: movslq 50(%rcx), %rdi - [-,%rdi] v144 = sload32.i64 v1+50 ; bin: 48 63 79 32 + [-,%rdi] v144 = sload32.i64 v1+50 ; bin: heap_oob 48 63 79 32 ; asm: movslq -50(%rsi), %rdx - [-,%rdx] v145 = sload32.i64 v2-50 ; bin: 48 63 56 ce + [-,%rdx] v145 = sload32.i64 v2-50 ; bin: heap_oob 48 63 56 ce ; asm: movzwq 50(%rcx), %rdi - [-,%rdi] v146 = uload16.i64 v1+50 ; bin: 48 0f b7 79 32 + [-,%rdi] v146 = uload16.i64 v1+50 ; bin: heap_oob 48 0f b7 79 32 ; asm: movzwq -50(%rsi), %rdx - [-,%rdx] v147 = uload16.i64 v2-50 ; bin: 48 0f b7 56 ce + [-,%rdx] v147 = uload16.i64 v2-50 ; bin: heap_oob 48 0f b7 56 ce ; asm: movswq 50(%rcx), %rdi - [-,%rdi] v148 = sload16.i64 v1+50 ; bin: 48 0f bf 79 32 + [-,%rdi] v148 = sload16.i64 v1+50 ; bin: heap_oob 48 0f bf 79 32 ; asm: movswq -50(%rsi), %rdx - [-,%rdx] v149 = sload16.i64 v2-50 ; bin: 48 0f bf 56 ce + [-,%rdx] v149 = sload16.i64 v2-50 ; bin: heap_oob 48 0f bf 56 ce ; asm: movzbq 50(%rcx), %rdi - [-,%rdi] v150 = uload8.i64 v1+50 ; bin: 48 0f b6 79 32 + [-,%rdi] v150 = uload8.i64 v1+50 ; bin: heap_oob 48 0f b6 79 32 ; asm: movzbq -50(%rsi), %rdx - [-,%rdx] v151 = uload8.i64 v2-50 ; bin: 48 0f b6 56 ce + [-,%rdx] v151 = uload8.i64 v2-50 ; bin: heap_oob 48 0f b6 56 ce ; asm: movsbq 50(%rcx), %rdi - [-,%rdi] v152 = sload8.i64 v1+50 ; bin: 48 0f be 79 32 + [-,%rdi] v152 = sload8.i64 v1+50 ; bin: heap_oob 48 0f be 79 32 ; asm: movsbq -50(%rsi), %rdx - [-,%rdx] v153 = sload8.i64 v2-50 ; bin: 48 0f be 56 ce + [-,%rdx] v153 = sload8.i64 v2-50 ; bin: heap_oob 48 0f be 56 ce ; Register-indirect with 32-bit signed displacement. ; asm: movq %rcx, 10000(%r10) - store v1, v3+10000 ; bin: 49 89 8a 00002710 + store v1, v3+10000 ; bin: heap_oob 49 89 8a 00002710 ; asm: movq %r10, -10000(%rcx) - store v3, v1-10000 ; bin: 4c 89 91 ffffd8f0 + store v3, v1-10000 ; bin: heap_oob 4c 89 91 ffffd8f0 ; asm: movl %ecx, 10000(%rsi) - istore32 v1, v2+10000 ; bin: 89 8e 00002710 + istore32 v1, v2+10000 ; bin: heap_oob 89 8e 00002710 ; asm: movl %esi, -10000(%rcx) - istore32 v2, v1-10000 ; bin: 89 b1 ffffd8f0 + istore32 v2, v1-10000 ; bin: heap_oob 89 b1 ffffd8f0 ; asm: movw %cx, 10000(%rsi) - istore16 v1, v2+10000 ; bin: 66 89 8e 00002710 + istore16 v1, v2+10000 ; bin: heap_oob 66 89 8e 00002710 ; asm: movw %si, -10000(%rcx) - istore16 v2, v1-10000 ; bin: 66 89 b1 ffffd8f0 + istore16 v2, v1-10000 ; bin: heap_oob 66 89 b1 ffffd8f0 ; asm: movb %cl, 10000(%rsi) - istore8 v1, v2+10000 ; bin: 88 8e 00002710 + istore8 v1, v2+10000 ; bin: heap_oob 88 8e 00002710 ; asm: movb %sil, 10000(%rcx) - istore8 v2, v1+10000 ; bin: 40 88 b1 00002710 + istore8 v2, v1+10000 ; bin: heap_oob 40 88 b1 00002710 ; asm: movq 50000(%rcx), %r10 - [-,%r10] v160 = load.i64 v1+50000 ; bin: 4c 8b 91 0000c350 + [-,%r10] v160 = load.i64 v1+50000 ; bin: heap_oob 4c 8b 91 0000c350 ; asm: movq -50000(%r10), %rdx - [-,%rdx] v161 = load.i64 v3-50000 ; bin: 49 8b 92 ffff3cb0 + [-,%rdx] v161 = load.i64 v3-50000 ; bin: heap_oob 49 8b 92 ffff3cb0 ; asm: movl 50000(%rcx), %edi - [-,%rdi] v162 = uload32.i64 v1+50000 ; bin: 8b b9 0000c350 + [-,%rdi] v162 = uload32.i64 v1+50000 ; bin: heap_oob 8b b9 0000c350 ; asm: movl -50000(%rsi), %edx - [-,%rdx] v163 = uload32.i64 v2-50000 ; bin: 8b 96 ffff3cb0 + [-,%rdx] v163 = uload32.i64 v2-50000 ; bin: heap_oob 8b 96 ffff3cb0 ; asm: movslq 50000(%rcx), %rdi - [-,%rdi] v164 = sload32.i64 v1+50000 ; bin: 48 63 b9 0000c350 + [-,%rdi] v164 = sload32.i64 v1+50000 ; bin: heap_oob 48 63 b9 0000c350 ; asm: movslq -50000(%rsi), %rdx - [-,%rdx] v165 = sload32.i64 v2-50000 ; bin: 48 63 96 ffff3cb0 + [-,%rdx] v165 = sload32.i64 v2-50000 ; bin: heap_oob 48 63 96 ffff3cb0 ; asm: movzwq 50000(%rcx), %rdi - [-,%rdi] v166 = uload16.i64 v1+50000 ; bin: 48 0f b7 b9 0000c350 + [-,%rdi] v166 = uload16.i64 v1+50000 ; bin: heap_oob 48 0f b7 b9 0000c350 ; asm: movzwq -50000(%rsi), %rdx - [-,%rdx] v167 = uload16.i64 v2-50000 ; bin: 48 0f b7 96 ffff3cb0 + [-,%rdx] v167 = uload16.i64 v2-50000 ; bin: heap_oob 48 0f b7 96 ffff3cb0 ; asm: movswq 50000(%rcx), %rdi - [-,%rdi] v168 = sload16.i64 v1+50000 ; bin: 48 0f bf b9 0000c350 + [-,%rdi] v168 = sload16.i64 v1+50000 ; bin: heap_oob 48 0f bf b9 0000c350 ; asm: movswq -50000(%rsi), %rdx - [-,%rdx] v169 = sload16.i64 v2-50000 ; bin: 48 0f bf 96 ffff3cb0 + [-,%rdx] v169 = sload16.i64 v2-50000 ; bin: heap_oob 48 0f bf 96 ffff3cb0 ; asm: movzbq 50000(%rcx), %rdi - [-,%rdi] v170 = uload8.i64 v1+50000 ; bin: 48 0f b6 b9 0000c350 + [-,%rdi] v170 = uload8.i64 v1+50000 ; bin: heap_oob 48 0f b6 b9 0000c350 ; asm: movzbq -50000(%rsi), %rdx - [-,%rdx] v171 = uload8.i64 v2-50000 ; bin: 48 0f b6 96 ffff3cb0 + [-,%rdx] v171 = uload8.i64 v2-50000 ; bin: heap_oob 48 0f b6 96 ffff3cb0 ; asm: movsbq 50000(%rcx), %rdi - [-,%rdi] v172 = sload8.i64 v1+50000 ; bin: 48 0f be b9 0000c350 + [-,%rdi] v172 = sload8.i64 v1+50000 ; bin: heap_oob 48 0f be b9 0000c350 ; asm: movsbq -50000(%rsi), %rdx - [-,%rdx] v173 = sload8.i64 v2-50000 ; bin: 48 0f be 96 ffff3cb0 + [-,%rdx] v173 = sload8.i64 v2-50000 ; bin: heap_oob 48 0f be 96 ffff3cb0 ; More arithmetic. @@ -324,17 +329,17 @@ ebb0: [-,%rax] v190 = iconst.i64 1 [-,%rdx] v191 = iconst.i64 2 ; asm: idivq %rcx - [-,%rax,%rdx] v192, v193 = x86_sdivmodx v190, v191, v1 ; bin: 48 f7 f9 + [-,%rax,%rdx] v192, v193 = x86_sdivmodx v190, v191, v1 ; bin: int_divz 48 f7 f9 ; asm: idivq %rsi - [-,%rax,%rdx] v194, v195 = x86_sdivmodx v190, v191, v2 ; bin: 48 f7 fe + [-,%rax,%rdx] v194, v195 = x86_sdivmodx v190, v191, v2 ; bin: int_divz 48 f7 fe ; asm: idivq %r10 - [-,%rax,%rdx] v196, v197 = x86_sdivmodx v190, v191, v3 ; bin: 49 f7 fa + [-,%rax,%rdx] v196, v197 = x86_sdivmodx v190, v191, v3 ; bin: int_divz 49 f7 fa ; asm: divq %rcx - [-,%rax,%rdx] v198, v199 = x86_udivmodx v190, v191, v1 ; bin: 48 f7 f1 + [-,%rax,%rdx] v198, v199 = x86_udivmodx v190, v191, v1 ; bin: int_divz 48 f7 f1 ; asm: divq %rsi - [-,%rax,%rdx] v200, v201 = x86_udivmodx v190, v191, v2 ; bin: 48 f7 f6 + [-,%rax,%rdx] v200, v201 = x86_udivmodx v190, v191, v2 ; bin: int_divz 48 f7 f6 ; asm: divq %r10 - [-,%rax,%rdx] v202, v203 = x86_udivmodx v190, v191, v3 ; bin: 49 f7 f2 + [-,%rax,%rdx] v202, v203 = x86_udivmodx v190, v191, v3 ; bin: int_divz 49 f7 f2 ; double-length multiply instructions, 64 bit [-,%rax] v1001 = iconst.i64 1 @@ -453,6 +458,14 @@ ebb0: ; asm: setbe %dl [-,%rdx] v319 = icmp ule v2, v3 ; bin: 4c 39 d6 0f 96 c2 + ; asm: cmpq $37, %rcx + ; asm: setl %bl + [-,%rbx] v320 = icmp_imm slt v1, 37 ; bin: 48 83 f9 25 0f 9c c3 + + ; asm: cmpq $100000, %rcx + ; asm: setl %bl + [-,%rbx] v321 = icmp_imm slt v1, 100000 ; bin: 48 81 f9 000186a0 0f 9c c3 + ; Bool-to-int conversions. ; asm: movzbq %bl, %rcx @@ -529,6 +542,21 @@ ebb0: ; asm: addq $-2147483648, %rsp adjust_sp_imm -2147483648 ; bin: 48 81 c4 80000000 + ; Shift immediates + ; asm: shlq $12, %rsi + [-,%rsi] v515 = ishl_imm v2, 12 ; bin: 48 c1 e6 0c + ; asm: shlq $13, %r8 + [-,%r8] v516 = ishl_imm v4, 13 ; bin: 49 c1 e0 0d + ; asm: sarq $32, %rsi + [-,%rsi] v517 = sshr_imm v2, 32 ; bin: 48 c1 fe 20 + ; asm: sarq $33, %r8 + [-,%r8] v518 = sshr_imm v4, 33 ; bin: 49 c1 f8 21 + ; asm: shrl $62, %rsi + [-,%rsi] v519 = ushr_imm v2, 62 ; bin: 48 c1 ee 3e + ; asm: shrl $63, %r8 + [-,%r8] v520 = ushr_imm v4, 63 ; bin: 49 c1 e8 3f + + ; asm: testq %rcx, %rcx ; asm: je ebb1 brz v1, ebb1 ; bin: 48 85 c9 74 1b @@ -569,9 +597,9 @@ ebb0: ebb1: ; asm: cmpq %r10, %rcx - [-,%eflags] v10 = ifcmp v1, v2 ; bin: 4c 39 d1 + [-,%rflags] v10 = ifcmp v1, v2 ; bin: 4c 39 d1 ; asm: cmpq %rcx, %r10 - [-,%eflags] v11 = ifcmp v2, v1 ; bin: 49 39 ca + [-,%rflags] v11 = ifcmp v2, v1 ; bin: 49 39 ca ; asm: je ebb1 brif eq v11, ebb1 ; bin: 74 f8 @@ -617,41 +645,42 @@ ebb1: ; The trapif instructions are encoded as macros: a conditional jump over a ud2. ; asm: jne .+4; ud2 - trapif eq v11, user0 ; bin: 75 02 0f 0b + trapif eq v11, user0 ; bin: 75 02 user0 0f 0b ; asm: je .+4; ud2 - trapif ne v11, user0 ; bin: 74 02 0f 0b + trapif ne v11, user0 ; bin: 74 02 user0 0f 0b ; asm: jnl .+4; ud2 - trapif slt v11, user0 ; bin: 7d 02 0f 0b + trapif slt v11, user0 ; bin: 7d 02 user0 0f 0b ; asm: jnge .+4; ud2 - trapif sge v11, user0 ; bin: 7c 02 0f 0b + trapif sge v11, user0 ; bin: 7c 02 user0 0f 0b ; asm: jng .+4; ud2 - trapif sgt v11, user0 ; bin: 7e 02 0f 0b + trapif sgt v11, user0 ; bin: 7e 02 user0 0f 0b ; asm: jnle .+4; ud2 - trapif sle v11, user0 ; bin: 7f 02 0f 0b + trapif sle v11, user0 ; bin: 7f 02 user0 0f 0b ; asm: jnb .+4; ud2 - trapif ult v11, user0 ; bin: 73 02 0f 0b + trapif ult v11, user0 ; bin: 73 02 user0 0f 0b ; asm: jnae .+4; ud2 - trapif uge v11, user0 ; bin: 72 02 0f 0b + trapif uge v11, user0 ; bin: 72 02 user0 0f 0b ; asm: jna .+4; ud2 - trapif ugt v11, user0 ; bin: 76 02 0f 0b + trapif ugt v11, user0 ; bin: 76 02 user0 0f 0b ; asm: jnbe .+4; ud2 - trapif ule v11, user0 ; bin: 77 02 0f 0b + trapif ule v11, user0 ; bin: 77 02 user0 0f 0b ; Stack check. ; asm: cmpq %rsp, %rcx - [-,%eflags] v40 = ifcmp_sp v1 ; bin: 48 39 e1 + [-,%rflags] v40 = ifcmp_sp v1 ; bin: 48 39 e1 ; asm: cmpq %rsp, %r10 - [-,%eflags] v41 = ifcmp_sp v2 ; bin: 49 39 e2 + [-,%rflags] v41 = ifcmp_sp v2 ; bin: 49 39 e2 ; asm: cmpq $-100, %rcx - [-,%eflags] v522 = ifcmp_imm v1, -100 ; bin: 48 83 f9 9c + [-,%rflags] v522 = ifcmp_imm v1, -100 ; bin: 48 83 f9 9c ; asm: cmpq $100, %r10 - [-,%eflags] v523 = ifcmp_imm v2, 100 ; bin: 49 83 fa 64 + [-,%rflags] v523 = ifcmp_imm v2, 100 ; bin: 49 83 fa 64 ; asm: cmpq $-10000, %rcx - [-,%eflags] v524 = ifcmp_imm v1, -10000 ; bin: 48 81 f9 ffffd8f0 + [-,%rflags] v524 = ifcmp_imm v1, -10000 ; bin: 48 81 f9 ffffd8f0 ; asm: cmpq $10000, %r10 - [-,%eflags] v525 = ifcmp_imm v2, 10000 ; bin: 49 81 fa 00002710 + [-,%rflags] v525 = ifcmp_imm v2, 10000 ; bin: 49 81 fa 00002710 + return } @@ -708,71 +737,71 @@ ebb0: ; Register indirect addressing with no displacement. ; asm: movl (%rcx), %edi - [-,%rdi] v10 = load.i32 v1 ; bin: 8b 39 + [-,%rdi] v10 = load.i32 v1 ; bin: heap_oob 8b 39 ; asm: movl (%rsi), %edx - [-,%rdx] v11 = load.i32 v2 ; bin: 8b 16 + [-,%rdx] v11 = load.i32 v2 ; bin: heap_oob 8b 16 ; asm: movzwl (%rcx), %edi - [-,%rdi] v12 = uload16.i32 v1 ; bin: 0f b7 39 + [-,%rdi] v12 = uload16.i32 v1 ; bin: heap_oob 0f b7 39 ; asm: movzwl (%rsi), %edx - [-,%rdx] v13 = uload16.i32 v2 ; bin: 0f b7 16 + [-,%rdx] v13 = uload16.i32 v2 ; bin: heap_oob 0f b7 16 ; asm: movswl (%rcx), %edi - [-,%rdi] v14 = sload16.i32 v1 ; bin: 0f bf 39 + [-,%rdi] v14 = sload16.i32 v1 ; bin: heap_oob 0f bf 39 ; asm: movswl (%rsi), %edx - [-,%rdx] v15 = sload16.i32 v2 ; bin: 0f bf 16 + [-,%rdx] v15 = sload16.i32 v2 ; bin: heap_oob 0f bf 16 ; asm: movzbl (%rcx), %edi - [-,%rdi] v16 = uload8.i32 v1 ; bin: 0f b6 39 + [-,%rdi] v16 = uload8.i32 v1 ; bin: heap_oob 0f b6 39 ; asm: movzbl (%rsi), %edx - [-,%rdx] v17 = uload8.i32 v2 ; bin: 0f b6 16 + [-,%rdx] v17 = uload8.i32 v2 ; bin: heap_oob 0f b6 16 ; asm: movsbl (%rcx), %edi - [-,%rdi] v18 = sload8.i32 v1 ; bin: 0f be 39 + [-,%rdi] v18 = sload8.i32 v1 ; bin: heap_oob 0f be 39 ; asm: movsbl (%rsi), %edx - [-,%rdx] v19 = sload8.i32 v2 ; bin: 0f be 16 + [-,%rdx] v19 = sload8.i32 v2 ; bin: heap_oob 0f be 16 ; Register-indirect with 8-bit signed displacement. ; asm: movl 50(%rcx), %edi - [-,%rdi] v20 = load.i32 v1+50 ; bin: 8b 79 32 + [-,%rdi] v20 = load.i32 v1+50 ; bin: heap_oob 8b 79 32 ; asm: movl -50(%rsi), %edx - [-,%rdx] v21 = load.i32 v2-50 ; bin: 8b 56 ce + [-,%rdx] v21 = load.i32 v2-50 ; bin: heap_oob 8b 56 ce ; asm: movzwl 50(%rcx), %edi - [-,%rdi] v22 = uload16.i32 v1+50 ; bin: 0f b7 79 32 + [-,%rdi] v22 = uload16.i32 v1+50 ; bin: heap_oob 0f b7 79 32 ; asm: movzwl -50(%rsi), %edx - [-,%rdx] v23 = uload16.i32 v2-50 ; bin: 0f b7 56 ce + [-,%rdx] v23 = uload16.i32 v2-50 ; bin: heap_oob 0f b7 56 ce ; asm: movswl 50(%rcx), %edi - [-,%rdi] v24 = sload16.i32 v1+50 ; bin: 0f bf 79 32 + [-,%rdi] v24 = sload16.i32 v1+50 ; bin: heap_oob 0f bf 79 32 ; asm: movswl -50(%rsi), %edx - [-,%rdx] v25 = sload16.i32 v2-50 ; bin: 0f bf 56 ce + [-,%rdx] v25 = sload16.i32 v2-50 ; bin: heap_oob 0f bf 56 ce ; asm: movzbl 50(%rcx), %edi - [-,%rdi] v26 = uload8.i32 v1+50 ; bin: 0f b6 79 32 + [-,%rdi] v26 = uload8.i32 v1+50 ; bin: heap_oob 0f b6 79 32 ; asm: movzbl -50(%rsi), %edx - [-,%rdx] v27 = uload8.i32 v2-50 ; bin: 0f b6 56 ce + [-,%rdx] v27 = uload8.i32 v2-50 ; bin: heap_oob 0f b6 56 ce ; asm: movsbl 50(%rcx), %edi - [-,%rdi] v28 = sload8.i32 v1+50 ; bin: 0f be 79 32 + [-,%rdi] v28 = sload8.i32 v1+50 ; bin: heap_oob 0f be 79 32 ; asm: movsbl -50(%rsi), %edx - [-,%rdx] v29 = sload8.i32 v2-50 ; bin: 0f be 56 ce + [-,%rdx] v29 = sload8.i32 v2-50 ; bin: heap_oob 0f be 56 ce ; Register-indirect with 32-bit signed displacement. ; asm: movl 50000(%rcx), %edi - [-,%rdi] v30 = load.i32 v1+50000 ; bin: 8b b9 0000c350 + [-,%rdi] v30 = load.i32 v1+50000 ; bin: heap_oob 8b b9 0000c350 ; asm: movl -50000(%rsi), %edx - [-,%rdx] v31 = load.i32 v2-50000 ; bin: 8b 96 ffff3cb0 + [-,%rdx] v31 = load.i32 v2-50000 ; bin: heap_oob 8b 96 ffff3cb0 ; asm: movzwl 50000(%rcx), %edi - [-,%rdi] v32 = uload16.i32 v1+50000 ; bin: 0f b7 b9 0000c350 + [-,%rdi] v32 = uload16.i32 v1+50000 ; bin: heap_oob 0f b7 b9 0000c350 ; asm: movzwl -50000(%rsi), %edx - [-,%rdx] v33 = uload16.i32 v2-50000 ; bin: 0f b7 96 ffff3cb0 + [-,%rdx] v33 = uload16.i32 v2-50000 ; bin: heap_oob 0f b7 96 ffff3cb0 ; asm: movswl 50000(%rcx), %edi - [-,%rdi] v34 = sload16.i32 v1+50000 ; bin: 0f bf b9 0000c350 + [-,%rdi] v34 = sload16.i32 v1+50000 ; bin: heap_oob 0f bf b9 0000c350 ; asm: movswl -50000(%rsi), %edx - [-,%rdx] v35 = sload16.i32 v2-50000 ; bin: 0f bf 96 ffff3cb0 + [-,%rdx] v35 = sload16.i32 v2-50000 ; bin: heap_oob 0f bf 96 ffff3cb0 ; asm: movzbl 50000(%rcx), %edi - [-,%rdi] v36 = uload8.i32 v1+50000 ; bin: 0f b6 b9 0000c350 + [-,%rdi] v36 = uload8.i32 v1+50000 ; bin: heap_oob 0f b6 b9 0000c350 ; asm: movzbl -50000(%rsi), %edx - [-,%rdx] v37 = uload8.i32 v2-50000 ; bin: 0f b6 96 ffff3cb0 + [-,%rdx] v37 = uload8.i32 v2-50000 ; bin: heap_oob 0f b6 96 ffff3cb0 ; asm: movsbl 50000(%rcx), %edi - [-,%rdi] v38 = sload8.i32 v1+50000 ; bin: 0f be b9 0000c350 + [-,%rdi] v38 = sload8.i32 v1+50000 ; bin: heap_oob 0f be b9 0000c350 ; asm: movsbl -50000(%rsi), %edx - [-,%rdx] v39 = sload8.i32 v2-50000 ; bin: 0f be 96 ffff3cb0 + [-,%rdx] v39 = sload8.i32 v2-50000 ; bin: heap_oob 0f be 96 ffff3cb0 ; Integer Register-Register Operations. @@ -903,17 +932,17 @@ ebb0: [-,%rax] v160 = iconst.i32 1 [-,%rdx] v161 = iconst.i32 2 ; asm: idivl %ecx - [-,%rax,%rdx] v162, v163 = x86_sdivmodx v160, v161, v1 ; bin: f7 f9 + [-,%rax,%rdx] v162, v163 = x86_sdivmodx v160, v161, v1 ; bin: int_divz f7 f9 ; asm: idivl %esi - [-,%rax,%rdx] v164, v165 = x86_sdivmodx v160, v161, v2 ; bin: f7 fe + [-,%rax,%rdx] v164, v165 = x86_sdivmodx v160, v161, v2 ; bin: int_divz f7 fe ; asm: idivl %r10d - [-,%rax,%rdx] v166, v167 = x86_sdivmodx v160, v161, v3 ; bin: 41 f7 fa + [-,%rax,%rdx] v166, v167 = x86_sdivmodx v160, v161, v3 ; bin: int_divz 41 f7 fa ; asm: divl %ecx - [-,%rax,%rdx] v168, v169 = x86_udivmodx v160, v161, v1 ; bin: f7 f1 + [-,%rax,%rdx] v168, v169 = x86_udivmodx v160, v161, v1 ; bin: int_divz f7 f1 ; asm: divl %esi - [-,%rax,%rdx] v170, v171 = x86_udivmodx v160, v161, v2 ; bin: f7 f6 + [-,%rax,%rdx] v170, v171 = x86_udivmodx v160, v161, v2 ; bin: int_divz f7 f6 ; asm: divl %r10d - [-,%rax,%rdx] v172, v173 = x86_udivmodx v160, v161, v3 ; bin: 41 f7 f2 + [-,%rax,%rdx] v172, v173 = x86_udivmodx v160, v161, v3 ; bin: int_divz 41 f7 f2 ; Bit-counting instructions. @@ -1010,6 +1039,14 @@ ebb0: ; asm: setbe %dl [-,%rdx] v319 = icmp ule v2, v3 ; bin: 44 39 d6 0f 96 c2 + ; asm: cmpl $37, %ecx + ; asm: setl %bl + [-,%rbx] v320 = icmp_imm slt v1, 37 ; bin: 83 f9 25 0f 9c c3 + + ; asm: cmpq $100000, %ecx + ; asm: setl %bl + [-,%rbx] v321 = icmp_imm slt v1, 100000 ; bin: 81 f9 000186a0 0f 9c c3 + ; Bool-to-int conversions. ; asm: movzbl %bl, %ecx @@ -1039,19 +1076,32 @@ ebb0: regfill v1, ss1 -> %rcx ; bin: 8b 8c 24 00000408 ; asm: cmpl %esi, %ecx - [-,%eflags] v520 = ifcmp v1, v2 ; bin: 39 f1 + [-,%rflags] v520 = ifcmp v1, v2 ; bin: 39 f1 ; asm: cmpl %r10d, %esi - [-,%eflags] v521 = ifcmp v2, v3 ; bin: 44 39 d6 + [-,%rflags] v521 = ifcmp v2, v3 ; bin: 44 39 d6 ; asm: cmpl $-100, %ecx - [-,%eflags] v522 = ifcmp_imm v1, -100 ; bin: 83 f9 9c + [-,%rflags] v522 = ifcmp_imm v1, -100 ; bin: 83 f9 9c ; asm: cmpl $100, %r10d - [-,%eflags] v523 = ifcmp_imm v3, 100 ; bin: 41 83 fa 64 + [-,%rflags] v523 = ifcmp_imm v3, 100 ; bin: 41 83 fa 64 ; asm: cmpl $-10000, %ecx - [-,%eflags] v524 = ifcmp_imm v1, -10000 ; bin: 81 f9 ffffd8f0 + [-,%rflags] v524 = ifcmp_imm v1, -10000 ; bin: 81 f9 ffffd8f0 ; asm: cmpl $10000, %r10d - [-,%eflags] v525 = ifcmp_imm v3, 10000 ; bin: 41 81 fa 00002710 + [-,%rflags] v525 = ifcmp_imm v3, 10000 ; bin: 41 81 fa 00002710 + + ; asm: shll $2, %esi + [-,%rsi] v526 = ishl_imm v2, 2 ; bin: c1 e6 02 + ; asm: shll $12, %r10d + [-,%r10] v527 = ishl_imm v3, 12 ; bin: 41 c1 e2 0c + ; asm: sarl $5, %esi + [-,%rsi] v529 = sshr_imm v2, 5 ; bin: c1 fe 05 + ; asm: sarl $32, %r10d + [-,%r10] v530 = sshr_imm v3, 32 ; bin: 41 c1 fa 20 + ; asm: shrl $8, %esi + [-,%rsi] v532 = ushr_imm v2, 8 ; bin: c1 ee 08 + ; asm: shrl $31, %r10d + [-,%r10] v533 = ushr_imm v3, 31 ; bin: 41 c1 ea 1f ; asm: testl %ecx, %ecx ; asm: je ebb1x @@ -1082,6 +1132,7 @@ ebb1: ; asm: ebb2x: ebb2: jump ebb1 ; bin: eb fd + } ; Tests for i32/i8 conversion instructions. @@ -1109,7 +1160,7 @@ ebb0: ; asm: movzbl %r10b, %ecx [-,%rcx] v32 = uextend.i32 v13 ; bin: 41 0f b6 ca - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } ; Tests for i32/i16 conversion instructions. @@ -1137,7 +1188,7 @@ ebb0: ; asm: movzwl %r10w, %ecx [-,%rcx] v32 = uextend.i32 v13 ; bin: 41 0f b7 ca - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } ; Tests for i64/i8 conversion instructions. @@ -1165,7 +1216,7 @@ ebb0: ; asm: movzbl %r10b, %ecx [-,%rcx] v32 = uextend.i64 v13 ; bin: 41 0f b6 ca - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } ; Tests for i64/i16 conversion instructions. @@ -1193,7 +1244,7 @@ ebb0: ; asm: movzwl %r10w, %ecx [-,%rcx] v32 = uextend.i64 v13 ; bin: 41 0f b7 ca - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } ; Tests for i64/i32 conversion instructions. @@ -1221,5 +1272,5 @@ ebb0: ; asm: movl %r10d, %ecx [-,%rcx] v32 = uextend.i64 v13 ; bin: 44 89 d1 - trap user0 ; bin: 0f 0b + trap user0 ; bin: user0 0f 0b } diff --git a/cranelift/filetests/isa/intel/legalize-div-traps.cton b/cranelift/filetests/isa/intel/legalize-div-traps.cton index fa070fba1a..3869e66325 100644 --- a/cranelift/filetests/isa/intel/legalize-div-traps.cton +++ b/cranelift/filetests/isa/intel/legalize-div-traps.cton @@ -40,7 +40,7 @@ ebb0(v0: i64, v1: i64): ; nextln: brif eq $fm1, $(m1=$EBB) ; nextln: $(fz=$V) = ifcmp_imm v1, 0 ; nextln: trapif eq $fz, int_divz - ; check: $(hi=$V) = sshr + ; check: $(hi=$V) = sshr_imm ; nextln: $(q=$V), $(r=$V) = x86_sdivmodx v0, $hi, v1 ; nextln: jump $(done=$EBB)($q) ; check: $m1: @@ -60,7 +60,7 @@ ebb0(v0: i64, v1: i64): v2 = srem v0, v1 ; nextln: $(fm1=$V) = ifcmp_imm v1, -1 ; nextln: brif eq $fm1, $(m1=$EBB) - ; check: $(hi=$V) = sshr + ; check: $(hi=$V) = sshr_imm ; nextln: $(d=$V), $(r=$V) = x86_sdivmodx v0, $hi, v1 ; nextln: jump $(done=$EBB)($r) ; check: $m1: diff --git a/cranelift/filetests/isa/intel/legalize-div.cton b/cranelift/filetests/isa/intel/legalize-div.cton index ec3f8ec5d3..d6179b2611 100644 --- a/cranelift/filetests/isa/intel/legalize-div.cton +++ b/cranelift/filetests/isa/intel/legalize-div.cton @@ -32,7 +32,7 @@ function %sdiv(i64, i64) -> i64 { ebb0(v0: i64, v1: i64): ; check: ebb0( v2 = sdiv v0, v1 - ; check: $(hi=$V) = sshr + ; check: $(hi=$V) = sshr_imm ; nextln: $(d=$V), $(r=$V) = x86_sdivmodx v0, $hi, v1 return v2 ; nextln: return $d @@ -46,7 +46,7 @@ ebb0(v0: i64, v1: i64): v2 = srem v0, v1 ; nextln: $(fm1=$V) = ifcmp_imm v1, -1 ; nextln: brif eq $fm1, $(m1=$EBB) - ; check: $(hi=$V) = sshr + ; check: $(hi=$V) = sshr_imm ; nextln: $(d=$V), $(r=$V) = x86_sdivmodx v0, $hi, v1 ; nextln: jump $(done=$EBB)($r) ; check: $m1: diff --git a/cranelift/filetests/isa/intel/legalize-libcall.cton b/cranelift/filetests/isa/intel/legalize-libcall.cton index 562efb6beb..c15587fc7f 100644 --- a/cranelift/filetests/isa/intel/legalize-libcall.cton +++ b/cranelift/filetests/isa/intel/legalize-libcall.cton @@ -9,7 +9,7 @@ ebb0(v0: f32): v1 = floor v0 return v1 } -; check: function %floor(f32 [%xmm0]) -> f32 [%xmm0] native { -; check: sig0 = (f32) -> f32 native +; check: function %floor(f32 [%xmm0]) -> f32 [%xmm0] system_v { +; check: sig0 = (f32) -> f32 system_v ; check: fn0 = sig0 %FloorF32 ; check: v1 = call fn0(v0) diff --git a/cranelift/filetests/isa/intel/legalize-memory.cton b/cranelift/filetests/isa/intel/legalize-memory.cton index 5b7246a2e3..1ba979deb1 100644 --- a/cranelift/filetests/isa/intel/legalize-memory.cton +++ b/cranelift/filetests/isa/intel/legalize-memory.cton @@ -23,7 +23,7 @@ function %deref(i64 vmctx) -> i64 { ebb1(v1: i64): v2 = global_addr.i64 gv2 ; check: $(a1=$V) = iadd_imm v1, -16 - ; check: $(p1=$V) = load.i64 $a1 + ; check: $(p1=$V) = load.i64 notrap aligned $a1 ; check: v2 = iadd_imm $p1, 32 return v2 ; check: return v2 @@ -55,7 +55,7 @@ ebb0(v0: i32, v999: i64): ; Checks here are assuming that no pipehole opts fold the load offsets. ; nextln: $(xoff=$V) = uextend.i64 v0 ; nextln: $(haddr=$V) = iadd_imm v999, 64 - ; nextln: $(hbase=$V) = load.i64 $haddr + ; nextln: $(hbase=$V) = load.i64 notrap aligned $haddr ; nextln: v1 = iadd $hbase, $xoff v2 = load.f32 v1+16 ; nextln: v2 = load.f32 v1+16 @@ -103,7 +103,7 @@ ebb0(v0: i32, v999: i64): ; Checks here are assuming that no pipehole opts fold the load offsets. ; nextln: $(xoff=$V) = uextend.i64 v0 ; nextln: $(haddr=$V) = iadd_imm.i64 v999, 64 - ; nextln: $(hbase=$V) = load.i64 $haddr + ; nextln: $(hbase=$V) = load.i64 notrap aligned $haddr ; nextln: v1 = iadd $hbase, $xoff v2 = load.f32 v1+0x7fff_ffff ; nextln: v2 = load.f32 v1+0x7fff_ffff diff --git a/cranelift/filetests/isa/intel/prologue-epilogue.cton b/cranelift/filetests/isa/intel/prologue-epilogue.cton index fc53f72008..cd774fe6bd 100644 --- a/cranelift/filetests/isa/intel/prologue-epilogue.cton +++ b/cranelift/filetests/isa/intel/prologue-epilogue.cton @@ -9,7 +9,7 @@ ebb0: return } -; check: function %foo(i64 fp [%rbp], i64 csr [%rbx], i64 csr [%r12], i64 csr [%r13], i64 csr [%r14], i64 csr [%r15]) -> i64 fp [%rbp], i64 csr [%rbx], i64 csr [%r12], i64 csr [%r13], i64 csr [%r14], i64 csr [%r15] native { +; check: function %foo(i64 fp [%rbp], i64 csr [%rbx], i64 csr [%r12], i64 csr [%r13], i64 csr [%r14], i64 csr [%r15]) -> i64 fp [%rbp], i64 csr [%rbx], i64 csr [%r12], i64 csr [%r13], i64 csr [%r14], i64 csr [%r15] system_v { ; nextln: ss0 = explicit_slot 168, offset -224 ; nextln: ss1 = incoming_arg 56, offset -56 ; check: ebb0(v0: i64 [%rbp], v1: i64 [%rbx], v2: i64 [%r12], v3: i64 [%r13], v4: i64 [%r14], v5: i64 [%r15]): diff --git a/cranelift/filetests/isa/riscv/abi-e.cton b/cranelift/filetests/isa/riscv/abi-e.cton index df06402283..50100ebf4e 100644 --- a/cranelift/filetests/isa/riscv/abi-e.cton +++ b/cranelift/filetests/isa/riscv/abi-e.cton @@ -7,8 +7,8 @@ isa riscv enable_e function %f() { ; Spilling into the stack args after %x15 since %16 and up are not ; available in RV32E. - sig0 = (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 + sig0 = (i64, i64, i64, i64) -> i64 system_v + ; check: sig0 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [0], i32 [4]) -> i32 [%x10], i32 [%x11] system_v ebb0: return } diff --git a/cranelift/filetests/isa/riscv/abi.cton b/cranelift/filetests/isa/riscv/abi.cton index c57c09fd97..12371ac537 100644 --- a/cranelift/filetests/isa/riscv/abi.cton +++ b/cranelift/filetests/isa/riscv/abi.cton @@ -5,27 +5,27 @@ isa riscv ; regex: V=v\d+ function %f() { - sig0 = (i32) -> i32 native - ; check: sig0 = (i32 [%x10]) -> i32 [%x10] native + sig0 = (i32) -> i32 system_v + ; check: sig0 = (i32 [%x10]) -> i32 [%x10] system_v - sig1 = (i64) -> b1 native - ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native + sig1 = (i64) -> b1 system_v + ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] system_v ; 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 + sig2 = (f32, i64) -> f64 system_v + ; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] system_v ; 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 + sig3 = (f64, f64, f64, f64, f64, f64, f64, i64) -> f64 system_v + ; check: sig3 = (f64 [%f10], f64 [%f11], f64 [%f12], f64 [%f13], f64 [%f14], f64 [%f15], f64 [%f16], i32 [0], i32 [4]) -> f64 [%f10] system_v ; Splitting vectors. - sig4 = (i32x4) native - ; check: sig4 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) native + sig4 = (i32x4) system_v + ; check: sig4 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13]) system_v ; 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 + sig5 = (i64x4) system_v + ; check: sig5 = (i32 [%x10], i32 [%x11], i32 [%x12], i32 [%x13], i32 [%x14], i32 [%x15], i32 [%x16], i32 [%x17]) system_v ebb0: return diff --git a/cranelift/filetests/isa/riscv/legalize-abi.cton b/cranelift/filetests/isa/riscv/legalize-abi.cton index 3234407052..44865120ea 100644 --- a/cranelift/filetests/isa/riscv/legalize-abi.cton +++ b/cranelift/filetests/isa/riscv/legalize-abi.cton @@ -106,7 +106,7 @@ ebb0(v0: i64x4): } function %indirect(i32) { - sig1 = () native + sig1 = () system_v ebb0(v0: i32): call_indirect sig1, v0() return @@ -114,7 +114,7 @@ ebb0(v0: i32): ; The first argument to call_indirect doesn't get altered. function %indirect_arg(i32, f32x2) { - sig1 = (f32x2) native + sig1 = (f32x2) system_v ebb0(v0: i32, v1: f32x2): call_indirect sig1, v0(v1) ; check: call_indirect sig1, v0($V, $V) diff --git a/cranelift/filetests/isa/riscv/parse-encoding.cton b/cranelift/filetests/isa/riscv/parse-encoding.cton index e955ad296a..0fc0879f38 100644 --- a/cranelift/filetests/isa/riscv/parse-encoding.cton +++ b/cranelift/filetests/isa/riscv/parse-encoding.cton @@ -3,32 +3,32 @@ test legalizer isa riscv function %parse_encoding(i32 [%x5]) -> i32 [%x10] { - ; check: function %parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] native { + ; check: function %parse_encoding(i32 [%x5], i32 link [%x1]) -> i32 [%x10], i32 link [%x1] system_v { - sig0 = (i32 [%x10]) -> i32 [%x10] native - ; check: sig0 = (i32 [%x10]) -> i32 [%x10] native + sig0 = (i32 [%x10]) -> i32 [%x10] system_v + ; check: sig0 = (i32 [%x10]) -> i32 [%x10] system_v - sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native - ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] native + sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] system_v + ; check: sig1 = (i32 [%x10], i32 [%x11]) -> b1 [%x10] system_v - sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] native - ; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] native + sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] system_v + ; check: sig2 = (f32 [%f10], i32 [%x12], i32 [%x13]) -> f64 [%f10] system_v ; 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 + sig3 = (f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] system_v + ; check: sig3 = (f64 [%f10], i32 [0], i32 [4]) -> f64 [%f10] system_v ; Stack argument before register argument - sig4 = (f32 [72], i32 [%x10]) native - ; check: sig4 = (f32 [72], i32 [%x10]) native + sig4 = (f32 [72], i32 [%x10]) system_v + ; check: sig4 = (f32 [72], i32 [%x10]) system_v ; Return value on stack - sig5 = () -> f32 [0] native - ; check: sig5 = () -> f32 [0] native + sig5 = () -> f32 [0] system_v + ; check: sig5 = () -> f32 [0] system_v ; function + signature - fn0 = function %bar(i32 [%x10]) -> b1 [%x10] native - ; check: sig6 = (i32 [%x10]) -> b1 [%x10] native + fn0 = function %bar(i32 [%x10]) -> b1 [%x10] system_v + ; check: sig6 = (i32 [%x10]) -> b1 [%x10] system_v ; nextln: fn0 = sig6 %bar ebb0(v0: i32): diff --git a/cranelift/filetests/licm/complex.cton b/cranelift/filetests/licm/complex.cton index e9012b1a4f..3b339574a8 100644 --- a/cranelift/filetests/licm/complex.cton +++ b/cranelift/filetests/licm/complex.cton @@ -1,6 +1,6 @@ test licm -function %complex(i32) -> i32 native { +function %complex(i32) -> i32 system_v { ebb0(v0: i32): jump ebb1(v0) diff --git a/cranelift/filetests/licm/reject.cton b/cranelift/filetests/licm/reject.cton new file mode 100644 index 0000000000..052ae2b6f2 --- /dev/null +++ b/cranelift/filetests/licm/reject.cton @@ -0,0 +1,81 @@ +test licm + +function %other_side_effects(i32) -> i32 { + +ebb0(v0: i32): + jump ebb1(v0) + +ebb1(v1: i32): + regmove.i32 v0, %10 -> %20 +; check: ebb1(v1: i32): +; check: regmove.i32 v0, %10 -> %20 + v2 = iconst.i32 1 + brz v1, ebb2(v1) + v5 = isub v1, v2 + jump ebb1(v5) + +ebb2(v6: i32): + return v6 + +} + +function %cpu_flags(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + jump ebb1(v0, v1) + +ebb1(v2: i32, v3: i32): + v4 = ifcmp.i32 v0, v1 + v5 = selectif.i32 eq v4, v2, v3 +; check: ebb1(v2: i32, v3: i32): +; check: ifcmp.i32 v0, v1 +; check: v5 = selectif.i32 eq v4, v2, v3 + v8 = iconst.i32 1 + brz v1, ebb2(v1) + v9 = isub v1, v8 + v10 = iadd v1, v8 + jump ebb1(v9, v10) + +ebb2(v6: i32): + return v6 +} + +function %spill(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): + v2 = spill.i32 v0 + jump ebb1(v0, v1) + +ebb1(v3: i32, v4: i32): + v5 = spill.i32 v1 + v6 = fill.i32 v2 + v7 = fill.i32 v5 +; check: ebb1(v3: i32, v4: i32): +; check: v5 = spill.i32 v1 +; check: v6 = fill.i32 v2 +; check: v7 = fill v5 + brz v1, ebb2(v1) + v9 = isub v1, v4 + jump ebb1(v9, v3) + +ebb2(v10: i32): + return v10 +} + +function %non_invariant_aliases(i32) -> i32 { + +ebb0(v0: i32): + jump ebb1(v0) + +ebb1(v1: i32): + v8 -> v1 + v9 -> v1 + v2 = iadd v8, v9 +; check: ebb1(v1: i32): +; check: v2 = iadd v8, v9 + brz v1, ebb2(v1) + v5 = isub v1, v2 + jump ebb1(v5) + +ebb2(v6: i32): + return v6 + +} diff --git a/cranelift/filetests/parser/branch.cton b/cranelift/filetests/parser/branch.cton index 868b92d024..4e35147a6b 100644 --- a/cranelift/filetests/parser/branch.cton +++ b/cranelift/filetests/parser/branch.cton @@ -9,7 +9,7 @@ ebb0: ebb1: jump ebb0() } -; sameln: function %minimal() native { +; sameln: function %minimal() system_v { ; nextln: ebb0: ; nextln: jump ebb1 ; nextln: @@ -25,7 +25,7 @@ ebb0(v90: i32): ebb1(v91: i32): jump ebb0(v91) } -; sameln: function %onearg(i32) native { +; sameln: function %onearg(i32) system_v { ; nextln: ebb0(v90: i32): ; nextln: jump ebb1(v90) ; nextln: @@ -41,7 +41,7 @@ ebb0(v90: i32, v91: f32): ebb1(v92: i32, v93: f32): jump ebb0(v92, v93) } -; sameln: function %twoargs(i32, f32) native { +; sameln: function %twoargs(i32, f32) system_v { ; nextln: ebb0(v90: i32, v91: f32): ; nextln: jump ebb1(v90, v91) ; nextln: @@ -57,7 +57,7 @@ ebb0(v90: i32): ebb1: brnz v90, ebb1() } -; sameln: function %minimal(i32) native { +; sameln: function %minimal(i32) system_v { ; nextln: ebb0(v90: i32): ; nextln: brz v90, ebb1 ; nextln: @@ -72,7 +72,7 @@ ebb0(v90: i32, v91: f32): ebb1(v92: i32, v93: f32): brnz v90, ebb0(v92, v93) } -; sameln: function %twoargs(i32, f32) native { +; sameln: function %twoargs(i32, f32) system_v { ; nextln: ebb0(v90: i32, v91: f32): ; nextln: brz v90, ebb1(v90, v91) ; nextln: @@ -94,7 +94,7 @@ ebb30: ebb40: trap user4 } -; sameln: function %jumptable(i32) native { +; sameln: function %jumptable(i32) system_v { ; check: jt2 = jump_table 0, 0, ebb10, ebb40, ebb20, ebb30 ; check: jt200 = jump_table 0 ; check: ebb10(v3: i32): diff --git a/cranelift/filetests/parser/call.cton b/cranelift/filetests/parser/call.cton index 77543800d0..3413696caf 100644 --- a/cranelift/filetests/parser/call.cton +++ b/cranelift/filetests/parser/call.cton @@ -5,7 +5,7 @@ function %mini() { ebb1: return } -; sameln: function %mini() native { +; sameln: function %mini() system_v { ; nextln: ebb1: ; nextln: return ; nextln: } @@ -29,10 +29,10 @@ function %signatures() { fn5 = sig11 %foo fn8 = function %bar(i32) -> b1 } -; sameln: function %signatures() native { -; check: sig10 = () native +; sameln: function %signatures() system_v { +; check: sig10 = () system_v ; check: sig11 = (i32, f64) -> i32, b1 spiderwasm -; check: sig12 = (i32) -> b1 native +; check: sig12 = (i32) -> b1 system_v ; not: fn0 ; check: fn5 = sig11 %foo ; check: fn8 = sig12 %bar @@ -88,7 +88,7 @@ function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 ebb0(v1: i32, v2: i32, v3: i32, v4: i32): return v4, v2, v3, v1 } -; check: function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret native { +; check: function %special1(i32 sret, i32 fp, i32 csr, i32 link) -> i32 link, i32 fp, i32 csr, i32 sret system_v { ; check: ebb0(v1: i32, v2: i32, v3: i32, v4: i32): ; check: return v4, v2, v3, v1 ; check: } diff --git a/cranelift/filetests/parser/instruction_encoding.cton b/cranelift/filetests/parser/instruction_encoding.cton index a16c24a364..e0a2de789a 100644 --- a/cranelift/filetests/parser/instruction_encoding.cton +++ b/cranelift/filetests/parser/instruction_encoding.cton @@ -13,7 +13,7 @@ ebb1(v0: i32 [%x8], v1: i32): @55 v9 = iadd v8, v7 @a5 [Iret#5] return v0, v8 } -; sameln: function %foo(i32, i32) native { +; sameln: function %foo(i32, i32) system_v { ; nextln: ebb1(v0: i32 [%x8], v1: i32): ; nextln: [-,-]$WS v2 = iadd v0, v1 ; nextln: [-]$WS trap heap_oob diff --git a/cranelift/filetests/parser/keywords.cton b/cranelift/filetests/parser/keywords.cton index a4b894574e..aaf8403c0a 100644 --- a/cranelift/filetests/parser/keywords.cton +++ b/cranelift/filetests/parser/keywords.cton @@ -2,4 +2,4 @@ test cat ; 'function' is not a keyword, and can be used as the name of a function too. function %function() {} -; check: function %function() native +; check: function %function() system_v diff --git a/cranelift/filetests/parser/rewrite.cton b/cranelift/filetests/parser/rewrite.cton index a62157082a..679f00d2bc 100644 --- a/cranelift/filetests/parser/rewrite.cton +++ b/cranelift/filetests/parser/rewrite.cton @@ -9,7 +9,7 @@ ebb100(v20: i32): v9200 = f64const 0x4.0p0 trap user4 } -; sameln: function %defs() native { +; sameln: function %defs() system_v { ; nextln: ebb100(v20: i32): ; nextln: v1000 = iconst.i32x8 5 ; nextln: v9200 = f64const 0x1.0000000000000p2 @@ -23,7 +23,7 @@ ebb100(v20: i32): v200 = iadd v20, v1000 jump ebb100(v1000) } -; sameln: function %use_value() native { +; sameln: function %use_value() system_v { ; nextln: ebb100(v20: i32): ; nextln: v1000 = iadd_imm v20, 5 ; nextln: v200 = iadd v20, v1000 diff --git a/cranelift/filetests/parser/tiny.cton b/cranelift/filetests/parser/tiny.cton index 0c619565a0..e800d44c26 100644 --- a/cranelift/filetests/parser/tiny.cton +++ b/cranelift/filetests/parser/tiny.cton @@ -5,7 +5,7 @@ function %minimal() { ebb0: trap user0 } -; sameln: function %minimal() native { +; sameln: function %minimal() system_v { ; nextln: ebb0: ; nextln: trap user0 ; nextln: } @@ -18,7 +18,7 @@ ebb0: v1 = iconst.i8 6 v2 = ishl v0, v1 } -; sameln: function %ivalues() native { +; sameln: function %ivalues() system_v { ; nextln: ebb0: ; nextln: v0 = iconst.i32 2 ; nextln: v1 = iconst.i8 6 @@ -34,7 +34,7 @@ ebb0: v2 = bextend.b32 v1 v3 = bxor v0, v2 } -; sameln: function %bvalues() native { +; sameln: function %bvalues() system_v { ; nextln: ebb0: ; nextln: v0 = bconst.b32 true ; nextln: v1 = bconst.b8 false @@ -47,17 +47,17 @@ function %select() { ebb0(v90: i32, v91: i32, v92: b1): v0 = select v92, v90, v91 } -; sameln: function %select() native { +; sameln: function %select() system_v { ; nextln: ebb0(v90: i32, v91: i32, v92: b1): ; nextln: v0 = select v92, v90, v91 ; nextln: } ; Polymorphic instruction controlled by third operand. -function %selectif() native { +function %selectif() system_v { ebb0(v95: i32, v96: i32, v97: b1): v98 = selectif.i32 eq v97, v95, v96 } -; sameln: function %selectif() native { +; sameln: function %selectif() system_v { ; nextln: ebb0(v95: i32, v96: i32, v97: b1): ; nextln: v98 = selectif.i32 eq v97, v95, v96 ; nextln: } @@ -69,7 +69,7 @@ ebb0: v1 = extractlane v0, 3 v2 = insertlane v0, 1, v1 } -; sameln: function %lanes() native { +; sameln: function %lanes() system_v { ; nextln: ebb0: ; nextln: v0 = iconst.i32x4 2 ; nextln: v1 = extractlane v0, 3 @@ -85,7 +85,7 @@ ebb0(v90: i32, v91: i32): v3 = irsub_imm v91, 45 br_icmp eq v90, v91, ebb0(v91, v90) } -; sameln: function %icmp(i32, i32) native { +; sameln: function %icmp(i32, i32) system_v { ; nextln: ebb0(v90: i32, v91: i32): ; nextln: v0 = icmp eq v90, v91 ; nextln: v1 = icmp ult v90, v91 @@ -101,7 +101,7 @@ ebb0(v90: f32, v91: f32): v1 = fcmp uno v90, v91 v2 = fcmp lt v90, v91 } -; sameln: function %fcmp(f32, f32) native { +; sameln: function %fcmp(f32, f32) system_v { ; nextln: ebb0(v90: f32, v91: f32): ; nextln: v0 = fcmp eq v90, v91 ; nextln: v1 = fcmp uno v90, v91 @@ -115,7 +115,7 @@ ebb0(v90: i32, v91: f32): v0 = bitcast.i8x4 v90 v1 = bitcast.i32 v91 } -; sameln: function %bitcast(i32, f32) native { +; sameln: function %bitcast(i32, f32) system_v { ; nextln: ebb0(v90: i32, v91: f32): ; nextln: v0 = bitcast.i8x4 v90 ; nextln: v1 = bitcast.i32 v91 @@ -135,7 +135,7 @@ ebb0: stack_store v1, ss10+2 stack_store v2, ss2 } -; sameln: function %stack() native { +; sameln: function %stack() system_v { ; check: ss2 = explicit_slot 4 ; check: ss3 = incoming_arg 4, offset 8 ; check: ss4 = outgoing_arg 4 @@ -162,7 +162,7 @@ ebb0(v1: i32): store aligned v3, v1+12 store notrap aligned v3, v1-12 } -; sameln: function %memory(i32) native { +; sameln: function %memory(i32) system_v { ; nextln: ebb0(v1: i32): ; nextln: v2 = load.i64 v1 ; nextln: v3 = load.i64 aligned v1 @@ -187,7 +187,7 @@ ebb0(v1: i32): regfill v1, ss0 -> %10 return } -; sameln: function %diversion(i32) native { +; sameln: function %diversion(i32) system_v { ; nextln: ss0 = spill_slot 4 ; check: ebb0(v1: i32): ; nextln: regmove v1, %10 -> %20 @@ -204,7 +204,7 @@ ebb0: copy_special %20 -> %10 return } -; sameln: function %copy_special() native { +; sameln: function %copy_special() system_v { ; nextln: ebb0: ; nextln: copy_special %10 -> %20 ; nextln: copy_special %20 -> %10 diff --git a/cranelift/filetests/postopt/basic.cton b/cranelift/filetests/postopt/basic.cton new file mode 100644 index 0000000000..218d4dfee4 --- /dev/null +++ b/cranelift/filetests/postopt/basic.cton @@ -0,0 +1,100 @@ +test postopt +isa intel + +; Test that compare+branch sequences are folded effectively on x86. + +function %br_icmp(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): +[Op1icscc#39,%rdx] v2 = icmp slt v0, v1 +[Op1t8jccd_long#85] brnz v2, ebb1 +[Op1ret#c3] return v1 + +ebb1: +[Op1puid#b8,%rax] v8 = iconst.i32 3 +[Op1ret#c3] return v8 +} +; sameln: function %br_icmp +; nextln: ebb0(v0: i32, v1: i32): +; nextln: v9 = ifcmp v0, v1 +; nextln: v2 = trueif slt v9 +; nextln: brif slt v9, ebb1 +; nextln: return v1 +; nextln: +; nextln: ebb1: +; nextln: v8 = iconst.i32 3 +; nextln: return v8 +; nextln: } + +; Use brz instead of brnz, so the condition is inverted. + +function %br_icmp_inverse(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): +[Op1icscc#39,%rdx] v2 = icmp slt v0, v1 +[Op1t8jccd_long#84] brz v2, ebb1 +[Op1ret#c3] return v1 + +ebb1: +[Op1puid#b8,%rax] v8 = iconst.i32 3 +[Op1ret#c3] return v8 +} +; sameln: function %br_icmp_inverse +; nextln: ebb0(v0: i32, v1: i32): +; nextln: v9 = ifcmp v0, v1 +; nextln: v2 = trueif slt v9 +; nextln: brif sge v9, ebb1 +; nextln: return v1 +; nextln: +; nextln: ebb1: +; nextln: v8 = iconst.i32 3 +; nextln: return v8 +; nextln: } + +; Use icmp_imm instead of icmp. + +function %br_icmp_imm(i32, i32) -> i32 { +ebb0(v0: i32, v1: i32): +[Op1icsccib#7083] v2 = icmp_imm slt v0, 2 +[Op1t8jccd_long#84] brz v2, ebb1 +[Op1ret#c3] return v1 + +ebb1: +[Op1puid#b8,%rax] v8 = iconst.i32 3 +[Op1ret#c3] return v8 +} +; sameln: function %br_icmp_imm +; nextln: ebb0(v0: i32, v1: i32): +; nextln: v9 = ifcmp_imm v0, 2 +; nextln: v2 = trueif slt v9 +; nextln: brif sge v9, ebb1 +; nextln: return v1 +; nextln: +; nextln: ebb1: +; nextln: v8 = iconst.i32 3 +; nextln: return v8 +; nextln: } + +; Use fcmp instead of icmp. + +function %br_fcmp(f32, f32) -> f32 { +ebb0(v0: f32, v1: f32): +[Op2fcscc#42e,%rdx] v2 = fcmp gt v0, v1 +[Op1t8jccd_long#84] brz v2, ebb1 +[Op1ret#c3] return v1 + +ebb1: +[Op1puid#b8,%rax] v18 = iconst.i32 0x40a8_0000 +[Mp2frurm#56e,%xmm0] v8 = bitcast.f32 v18 +[Op1ret#c3] return v8 +} +; sameln: function %br_fcmp +; nextln: ebb0(v0: f32, v1: f32): +; nextln: v19 = ffcmp v0, v1 +; nextln: v2 = trueff gt v19 +; nextln: brff ule v19, ebb1 +; nextln: return v1 +; nextln: +; nextln: ebb1: +; nextln: v18 = iconst.i32 0x40a8_0000 +; nextln: v8 = bitcast.f32 v18 +; nextln: return v8 +; nextln: } diff --git a/cranelift/filetests/preopt/simplify.cton b/cranelift/filetests/preopt/simplify.cton new file mode 100644 index 0000000000..a2e67caf31 --- /dev/null +++ b/cranelift/filetests/preopt/simplify.cton @@ -0,0 +1,80 @@ +test preopt +isa intel + +function %iadd_imm(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 2 + v2 = iadd v0, v1 + return v2 +} +; sameln: function %iadd_imm +; nextln: ebb0(v0: i32): +; nextln: v1 = iconst.i32 2 +; nextln: v2 = iadd_imm v0, 2 +; nextln: return v2 +; nextln: } + +function %isub_imm(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 2 + v2 = isub v0, v1 + return v2 +} +; sameln: function %isub_imm +; nextln: ebb0(v0: i32): +; nextln: v1 = iconst.i32 2 +; nextln: v2 = iadd_imm v0, -2 +; nextln: return v2 +; nextln: } + +function %icmp_imm(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 2 + v2 = icmp slt v0, v1 + v3 = bint.i32 v2 + return v3 +} +; sameln: function %icmp_imm +; nextln: ebb0(v0: i32): +; nextln: v1 = iconst.i32 2 +; nextln: v2 = icmp_imm slt v0, 2 +; nextln: v3 = bint.i32 v2 +; nextln: return v3 +; nextln: } + +function %brz_bint(i32) { +ebb0(v0: i32): + v3 = icmp_imm slt v0, 0 + v1 = bint.i32 v3 + v2 = select v1, v1, v1 + trapz v1, user0 + brz v1, ebb1 + jump ebb2 + +ebb1: + return + +ebb2: + return +} +; sameln: function %brz_bint +; nextln: (v0: i32): +; nextln: v3 = icmp_imm slt v0, 0 +; nextln: v1 = bint.i32 v3 +; nextln: v2 = select v3, v1, v1 +; nextln: trapz v3, user0 +; nextln: brz v3, ebb1 +; nextln: jump ebb2 + +function %irsub_imm(i32) -> i32 { +ebb0(v0: i32): + v1 = iconst.i32 2 + v2 = isub v1, v0 + return v2 +} +; sameln: function %irsub_imm +; nextln: ebb0(v0: i32): +; nextln: v1 = iconst.i32 2 +; nextln: v2 = irsub_imm v1, 2 +; nextln: return v2 +; nextln: } diff --git a/cranelift/filetests/regalloc/coalesce.cton b/cranelift/filetests/regalloc/coalesce.cton index b2700b01dd..04782fc1fc 100644 --- a/cranelift/filetests/regalloc/coalesce.cton +++ b/cranelift/filetests/regalloc/coalesce.cton @@ -109,7 +109,7 @@ ebb1(v10: i32): return v11 } -function %gvn_unremovable_phi(i32) native { +function %gvn_unremovable_phi(i32) system_v { ebb0(v0: i32): v2 = iconst.i32 0 jump ebb2(v2, v0) diff --git a/cranelift/filetests/regalloc/coalescing-207.cton b/cranelift/filetests/regalloc/coalescing-207.cton index 8641c87354..c4d348e10b 100644 --- a/cranelift/filetests/regalloc/coalescing-207.cton +++ b/cranelift/filetests/regalloc/coalescing-207.cton @@ -5,12 +5,12 @@ isa intel haswell ; Reported as https://github.com/Cretonne/cretonne/issues/207 ; ; The coalescer creates a virtual register with two interfering values. -function %pr207(i64 vmctx, i32, i32) -> i32 native { +function %pr207(i64 vmctx, i32, i32) -> i32 system_v { gv0 = vmctx-8 heap0 = static gv0, min 0, bound 0x5000, guard 0x0040_0000 - sig0 = (i64 vmctx, i32, i32) -> i32 native - sig1 = (i64 vmctx, i32, i32, i32) -> i32 native - sig2 = (i64 vmctx, i32, i32, i32) -> i32 native + sig0 = (i64 vmctx, i32, i32) -> i32 system_v + sig1 = (i64 vmctx, i32, i32, i32) -> i32 system_v + sig2 = (i64 vmctx, i32, i32, i32) -> i32 system_v fn0 = sig0 u0:2 fn1 = sig1 u0:0 fn2 = sig2 u0:1 @@ -1034,10 +1034,10 @@ ebb92(v767: i32): } ; Same problem from musl.wasm. -function %musl(f64 [%xmm0], i64 vmctx [%rdi]) -> f64 [%xmm0] native { +function %musl(f64 [%xmm0], i64 vmctx [%rdi]) -> f64 [%xmm0] system_v { gv0 = vmctx heap0 = static gv0, min 0, bound 0x0001_0000_0000, guard 0x8000_0000 - sig0 = (f64 [%xmm0], i32 [%rdi], i64 vmctx [%rsi]) -> f64 [%xmm0] native + sig0 = (f64 [%xmm0], i32 [%rdi], i64 vmctx [%rsi]) -> f64 [%xmm0] system_v fn0 = sig0 u0:517 ebb0(v0: f64, v1: i64): diff --git a/cranelift/filetests/regalloc/coalescing-216.cton b/cranelift/filetests/regalloc/coalescing-216.cton index de44d53d5b..a6cf7b3b42 100644 --- a/cranelift/filetests/regalloc/coalescing-216.cton +++ b/cranelift/filetests/regalloc/coalescing-216.cton @@ -5,7 +5,7 @@ isa intel haswell ; Reported as https://github.com/Cretonne/cretonne/issues/216 from the Binaryen fuzzer. ; ; The (old) coalescer creates a virtual register with two identical values. -function %pr216(i32 [%rdi], i64 vmctx [%rsi]) -> i64 [%rax] native { +function %pr216(i32 [%rdi], i64 vmctx [%rsi]) -> i64 [%rax] system_v { ebb0(v0: i32, v1: i64): v3 = iconst.i64 0 v5 = iconst.i32 0 diff --git a/cranelift/filetests/regalloc/coloring-227.cton b/cranelift/filetests/regalloc/coloring-227.cton index 07681a7509..accfb3528b 100644 --- a/cranelift/filetests/regalloc/coloring-227.cton +++ b/cranelift/filetests/regalloc/coloring-227.cton @@ -2,7 +2,7 @@ test regalloc set is_64bit isa intel haswell -function %pr227(i32 [%rdi], i32 [%rsi], i32 [%rdx], i32 [%rcx], i64 vmctx [%r8]) native { +function %pr227(i32 [%rdi], i32 [%rsi], i32 [%rdx], i32 [%rcx], i64 vmctx [%r8]) system_v { gv0 = vmctx heap0 = static gv0, min 0, bound 0x0001_0000_0000, guard 0x8000_0000 @@ -21,7 +21,7 @@ function %pr227(i32 [%rdi], i32 [%rsi], i32 [%rdx], i32 [%rcx], i64 vmctx [%r8]) @0011 [RexOp1puid#b8] v9 = iconst.i32 0 @0015 [RexOp1puid#b8] v11 = iconst.i32 0 @0017 [RexOp1icscc#39] v12 = icmp.i32 eq v15, v11 -@0017 [RexOp2urm#4b6] v13 = bint.i32 v12 +@0017 [RexOp2urm_noflags#4b6] v13 = bint.i32 v12 @001a [RexOp1rr#21] v14 = band v9, v13 @001b [RexOp1tjccb#75] brnz v14, ebb6 @001d [RexOp1jmpb#eb] jump ebb7 diff --git a/cranelift/filetests/regalloc/ghost-param.cton b/cranelift/filetests/regalloc/ghost-param.cton index 8b0ba6c2ab..c4ce229360 100644 --- a/cranelift/filetests/regalloc/ghost-param.cton +++ b/cranelift/filetests/regalloc/ghost-param.cton @@ -9,7 +9,7 @@ isa intel haswell ; ; Test case by binaryen fuzzer! -function %pr215(i64 vmctx [%rdi]) native { +function %pr215(i64 vmctx [%rdi]) system_v { ebb0(v0: i64): v10 = iconst.i64 0 v1 = bitcast.f64 v10 diff --git a/cranelift/filetests/regalloc/global-fixed.cton b/cranelift/filetests/regalloc/global-fixed.cton index a332311b6e..14b9d8fecc 100644 --- a/cranelift/filetests/regalloc/global-fixed.cton +++ b/cranelift/filetests/regalloc/global-fixed.cton @@ -2,7 +2,7 @@ test regalloc set is_64bit=1 isa intel haswell -function %foo() native { +function %foo() system_v { ebb4: v3 = iconst.i32 0 jump ebb3 diff --git a/cranelift/filetests/regalloc/intel-regres.cton b/cranelift/filetests/regalloc/intel-regres.cton index 50384d6349..0f76e8f2d0 100644 --- a/cranelift/filetests/regalloc/intel-regres.cton +++ b/cranelift/filetests/regalloc/intel-regres.cton @@ -11,7 +11,7 @@ isa intel ; This ended up confusong the constraint solver which had not made a record of ; the fixed register assignment for v9 since it was already in the correct ; register. -function %pr147(i32) -> i32 native { +function %pr147(i32) -> i32 system_v { ebb0(v0: i32): v1 = iconst.i32 0 v2 = iconst.i32 1 diff --git a/cranelift/filetests/regalloc/output-interference.cton b/cranelift/filetests/regalloc/output-interference.cton index 666c13c0d7..4a5b90c856 100644 --- a/cranelift/filetests/regalloc/output-interference.cton +++ b/cranelift/filetests/regalloc/output-interference.cton @@ -2,7 +2,7 @@ test regalloc set is_64bit=1 isa intel haswell -function %test(i64) -> i64 native { +function %test(i64) -> i64 system_v { ebb0(v0: i64): v2 = iconst.i64 12 ; This division clobbers two of its fixed input registers on Intel. diff --git a/cranelift/filetests/regalloc/reload-208.cton b/cranelift/filetests/regalloc/reload-208.cton index 1897a03c5d..70dc1c4523 100644 --- a/cranelift/filetests/regalloc/reload-208.cton +++ b/cranelift/filetests/regalloc/reload-208.cton @@ -11,11 +11,11 @@ isa intel haswell ; ; The problem was the reload pass rewriting EBB arguments on "brnz v9, ebb3(v9)" -function %pr208(i64 vmctx [%rdi]) native { +function %pr208(i64 vmctx [%rdi]) system_v { gv0 = vmctx-8 heap0 = static gv0, min 0, bound 0x5000, guard 0x0040_0000 - sig0 = (i64 vmctx [%rdi]) -> i32 [%rax] native - sig1 = (i64 vmctx [%rdi], i32 [%rsi]) native + sig0 = (i64 vmctx [%rdi]) -> i32 [%rax] system_v + sig1 = (i64 vmctx [%rdi], i32 [%rsi]) system_v fn0 = sig0 u0:1 fn1 = sig1 u0:3 diff --git a/cranelift/filetests/regalloc/reload.cton b/cranelift/filetests/regalloc/reload.cton index 68a6e8a149..5e62db3213 100644 --- a/cranelift/filetests/regalloc/reload.cton +++ b/cranelift/filetests/regalloc/reload.cton @@ -5,7 +5,7 @@ isa riscv enable_e ; Check that we can handle a function return value that got spilled. function %spill_return() -> i32 { - fn0 = function %foo() -> i32 native + fn0 = function %foo() -> i32 system_v ebb0: v0 = call fn0() diff --git a/cranelift/filetests/regalloc/schedule-moves.cton b/cranelift/filetests/regalloc/schedule-moves.cton index e3bae45be3..6ba78db893 100644 --- a/cranelift/filetests/regalloc/schedule-moves.cton +++ b/cranelift/filetests/regalloc/schedule-moves.cton @@ -1,7 +1,7 @@ test regalloc isa intel haswell -function %pr165() native { +function %pr165() system_v { ebb0: v0 = iconst.i32 0x0102_0304 v1 = iconst.i32 0x1102_0304 @@ -19,7 +19,7 @@ ebb0: ; Same as above, but use so many registers that spilling is required. ; Note: This is also a candidate for using xchg instructions. -function %emergency_spill() native { +function %emergency_spill() system_v { ebb0: v0 = iconst.i32 0x0102_0304 v1 = iconst.i32 0x1102_0304 diff --git a/cranelift/filetests/regalloc/spill-noregs.cton b/cranelift/filetests/regalloc/spill-noregs.cton index de4fa618bf..733606b828 100644 --- a/cranelift/filetests/regalloc/spill-noregs.cton +++ b/cranelift/filetests/regalloc/spill-noregs.cton @@ -13,7 +13,7 @@ isa intel ; ; The spiller was not releasing register pressure for dead EBB parameters. -function %pr223(i32 [%rdi], i64 vmctx [%rsi]) -> i64 [%rax] native { +function %pr223(i32 [%rdi], i64 vmctx [%rsi]) -> i64 [%rax] system_v { ebb0(v0: i32, v1: i64): v2 = iconst.i32 0 v3 = iconst.i64 0 diff --git a/cranelift/filetests/regalloc/spill.cton b/cranelift/filetests/regalloc/spill.cton index 8d18baa525..fb822e1a6f 100644 --- a/cranelift/filetests/regalloc/spill.cton +++ b/cranelift/filetests/regalloc/spill.cton @@ -93,7 +93,7 @@ ebb0(v0: i32): ; The same value used as indirect callee and argument. function %doubleuse_icall1(i32) { - sig0 = (i32) native + sig0 = (i32) system_v ebb0(v0: i32): ; not:copy call_indirect sig0, v0(v0) @@ -102,7 +102,7 @@ ebb0(v0: i32): ; The same value used as indirect callee and two arguments. function %doubleuse_icall2(i32) { - sig0 = (i32, i32) native + sig0 = (i32, i32) system_v ebb0(v0: i32): ; check: $(c=$V) = copy v0 call_indirect sig0, v0(v0, v0) diff --git a/cranelift/filetests/verifier/defs_dominates_uses.cton b/cranelift/filetests/verifier/defs_dominates_uses.cton new file mode 100644 index 0000000000..ba3dc98eb4 --- /dev/null +++ b/cranelift/filetests/verifier/defs_dominates_uses.cton @@ -0,0 +1,16 @@ +test verifier + +; Test verification that uses properly dominate defs. + +function %non_dominating(i32) -> i32 system_v { +ebb0(v0: i32): + v1 = iadd.i32 v2, v0 ; error: uses value from non-dominating + v2 = iadd.i32 v1, v0 + return v2 +} + +function %inst_uses_its_own_values(i32) -> i32 system_v { +ebb0(v0: i32): + v1 = iadd.i32 v1, v0 ; error: uses value from itself + return v1 +} diff --git a/cranelift/filetests/verifier/flags.cton b/cranelift/filetests/verifier/flags.cton index dff01244e2..b4a01621c4 100644 --- a/cranelift/filetests/verifier/flags.cton +++ b/cranelift/filetests/verifier/flags.cton @@ -4,65 +4,65 @@ isa intel ; Simple, correct use of CPU flags. function %simple(i32) -> i32 { ebb0(v0: i32): - [Op1rcmp#39] v1 = ifcmp v0, v0 - [Op2seti_abcd#490] v2 = trueif ugt v1 - [Op2urm_abcd#4b6] v3 = bint.i32 v2 - [Op1ret#c3] return v3 + [Op1rcmp#39] v1 = ifcmp v0, v0 + [Op2seti_abcd#490] v2 = trueif ugt v1 + [Op2urm_noflags_abcd#4b6] v3 = bint.i32 v2 + [Op1ret#c3] return v3 } ; Overlapping flag values of different types. function %overlap(i32, f32) -> i32 { ebb0(v0: i32, v1: f32): - [Op1rcmp#39] v2 = ifcmp v0, v0 - [Op2fcmp#42e] v3 = ffcmp v1, v1 - [Op2setf_abcd#490] v4 = trueff gt v3 ; error: conflicting live CPU flags: v2 and v3 - [Op2seti_abcd#490] v5 = trueif ugt v2 - [Op1rr#21] v6 = band v4, v5 - [Op2urm_abcd#4b6] v7 = bint.i32 v6 - [Op1ret#c3] return v7 + [Op1rcmp#39] v2 = ifcmp v0, v0 + [Op2fcmp#42e] v3 = ffcmp v1, v1 + [Op2setf_abcd#490] v4 = trueff gt v3 ; error: conflicting live CPU flags: v2 and v3 + [Op2seti_abcd#490] v5 = trueif ugt v2 + [Op1rr#21] v6 = band v4, v5 + [Op2urm_noflags_abcd#4b6] v7 = bint.i32 v6 + [Op1ret#c3] return v7 } ; CPU flags clobbered by arithmetic. function %clobbered(i32) -> i32 { ebb0(v0: i32): - [Op1rcmp#39] v1 = ifcmp v0, v0 - [Op1rr#01] v2 = iadd v0, v0 ; error: encoding clobbers live CPU flags in v1 - [Op2seti_abcd#490] v3 = trueif ugt v1 - [Op2urm_abcd#4b6] v4 = bint.i32 v3 - [Op1ret#c3] return v4 + [Op1rcmp#39] v1 = ifcmp v0, v0 + [Op1rr#01] v2 = iadd v0, v0 ; error: encoding clobbers live CPU flags in v1 + [Op2seti_abcd#490] v3 = trueif ugt v1 + [Op2urm_noflags_abcd#4b6] v4 = bint.i32 v3 + [Op1ret#c3] return v4 } ; CPU flags not clobbered by load. function %live_across_load(i32) -> i32 { ebb0(v0: i32): - [Op1rcmp#39] v1 = ifcmp v0, v0 - [Op1ld#8b] v2 = load.i32 v0 - [Op2seti_abcd#490] v3 = trueif ugt v1 - [Op2urm_abcd#4b6] v4 = bint.i32 v3 - [Op1ret#c3] return v4 + [Op1rcmp#39] v1 = ifcmp v0, v0 + [Op1ld#8b] v2 = load.i32 v0 + [Op2seti_abcd#490] v3 = trueif ugt v1 + [Op2urm_noflags_abcd#4b6] v4 = bint.i32 v3 + [Op1ret#c3] return v4 } ; Correct use of CPU flags across EBB. function %live_across_ebb(i32) -> i32 { - ebb0(v0: i32): - [Op1rcmp#39] v1 = ifcmp v0, v0 - [Op1jmpb#eb] jump ebb1 - ebb1: - [Op2seti_abcd#490] v2 = trueif ugt v1 - [Op2urm_abcd#4b6] v3 = bint.i32 v2 - [Op1ret#c3] return v3 + ebb0(v0: i32): + [Op1rcmp#39] v1 = ifcmp v0, v0 + [Op1jmpb#eb] jump ebb1 + ebb1: + [Op2seti_abcd#490] v2 = trueif ugt v1 + [Op2urm_noflags_abcd#4b6] v3 = bint.i32 v2 + [Op1ret#c3] return v3 } function %live_across_ebb_backwards(i32) -> i32 { - ebb0(v0: i32): - [Op1jmpb#eb] jump ebb2 - ebb1: - [Op2seti_abcd#490] v2 = trueif ugt v1 - [Op2urm_abcd#4b6] v3 = bint.i32 v2 - [Op1ret#c3] return v3 - ebb2: - [Op1rcmp#39] v1 = ifcmp v0, v0 - [Op1jmpb#eb] jump ebb1 + ebb0(v0: i32): + [Op1jmpb#eb] jump ebb2 + ebb1: + [Op2seti_abcd#490] v2 = trueif ugt v1 + [Op2urm_noflags_abcd#4b6] v3 = bint.i32 v2 + [Op1ret#c3] return v3 + ebb2: + [Op1rcmp#39] v1 = ifcmp v0, v0 + [Op1jmpb#eb] jump ebb1 } ; Flags live into loop. @@ -73,4 +73,4 @@ function %live_into_loop(i32) -> i32 { ebb1: [Op2seti_abcd#490] v2 = trueif ugt v1 [Op1jmpb#eb] jump ebb1 -} \ No newline at end of file +} diff --git a/cranelift/publish-all.sh b/cranelift/publish-all.sh index 7d8b7f7f3c..a29f308929 100755 --- a/cranelift/publish-all.sh +++ b/cranelift/publish-all.sh @@ -4,17 +4,13 @@ cd $(dirname "$0") topdir="$(pwd)" # All the cretonne-* crates have the same version number -# The filecheck crate version is managed independently. -version="0.3.4" +version="0.4.1" # Update all of the Cargo.toml files. # # The main Cargo.toml in the top-level directory is the cretonne-tools crate which we don't publish. echo "Updating crate versions to $version" for crate in . lib/*; do - if [ "$crate" = "lib/filecheck" ]; then - continue - fi # Update the version number of this crate to $version. sed -i.bk -e "s/^version = .*/version = \"$version\"/" "$crate/Cargo.toml" # Update the required version number of any cretonne* dependencies. @@ -31,7 +27,7 @@ cargo update echo git commit -a -m "\"Bump version to $version"\" echo git push -for crate in filecheck cretonne frontend native reader wasm; do +for crate in cretonne frontend native reader wasm; do echo cargo publish --manifest-path "lib/$crate/Cargo.toml" done echo diff --git a/cranelift/src/cat.rs b/cranelift/src/cat.rs index 36ae818e2c..e82af42624 100644 --- a/cranelift/src/cat.rs +++ b/cranelift/src/cat.rs @@ -1,16 +1,13 @@ //! The `cat` sub-command. //! -//! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of +//! Read a sequence of Cretonne IR files and print them again to stdout. This has the effect of //! normalizing formatting and removing comments. -use std::borrow::Cow; -use cretonne::ir::Function; -use cton_reader::{parse_functions, TestCommand}; use CommandResult; +use cton_reader::parse_functions; use utils::read_to_string; -use filetest::subtest::{self, SubTest, Context, Result as STResult}; -pub fn run(files: Vec) -> CommandResult { +pub fn run(files: &[String]) -> CommandResult { for (i, f) in files.into_iter().enumerate() { if i != 0 { println!(); @@ -20,7 +17,7 @@ pub fn run(files: Vec) -> CommandResult { Ok(()) } -fn cat_one(filename: String) -> CommandResult { +fn cat_one(filename: &str) -> CommandResult { let buffer = read_to_string(&filename).map_err( |e| format!("{}: {}", filename, e), )?; @@ -37,34 +34,3 @@ fn cat_one(filename: String) -> CommandResult { Ok(()) } - -/// Object implementing the `test cat` sub-test. -/// -/// This command is used for testing the parser and function printer. It simply parses a function -/// and prints it out again. -/// -/// The result is verified by filecheck. -struct TestCat; - -pub fn subtest(parsed: &TestCommand) -> STResult> { - assert_eq!(parsed.command, "cat"); - if !parsed.options.is_empty() { - Err(format!("No options allowed on {}", parsed)) - } else { - Ok(Box::new(TestCat)) - } -} - -impl SubTest for TestCat { - fn name(&self) -> Cow { - Cow::from("cat") - } - - fn needs_verifier(&self) -> bool { - false - } - - fn run(&self, func: Cow, context: &Context) -> STResult<()> { - subtest::run_filecheck(&func.display(context.isa).to_string(), context) - } -} diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index 9a9644b1cf..6bab77e0d2 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -1,14 +1,13 @@ -//! CLI tool to compile cretonne IL into native code. -//! -//! Reads IR files into Cretonne IL and compiles it. +//! CLI tool to read Cretonne IR files and compile them into native code. -use cton_reader::parse_test; -use std::path::PathBuf; use cretonne::Context; +use cretonne::print_errors::pretty_error; use cretonne::settings::FlagsOrIsa; use cretonne::{binemit, ir}; +use cton_reader::parse_test; use std::path::Path; -use utils::{pretty_error, read_to_string, parse_sets_and_isa}; +use std::path::PathBuf; +use utils::{parse_sets_and_isa, read_to_string}; struct PrintRelocs { flag_print: bool, @@ -45,26 +44,38 @@ impl binemit::RelocSink for PrintRelocs { } } +struct PrintTraps { + flag_print: bool, +} + +impl binemit::TrapSink for PrintTraps { + fn trap(&mut self, offset: binemit::CodeOffset, _srcloc: ir::SourceLoc, code: ir::TrapCode) { + if self.flag_print { + println!("trap: {} at {}", code, offset); + } + } +} + pub fn run( files: Vec, flag_print: bool, - flag_set: Vec, - flag_isa: String, + flag_set: &[String], + flag_isa: &str, ) -> Result<(), String> { let parsed = parse_sets_and_isa(flag_set, flag_isa)?; for filename in files { let path = Path::new(&filename); let name = String::from(path.as_os_str().to_string_lossy()); - handle_module(flag_print, path.to_path_buf(), name, parsed.as_fisa())?; + handle_module(flag_print, &path.to_path_buf(), &name, parsed.as_fisa())?; } Ok(()) } fn handle_module( flag_print: bool, - path: PathBuf, - name: String, + path: &PathBuf, + name: &str, fisa: FlagsOrIsa, ) -> Result<(), String> { let buffer = read_to_string(&path).map_err( @@ -95,8 +106,9 @@ fn handle_module( // Encode the result as machine code. let mut mem = Vec::new(); let mut relocs = PrintRelocs { flag_print }; + let mut traps = PrintTraps { flag_print }; mem.resize(size as usize, 0); - context.emit_to_memory(mem.as_mut_ptr(), &mut relocs, &*isa); + context.emit_to_memory(mem.as_mut_ptr(), &mut relocs, &mut traps, &*isa); if flag_print { print!(".byte "); diff --git a/cranelift/src/cton-util.rs b/cranelift/src/cton-util.rs index 9f6891489e..3968030709 100644 --- a/cranelift/src/cton-util.rs +++ b/cranelift/src/cton-util.rs @@ -1,27 +1,25 @@ -#[macro_use(dbg)] extern crate cretonne; +extern crate cton_filetests; extern crate cton_reader; extern crate cton_wasm; extern crate docopt; +extern crate filecheck; #[macro_use] extern crate serde_derive; -extern crate filecheck; -extern crate num_cpus; extern crate tempdir; extern crate term; -use cretonne::{VERSION, timing}; +use cretonne::{timing, VERSION}; use docopt::Docopt; use std::io::{self, Write}; use std::process; -mod utils; -mod filetest; mod cat; +mod compile; mod print_cfg; mod rsfilecheck; +mod utils; mod wasm; -mod compile; const USAGE: &str = " Cretonne code generator utility @@ -40,12 +38,12 @@ Options: -T, --time-passes print pass timing report -t, --just-decode - just decode WebAssembly to Cretonne IL + just decode WebAssembly to Cretonne IR -s, --print-size prints generated code size -c, --check-translation - just checks the correctness of Cretonne IL translated from WebAssembly - -p, --print print the resulting Cretonne IL + just checks the correctness of Cretonne IR translated from WebAssembly + -p, --print print the resulting Cretonne IR -h, --help print this help message --set= configure Cretonne settings --isa= specify the Cretonne ISA @@ -88,15 +86,20 @@ fn cton_util() -> CommandResult { // Find the sub-command to execute. let result = if args.cmd_test { - filetest::run(args.flag_verbose, args.arg_file) + cton_filetests::run(args.flag_verbose, &args.arg_file).map(|_time| ()) } else if args.cmd_cat { - cat::run(args.arg_file) + cat::run(&args.arg_file) } else if args.cmd_filecheck { - rsfilecheck::run(args.arg_file, args.flag_verbose) + rsfilecheck::run(&args.arg_file, args.flag_verbose) } else if args.cmd_print_cfg { - print_cfg::run(args.arg_file) + print_cfg::run(&args.arg_file) } else if args.cmd_compile { - compile::run(args.arg_file, args.flag_print, args.flag_set, args.flag_isa) + compile::run( + args.arg_file, + args.flag_print, + &args.flag_set, + &args.flag_isa, + ) } else if args.cmd_wasm { wasm::run( args.arg_file, @@ -104,8 +107,8 @@ fn cton_util() -> CommandResult { args.flag_just_decode, args.flag_check_translation, args.flag_print, - args.flag_set, - args.flag_isa, + &args.flag_set, + &args.flag_isa, args.flag_print_size, ) } else { diff --git a/cranelift/src/filetest/mod.rs b/cranelift/src/filetest/mod.rs deleted file mode 100644 index 5286307811..0000000000 --- a/cranelift/src/filetest/mod.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! File tests. -//! -//! This module contains the main driver for `cton-util test` as well as implementations of the -//! available test commands. - -use std::path::Path; -use std::time; -use cton_reader::TestCommand; -use CommandResult; -use cat; -use print_cfg; -use filetest::runner::TestRunner; - -pub mod subtest; - -mod binemit; -mod compile; -mod concurrent; -mod domtree; -mod legalizer; -mod licm; -mod preopt; -mod regalloc; -mod runner; -mod runone; -mod simple_gvn; -mod verifier; - -/// The result of running the test in a file. -pub type TestResult = Result; - -/// Main entry point for `cton-util test`. -/// -/// Take a list of filenames which can be either `.cton` files or directories. -/// -/// Files are interpreted as test cases and executed immediately. -/// -/// Directories are scanned recursively for test cases ending in `.cton`. These test cases are -/// executed on background threads. -/// -pub fn run(verbose: bool, files: Vec) -> CommandResult { - let mut runner = TestRunner::new(verbose); - - for path in files.iter().map(Path::new) { - if path.is_file() { - runner.push_test(path); - } else { - runner.push_dir(path); - } - } - - runner.start_threads(); - runner.run() -} - -/// Create a new subcommand trait object to match `parsed.command`. -/// -/// This function knows how to create all of the possible `test ` commands that can appear in -/// a `.cton` test file. -fn new_subtest(parsed: &TestCommand) -> subtest::Result> { - match parsed.command { - "binemit" => binemit::subtest(parsed), - "cat" => cat::subtest(parsed), - "compile" => compile::subtest(parsed), - "domtree" => domtree::subtest(parsed), - "legalizer" => legalizer::subtest(parsed), - "licm" => licm::subtest(parsed), - "preopt" => preopt::subtest(parsed), - "print-cfg" => print_cfg::subtest(parsed), - "regalloc" => regalloc::subtest(parsed), - "simple-gvn" => simple_gvn::subtest(parsed), - "verifier" => verifier::subtest(parsed), - _ => Err(format!("unknown test command '{}'", parsed.command)), - } -} diff --git a/cranelift/src/print_cfg.rs b/cranelift/src/print_cfg.rs index df624993ad..846ee3e8fa 100644 --- a/cranelift/src/print_cfg.rs +++ b/cranelift/src/print_cfg.rs @@ -1,20 +1,14 @@ //! The `print-cfg` sub-command. //! -//! Read a series of Cretonne IL files and print their control flow graphs +//! Read a series of Cretonne IR files and print their control flow graphs //! in graphviz format. -use std::borrow::Cow; -use std::fmt::{Result, Write, Display, Formatter}; - use CommandResult; -use cretonne::flowgraph::ControlFlowGraph; -use cretonne::ir::Function; -use cretonne::ir::instructions::BranchInfo; -use cton_reader::{parse_functions, TestCommand}; -use filetest::subtest::{self, SubTest, Context, Result as STResult}; +use cretonne::cfg_printer::CFGPrinter; +use cton_reader::parse_functions; use utils::read_to_string; -pub fn run(files: Vec) -> CommandResult { +pub fn run(files: &[String]) -> CommandResult { for (i, f) in files.into_iter().enumerate() { if i != 0 { println!(); @@ -24,74 +18,8 @@ pub fn run(files: Vec) -> CommandResult { Ok(()) } -struct CFGPrinter<'a> { - func: &'a Function, - cfg: ControlFlowGraph, -} - -impl<'a> CFGPrinter<'a> { - pub fn new(func: &'a Function) -> CFGPrinter<'a> { - CFGPrinter { - func, - cfg: ControlFlowGraph::with_function(func), - } - } - - /// Write the CFG for this function to `w`. - pub fn write(&self, w: &mut Write) -> Result { - self.header(w)?; - self.ebb_nodes(w)?; - self.cfg_connections(w)?; - writeln!(w, "}}") - } - - fn header(&self, w: &mut Write) -> Result { - writeln!(w, "digraph \"{}\" {{", self.func.name)?; - if let Some(entry) = self.func.layout.entry_block() { - writeln!(w, " {{rank=min; {}}}", entry)?; - } - Ok(()) - } - - fn ebb_nodes(&self, w: &mut Write) -> Result { - for ebb in &self.func.layout { - write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)?; - // Add all outgoing branch instructions to the label. - for inst in self.func.layout.ebb_insts(ebb) { - let idata = &self.func.dfg[inst]; - match idata.analyze_branch(&self.func.dfg.value_lists) { - BranchInfo::SingleDest(dest, _) => { - write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)? - } - BranchInfo::Table(table) => { - write!(w, " | <{}>{} {}", inst, idata.opcode(), table)? - } - BranchInfo::NotABranch => {} - } - } - writeln!(w, "}}\"]")? - } - Ok(()) - } - - fn cfg_connections(&self, w: &mut Write) -> Result { - for ebb in &self.func.layout { - for (parent, inst) in self.cfg.pred_iter(ebb) { - writeln!(w, " {}:{} -> {}", parent, inst, ebb)?; - } - } - Ok(()) - } -} - -impl<'a> Display for CFGPrinter<'a> { - fn fmt(&self, f: &mut Formatter) -> Result { - self.write(f) - } -} - -fn print_cfg(filename: String) -> CommandResult { - let buffer = read_to_string(&filename).map_err( +fn print_cfg(filename: &str) -> CommandResult { + let buffer = read_to_string(filename).map_err( |e| format!("{}: {}", filename, e), )?; let items = parse_functions(&buffer).map_err( @@ -107,29 +35,3 @@ fn print_cfg(filename: String) -> CommandResult { Ok(()) } - -/// Object implementing the `test print-cfg` sub-test. -struct TestPrintCfg; - -pub fn subtest(parsed: &TestCommand) -> STResult> { - assert_eq!(parsed.command, "print-cfg"); - if !parsed.options.is_empty() { - Err(format!("No options allowed on {}", parsed)) - } else { - Ok(Box::new(TestPrintCfg)) - } -} - -impl SubTest for TestPrintCfg { - fn name(&self) -> Cow { - Cow::from("print-cfg") - } - - fn needs_verifier(&self) -> bool { - false - } - - fn run(&self, func: Cow, context: &Context) -> STResult<()> { - subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context) - } -} diff --git a/cranelift/src/rsfilecheck.rs b/cranelift/src/rsfilecheck.rs index 46605bb368..6269ed226b 100644 --- a/cranelift/src/rsfilecheck.rs +++ b/cranelift/src/rsfilecheck.rs @@ -1,9 +1,13 @@ -use CommandResult; -use utils::read_to_string; -use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; -use std::io::{self, Read}; +//! The `filecheck` sub-command. +//! +//! This file is named to avoid a name collision with the filecheck crate. -pub fn run(files: Vec, verbose: bool) -> CommandResult { +use CommandResult; +use filecheck::{Checker, CheckerBuilder, NO_VARIABLES}; +use std::io::{self, Read}; +use utils::read_to_string; + +pub fn run(files: &[String], verbose: bool) -> CommandResult { if files.is_empty() { return Err("No check files".to_string()); } diff --git a/cranelift/src/utils.rs b/cranelift/src/utils.rs index 25c6dbf067..8a2994a7ab 100644 --- a/cranelift/src/utils.rs +++ b/cranelift/src/utils.rs @@ -1,13 +1,9 @@ //! Utility functions. -use cretonne::ir::entities::AnyEntity; -use cretonne::{ir, verifier}; -use cretonne::result::CtonError; +use cretonne::isa; use cretonne::isa::TargetIsa; use cretonne::settings::{self, FlagsOrIsa}; -use cretonne::isa; use cton_reader::{parse_options, Location}; -use std::fmt::Write; use std::fs::File; use std::io::{self, Read}; use std::path::Path; @@ -28,51 +24,6 @@ pub fn read_to_end>(path: P) -> io::Result> { Ok(buffer) } -/// Look for a directive in a comment string. -/// The directive is of the form "foo:" and should follow the leading `;` in the comment: -/// -/// ; dominates: ebb3 ebb4 -/// -/// Return the comment text following the directive. -pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> { - assert!( - directive.ends_with(':'), - "Directive must include trailing colon" - ); - let text = comment.trim_left_matches(';').trim_left(); - if text.starts_with(directive) { - Some(text[directive.len()..].trim()) - } else { - None - } -} - -/// Pretty-print a verifier error. -pub fn pretty_verifier_error( - func: &ir::Function, - isa: Option<&TargetIsa>, - err: verifier::Error, -) -> String { - let mut msg = err.to_string(); - match err.location { - AnyEntity::Inst(inst) => { - write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap() - } - _ => msg.push('\n'), - } - write!(msg, "{}", func.display(isa)).unwrap(); - msg -} - -/// Pretty-print a Cretonne error. -pub fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String { - if let CtonError::Verifier(e) = err { - pretty_verifier_error(func, isa, e) - } else { - err.to_string() - } -} - /// Like `FlagsOrIsa`, but holds ownership. pub enum OwnedFlagsOrIsa { Flags(settings::Flags), @@ -90,10 +41,7 @@ impl OwnedFlagsOrIsa { } /// Parse "set" and "isa" commands. -pub fn parse_sets_and_isa( - flag_set: Vec, - flag_isa: String, -) -> Result { +pub fn parse_sets_and_isa(flag_set: &[String], flag_isa: &str) -> Result { let mut flag_builder = settings::builder(); parse_options( flag_set.iter().map(|x| x.as_str()), @@ -119,12 +67,3 @@ pub fn parse_sets_and_isa( Ok(OwnedFlagsOrIsa::Flags(settings::Flags::new(&flag_builder))) } } - -#[test] -fn test_match_directive() { - assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar")); - assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar")); - assert_eq!(match_directive("foo:bar", "foo:"), Some("bar")); - assert_eq!(match_directive(";x foo: bar", "foo:"), None); - assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar")); -} diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs index ab6a245d3a..029c7ad5c0 100644 --- a/cranelift/src/wasm.rs +++ b/cranelift/src/wasm.rs @@ -1,19 +1,21 @@ //! CLI tool to use the functions provided by the [cretonne-wasm](../cton_wasm/index.html) crate. //! -//! Reads Wasm binary files, translates the functions' code to Cretonne IL. +//! Reads Wasm binary files, translates the functions' code to Cretonne IR. +#![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments, cyclomatic_complexity))] -use cton_wasm::{translate_module, DummyEnvironment, ModuleEnvironment}; -use std::path::PathBuf; use cretonne::Context; +use cretonne::print_errors::{pretty_error, pretty_verifier_error}; use cretonne::settings::FlagsOrIsa; -use std::fs::File; +use cton_wasm::{translate_module, DummyEnvironment, ModuleEnvironment}; use std::error::Error; +use std::fs::File; use std::io; use std::path::Path; +use std::path::PathBuf; use std::process::Command; use tempdir::TempDir; use term; -use utils::{pretty_verifier_error, pretty_error, parse_sets_and_isa, read_to_end}; +use utils::{parse_sets_and_isa, read_to_end}; macro_rules! vprintln { ($x: expr, $($tts:tt)*) => { @@ -37,8 +39,8 @@ pub fn run( flag_just_decode: bool, flag_check_translation: bool, flag_print: bool, - flag_set: Vec, - flag_isa: String, + flag_set: &[String], + flag_isa: &str, flag_print_size: bool, ) -> Result<(), String> { let parsed = parse_sets_and_isa(flag_set, flag_isa)?; @@ -52,8 +54,8 @@ pub fn run( flag_check_translation, flag_print, flag_print_size, - path.to_path_buf(), - name, + &path.to_path_buf(), + &name, parsed.as_fisa(), )?; } @@ -66,8 +68,8 @@ fn handle_module( flag_check_translation: bool, flag_print: bool, flag_print_size: bool, - path: PathBuf, - name: String, + path: &PathBuf, + name: &str, fisa: FlagsOrIsa, ) -> Result<(), String> { let mut terminal = term::stdout().unwrap(); @@ -152,29 +154,27 @@ fn handle_module( context.func = func.clone(); if flag_check_translation { context.verify(fisa).map_err(|err| { - pretty_verifier_error(&context.func, fisa.isa, err) + pretty_verifier_error(&context.func, fisa.isa, &err) })?; - } else { - if let Some(isa) = fisa.isa { - let compiled_size = context.compile(isa).map_err(|err| { - pretty_error(&context.func, fisa.isa, err) - })?; - if flag_print_size { - println!( - "Function #{} code size: {} bytes", - func_index, - compiled_size - ); - total_module_code_size += compiled_size; - println!( - "Function #{} bytecode size: {} bytes", - func_index, - dummy_environ.func_bytecode_sizes[func_index] - ); - } - } else { - return Err(String::from("compilation requires a target isa")); + } else if let Some(isa) = fisa.isa { + let compiled_size = context.compile(isa).map_err(|err| { + pretty_error(&context.func, fisa.isa, err) + })?; + if flag_print_size { + println!( + "Function #{} code size: {} bytes", + func_index, + compiled_size + ); + total_module_code_size += compiled_size; + println!( + "Function #{} bytecode size: {} bytes", + func_index, + dummy_environ.func_bytecode_sizes[def_index] + ); } + } else { + return Err(String::from("compilation requires a target isa")); } if flag_print { vprintln!(flag_verbose, ""); @@ -193,10 +193,7 @@ fn handle_module( if !flag_check_translation && flag_print_size { println!("Total module code size: {} bytes", total_module_code_size); - let total_bytecode_size = dummy_environ.func_bytecode_sizes.iter().fold( - 0, - |sum, x| sum + x, - ); + let total_bytecode_size: usize = dummy_environ.func_bytecode_sizes.iter().sum(); println!("Total module bytecode size: {} bytes", total_bytecode_size); } diff --git a/cranelift/test-all.sh b/cranelift/test-all.sh index 2402eb7a39..572bf5de7b 100755 --- a/cranelift/test-all.sh +++ b/cranelift/test-all.sh @@ -3,11 +3,10 @@ set -euo pipefail # This is the top-level test script: # -# - Build documentation for Rust code in 'src/tools/target/doc'. -# - Run unit tests for all Rust crates. -# - Make a debug build of all crates. -# - Make a release build of cton-util. -# - Run file-level tests with the release build of cton-util. +# - Make a debug build. +# - Make a release build. +# - Run unit tests for all Rust crates (including the filetests) +# - Build API documentation. # # All tests run by this script should be passing at all times. @@ -42,22 +41,26 @@ if [ -n "$needcheck" ]; then touch $tsfile || echo no target directory fi -cd "$topdir" -banner "Rust unit tests" -cargo test --all +# Make sure the code builds in debug mode. +banner "Rust debug build" +cargo build -# Build cton-util for parser testing. -cd "$topdir" -banner "Rust documentation" -echo "open $topdir/target/doc/cretonne/index.html" +# Make sure the code builds in release mode, and run the unit tests. We run +# these in release mode for speed, but note that the top-level Cargo.toml file +# does enable debug assertions in release builds. +banner "Rust release build and unit tests" +cargo test --all --release + +# Make sure the documentation builds. +banner "Rust documentation: $topdir/target/doc/cretonne/index.html" cargo doc -banner "Rust release build" -cargo build --release -export CTONUTIL="$topdir/target/release/cton-util" - -cd "$topdir" -banner "File tests" -"$CTONUTIL" test filetests docs +# Run clippy if we have it. +banner "Rust linter" +if $topdir/check-clippy.sh; then + $topdir/clippy-all.sh --write-mode=diff +else + echo "\`cargo +nightly install clippy\` for optional rust linting" +fi banner "OK" diff --git a/cranelift/tests/filetests.rs b/cranelift/tests/filetests.rs new file mode 100644 index 0000000000..6b5e392c76 --- /dev/null +++ b/cranelift/tests/filetests.rs @@ -0,0 +1,7 @@ +extern crate cton_filetests; + +#[test] +fn filetests() { + // Run all the filetests in the following directories. + cton_filetests::run(false, &["filetests".into(), "docs".into()]).expect("test harness"); +} diff --git a/lib/cretonne/Cargo.toml b/lib/cretonne/Cargo.toml index 01b7e2ab70..e0fe112009 100644 --- a/lib/cretonne/Cargo.toml +++ b/lib/cretonne/Cargo.toml @@ -1,13 +1,13 @@ [package] authors = ["The Cretonne Project Developers"] name = "cretonne" -version = "0.3.4" +version = "0.4.1" description = "Low-level code generator library" license = "Apache-2.0" documentation = "https://cretonne.readthedocs.io/" repository = "https://github.com/Cretonne/cretonne" readme = "README.md" -keywords = [ "compile", "compiler", "jit" ] +keywords = ["compile", "compiler", "jit"] build = "build.rs" [lib] @@ -32,3 +32,7 @@ optional = true default = ["std"] std = [] core = ["hashmap_core"] + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "Cretonne/cretonne" } diff --git a/lib/cretonne/README.md b/lib/cretonne/README.md index 73b2806f5d..c12dfbf5a9 100644 --- a/lib/cretonne/README.md +++ b/lib/cretonne/README.md @@ -1,2 +1,2 @@ This crate contains the core Cretonne code generator. It translates code from an -intermediate language into executable machine code. +intermediate representation into executable machine code. diff --git a/lib/cretonne/build.rs b/lib/cretonne/build.rs index 69be14de94..e13e9349a5 100644 --- a/lib/cretonne/build.rs +++ b/lib/cretonne/build.rs @@ -18,7 +18,6 @@ // 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; diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index b50824581b..595f09b9fa 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -2,7 +2,7 @@ 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 +Rust representation of Cretonne IR, so all instruction formats must be defined in this module. """ from __future__ import absolute_import diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index 8ba1aa65fa..c54f3ef570 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -588,6 +588,9 @@ stack_check = Instruction( Read the stack limit from ``GV`` and compare it to the stack pointer. If the stack pointer has reached or exceeded the limit, generate a trap with a ``stk_ovf`` code. + + The global variable must be accessible and naturally aligned for a + pointer-sized value. """, ins=GV, can_trap=True) diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index ecc8d5a436..c2392269df 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -41,6 +41,8 @@ widen = XFormGroup('widen', """ The transformations in the 'widen' group work by expressing instructions in terms of larger types. + + This group is not yet implemented. """) expand = XFormGroup('expand', """ diff --git a/lib/cretonne/meta/base/settings.py b/lib/cretonne/meta/base/settings.py index e8785fbc08..3bd90f107b 100644 --- a/lib/cretonne/meta/base/settings.py +++ b/lib/cretonne/meta/base/settings.py @@ -20,10 +20,10 @@ opt_level = EnumSetting( enable_verifier = BoolSetting( """ - Run the Cretonne IL verifier at strategic times during compilation. + Run the Cretonne IR 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. + disabled by default, except when reading Cretonne IR from a text file. """, default=True) diff --git a/lib/cretonne/meta/build.py b/lib/cretonne/meta/build.py index a94b9ce706..8c83300604 100644 --- a/lib/cretonne/meta/build.py +++ b/lib/cretonne/meta/build.py @@ -14,19 +14,27 @@ 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 +def main(): + # type: () -> None + parser = argparse.ArgumentParser( + description='Generate sources for Cretonne.') + parser.add_argument('--out-dir', help='set output directory') -isas = isa.all_isas() + args = parser.parse_args() + out_dir = args.out_dir -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() + 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() + + +if __name__ == "__main__": + main() diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 38d633e17d..5eefe222f8 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -559,7 +559,7 @@ class Enumerator(Literal): 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. + :param value: The textual IR representation of the value. `Enumerator` nodes are not usually created directly. They are created by using the dot syntax on immediate kinds: `intcc.ult`. diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 9fcfa440a3..77b071636a 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -12,7 +12,7 @@ from .instructions import InstructionGroup 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 .instructions import MaybeBoundInst, InstructionFormat # noqa from .predicates import PredNode, PredKey # noqa from .settings import SettingGroup # noqa from .registers import RegBank # noqa @@ -172,8 +172,7 @@ class TargetISA(object): """ for cpumode in self.cpumodes: self.legalize_code(cpumode.default_legalize) - for x in sorted(cpumode.type_legalize.values(), - key=lambda x: x.name): + for x in cpumode.type_legalize.values(): self.legalize_code(x) def legalize_code(self, xgrp): @@ -232,7 +231,7 @@ class CPUMode(object): # 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] + self.type_legalize = OrderedDict() # type: OrderedDict[ValueType, XFormGroup] # noqa def __str__(self): # type: () -> str diff --git a/lib/cretonne/meta/check.sh b/lib/cretonne/meta/check.sh index aa0e88ce1e..fa86bf0804 100755 --- a/lib/cretonne/meta/check.sh +++ b/lib/cretonne/meta/check.sh @@ -2,7 +2,7 @@ set -euo pipefail cd $(dirname "$0") -runif() { +function runif() { if command -v "$1" > /dev/null; then echo " === $1 ===" "$@" diff --git a/lib/cretonne/meta/gen_binemit.py b/lib/cretonne/meta/gen_binemit.py index c3d1b18e86..5888d89e10 100644 --- a/lib/cretonne/meta/gen_binemit.py +++ b/lib/cretonne/meta/gen_binemit.py @@ -152,7 +152,7 @@ def gen_isa(isa, fmt): fmt.line('let bits = encoding.bits();') with fmt.indented('match func.encodings[inst].recipe() {', '}'): for i, recipe in enumerate(isa.all_recipes): - fmt.comment(recipe.name) + fmt.comment('Recipe {}'.format(recipe.name)) with fmt.indented('{} => {{'.format(i), '}'): gen_recipe(recipe, fmt) fmt.line('_ => {},') diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 46fe22a913..61ceb8d498 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -600,8 +600,8 @@ def make_tables(cpumode): table[ty][inst].encodings.append(enc) # Ensure there are level 1 table entries for all types with a custom - # legalize action. Try to be stable relative to dict ordering. - for ty in sorted(cpumode.type_legalize.keys(), key=str): + # legalize action. + for ty in cpumode.type_legalize.keys(): table[ty] return table @@ -756,7 +756,7 @@ def emit_recipe_constraints(isa, fmt): 'static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = [' .format(len(isa.all_recipes)), '];'): for r in isa.all_recipes: - fmt.comment(r.name) + fmt.comment('Constraints for recipe {}:'.format(r.name)) tied_i2o, tied_o2i = r.ties() fixed_ins, fixed_outs = r.fixed_ops() with fmt.indented('RecipeConstraints {', '},'): @@ -830,7 +830,7 @@ def emit_recipe_sizing(isa, fmt): 'static RECIPE_SIZING: [RecipeSizing; {}] = [' .format(len(isa.all_recipes)), '];'): for r in isa.all_recipes: - fmt.comment(r.name) + fmt.comment('Code size information for recipe {}:'.format(r.name)) with fmt.indented('RecipeSizing {', '},'): fmt.format('bytes: {},', r.size) if r.branch_range: diff --git a/lib/cretonne/meta/gen_instr.py b/lib/cretonne/meta/gen_instr.py index 28db9e81d2..4e0bc6cb78 100644 --- a/lib/cretonne/meta/gen_instr.py +++ b/lib/cretonne/meta/gen_instr.py @@ -49,11 +49,11 @@ def gen_formats(fmt): with fmt.indented( "fn from(inst: &'a InstructionData) -> InstructionFormat {", '}'): - with fmt.indented('match *inst {', '}'): - for f in InstructionFormat.all_formats: - fmt.line(('InstructionData::{} {{ .. }} => ' + - 'InstructionFormat::{},') - .format(f.name, f.name)) + m = srcgen.Match('*inst') + for f in InstructionFormat.all_formats: + m.arm('InstructionData::' + f.name, ['..'], + 'InstructionFormat::' + f.name) + fmt.match(m) fmt.line() @@ -74,33 +74,64 @@ def gen_arguments_method(fmt, is_mut): 'pool: &\'a {m}ir::ValueListPool) -> ' '&{m}[Value] {{' .format(f=method, m=mut), '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - n = 'InstructionData::' + f.name + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name - # Formats with a value list put all of their arguments in the - # list. We don't split them up, just return it all as variable - # arguments. (I expect the distinction to go away). - if f.has_value_list: - arg = ''.format(mut) - fmt.line( - '{} {{ ref {}args, .. }} => args.{}(pool),' - .format(n, mut, as_slice)) - continue + # Formats with a value list put all of their arguments in the + # list. We don't split them up, just return it all as variable + # arguments. (I expect the distinction to go away). + if f.has_value_list: + m.arm(n, ['ref {}args'.format(mut), '..'], + 'args.{}(pool)'.format(as_slice)) + continue - # Fixed args. - if f.num_value_operands == 0: - arg = '&{}[]'.format(mut) - capture = '' + # Fixed args. + fields = [] + if f.num_value_operands == 0: + arg = '&{}[]'.format(mut) + elif f.num_value_operands == 1: + fields.append('ref {}arg'.format(mut)) + arg = '{}(arg)'.format(rslice) + else: + args = 'args_arity{}'.format(f.num_value_operands) + fields.append('args: ref {}{}'.format(mut, args)) + arg = args + fields.append('..') + m.arm(n, fields, arg) + fmt.match(m) + + +def gen_instruction_data(fmt): + # type: (srcgen.Formatter) -> None + """ + Generate the InstructionData enum. + + Every variant must contain `opcode` and `ty` fields. An instruction that + doesn't produce a value should have its `ty` field set to `VOID`. The size + of `InstructionData` should be kept at 16 bytes on 64-bit architectures. If + more space is needed to represent an instruction, use a `Box` to + store the additional information out of line. + """ + + fmt.line('#[derive(Clone, Debug, Hash, PartialEq, Eq)]') + fmt.line('#[allow(missing_docs)]') + with fmt.indented('pub enum InstructionData {', '}'): + for f in InstructionFormat.all_formats: + with fmt.indented('{} {{'.format(f.name), '},'): + fmt.line('opcode: Opcode,') + if f.typevar_operand is None: + pass + elif f.has_value_list: + fmt.line('args: ValueList,') elif f.num_value_operands == 1: - capture = 'ref {}arg, '.format(mut) - arg = '{}(arg)'.format(rslice) + fmt.line('arg: Value,') else: - capture = 'ref {}args, '.format(mut) - arg = 'args' - fmt.line( - '{} {{ {}.. }} => {},' - .format(n, capture, arg)) + fmt.line('args: [Value; {}],'.format(f.num_value_operands)) + for field in f.imm_fields: + fmt.line( + '{}: {},' + .format(field.member, field.kind.rust_type)) def gen_instruction_data_impl(fmt): @@ -123,39 +154,37 @@ def gen_instruction_data_impl(fmt): with fmt.indented('impl InstructionData {', '}'): fmt.doc_comment('Get the opcode of this instruction.') with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - fmt.line( - 'InstructionData::{} {{ opcode, .. }} => opcode,' - .format(f.name)) + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + m.arm('InstructionData::' + f.name, ['opcode', '..'], + 'opcode') + fmt.match(m) fmt.line() fmt.doc_comment('Get the controlling type variable operand.') with fmt.indented( 'pub fn typevar_operand(&self, pool: &ir::ValueListPool) -> ' 'Option {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - n = 'InstructionData::' + f.name - if f.typevar_operand is None: - fmt.line(n + ' { .. } => None,') - elif f.has_value_list: - # We keep all arguments in a value list. - i = f.typevar_operand - fmt.line( - '{} {{ ref args, .. }} => ' - 'args.get({}, pool),'.format(n, i)) - elif f.num_value_operands == 1: - # We have a single value operand called 'arg'. - fmt.line(n + ' { arg, .. } => Some(arg),') - else: - # We have multiple value operands and an array `args`. - # Which `args` index to use? - i = f.typevar_operand - fmt.line( - n + - ' {{ ref args, .. }} => Some(args[{}]),' - .format(i)) + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.typevar_operand is None: + m.arm(n, ['..'], 'None') + elif f.has_value_list: + # We keep all arguments in a value list. + i = f.typevar_operand + m.arm(n, ['ref args', '..'], + 'args.get({}, pool)'.format(i)) + elif f.num_value_operands == 1: + # We have a single value operand called 'arg'. + m.arm(n, ['arg', '..'], 'Some(arg)') + else: + # We have multiple value operands and an array `args`. + # Which `args` index to use? + args = 'args_arity{}'.format(f.num_value_operands) + m.arm(n, ['args: ref {}'.format(args), '..'], + 'Some({}[{}])'.format(args, f.typevar_operand)) + fmt.match(m) fmt.line() fmt.doc_comment( @@ -184,13 +213,13 @@ def gen_instruction_data_impl(fmt): with fmt.indented( 'pub fn take_value_list(&mut self) -> Option {', '}'): - with fmt.indented('match *self {', '}'): - for f in InstructionFormat.all_formats: - n = 'InstructionData::' + f.name - if f.has_value_list: - fmt.line( - n + ' { ref mut args, .. } => Some(args.take()),') - fmt.line('_ => None,') + m = srcgen.Match('*self') + for f in InstructionFormat.all_formats: + n = 'InstructionData::' + f.name + if f.has_value_list: + m.arm(n, ['ref mut args', '..'], 'Some(args.take())') + m.arm('_', [], 'None') + fmt.match(m) fmt.line() fmt.doc_comment( @@ -275,14 +304,12 @@ def gen_opcodes(groups, fmt): fmt.doc_comment(Instruction.ATTRIBS[attr]) with fmt.indented('pub fn {}(self) -> bool {{' .format(attr), '}'): - with fmt.indented('match self {', '}'): - for i in instrs: - if getattr(i, attr): - fmt.format( - 'Opcode::{} => true,', - i.camel_name, i.name) - - fmt.line('_ => false,') + m = srcgen.Match('self') + for i in instrs: + if getattr(i, attr): + m.arm('Opcode::' + i.camel_name, [], 'true') + m.arm('_', [], 'false') + fmt.match(m) fmt.line() fmt.line() @@ -299,9 +326,10 @@ def gen_opcodes(groups, fmt): # Generate a private opcode_name function. with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'): - with fmt.indented('match opc {', '}'): - for i in instrs: - fmt.format('Opcode::{} => "{}",', i.camel_name, i.name) + m = srcgen.Match('opc') + for i in instrs: + m.arm('Opcode::' + i.camel_name, [], '"{}"'.format(i.name)) + fmt.match(m) fmt.line() # Generate an opcode hash table for looking up opcodes by name. @@ -655,7 +683,7 @@ def gen_builder(insts, fmt): fmt.doc_comment(""" Convenience methods for building instructions. - The `InstrBuilder` trait has one method per instruction opcode for + The `InstBuilder` trait has one method per instruction opcode for conveniently constructing the instruction with minimum arguments. Polymorphic instructions infer their result types from the input arguments when possible. In some cases, an explicit `ctrl_typevar` @@ -682,13 +710,15 @@ def generate(isas, out_dir): # opcodes.rs fmt = srcgen.Formatter() gen_formats(fmt) + gen_instruction_data(fmt) + fmt.line() gen_instruction_data_impl(fmt) fmt.line() instrs = gen_opcodes(groups, fmt) gen_type_constraints(fmt, instrs) fmt.update_file('opcodes.rs', out_dir) - # builder.rs + # inst_builder.rs fmt = srcgen.Formatter() gen_builder(instrs, fmt) - fmt.update_file('builder.rs', out_dir) + fmt.update_file('inst_builder.rs', out_dir) diff --git a/lib/cretonne/meta/gen_legalizer.py b/lib/cretonne/meta/gen_legalizer.py index 04c48ade51..9051337238 100644 --- a/lib/cretonne/meta/gen_legalizer.py +++ b/lib/cretonne/meta/gen_legalizer.py @@ -103,18 +103,19 @@ def emit_runtime_typecheck(check, fmt, type_sets): base_exp = build_derived_expr(tv.base) if (tv.derived_func == TypeVar.LANEOF): - return "{}.map(|t: Type| -> t.lane_type())".format(base_exp) + return "{}.map(|t: ir::Type| t.lane_type())".format(base_exp) elif (tv.derived_func == TypeVar.ASBOOL): - return "{}.map(|t: Type| -> t.as_bool())".format(base_exp) + return "{}.map(|t: ir::Type| t.as_bool())".format(base_exp) elif (tv.derived_func == TypeVar.HALFWIDTH): - return "{}.and_then(|t: Type| -> t.half_width())".format(base_exp) + return "{}.and_then(|t: ir::Type| t.half_width())".format(base_exp) elif (tv.derived_func == TypeVar.DOUBLEWIDTH): - return "{}.and_then(|t: Type| -> t.double_width())"\ + return "{}.and_then(|t: ir::Type| t.double_width())"\ .format(base_exp) elif (tv.derived_func == TypeVar.HALFVECTOR): - return "{}.and_then(|t: Type| -> t.half_vector())".format(base_exp) + return "{}.and_then(|t: ir::Type| t.half_vector())"\ + .format(base_exp) elif (tv.derived_func == TypeVar.DOUBLEVECTOR): - return "{}.and_then(|t: Type| -> t.by(2))".format(base_exp) + return "{}.and_then(|t: ir::Type| t.by(2))".format(base_exp) else: assert False, "Unknown derived function {}".format(tv.derived_func) diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index 1560157915..373c9cb1a9 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -28,7 +28,7 @@ def gen_enum_types(sgrp, fmt): if not isinstance(setting, EnumSetting): continue ty = camel_case(setting.name) - fmt.doc_comment('Values for {}.'.format(setting)) + fmt.doc_comment('Values for `{}`.'.format(setting)) fmt.line('#[derive(Debug, PartialEq, Eq)]') with fmt.indented('pub enum {} {{'.format(ty), '}'): for v in setting.values: @@ -57,12 +57,11 @@ def gen_getter(setting, sgrp, fmt): ty = camel_case(setting.name) proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty) with fmt.indented(proto + ' {', '}'): - with fmt.indented( - 'match self.bytes[{}] {{' - .format(setting.byte_offset), '}'): - for i, v in enumerate(setting.values): - fmt.line('{} => {}::{},'.format(i, ty, camel_case(v))) - fmt.line('_ => panic!("Invalid enum value"),') + m = srcgen.Match('self.bytes[{}]'.format(setting.byte_offset)) + for i, v in enumerate(setting.values): + m.arm(str(i), [], '{}::{}'.format(ty, camel_case(v))) + m.arm('_', [], 'panic!("Invalid enum value")') + fmt.match(m) else: raise AssertionError("Unknown setting kind") diff --git a/lib/cretonne/meta/isa/intel/defs.py b/lib/cretonne/meta/isa/intel/defs.py index d5bb0b5a1f..b6a4d37206 100644 --- a/lib/cretonne/meta/isa/intel/defs.py +++ b/lib/cretonne/meta/isa/intel/defs.py @@ -12,8 +12,8 @@ from base.immediates import floatcc ISA = TargetISA('intel', [base.instructions.GROUP, x86.GROUP]) # CPU modes for 32-bit and 64-bit operation. -I64 = CPUMode('I64', ISA) -I32 = CPUMode('I32', ISA) +X86_64 = CPUMode('I64', ISA) +X86_32 = CPUMode('I32', ISA) # The set of floating point condition codes that are directly supported. # Other condition codes need to be reversed or expressed as two tests. diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index 162caccf65..0fc507bac5 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -5,7 +5,7 @@ from __future__ import absolute_import from cdsl.predicates import IsUnsignedInt, Not, And from base import instructions as base from base.formats import UnaryImm -from .defs import I32, I64 +from .defs import X86_64, X86_32 from . import recipes as r from . import settings as cfg from . import instructions as x86 @@ -22,83 +22,83 @@ except ImportError: pass -I32.legalize_monomorphic(expand_flags) -I32.legalize_type( - default=narrow, - b1=expand_flags, - i32=intel_expand, - f32=intel_expand, - f64=intel_expand) +X86_32.legalize_monomorphic(expand_flags) +X86_32.legalize_type( + default=narrow, + b1=expand_flags, + i32=intel_expand, + f32=intel_expand, + f64=intel_expand) -I64.legalize_monomorphic(expand_flags) -I64.legalize_type( - default=narrow, - b1=expand_flags, - i32=intel_expand, - i64=intel_expand, - f32=intel_expand, - f64=intel_expand) +X86_64.legalize_monomorphic(expand_flags) +X86_64.legalize_type( + default=narrow, + b1=expand_flags, + i32=intel_expand, + i64=intel_expand, + f32=intel_expand, + f64=intel_expand) # # Helper functions for generating encodings. # -def enc_i64(inst, recipe, *args, **kwargs): +def enc_x86_64(inst, recipe, *args, **kwargs): # type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None """ - Add encodings for `inst` to I64 with and without a REX prefix. + Add encodings for `inst` to X86_64 with and without a REX prefix. """ - I64.enc(inst, *recipe.rex(*args, **kwargs)) - I64.enc(inst, *recipe(*args, **kwargs)) + X86_64.enc(inst, *recipe.rex(*args, **kwargs)) + X86_64.enc(inst, *recipe(*args, **kwargs)) def enc_both(inst, recipe, *args, **kwargs): # type: (MaybeBoundInst, r.TailRecipe, *int, **Any) -> None """ - Add encodings for `inst` to both I32 and I64. + Add encodings for `inst` to both X86_32 and X86_64. """ - I32.enc(inst, *recipe(*args, **kwargs)) - enc_i64(inst, recipe, *args, **kwargs) + X86_32.enc(inst, *recipe(*args, **kwargs)) + enc_x86_64(inst, recipe, *args, **kwargs) def enc_i32_i64(inst, recipe, *args, **kwargs): # type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None """ - Add encodings for `inst.i32` to I32. - Add encodings for `inst.i32` to I64 with and without REX. - Add encodings for `inst.i64` to I64 with a REX.W prefix. + Add encodings for `inst.i32` to X86_32. + Add encodings for `inst.i32` to X86_64 with and without REX. + Add encodings for `inst.i64` to X86_64 with a REX.W prefix. """ - I32.enc(inst.i32, *recipe(*args, **kwargs)) + X86_32.enc(inst.i32, *recipe(*args, **kwargs)) # REX-less encoding must come after REX encoding so we don't use it by # default. Otherwise reg-alloc would never use r8 and up. - I64.enc(inst.i32, *recipe.rex(*args, **kwargs)) - I64.enc(inst.i32, *recipe(*args, **kwargs)) + X86_64.enc(inst.i32, *recipe.rex(*args, **kwargs)) + X86_64.enc(inst.i32, *recipe(*args, **kwargs)) - I64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs)) + X86_64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs)) def enc_i32_i64_ld_st(inst, w_bit, recipe, *args, **kwargs): # type: (MaybeBoundInst, bool, r.TailRecipe, *int, **int) -> None """ - Add encodings for `inst.i32` to I32. - Add encodings for `inst.i32` to I64 with and without REX. - Add encodings for `inst.i64` to I64 with a REX prefix, using the `w_bit` + Add encodings for `inst.i32` to X86_32. + Add encodings for `inst.i32` to X86_64 with and without REX. + Add encodings for `inst.i64` to X86_64 with a REX prefix, using the `w_bit` argument to determine whether or not to set the REX.W bit. """ - I32.enc(inst.i32.any, *recipe(*args, **kwargs)) + X86_32.enc(inst.i32.any, *recipe(*args, **kwargs)) # REX-less encoding must come after REX encoding so we don't use it by # default. Otherwise reg-alloc would never use r8 and up. - I64.enc(inst.i32.any, *recipe.rex(*args, **kwargs)) - I64.enc(inst.i32.any, *recipe(*args, **kwargs)) + X86_64.enc(inst.i32.any, *recipe.rex(*args, **kwargs)) + X86_64.enc(inst.i32.any, *recipe(*args, **kwargs)) if w_bit: - I64.enc(inst.i64.any, *recipe.rex(*args, w=1, **kwargs)) + X86_64.enc(inst.i64.any, *recipe.rex(*args, w=1, **kwargs)) else: - I64.enc(inst.i64.any, *recipe.rex(*args, **kwargs)) - I64.enc(inst.i64.any, *recipe(*args, **kwargs)) + X86_64.enc(inst.i64.any, *recipe.rex(*args, **kwargs)) + X86_64.enc(inst.i64.any, *recipe(*args, **kwargs)) for inst, opc in [ @@ -141,19 +141,22 @@ for inst, rrr in [ # band_imm.i32. Can even use the single-byte immediate for 0xffff_ffXX masks. # Immediate constants. -I32.enc(base.iconst.i32, *r.puid(0xb8)) +X86_32.enc(base.iconst.i32, *r.puid(0xb8)) -I64.enc(base.iconst.i32, *r.puid.rex(0xb8)) -I64.enc(base.iconst.i32, *r.puid(0xb8)) +X86_64.enc(base.iconst.i32, *r.puid.rex(0xb8)) +X86_64.enc(base.iconst.i32, *r.puid(0xb8)) # The 32-bit immediate movl also zero-extends to 64 bits. -I64.enc(base.iconst.i64, *r.puid.rex(0xb8), - instp=IsUnsignedInt(UnaryImm.imm, 32)) -I64.enc(base.iconst.i64, *r.puid(0xb8), - instp=IsUnsignedInt(UnaryImm.imm, 32)) +X86_64.enc(base.iconst.i64, *r.puid.rex(0xb8), + instp=IsUnsignedInt(UnaryImm.imm, 32)) +X86_64.enc(base.iconst.i64, *r.puid(0xb8), + instp=IsUnsignedInt(UnaryImm.imm, 32)) # Sign-extended 32-bit immediate. -I64.enc(base.iconst.i64, *r.uid.rex(0xc7, rrr=0, w=1)) +X86_64.enc(base.iconst.i64, *r.uid.rex(0xc7, rrr=0, w=1)) # Finally, the 0xb8 opcode takes an 8-byte immediate with a REX.W prefix. -I64.enc(base.iconst.i64, *r.puiq.rex(0xb8, w=1)) +X86_64.enc(base.iconst.i64, *r.puiq.rex(0xb8, w=1)) + +# bool constants. +enc_both(base.bconst.b1, r.puid_bool, 0xb8) # Shifts and rotates. # Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit @@ -164,38 +167,46 @@ for inst, rrr in [ (base.ishl, 4), (base.ushr, 5), (base.sshr, 7)]: - I32.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr)) - I64.enc(inst.i64.any, *r.rc.rex(0xd3, rrr=rrr, w=1)) - I64.enc(inst.i32.any, *r.rc.rex(0xd3, rrr=rrr)) - I64.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr)) + # Cannot use enc_i32_i64 for this pattern because instructions require + # .any suffix. + X86_32.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr)) + X86_64.enc(inst.i64.any, *r.rc.rex(0xd3, rrr=rrr, w=1)) + X86_64.enc(inst.i32.any, *r.rc.rex(0xd3, rrr=rrr)) + X86_64.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr)) + +for inst, rrr in [ + (base.ishl_imm, 4), + (base.ushr_imm, 5), + (base.sshr_imm, 7)]: + enc_i32_i64(inst, r.rib, 0xc1, rrr=rrr) # Population count. -I32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) -I64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1), - isap=cfg.use_popcnt) -I64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) -I64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) +X86_32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) +X86_64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1), + isap=cfg.use_popcnt) +X86_64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) +X86_64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt) # Count leading zero bits. -I32.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) -I64.enc(base.clz.i64, *r.urm.rex(0xf3, 0x0f, 0xbd, w=1), - isap=cfg.use_lzcnt) -I64.enc(base.clz.i32, *r.urm.rex(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) -I64.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) +X86_32.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) +X86_64.enc(base.clz.i64, *r.urm.rex(0xf3, 0x0f, 0xbd, w=1), + isap=cfg.use_lzcnt) +X86_64.enc(base.clz.i32, *r.urm.rex(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) +X86_64.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt) # Count trailing zero bits. -I32.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) -I64.enc(base.ctz.i64, *r.urm.rex(0xf3, 0x0f, 0xbc, w=1), - isap=cfg.use_bmi1) -I64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) -I64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) +X86_32.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) +X86_64.enc(base.ctz.i64, *r.urm.rex(0xf3, 0x0f, 0xbc, w=1), + isap=cfg.use_bmi1) +X86_64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) +X86_64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1) # # Loads and stores. # for recipe in [r.st, r.stDisp8, r.stDisp32]: enc_i32_i64_ld_st(base.store, True, recipe, 0x89) - enc_i64(base.istore32.i64.any, recipe, 0x89) + enc_x86_64(base.istore32.i64.any, recipe, 0x89) enc_i32_i64_ld_st(base.istore16, False, recipe, 0x66, 0x89) # Byte stores are more complicated because the registers they can address @@ -203,121 +214,121 @@ for recipe in [r.st, r.stDisp8, r.stDisp32]: # the corresponding st* recipes when a REX prefix is applied. for recipe in [r.st_abcd, r.stDisp8_abcd, r.stDisp32_abcd]: enc_both(base.istore8.i32.any, recipe, 0x88) - enc_i64(base.istore8.i64.any, recipe, 0x88) + enc_x86_64(base.istore8.i64.any, recipe, 0x88) -enc_i32_i64(base.spill, r.spSib32, 0x89) -enc_i32_i64(base.regspill, r.rsp32, 0x89) +enc_i32_i64(base.spill, r.spillSib32, 0x89) +enc_i32_i64(base.regspill, r.regspill32, 0x89) # Use a 32-bit write for spilling `b1` to avoid constraining the permitted # registers. # See MIN_SPILL_SLOT_SIZE which makes this safe. -enc_both(base.spill.b1, r.spSib32, 0x89) -enc_both(base.regspill.b1, r.rsp32, 0x89) +enc_both(base.spill.b1, r.spillSib32, 0x89) +enc_both(base.regspill.b1, r.regspill32, 0x89) for recipe in [r.ld, r.ldDisp8, r.ldDisp32]: enc_i32_i64_ld_st(base.load, True, recipe, 0x8b) - enc_i64(base.uload32.i64, recipe, 0x8b) - I64.enc(base.sload32.i64, *recipe.rex(0x63, w=1)) + enc_x86_64(base.uload32.i64, recipe, 0x8b) + X86_64.enc(base.sload32.i64, *recipe.rex(0x63, w=1)) enc_i32_i64_ld_st(base.uload16, True, recipe, 0x0f, 0xb7) enc_i32_i64_ld_st(base.sload16, True, recipe, 0x0f, 0xbf) enc_i32_i64_ld_st(base.uload8, True, recipe, 0x0f, 0xb6) enc_i32_i64_ld_st(base.sload8, True, recipe, 0x0f, 0xbe) -enc_i32_i64(base.fill, r.fiSib32, 0x8b) -enc_i32_i64(base.regfill, r.rfi32, 0x8b) +enc_i32_i64(base.fill, r.fillSib32, 0x8b) +enc_i32_i64(base.regfill, r.regfill32, 0x8b) # Load 32 bits from `b1` spill slots. See `spill.b1` above. -enc_both(base.fill.b1, r.fiSib32, 0x8b) -enc_both(base.regfill.b1, r.rfi32, 0x8b) +enc_both(base.fill.b1, r.fillSib32, 0x8b) +enc_both(base.regfill.b1, r.regfill32, 0x8b) # Push and Pop -I32.enc(x86.push.i32, *r.pushq(0x50)) -enc_i64(x86.push.i64, r.pushq, 0x50) +X86_32.enc(x86.push.i32, *r.pushq(0x50)) +enc_x86_64(x86.push.i64, r.pushq, 0x50) -I32.enc(x86.pop.i32, *r.popq(0x58)) -enc_i64(x86.pop.i64, r.popq, 0x58) +X86_32.enc(x86.pop.i32, *r.popq(0x58)) +enc_x86_64(x86.pop.i64, r.popq, 0x58) # Copy Special -I64.enc(base.copy_special, *r.copysp.rex(0x89, w=1)) -I32.enc(base.copy_special, *r.copysp(0x89)) +X86_64.enc(base.copy_special, *r.copysp.rex(0x89, w=1)) +X86_32.enc(base.copy_special, *r.copysp(0x89)) # Adjust SP Imm -I32.enc(base.adjust_sp_imm, *r.adjustsp8(0x83)) -I32.enc(base.adjust_sp_imm, *r.adjustsp32(0x81)) -I64.enc(base.adjust_sp_imm, *r.adjustsp8.rex(0x83, w=1)) -I64.enc(base.adjust_sp_imm, *r.adjustsp32.rex(0x81, w=1)) +X86_32.enc(base.adjust_sp_imm, *r.adjustsp8(0x83)) +X86_32.enc(base.adjust_sp_imm, *r.adjustsp32(0x81)) +X86_64.enc(base.adjust_sp_imm, *r.adjustsp8.rex(0x83, w=1)) +X86_64.enc(base.adjust_sp_imm, *r.adjustsp32.rex(0x81, w=1)) # # Float loads and stores. # -enc_both(base.load.f32.any, r.fld, 0x66, 0x0f, 0x6e) -enc_both(base.load.f32.any, r.fldDisp8, 0x66, 0x0f, 0x6e) -enc_both(base.load.f32.any, r.fldDisp32, 0x66, 0x0f, 0x6e) +enc_both(base.load.f32.any, r.fld, 0xf3, 0x0f, 0x10) +enc_both(base.load.f32.any, r.fldDisp8, 0xf3, 0x0f, 0x10) +enc_both(base.load.f32.any, r.fldDisp32, 0xf3, 0x0f, 0x10) -enc_both(base.load.f64.any, r.fld, 0xf3, 0x0f, 0x7e) -enc_both(base.load.f64.any, r.fldDisp8, 0xf3, 0x0f, 0x7e) -enc_both(base.load.f64.any, r.fldDisp32, 0xf3, 0x0f, 0x7e) +enc_both(base.load.f64.any, r.fld, 0xf2, 0x0f, 0x10) +enc_both(base.load.f64.any, r.fldDisp8, 0xf2, 0x0f, 0x10) +enc_both(base.load.f64.any, r.fldDisp32, 0xf2, 0x0f, 0x10) -enc_both(base.store.f32.any, r.fst, 0x66, 0x0f, 0x7e) -enc_both(base.store.f32.any, r.fstDisp8, 0x66, 0x0f, 0x7e) -enc_both(base.store.f32.any, r.fstDisp32, 0x66, 0x0f, 0x7e) +enc_both(base.store.f32.any, r.fst, 0xf3, 0x0f, 0x11) +enc_both(base.store.f32.any, r.fstDisp8, 0xf3, 0x0f, 0x11) +enc_both(base.store.f32.any, r.fstDisp32, 0xf3, 0x0f, 0x11) -enc_both(base.store.f64.any, r.fst, 0x66, 0x0f, 0xd6) -enc_both(base.store.f64.any, r.fstDisp8, 0x66, 0x0f, 0xd6) -enc_both(base.store.f64.any, r.fstDisp32, 0x66, 0x0f, 0xd6) +enc_both(base.store.f64.any, r.fst, 0xf2, 0x0f, 0x11) +enc_both(base.store.f64.any, r.fstDisp8, 0xf2, 0x0f, 0x11) +enc_both(base.store.f64.any, r.fstDisp32, 0xf2, 0x0f, 0x11) -enc_both(base.fill.f32, r.ffiSib32, 0x66, 0x0f, 0x6e) -enc_both(base.regfill.f32, r.frfi32, 0x66, 0x0f, 0x6e) -enc_both(base.fill.f64, r.ffiSib32, 0xf3, 0x0f, 0x7e) -enc_both(base.regfill.f64, r.frfi32, 0xf3, 0x0f, 0x7e) +enc_both(base.fill.f32, r.ffillSib32, 0xf3, 0x0f, 0x10) +enc_both(base.regfill.f32, r.fregfill32, 0xf3, 0x0f, 0x10) +enc_both(base.fill.f64, r.ffillSib32, 0xf2, 0x0f, 0x10) +enc_both(base.regfill.f64, r.fregfill32, 0xf2, 0x0f, 0x10) -enc_both(base.spill.f32, r.fspSib32, 0x66, 0x0f, 0x7e) -enc_both(base.regspill.f32, r.frsp32, 0x66, 0x0f, 0x7e) -enc_both(base.spill.f64, r.fspSib32, 0x66, 0x0f, 0xd6) -enc_both(base.regspill.f64, r.frsp32, 0x66, 0x0f, 0xd6) +enc_both(base.spill.f32, r.fspillSib32, 0xf3, 0x0f, 0x11) +enc_both(base.regspill.f32, r.fregspill32, 0xf3, 0x0f, 0x11) +enc_both(base.spill.f64, r.fspillSib32, 0xf2, 0x0f, 0x11) +enc_both(base.regspill.f64, r.fregspill32, 0xf2, 0x0f, 0x11) # # Function addresses. # -I32.enc(base.func_addr.i32, *r.fnaddr4(0xb8), - isap=Not(allones_funcaddrs)) -I64.enc(base.func_addr.i64, *r.fnaddr8.rex(0xb8, w=1), - isap=And(Not(allones_funcaddrs), Not(is_pic))) +X86_32.enc(base.func_addr.i32, *r.fnaddr4(0xb8), + isap=Not(allones_funcaddrs)) +X86_64.enc(base.func_addr.i64, *r.fnaddr8.rex(0xb8, w=1), + isap=And(Not(allones_funcaddrs), Not(is_pic))) -I32.enc(base.func_addr.i32, *r.allones_fnaddr4(0xb8), - isap=allones_funcaddrs) -I64.enc(base.func_addr.i64, *r.allones_fnaddr8.rex(0xb8, w=1), - isap=And(allones_funcaddrs, Not(is_pic))) +X86_32.enc(base.func_addr.i32, *r.allones_fnaddr4(0xb8), + isap=allones_funcaddrs) +X86_64.enc(base.func_addr.i64, *r.allones_fnaddr8.rex(0xb8, w=1), + isap=And(allones_funcaddrs, Not(is_pic))) -I64.enc(base.func_addr.i64, *r.got_fnaddr8.rex(0x8b, w=1), - isap=is_pic) +X86_64.enc(base.func_addr.i64, *r.got_fnaddr8.rex(0x8b, w=1), + isap=is_pic) # # Global addresses. # -I32.enc(base.globalsym_addr.i32, *r.gvaddr4(0xb8)) -I64.enc(base.globalsym_addr.i64, *r.gvaddr8.rex(0xb8, w=1), - isap=Not(is_pic)) +X86_32.enc(base.globalsym_addr.i32, *r.gvaddr4(0xb8)) +X86_64.enc(base.globalsym_addr.i64, *r.gvaddr8.rex(0xb8, w=1), + isap=Not(is_pic)) -I64.enc(base.globalsym_addr.i64, *r.got_gvaddr8.rex(0x8b, w=1), - isap=is_pic) +X86_64.enc(base.globalsym_addr.i64, *r.got_gvaddr8.rex(0x8b, w=1), + isap=is_pic) # # Call/return # -I32.enc(base.call, *r.call_id(0xe8)) -I64.enc(base.call, *r.call_id(0xe8), isap=Not(is_pic)) -I64.enc(base.call, *r.call_plt_id(0xe8), isap=is_pic) +X86_32.enc(base.call, *r.call_id(0xe8)) +X86_64.enc(base.call, *r.call_id(0xe8), isap=Not(is_pic)) +X86_64.enc(base.call, *r.call_plt_id(0xe8), isap=is_pic) -I32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2)) -I64.enc(base.call_indirect.i64, *r.call_r.rex(0xff, rrr=2)) -I64.enc(base.call_indirect.i64, *r.call_r(0xff, rrr=2)) +X86_32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2)) +X86_64.enc(base.call_indirect.i64, *r.call_r.rex(0xff, rrr=2)) +X86_64.enc(base.call_indirect.i64, *r.call_r(0xff, rrr=2)) -I32.enc(base.x_return, *r.ret(0xc3)) -I64.enc(base.x_return, *r.ret(0xc3)) +X86_32.enc(base.x_return, *r.ret(0xc3)) +X86_64.enc(base.x_return, *r.ret(0xc3)) # # Branches @@ -341,10 +352,10 @@ enc_i32_i64(base.brnz, r.tjccd, 0x85) # Branch on a b1 value in a register only looks at the low 8 bits. See also # bint encodings below. # -# Start with the worst-case encoding for I32 only. The register allocator can't -# handle a branch with an ABCD-constrained operand. -I32.enc(base.brz.b1, *r.t8jccd_long(0x84)) -I32.enc(base.brnz.b1, *r.t8jccd_long(0x85)) +# Start with the worst-case encoding for X86_32 only. The register allocator +# can't handle a branch with an ABCD-constrained operand. +X86_32.enc(base.brz.b1, *r.t8jccd_long(0x84)) +X86_32.enc(base.brnz.b1, *r.t8jccd_long(0x85)) enc_both(base.brz.b1, r.t8jccb_abcd, 0x74) enc_both(base.brz.b1, r.t8jccd_abcd, 0x84) @@ -354,26 +365,28 @@ enc_both(base.brnz.b1, r.t8jccd_abcd, 0x85) # # Trap as ud2 # -I32.enc(base.trap, *r.trap(0x0f, 0x0b)) -I64.enc(base.trap, *r.trap(0x0f, 0x0b)) +X86_32.enc(base.trap, *r.trap(0x0f, 0x0b)) +X86_64.enc(base.trap, *r.trap(0x0f, 0x0b)) # Using a standard EncRecipe, not the TailRecipe. -I32.enc(base.trapif, r.trapif, 0) -I64.enc(base.trapif, r.trapif, 0) -I32.enc(base.trapff, r.trapff, 0) -I64.enc(base.trapff, r.trapff, 0) +X86_32.enc(base.trapif, r.trapif, 0) +X86_64.enc(base.trapif, r.trapif, 0) +X86_32.enc(base.trapff, r.trapff, 0) +X86_64.enc(base.trapff, r.trapff, 0) # # Comparisons # enc_i32_i64(base.icmp, r.icscc, 0x39) +enc_i32_i64(base.icmp_imm, r.icsccib, 0x83, rrr=7) +enc_i32_i64(base.icmp_imm, r.icsccid, 0x81, rrr=7) enc_i32_i64(base.ifcmp, r.rcmp, 0x39) enc_i32_i64(base.ifcmp_imm, r.rcmpib, 0x83, rrr=7) enc_i32_i64(base.ifcmp_imm, r.rcmpid, 0x81, rrr=7) # TODO: We could special-case ifcmp_imm(x, 0) to TEST(x, x). -I32.enc(base.ifcmp_sp.i32, *r.rcmp_sp(0x39)) -I64.enc(base.ifcmp_sp.i64, *r.rcmp_sp.rex(0x39, w=1)) +X86_32.enc(base.ifcmp_sp.i32, *r.rcmp_sp(0x39)) +X86_64.enc(base.ifcmp_sp.i64, *r.rcmp_sp.rex(0x39, w=1)) # # Convert flags to bool. @@ -398,66 +411,68 @@ enc_i32_i64(x86.bsr, r.bsf_and_bsr, 0x0F, 0xBD) # # This assumes that b1 is represented as an 8-bit low register with the value 0 # or 1. -I32.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6)) -I64.enc(base.bint.i64.b1, *r.urm.rex(0x0f, 0xb6)) # zext to i64 implicit. -I64.enc(base.bint.i64.b1, *r.urm_abcd(0x0f, 0xb6)) # zext to i64 implicit. -I64.enc(base.bint.i32.b1, *r.urm.rex(0x0f, 0xb6)) -I64.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6)) +# +# Encode movzbq as movzbl, because it's equivalent and shorter. +X86_32.enc(base.bint.i32.b1, *r.urm_noflags_abcd(0x0f, 0xb6)) +X86_64.enc(base.bint.i64.b1, *r.urm_noflags.rex(0x0f, 0xb6)) +X86_64.enc(base.bint.i64.b1, *r.urm_noflags_abcd(0x0f, 0xb6)) +X86_64.enc(base.bint.i32.b1, *r.urm_noflags.rex(0x0f, 0xb6)) +X86_64.enc(base.bint.i32.b1, *r.urm_noflags_abcd(0x0f, 0xb6)) # Numerical conversions. # Reducing an integer is a no-op. -I32.enc(base.ireduce.i8.i32, r.null, 0) -I32.enc(base.ireduce.i16.i32, r.null, 0) -I64.enc(base.ireduce.i8.i32, r.null, 0) -I64.enc(base.ireduce.i16.i32, r.null, 0) -I64.enc(base.ireduce.i8.i64, r.null, 0) -I64.enc(base.ireduce.i16.i64, r.null, 0) -I64.enc(base.ireduce.i32.i64, r.null, 0) +X86_32.enc(base.ireduce.i8.i32, r.null, 0) +X86_32.enc(base.ireduce.i16.i32, r.null, 0) +X86_64.enc(base.ireduce.i8.i32, r.null, 0) +X86_64.enc(base.ireduce.i16.i32, r.null, 0) +X86_64.enc(base.ireduce.i8.i64, r.null, 0) +X86_64.enc(base.ireduce.i16.i64, r.null, 0) +X86_64.enc(base.ireduce.i32.i64, r.null, 0) # TODO: Add encodings for cbw, cwde, cdqe, which are sign-extending # instructions for %al/%ax/%eax to %ax/%eax/%rax. # movsbl -I32.enc(base.sextend.i32.i8, *r.urm(0x0f, 0xbe)) -I64.enc(base.sextend.i32.i8, *r.urm.rex(0x0f, 0xbe)) -I64.enc(base.sextend.i32.i8, *r.urm(0x0f, 0xbe)) +X86_32.enc(base.sextend.i32.i8, *r.urm_noflags(0x0f, 0xbe)) +X86_64.enc(base.sextend.i32.i8, *r.urm_noflags.rex(0x0f, 0xbe)) +X86_64.enc(base.sextend.i32.i8, *r.urm_noflags(0x0f, 0xbe)) # movswl -I32.enc(base.sextend.i32.i16, *r.urm(0x0f, 0xbf)) -I64.enc(base.sextend.i32.i16, *r.urm.rex(0x0f, 0xbf)) -I64.enc(base.sextend.i32.i16, *r.urm(0x0f, 0xbf)) +X86_32.enc(base.sextend.i32.i16, *r.urm_noflags(0x0f, 0xbf)) +X86_64.enc(base.sextend.i32.i16, *r.urm_noflags.rex(0x0f, 0xbf)) +X86_64.enc(base.sextend.i32.i16, *r.urm_noflags(0x0f, 0xbf)) # movsbq -I64.enc(base.sextend.i64.i8, *r.urm.rex(0x0f, 0xbe, w=1)) +X86_64.enc(base.sextend.i64.i8, *r.urm_noflags.rex(0x0f, 0xbe, w=1)) # movswq -I64.enc(base.sextend.i64.i16, *r.urm.rex(0x0f, 0xbf, w=1)) +X86_64.enc(base.sextend.i64.i16, *r.urm_noflags.rex(0x0f, 0xbf, w=1)) # movslq -I64.enc(base.sextend.i64.i32, *r.urm.rex(0x63, w=1)) +X86_64.enc(base.sextend.i64.i32, *r.urm_noflags.rex(0x63, w=1)) # movzbl -I32.enc(base.uextend.i32.i8, *r.urm(0x0f, 0xb6)) -I64.enc(base.uextend.i32.i8, *r.urm.rex(0x0f, 0xb6)) -I64.enc(base.uextend.i32.i8, *r.urm(0x0f, 0xb6)) +X86_32.enc(base.uextend.i32.i8, *r.urm_noflags(0x0f, 0xb6)) +X86_64.enc(base.uextend.i32.i8, *r.urm_noflags.rex(0x0f, 0xb6)) +X86_64.enc(base.uextend.i32.i8, *r.urm_noflags(0x0f, 0xb6)) # movzwl -I32.enc(base.uextend.i32.i16, *r.urm(0x0f, 0xb7)) -I64.enc(base.uextend.i32.i16, *r.urm.rex(0x0f, 0xb7)) -I64.enc(base.uextend.i32.i16, *r.urm(0x0f, 0xb7)) +X86_32.enc(base.uextend.i32.i16, *r.urm_noflags(0x0f, 0xb7)) +X86_64.enc(base.uextend.i32.i16, *r.urm_noflags.rex(0x0f, 0xb7)) +X86_64.enc(base.uextend.i32.i16, *r.urm_noflags(0x0f, 0xb7)) # movzbq, encoded as movzbl because it's equivalent and shorter -I64.enc(base.uextend.i64.i8, *r.urm.rex(0x0f, 0xb6)) -I64.enc(base.uextend.i64.i8, *r.urm(0x0f, 0xb6)) +X86_64.enc(base.uextend.i64.i8, *r.urm_noflags.rex(0x0f, 0xb6)) +X86_64.enc(base.uextend.i64.i8, *r.urm_noflags(0x0f, 0xb6)) # movzwq, encoded as movzwl because it's equivalent and shorter -I64.enc(base.uextend.i64.i16, *r.urm.rex(0x0f, 0xb7)) -I64.enc(base.uextend.i64.i16, *r.urm(0x0f, 0xb7)) +X86_64.enc(base.uextend.i64.i16, *r.urm_noflags.rex(0x0f, 0xb7)) +X86_64.enc(base.uextend.i64.i16, *r.urm_noflags(0x0f, 0xb7)) # A 32-bit register copy clears the high 32 bits. -I64.enc(base.uextend.i64.i32, *r.umr.rex(0x89)) -I64.enc(base.uextend.i64.i32, *r.umr(0x89)) +X86_64.enc(base.uextend.i64.i32, *r.umr.rex(0x89)) +X86_64.enc(base.uextend.i64.i32, *r.umr(0x89)) # @@ -469,8 +484,8 @@ enc_both(base.bitcast.f32.i32, r.frurm, 0x66, 0x0f, 0x6e) enc_both(base.bitcast.i32.f32, r.rfumr, 0x66, 0x0f, 0x7e) # movq -I64.enc(base.bitcast.f64.i64, *r.frurm.rex(0x66, 0x0f, 0x6e, w=1)) -I64.enc(base.bitcast.i64.f64, *r.rfumr.rex(0x66, 0x0f, 0x7e, w=1)) +X86_64.enc(base.bitcast.f64.i64, *r.frurm.rex(0x66, 0x0f, 0x6e, w=1)) +X86_64.enc(base.bitcast.i64.f64, *r.rfumr.rex(0x66, 0x0f, 0x7e, w=1)) # movaps enc_both(base.copy.f32, r.furm, 0x0f, 0x28) @@ -492,11 +507,11 @@ enc_both(base.fdemote.f32.f64, r.furm, 0xf2, 0x0f, 0x5a) # cvttss2si enc_both(x86.cvtt2si.i32.f32, r.rfurm, 0xf3, 0x0f, 0x2c) -I64.enc(x86.cvtt2si.i64.f32, *r.rfurm.rex(0xf3, 0x0f, 0x2c, w=1)) +X86_64.enc(x86.cvtt2si.i64.f32, *r.rfurm.rex(0xf3, 0x0f, 0x2c, w=1)) # cvttsd2si enc_both(x86.cvtt2si.i32.f64, r.rfurm, 0xf2, 0x0f, 0x2c) -I64.enc(x86.cvtt2si.i64.f64, *r.rfurm.rex(0xf2, 0x0f, 0x2c, w=1)) +X86_64.enc(x86.cvtt2si.i64.f64, *r.rfurm.rex(0xf2, 0x0f, 0x2c, w=1)) # Exact square roots. enc_both(base.sqrt.f32, r.furm, 0xf3, 0x0f, 0x51) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 9d03d02053..54df437c67 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -5,9 +5,11 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt, IsEqual, Or from cdsl.registers import RegClass -from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry, NullAry +from base.formats import Unary, UnaryImm, UnaryBool, Binary, BinaryImm +from base.formats import MultiAry, NullAry from base.formats import Trap, Call, IndirectCall, Store, Load -from base.formats import IntCompare, FloatCompare, IntCond, FloatCond +from base.formats import IntCompare, IntCompareImm, FloatCompare +from base.formats import IntCond, FloatCond from base.formats import IntSelect, IntCondTrap, FloatCondTrap from base.formats import Jump, Branch, BranchInt, BranchFloat from base.formats import Ternary, FuncAddr, UnaryGlobalVar @@ -277,23 +279,27 @@ null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='') # XX opcode, no ModR/M. trap = TailRecipe( 'trap', Trap, size=0, ins=(), outs=(), - emit='PUT_OP(bits, BASE_REX, sink);') + emit=''' + sink.trap(code, func.srclocs[inst]); + PUT_OP(bits, BASE_REX, sink); + ''') # Macro: conditional jump over a ud2. trapif = EncRecipe( - 'trapif', IntCondTrap, size=4, ins=FLAG.eflags, outs=(), + 'trapif', IntCondTrap, size=4, ins=FLAG.rflags, outs=(), clobbers_flags=False, emit=''' // Jump over a 2-byte ud2. sink.put1(0x70 | (icc2opc(cond.inverse()) as u8)); sink.put1(2); // ud2. + sink.trap(code, func.srclocs[inst]); sink.put1(0x0f); sink.put1(0x0b); ''') trapff = EncRecipe( - 'trapff', FloatCondTrap, size=4, ins=FLAG.eflags, outs=(), + 'trapff', FloatCondTrap, size=4, ins=FLAG.rflags, outs=(), clobbers_flags=False, instp=floatccs(FloatCondTrap), emit=''' @@ -301,6 +307,7 @@ trapff = EncRecipe( sink.put1(0x70 | (fcc2opc(cond.inverse()) as u8)); sink.put1(2); // ud2. + sink.trap(code, func.srclocs[inst]); sink.put1(0x0f); sink.put1(0x0b); ''') @@ -358,7 +365,7 @@ rfumr = TailRecipe( ''') # XX /r, but for a unary operator with separate input/output register. -# RM form. +# RM form. Clobbers FLAGS. urm = TailRecipe( 'urm', Unary, size=1, ins=GPR, outs=GPR, emit=''' @@ -366,10 +373,19 @@ urm = TailRecipe( modrm_rr(in_reg0, out_reg0, sink); ''') -# XX /r. Same as urm, but input limited to ABCD. -urm_abcd = TailRecipe( - 'urm_abcd', Unary, size=1, ins=ABCD, outs=GPR, - when_prefixed=urm, +# XX /r. Same as urm, but doesn't clobber FLAGS. +urm_noflags = TailRecipe( + 'urm_noflags', Unary, size=1, ins=GPR, outs=GPR, + clobbers_flags=False, + emit=''' + PUT_OP(bits, rex2(in_reg0, out_reg0), sink); + modrm_rr(in_reg0, out_reg0, sink); + ''') + +# XX /r. Same as urm_noflags, but input limited to ABCD. +urm_noflags_abcd = TailRecipe( + 'urm_noflags_abcd', Unary, size=1, ins=ABCD, outs=GPR, + when_prefixed=urm_noflags, emit=''' PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_rr(in_reg0, out_reg0, sink); @@ -449,6 +465,7 @@ div = TailRecipe( 'div', Ternary, size=1, ins=(GPR.rax, GPR.rdx, GPR), outs=(GPR.rax, GPR.rdx), emit=''' + sink.trap(TrapCode::IntegerDivisionByZero, func.srclocs[inst]); PUT_OP(bits, rex1(in_reg2), sink); modrm_r_bits(in_reg2, bits, sink); ''') @@ -506,6 +523,17 @@ puid = TailRecipe( sink.put4(imm as u32); ''') +# XX+rd id unary with bool immediate. Note no recipe predicate. +puid_bool = TailRecipe( + 'puid_bool', UnaryBool, size=4, ins=(), outs=GPR, + emit=''' + // The destination register is encoded in the low bits of the opcode. + // No ModR/M. + PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); + let imm: u32 = if imm.into() { 1 } else { 0 }; + sink.put4(imm); + ''') + # XX+rd iq unary with 64-bit immediate. puiq = TailRecipe( 'puiq', UnaryImm, size=8, ins=(), outs=GPR, @@ -666,6 +694,9 @@ st = TailRecipe( instp=IsEqual(Store.offset, 0), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_rm(in_reg1, in_reg0, sink); ''') @@ -678,6 +709,9 @@ st_abcd = TailRecipe( when_prefixed=st, clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_rm(in_reg1, in_reg0, sink); ''') @@ -688,6 +722,9 @@ fst = TailRecipe( instp=IsEqual(Store.offset, 0), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_rm(in_reg1, in_reg0, sink); ''') @@ -698,6 +735,9 @@ stDisp8 = TailRecipe( instp=IsSignedInt(Store.offset, 8), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_disp8(in_reg1, in_reg0, sink); let offset: i32 = offset.into(); @@ -709,6 +749,9 @@ stDisp8_abcd = TailRecipe( when_prefixed=stDisp8, clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_disp8(in_reg1, in_reg0, sink); let offset: i32 = offset.into(); @@ -719,6 +762,9 @@ fstDisp8 = TailRecipe( instp=IsSignedInt(Store.offset, 8), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_disp8(in_reg1, in_reg0, sink); let offset: i32 = offset.into(); @@ -730,6 +776,9 @@ stDisp32 = TailRecipe( 'stDisp32', Store, size=5, ins=(GPR, GPR_DEREF_SAFE), outs=(), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_disp32(in_reg1, in_reg0, sink); let offset: i32 = offset.into(); @@ -740,6 +789,9 @@ stDisp32_abcd = TailRecipe( when_prefixed=stDisp32, clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_disp32(in_reg1, in_reg0, sink); let offset: i32 = offset.into(); @@ -749,6 +801,9 @@ fstDisp32 = TailRecipe( 'fstDisp32', Store, size=5, ins=(FPR, GPR_DEREF_SAFE), outs=(), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_disp32(in_reg1, in_reg0, sink); let offset: i32 = offset.into(); @@ -756,8 +811,8 @@ fstDisp32 = TailRecipe( ''') # Unary spill with SIB and 32-bit displacement. -spSib32 = TailRecipe( - 'spSib32', Unary, size=6, ins=GPR, outs=StackGPR32, +spillSib32 = TailRecipe( + 'spillSib32', Unary, size=6, ins=GPR, outs=StackGPR32, clobbers_flags=False, emit=''' let base = stk_base(out_stk0.base); @@ -766,8 +821,10 @@ spSib32 = TailRecipe( sib_noindex(base, sink); sink.put4(out_stk0.offset as u32); ''') -fspSib32 = TailRecipe( - 'fspSib32', Unary, size=6, ins=FPR, outs=StackFPR32, + +# Like spillSib32, but targeting an FPR rather than a GPR. +fspillSib32 = TailRecipe( + 'fspillSib32', Unary, size=6, ins=FPR, outs=StackFPR32, clobbers_flags=False, emit=''' let base = stk_base(out_stk0.base); @@ -778,8 +835,8 @@ fspSib32 = TailRecipe( ''') # Regspill using RSP-relative addressing. -rsp32 = TailRecipe( - 'rsp32', RegSpill, size=6, ins=GPR, outs=(), +regspill32 = TailRecipe( + 'regspill32', RegSpill, size=6, ins=GPR, outs=(), clobbers_flags=False, emit=''' let dst = StackRef::sp(dst, &func.stack_slots); @@ -789,8 +846,10 @@ rsp32 = TailRecipe( sib_noindex(base, sink); sink.put4(dst.offset as u32); ''') -frsp32 = TailRecipe( - 'frsp32', RegSpill, size=6, ins=FPR, outs=(), + +# Like regspill32, but targeting an FPR rather than a GPR. +fregspill32 = TailRecipe( + 'fregspill32', RegSpill, size=6, ins=FPR, outs=(), clobbers_flags=False, emit=''' let dst = StackRef::sp(dst, &func.stack_slots); @@ -811,6 +870,9 @@ ld = TailRecipe( instp=IsEqual(Load.offset, 0), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_rm(in_reg0, out_reg0, sink); ''') @@ -821,6 +883,9 @@ fld = TailRecipe( instp=IsEqual(Load.offset, 0), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_rm(in_reg0, out_reg0, sink); ''') @@ -831,6 +896,9 @@ ldDisp8 = TailRecipe( instp=IsSignedInt(Load.offset, 8), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_disp8(in_reg0, out_reg0, sink); let offset: i32 = offset.into(); @@ -843,6 +911,9 @@ fldDisp8 = TailRecipe( instp=IsSignedInt(Load.offset, 8), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_disp8(in_reg0, out_reg0, sink); let offset: i32 = offset.into(); @@ -855,6 +926,9 @@ ldDisp32 = TailRecipe( instp=IsSignedInt(Load.offset, 32), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_disp32(in_reg0, out_reg0, sink); let offset: i32 = offset.into(); @@ -867,6 +941,9 @@ fldDisp32 = TailRecipe( instp=IsSignedInt(Load.offset, 32), clobbers_flags=False, emit=''' + if !flags.notrap() { + sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); + } PUT_OP(bits, rex2(in_reg0, out_reg0), sink); modrm_disp32(in_reg0, out_reg0, sink); let offset: i32 = offset.into(); @@ -874,8 +951,8 @@ fldDisp32 = TailRecipe( ''') # Unary fill with SIB and 32-bit displacement. -fiSib32 = TailRecipe( - 'fiSib32', Unary, size=6, ins=StackGPR32, outs=GPR, +fillSib32 = TailRecipe( + 'fillSib32', Unary, size=6, ins=StackGPR32, outs=GPR, clobbers_flags=False, emit=''' let base = stk_base(in_stk0.base); @@ -884,8 +961,10 @@ fiSib32 = TailRecipe( sib_noindex(base, sink); sink.put4(in_stk0.offset as u32); ''') -ffiSib32 = TailRecipe( - 'ffiSib32', Unary, size=6, ins=StackFPR32, outs=FPR, + +# Like fillSib32, but targeting an FPR rather than a GPR. +ffillSib32 = TailRecipe( + 'ffillSib32', Unary, size=6, ins=StackFPR32, outs=FPR, clobbers_flags=False, emit=''' let base = stk_base(in_stk0.base); @@ -896,8 +975,8 @@ ffiSib32 = TailRecipe( ''') # Regfill with RSP-relative 32-bit displacement. -rfi32 = TailRecipe( - 'rfi32', RegFill, size=6, ins=StackGPR32, outs=(), +regfill32 = TailRecipe( + 'regfill32', RegFill, size=6, ins=StackGPR32, outs=(), clobbers_flags=False, emit=''' let src = StackRef::sp(src, &func.stack_slots); @@ -907,8 +986,10 @@ rfi32 = TailRecipe( sib_noindex(base, sink); sink.put4(src.offset as u32); ''') -frfi32 = TailRecipe( - 'frfi32', RegFill, size=6, ins=StackFPR32, outs=(), + +# Like regfill32, but targeting an FPR rather than a GPR. +fregfill32 = TailRecipe( + 'fregfill32', RegFill, size=6, ins=StackFPR32, outs=(), clobbers_flags=False, emit=''' let src = StackRef::sp(src, &func.stack_slots); @@ -977,7 +1058,7 @@ jmpd = TailRecipe( ''') brib = TailRecipe( - 'brib', BranchInt, size=1, ins=FLAG.eflags, outs=(), + 'brib', BranchInt, size=1, ins=FLAG.rflags, outs=(), branch_range=8, clobbers_flags=False, emit=''' @@ -986,7 +1067,7 @@ brib = TailRecipe( ''') brid = TailRecipe( - 'brid', BranchInt, size=4, ins=FLAG.eflags, outs=(), + 'brid', BranchInt, size=4, ins=FLAG.rflags, outs=(), branch_range=32, clobbers_flags=False, emit=''' @@ -995,7 +1076,7 @@ brid = TailRecipe( ''') brfb = TailRecipe( - 'brfb', BranchFloat, size=1, ins=FLAG.eflags, outs=(), + 'brfb', BranchFloat, size=1, ins=FLAG.rflags, outs=(), branch_range=8, clobbers_flags=False, instp=floatccs(BranchFloat), @@ -1005,7 +1086,7 @@ brfb = TailRecipe( ''') brfd = TailRecipe( - 'brfd', BranchFloat, size=4, ins=FLAG.eflags, outs=(), + 'brfd', BranchFloat, size=4, ins=FLAG.rflags, outs=(), branch_range=32, clobbers_flags=False, instp=floatccs(BranchFloat), @@ -1025,7 +1106,7 @@ brfd = TailRecipe( # seti = TailRecipe( - 'seti', IntCond, size=1, ins=FLAG.eflags, outs=GPR, + 'seti', IntCond, size=1, ins=FLAG.rflags, outs=GPR, requires_prefix=True, clobbers_flags=False, emit=''' @@ -1033,7 +1114,7 @@ seti = TailRecipe( modrm_r_bits(out_reg0, bits, sink); ''') seti_abcd = TailRecipe( - 'seti_abcd', IntCond, size=1, ins=FLAG.eflags, outs=ABCD, + 'seti_abcd', IntCond, size=1, ins=FLAG.rflags, outs=ABCD, when_prefixed=seti, clobbers_flags=False, emit=''' @@ -1042,7 +1123,7 @@ seti_abcd = TailRecipe( ''') setf = TailRecipe( - 'setf', FloatCond, size=1, ins=FLAG.eflags, outs=GPR, + 'setf', FloatCond, size=1, ins=FLAG.rflags, outs=GPR, requires_prefix=True, clobbers_flags=False, emit=''' @@ -1050,7 +1131,7 @@ setf = TailRecipe( modrm_r_bits(out_reg0, bits, sink); ''') setf_abcd = TailRecipe( - 'setf_abcd', FloatCond, size=1, ins=FLAG.eflags, outs=ABCD, + 'setf_abcd', FloatCond, size=1, ins=FLAG.rflags, outs=ABCD, when_prefixed=setf, clobbers_flags=False, emit=''' @@ -1064,7 +1145,7 @@ setf_abcd = TailRecipe( # 1 byte, modrm(r,r), is after the opcode # cmov = TailRecipe( - 'cmov', IntSelect, size=1, ins=(FLAG.eflags, GPR, GPR), outs=2, + 'cmov', IntSelect, size=1, ins=(FLAG.rflags, GPR, GPR), outs=2, requires_prefix=False, clobbers_flags=False, emit=''' @@ -1076,7 +1157,7 @@ cmov = TailRecipe( # Bit scan forwards and reverse # bsf_and_bsr = TailRecipe( - 'bsf_and_bsr', Unary, size=1, ins=GPR, outs=(GPR, FLAG.eflags), + 'bsf_and_bsr', Unary, size=1, ins=GPR, outs=(GPR, FLAG.rflags), requires_prefix=False, clobbers_flags=True, emit=''' @@ -1090,7 +1171,7 @@ bsf_and_bsr = TailRecipe( # XX /r, MR form. Compare two GPR registers and set flags. rcmp = TailRecipe( - 'rcmp', Binary, size=1, ins=(GPR, GPR), outs=FLAG.eflags, + 'rcmp', Binary, size=1, ins=(GPR, GPR), outs=FLAG.rflags, emit=''' PUT_OP(bits, rex2(in_reg0, in_reg1), sink); modrm_rr(in_reg0, in_reg1, sink); @@ -1098,7 +1179,7 @@ rcmp = TailRecipe( # XX /r, RM form. Compare two FPR registers and set flags. fcmp = TailRecipe( - 'fcmp', Binary, size=1, ins=(FPR, FPR), outs=FLAG.eflags, + 'fcmp', Binary, size=1, ins=(FPR, FPR), outs=FLAG.rflags, emit=''' PUT_OP(bits, rex2(in_reg1, in_reg0), sink); modrm_rr(in_reg1, in_reg0, sink); @@ -1106,7 +1187,7 @@ fcmp = TailRecipe( # XX /n, MI form with imm8. rcmpib = TailRecipe( - 'rcmpib', BinaryImm, size=2, ins=GPR, outs=FLAG.eflags, + 'rcmpib', BinaryImm, size=2, ins=GPR, outs=FLAG.rflags, instp=IsSignedInt(BinaryImm.imm, 8), emit=''' PUT_OP(bits, rex1(in_reg0), sink); @@ -1117,7 +1198,7 @@ rcmpib = TailRecipe( # XX /n, MI form with imm32. rcmpid = TailRecipe( - 'rcmpid', BinaryImm, size=5, ins=GPR, outs=FLAG.eflags, + 'rcmpid', BinaryImm, size=5, ins=GPR, outs=FLAG.rflags, instp=IsSignedInt(BinaryImm.imm, 32), emit=''' PUT_OP(bits, rex1(in_reg0), sink); @@ -1128,7 +1209,7 @@ rcmpid = TailRecipe( # Same as rcmp, but second operand is the stack pointer. rcmp_sp = TailRecipe( - 'rcmp_sp', Unary, size=1, ins=GPR, outs=FLAG.eflags, + 'rcmp_sp', Unary, size=1, ins=GPR, outs=FLAG.rflags, emit=''' PUT_OP(bits, rex2(in_reg0, RU::rsp.into()), sink); modrm_rr(in_reg0, RU::rsp.into(), sink); @@ -1289,12 +1370,67 @@ icscc = TailRecipe( modrm_rr(out_reg0, 0, sink); ''') +icsccib = TailRecipe( + 'icsccib', IntCompareImm, size=2 + 3, ins=GPR, outs=ABCD, + instp=IsSignedInt(IntCompareImm.imm, 8), + emit=''' + // Comparison instruction. + PUT_OP(bits, rex1(in_reg0), sink); + modrm_r_bits(in_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put1(imm as u8); + // `setCC` instruction, no REX. + use ir::condcodes::IntCC::*; + let setcc = match cond { + Equal => 0x94, + NotEqual => 0x95, + SignedLessThan => 0x9c, + SignedGreaterThanOrEqual => 0x9d, + SignedGreaterThan => 0x9f, + SignedLessThanOrEqual => 0x9e, + UnsignedLessThan => 0x92, + UnsignedGreaterThanOrEqual => 0x93, + UnsignedGreaterThan => 0x97, + UnsignedLessThanOrEqual => 0x96, + }; + sink.put1(0x0f); + sink.put1(setcc); + modrm_rr(out_reg0, 0, sink); + ''') + +icsccid = TailRecipe( + 'icsccid', IntCompareImm, size=5 + 3, ins=GPR, outs=ABCD, + instp=IsSignedInt(IntCompareImm.imm, 32), + emit=''' + // Comparison instruction. + PUT_OP(bits, rex1(in_reg0), sink); + modrm_r_bits(in_reg0, bits, sink); + let imm: i64 = imm.into(); + sink.put4(imm as u32); + // `setCC` instruction, no REX. + use ir::condcodes::IntCC::*; + let setcc = match cond { + Equal => 0x94, + NotEqual => 0x95, + SignedLessThan => 0x9c, + SignedGreaterThanOrEqual => 0x9d, + SignedGreaterThan => 0x9f, + SignedLessThanOrEqual => 0x9e, + UnsignedLessThan => 0x92, + UnsignedGreaterThanOrEqual => 0x93, + UnsignedGreaterThan => 0x97, + UnsignedLessThanOrEqual => 0x96, + }; + sink.put1(0x0f); + sink.put1(setcc); + modrm_rr(out_reg0, 0, sink); + ''') # Make a FloatCompare instruction predicate with the supported condition codes. # Same thing for floating point. # -# The ucomiss/ucomisd instructions set the EFLAGS bits CF/PF/CF like this: +# The ucomiss/ucomisd instructions set the FLAGS bits CF/PF/CF like this: # # ZPC OSA # UN 111 000 diff --git a/lib/cretonne/meta/isa/intel/registers.py b/lib/cretonne/meta/isa/intel/registers.py index 354001a102..a4832640cd 100644 --- a/lib/cretonne/meta/isa/intel/registers.py +++ b/lib/cretonne/meta/isa/intel/registers.py @@ -43,7 +43,7 @@ FlagRegs = RegBank( 'Flag registers', units=1, pressure_tracking=False, - names=['eflags']) + names=['rflags']) GPR = RegClass(IntRegs) # Certain types of deref encodings cannot be used with all registers. diff --git a/lib/cretonne/meta/srcgen.py b/lib/cretonne/meta/srcgen.py index 9761160487..df5794cedb 100644 --- a/lib/cretonne/meta/srcgen.py +++ b/lib/cretonne/meta/srcgen.py @@ -8,9 +8,10 @@ source code. from __future__ import absolute_import import sys import os +from collections import OrderedDict try: - from typing import Any, List # noqa + from typing import Any, List, Set, Tuple # noqa except ImportError: pass @@ -146,6 +147,52 @@ class Formatter(object): for l in parse_multiline(s): self.line('/// ' + l if l else '///') + def match(self, m): + # type: (Match) -> None + """ + Add a match expression. + + Example: + + >>> f = Formatter() + >>> m = Match('x') + >>> m.arm('Orange', ['a', 'b'], 'some body') + >>> m.arm('Yellow', ['a', 'b'], 'some body') + >>> m.arm('Green', ['a', 'b'], 'different body') + >>> m.arm('Blue', ['x', 'y'], 'some body') + >>> f.match(m) + >>> f.writelines() + match x { + Orange { a, b } | + Yellow { a, b } => { + some body + } + Green { a, b } => { + different body + } + Blue { x, y } => { + some body + } + } + + """ + with self.indented('match {} {{'.format(m.expr), '}'): + for (fields, body), names in m.arms.items(): + with self.indented('', '}'): + names_left = len(names) + for name in names.keys(): + fields_str = ', '.join(fields) + if len(fields) != 0: + fields_str = '{{ {} }} '.format(fields_str) + names_left -= 1 + if names_left > 0: + suffix = '|' + else: + suffix = '=> {' + self.outdented_line(name + ' ' + fields_str + suffix) + if names_left == 0: + self.multi_line(body) + def _indent(s): # type: (str) -> int @@ -195,3 +242,36 @@ def parse_multiline(s): while trimmed and not trimmed[0]: trimmed.pop(0) return trimmed + + +class Match(object): + """ + Match formatting class. + + Match objects collect all the information needed to emit a Rust `match` + expression, automatically deduplicating overlapping identical arms. + + Example: + + >>> m = Match('x') + >>> m.arm('Orange', ['a', 'b'], 'some body') + >>> m.arm('Yellow', ['a', 'b'], 'some body') + >>> m.arm('Green', ['a', 'b'], 'different body') + >>> m.arm('Blue', ['x', 'y'], 'some body') + >>> assert(len(m.arms) == 3) + + Note that this class is ignorant of Rust types, and considers two fields + with the same name to be equivalent. + """ + + def __init__(self, expr): + # type: (str) -> None + self.expr = expr + self.arms = OrderedDict() # type: OrderedDict[Tuple[Tuple[str, ...], str], OrderedDict[str, None]] # noqa + + def arm(self, name, fields, body): + # type: (str, List[str], str) -> None + key = (tuple(fields), body) + if key not in self.arms: + self.arms[key] = OrderedDict() + self.arms[key][name] = None diff --git a/lib/cretonne/meta/test_gen_legalizer.py b/lib/cretonne/meta/test_gen_legalizer.py index 793555a42c..3882bd0bf9 100644 --- a/lib/cretonne/meta/test_gen_legalizer.py +++ b/lib/cretonne/meta/test_gen_legalizer.py @@ -148,9 +148,9 @@ class TestRuntimeChecks(TestCase): self.v5 << vselect(self.v1, self.v3, self.v4), ) x = XForm(r, r) - tv2_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\ + tv2_exp = 'Some({}).map(|t: ir::Type| t.as_bool())'\ .format(self.v2.get_typevar().name) - tv3_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\ + tv3_exp = 'Some({}).map(|t: ir::Type| t.as_bool())'\ .format(self.v3.get_typevar().name) self.check_yo_check( diff --git a/lib/cretonne/src/abi.rs b/lib/cretonne/src/abi.rs index b69821e657..88a8a424a6 100644 --- a/lib/cretonne/src/abi.rs +++ b/lib/cretonne/src/abi.rs @@ -3,7 +3,7 @@ //! This module provides functions and data structures that are useful for implementing the //! `TargetIsa::legalize_signature()` method. -use ir::{ArgumentLoc, AbiParam, ArgumentExtension, Type}; +use ir::{AbiParam, ArgumentExtension, ArgumentLoc, Type}; use std::cmp::Ordering; use std::vec::Vec; @@ -186,8 +186,8 @@ pub fn legalize_abi_value(have: Type, arg: &AbiParam) -> ValueConversion { #[cfg(test)] mod tests { use super::*; - use ir::types; use ir::AbiParam; + use ir::types; #[test] fn legalize() { diff --git a/lib/cretonne/src/bforest/map.rs b/lib/cretonne/src/bforest/map.rs index 073f27529b..b8b4980b3f 100644 --- a/lib/cretonne/src/bforest/map.rs +++ b/lib/cretonne/src/bforest/map.rs @@ -1,8 +1,8 @@ //! Forest of maps. +use super::{Comparator, Forest, Node, NodeData, NodePool, Path, INNER_SIZE}; use packed_option::PackedOption; use std::marker::PhantomData; -use super::{INNER_SIZE, Comparator, Forest, NodePool, Node, NodeData, Path}; /// Tag type defining forest types for a map. struct MapTypes(PhantomData<(K, V, C)>); @@ -424,10 +424,10 @@ where #[cfg(test)] mod test { + use super::super::NodeData; + use super::*; use std::mem; use std::vec::Vec; - use super::*; - use super::super::NodeData; #[test] fn node_size() { diff --git a/lib/cretonne/src/bforest/mod.rs b/lib/cretonne/src/bforest/mod.rs index ff1b94cc0f..bc22cf88c0 100644 --- a/lib/cretonne/src/bforest/mod.rs +++ b/lib/cretonne/src/bforest/mod.rs @@ -22,8 +22,8 @@ mod path; mod pool; mod set; -pub use self::map::{MapForest, Map, MapCursor, MapIter}; -pub use self::set::{SetForest, Set, SetCursor, SetIter}; +pub use self::map::{Map, MapCursor, MapForest, MapIter}; +pub use self::set::{Set, SetCursor, SetForest, SetIter}; use self::node::NodeData; use self::path::Path; diff --git a/lib/cretonne/src/bforest/node.rs b/lib/cretonne/src/bforest/node.rs index d5c46f54bd..f4a85613b5 100644 --- a/lib/cretonne/src/bforest/node.rs +++ b/lib/cretonne/src/bforest/node.rs @@ -1,8 +1,8 @@ //! B+-tree nodes. +use super::{slice_insert, slice_shift, Forest, Node, SetValue, INNER_SIZE}; use std::borrow::{Borrow, BorrowMut}; use std::fmt; -use super::{Forest, Node, INNER_SIZE, SetValue, slice_insert, slice_shift}; /// B+-tree node. /// @@ -579,9 +579,9 @@ where #[cfg(test)] mod test { + use super::*; use std::mem; use std::string::ToString; - use super::*; // Forest impl for a set implementation. struct TF(); diff --git a/lib/cretonne/src/bforest/path.rs b/lib/cretonne/src/bforest/path.rs index 1add8f6e63..d011455571 100644 --- a/lib/cretonne/src/bforest/path.rs +++ b/lib/cretonne/src/bforest/path.rs @@ -1,9 +1,9 @@ //! A path from the root of a B+-tree to a leaf node. +use super::node::Removed; +use super::{slice_insert, slice_shift, Comparator, Forest, Node, NodeData, NodePool, MAX_PATH}; use std::borrow::Borrow; use std::marker::PhantomData; -use super::{Forest, Node, NodeData, NodePool, MAX_PATH, Comparator, slice_insert, slice_shift}; -use super::node::Removed; #[cfg(test)] use std::fmt; @@ -55,8 +55,8 @@ impl Path { for level in 0.. { self.size = level + 1; self.node[level] = node; - match &pool[node] { - &NodeData::Inner { size, keys, tree } => { + match pool[node] { + NodeData::Inner { size, keys, tree } => { // Invariant: `tree[i]` contains keys smaller than // `keys[i]`, greater or equal to `keys[i-1]`. let i = match comp.search(key, &keys[0..size.into()]) { @@ -68,7 +68,7 @@ impl Path { self.entry[level] = i as u8; node = tree[i]; } - &NodeData::Leaf { size, keys, vals } => { + NodeData::Leaf { size, keys, vals } => { // For a leaf we want either the found key or an insert position. return match comp.search(key, &keys.borrow()[0..size.into()]) { Ok(i) => { @@ -81,7 +81,7 @@ impl Path { } }; } - &NodeData::Free { .. } => panic!("Free {} reached from {}", node, root), + NodeData::Free { .. } => panic!("Free {} reached from {}", node, root), } } unreachable!(); @@ -94,10 +94,10 @@ impl Path { self.size = level + 1; self.node[level] = node; self.entry[level] = 0; - match &pool[node] { - &NodeData::Inner { tree, .. } => node = tree[0], - &NodeData::Leaf { keys, vals, .. } => return (keys.borrow()[0], vals.borrow()[0]), - &NodeData::Free { .. } => panic!("Free {} reached from {}", node, root), + match pool[node] { + NodeData::Inner { tree, .. } => node = tree[0], + NodeData::Leaf { keys, vals, .. } => return (keys.borrow()[0], vals.borrow()[0]), + NodeData::Free { .. } => panic!("Free {} reached from {}", node, root), } } unreachable!(); @@ -205,17 +205,17 @@ impl Path { let mut node = root; for l in level.. { self.node[l] = node; - match &pool[node] { - &NodeData::Inner { size, ref tree, .. } => { + match pool[node] { + NodeData::Inner { size, ref tree, .. } => { self.entry[l] = size; node = tree[usize::from(size)]; } - &NodeData::Leaf { size, .. } => { + NodeData::Leaf { size, .. } => { self.entry[l] = size - 1; self.size = l + 1; break; } - &NodeData::Free { .. } => panic!("Free {} reached from {}", node, root), + NodeData::Free { .. } => panic!("Free {} reached from {}", node, root), } } node @@ -405,8 +405,8 @@ impl Path { let crit_key = pool[self.leaf_node()].leaf_crit_key(); let crit_node = self.node[crit_level]; - match &mut pool[crit_node] { - &mut NodeData::Inner { size, ref mut keys, .. } => { + match pool[crit_node] { + NodeData::Inner { size, ref mut keys, .. } => { debug_assert!(crit_kidx < size); keys[usize::from(crit_kidx)] = crit_key; } @@ -414,7 +414,6 @@ impl Path { } } - /// Given that the current leaf node is in an unhealthy (underflowed or even empty) status, /// balance it with sibling nodes. /// @@ -437,7 +436,7 @@ impl Path { // Discard the root node if it has shrunk to a single sub-tree. let mut ns = 0; - while let &NodeData::Inner { size: 0, ref tree, .. } = &pool[self.node[ns]] { + while let NodeData::Inner { size: 0, ref tree, .. } = pool[self.node[ns]] { ns += 1; self.node[ns] = tree[0]; } @@ -529,12 +528,10 @@ impl Path { // current entry[level] was one off the end of the node, it will now point at a proper // entry. debug_assert!(usize::from(self.entry[level]) < pool[self.node[level]].entries()); - } else { + } else if usize::from(self.entry[level]) >= pool[self.node[level]].entries() { // There's no right sibling at this level, so the node can't be rebalanced. // Check if we are in an off-the-end position. - if usize::from(self.entry[level]) >= pool[self.node[level]].entries() { - self.size = 0; - } + self.size = 0; } } @@ -581,8 +578,8 @@ impl Path { /// /// Returns `None` if the current node is a right-most node so no right sibling exists. fn right_sibling_branch_level(&self, level: usize, pool: &NodePool) -> Option { - (0..level).rposition(|l| match &pool[self.node[l]] { - &NodeData::Inner { size, .. } => self.entry[l] < size, + (0..level).rposition(|l| match pool[self.node[l]] { + NodeData::Inner { size, .. } => self.entry[l] < size, _ => panic!("Expected inner node"), }) } @@ -622,8 +619,8 @@ impl Path { let bl = self.right_sibling_branch_level(level, pool).expect( "No right sibling exists", ); - match &mut pool[self.node[bl]] { - &mut NodeData::Inner { ref mut keys, .. } => { + match pool[self.node[bl]] { + NodeData::Inner { ref mut keys, .. } => { keys[usize::from(self.entry[bl])] = crit_key; } _ => panic!("Expected inner node"), @@ -647,8 +644,8 @@ impl Path { /// Check the internal consistency of this path. pub fn verify(&self, pool: &NodePool) { for level in 0..self.size { - match &pool[self.node[level]] { - &NodeData::Inner { size, tree, .. } => { + match pool[self.node[level]] { + NodeData::Inner { size, tree, .. } => { assert!( level < self.size - 1, "Expected leaf node at level {}", @@ -668,7 +665,7 @@ impl Path { level ); } - &NodeData::Leaf { size, .. } => { + NodeData::Leaf { size, .. } => { assert_eq!(level, self.size - 1, "Expected inner node"); assert!( self.entry[level] <= size, @@ -677,7 +674,7 @@ impl Path { size, ); } - &NodeData::Free { .. } => { + NodeData::Free { .. } => { panic!("Free {} in path", self.node[level]); } } @@ -702,9 +699,9 @@ impl fmt::Display for Path { #[cfg(test)] mod test { - use std::cmp::Ordering; + use super::super::{Forest, NodeData, NodePool}; use super::*; - use super::super::{Forest, NodePool, NodeData}; + use std::cmp::Ordering; struct TC(); diff --git a/lib/cretonne/src/bforest/pool.rs b/lib/cretonne/src/bforest/pool.rs index 18c455fdd1..0e312f9380 100644 --- a/lib/cretonne/src/bforest/pool.rs +++ b/lib/cretonne/src/bforest/pool.rs @@ -1,8 +1,8 @@ //! B+-tree node pool. +use super::{Forest, Node, NodeData}; use entity::PrimaryMap; use std::ops::{Index, IndexMut}; -use super::{Forest, Node, NodeData}; /// A pool of nodes, including a free list. pub(super) struct NodePool { @@ -57,6 +57,7 @@ impl NodePool { pub fn free_tree(&mut self, node: Node) { if let NodeData::Inner { size, tree, .. } = self[node] { // Note that we have to capture `tree` by value to avoid borrow checker trouble. + #[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))] for i in 0..usize::from(size + 1) { // Recursively free sub-trees. This recursion can never be deeper than `MAX_PATH`, // and since most trees have less than a handful of nodes, it is worthwhile to @@ -76,11 +77,11 @@ impl NodePool { NodeData: ::std::fmt::Display, F::Key: ::std::fmt::Display, { + use super::Comparator; + use entity::SparseSet; use std::borrow::Borrow; use std::cmp::Ordering; use std::vec::Vec; - use super::Comparator; - use entity::SparseSet; // The root node can't be an inner node with just a single sub-tree. It should have been // pruned. @@ -105,8 +106,8 @@ impl NodePool { ); let mut lower = lkey; - match &self[node] { - &NodeData::Inner { size, keys, tree } => { + match self[node] { + NodeData::Inner { size, keys, tree } => { let size = size as usize; let capacity = tree.len(); let keys = &keys[0..size]; @@ -148,7 +149,7 @@ impl NodePool { lower = upper; } } - &NodeData::Leaf { size, keys, .. } => { + NodeData::Leaf { size, keys, .. } => { let size = size as usize; let capacity = keys.borrow().len(); let keys = &keys.borrow()[0..size]; @@ -191,7 +192,7 @@ impl NodePool { lower = upper; } } - &NodeData::Free { .. } => panic!("Free {} reached", node), + NodeData::Free { .. } => panic!("Free {} reached", node), } } } diff --git a/lib/cretonne/src/bforest/set.rs b/lib/cretonne/src/bforest/set.rs index 22f5122c7c..e1434df39c 100644 --- a/lib/cretonne/src/bforest/set.rs +++ b/lib/cretonne/src/bforest/set.rs @@ -1,8 +1,8 @@ //! Forest of sets. +use super::{Comparator, Forest, Node, NodeData, NodePool, Path, SetValue, INNER_SIZE}; use packed_option::PackedOption; use std::marker::PhantomData; -use super::{INNER_SIZE, Comparator, Forest, NodePool, Node, NodeData, Path, SetValue}; /// Tag type defining forest types for a set. struct SetTypes(PhantomData<(K, C)>); @@ -351,10 +351,10 @@ where #[cfg(test)] mod test { + use super::super::NodeData; + use super::*; use std::mem; use std::vec::Vec; - use super::*; - use super::super::NodeData; #[test] fn node_size() { diff --git a/lib/cretonne/src/binemit/memorysink.rs b/lib/cretonne/src/binemit/memorysink.rs index 1d6758afe6..bed34cd1ac 100644 --- a/lib/cretonne/src/binemit/memorysink.rs +++ b/lib/cretonne/src/binemit/memorysink.rs @@ -14,13 +14,13 @@ //! relocations to a `RelocSink` trait object. Relocations are less frequent than the //! `CodeSink::put*` methods, so the performance impact of the virtual callbacks is less severe. -use ir::{ExternalName, JumpTable}; -use super::{CodeSink, CodeOffset, Reloc, Addend}; +use super::{Addend, CodeOffset, CodeSink, Reloc}; +use ir::{ExternalName, JumpTable, SourceLoc, TrapCode}; use std::ptr::write_unaligned; /// A `CodeSink` that writes binary machine code directly into memory. /// -/// A `MemoryCodeSink` object should be used when emitting a Cretonne IL function into executable +/// A `MemoryCodeSink` object should be used when emitting a Cretonne IR function into executable /// memory. It writes machine code directly to a raw pointer without any bounds checking, so make /// sure to allocate enough memory for the whole function. The number of bytes required is returned /// by the `Context::compile()` function. @@ -33,15 +33,21 @@ pub struct MemoryCodeSink<'a> { data: *mut u8, offset: isize, relocs: &'a mut RelocSink, + traps: &'a mut TrapSink, } impl<'a> MemoryCodeSink<'a> { /// Create a new memory code sink that writes a function to the memory pointed to by `data`. - pub fn new(data: *mut u8, relocs: &mut RelocSink) -> MemoryCodeSink { + pub fn new<'sink>( + data: *mut u8, + relocs: &'sink mut RelocSink, + traps: &'sink mut TrapSink, + ) -> MemoryCodeSink<'sink> { MemoryCodeSink { data, offset: 0, relocs, + traps, } } } @@ -58,6 +64,12 @@ pub trait RelocSink { fn reloc_jt(&mut self, CodeOffset, Reloc, JumpTable); } +/// A trait for receiving trap codes and offsets. +pub trait TrapSink { + /// Add trap information for a specific offset. + fn trap(&mut self, CodeOffset, SourceLoc, TrapCode); +} + impl<'a> CodeSink for MemoryCodeSink<'a> { fn offset(&self) -> CodeOffset { self.offset as CodeOffset @@ -105,4 +117,9 @@ impl<'a> CodeSink for MemoryCodeSink<'a> { let ofs = self.offset(); self.relocs.reloc_jt(ofs, rel, jt); } + + fn trap(&mut self, code: TrapCode, srcloc: SourceLoc) { + let ofs = self.offset(); + self.traps.trap(ofs, srcloc, code); + } } diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index bca1f6fa4d..ea31abaae5 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -3,14 +3,14 @@ //! The `binemit` module contains code for translating Cretonne's intermediate representation into //! binary machine code. -mod relaxation; mod memorysink; +mod relaxation; -pub use regalloc::RegDiversions; +pub use self::memorysink::{MemoryCodeSink, RelocSink, TrapSink}; pub use self::relaxation::relax_branches; -pub use self::memorysink::{MemoryCodeSink, RelocSink}; +pub use regalloc::RegDiversions; -use ir::{ExternalName, JumpTable, Function, Inst}; +use ir::{ExternalName, Function, Inst, JumpTable, SourceLoc, TrapCode}; use std::fmt; /// Offset in bytes from the beginning of the function. @@ -86,10 +86,13 @@ pub trait CodeSink { /// Add a relocation referencing a jump table. fn reloc_jt(&mut self, Reloc, JumpTable); + + /// Add trap information for the current offset. + fn trap(&mut self, TrapCode, SourceLoc); } /// Report a bad encoding error. -#[inline(never)] +#[cold] pub fn bad_encoding(func: &Function, inst: Inst) -> ! { panic!( "Bad encoding {} for {}", diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index b0bf6196c6..8123c5b92f 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -30,7 +30,7 @@ use binemit::CodeOffset; use cursor::{Cursor, FuncCursor}; use ir::{Function, InstructionData, Opcode}; -use isa::{TargetIsa, EncInfo}; +use isa::{EncInfo, TargetIsa}; use iterators::IteratorExtras; use result::CtonError; @@ -76,14 +76,13 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> Result and the u32 in the implementation of `max_bits()`. +use std::convert::{From, Into}; use std::mem::size_of; -use std::ops::{Shl, BitOr, Sub, Add}; -use std::convert::{Into, From}; +use std::ops::{Add, BitOr, Shl, Sub}; /// A small bitset built on a single primitive integer type #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/lib/cretonne/src/cfg_printer.rs b/lib/cretonne/src/cfg_printer.rs new file mode 100644 index 0000000000..46b8754e3e --- /dev/null +++ b/lib/cretonne/src/cfg_printer.rs @@ -0,0 +1,76 @@ +//! The `CFGPrinter` utility. + +use std::fmt::{Display, Formatter, Result, Write}; + +use flowgraph::ControlFlowGraph; +use ir::Function; +use ir::instructions::BranchInfo; + +/// A utility for pretty-printing the CFG of a `Function`. +pub struct CFGPrinter<'a> { + func: &'a Function, + cfg: ControlFlowGraph, +} + +/// A utility for pretty-printing the CFG of a `Function`. +impl<'a> CFGPrinter<'a> { + /// Create a new CFGPrinter. + pub fn new(func: &'a Function) -> CFGPrinter<'a> { + CFGPrinter { + func, + cfg: ControlFlowGraph::with_function(func), + } + } + + /// Write the CFG for this function to `w`. + pub fn write(&self, w: &mut Write) -> Result { + self.header(w)?; + self.ebb_nodes(w)?; + self.cfg_connections(w)?; + writeln!(w, "}}") + } + + fn header(&self, w: &mut Write) -> Result { + writeln!(w, "digraph \"{}\" {{", self.func.name)?; + if let Some(entry) = self.func.layout.entry_block() { + writeln!(w, " {{rank=min; {}}}", entry)?; + } + Ok(()) + } + + fn ebb_nodes(&self, w: &mut Write) -> Result { + for ebb in &self.func.layout { + write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)?; + // Add all outgoing branch instructions to the label. + for inst in self.func.layout.ebb_insts(ebb) { + let idata = &self.func.dfg[inst]; + match idata.analyze_branch(&self.func.dfg.value_lists) { + BranchInfo::SingleDest(dest, _) => { + write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)? + } + BranchInfo::Table(table) => { + write!(w, " | <{}>{} {}", inst, idata.opcode(), table)? + } + BranchInfo::NotABranch => {} + } + } + writeln!(w, "}}\"]")? + } + Ok(()) + } + + fn cfg_connections(&self, w: &mut Write) -> Result { + for ebb in &self.func.layout { + for (parent, inst) in self.cfg.pred_iter(ebb) { + writeln!(w, " {}:{} -> {}", parent, inst, ebb)?; + } + } + Ok(()) + } +} + +impl<'a> Display for CFGPrinter<'a> { + fn fmt(&self, f: &mut Formatter) -> Result { + self.write(f) + } +} diff --git a/lib/cretonne/src/constant_hash.rs b/lib/cretonne/src/constant_hash.rs index d049d8e752..0dd2895ad3 100644 --- a/lib/cretonne/src/constant_hash.rs +++ b/lib/cretonne/src/constant_hash.rs @@ -18,7 +18,6 @@ pub trait Table { fn key(&self, idx: usize) -> Option; } - /// Look for `key` in `table`. /// /// The provided `hash` value must have been computed from `key` using the same hash function that diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 0eb7ff5c05..ad31e9025b 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -9,22 +9,24 @@ //! contexts concurrently. Typically, you would have one context per compilation thread and only a //! single ISA instance. -use binemit::{CodeOffset, relax_branches, MemoryCodeSink, RelocSink}; +use binemit::{relax_branches, CodeOffset, MemoryCodeSink, RelocSink, TrapSink}; +use dce::do_dce; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; -use loop_analysis::LoopAnalysis; use isa::TargetIsa; use legalize_function; +use licm::do_licm; +use loop_analysis::LoopAnalysis; +use postopt::do_postopt; +use preopt::do_preopt; use regalloc; use result::{CtonError, CtonResult}; use settings::{FlagsOrIsa, OptLevel}; +use simple_gvn::do_simple_gvn; +use timing; use unreachable_code::eliminate_unreachable_code; use verifier; -use simple_gvn::do_simple_gvn; -use licm::do_licm; -use preopt::do_preopt; -use timing; /// Persistent data structures and compilation pipeline. pub struct Context { @@ -88,8 +90,13 @@ impl Context { self.verify_if(isa)?; self.compute_cfg(); - self.preopt(isa)?; + if isa.flags().opt_level() != OptLevel::Fastest { + self.preopt(isa)?; + } self.legalize(isa)?; + if isa.flags().opt_level() != OptLevel::Fastest { + self.postopt(isa)?; + } if isa.flags().opt_level() == OptLevel::Best { self.compute_domtree(); self.compute_loop_analysis(); @@ -98,6 +105,9 @@ impl Context { } self.compute_domtree(); self.eliminate_unreachable_code(isa)?; + if isa.flags().opt_level() != OptLevel::Fastest { + self.dce(isa)?; + } self.regalloc(isa)?; self.prologue_epilogue(isa)?; self.relax_branches(isa) @@ -109,9 +119,15 @@ impl Context { /// code is returned by `compile` above. /// /// The machine code is not relocated. Instead, any relocations are emitted into `relocs`. - pub fn emit_to_memory(&self, mem: *mut u8, relocs: &mut RelocSink, isa: &TargetIsa) { + pub fn emit_to_memory( + &self, + mem: *mut u8, + relocs: &mut RelocSink, + traps: &mut TrapSink, + isa: &TargetIsa, + ) { let _tt = timing::binemit(); - isa.emit_function(&self.func, &mut MemoryCodeSink::new(mem, relocs)); + isa.emit_function(&self.func, &mut MemoryCodeSink::new(mem, relocs, traps)); } /// Run the verifier on the function. @@ -132,12 +148,12 @@ impl Context { } /// Run the locations verifier on the function. - pub fn verify_locations<'a>(&self, isa: &TargetIsa) -> verifier::Result { + pub fn verify_locations(&self, isa: &TargetIsa) -> verifier::Result { verifier::verify_locations(isa, &self.func, None) } /// Run the locations verifier only if the `enable_verifier` setting is true. - pub fn verify_locations_if<'a>(&self, isa: &TargetIsa) -> CtonResult { + pub fn verify_locations_if(&self, isa: &TargetIsa) -> CtonResult { if isa.flags().enable_verifier() { self.verify_locations(isa).map_err(Into::into) } else { @@ -145,6 +161,13 @@ impl Context { } } + /// Perform dead-code elimination on the function. + pub fn dce<'a, FOI: Into>>(&mut self, fisa: FOI) -> CtonResult { + do_dce(&mut self.func, &mut self.domtree); + self.verify_if(fisa)?; + Ok(()) + } + /// Perform pre-legalization rewrites on the function. pub fn preopt(&mut self, isa: &TargetIsa) -> CtonResult { do_preopt(&mut self.func); @@ -162,6 +185,13 @@ impl Context { self.verify_if(isa) } + /// Perform post-legalization rewrites on the function. + pub fn postopt(&mut self, isa: &TargetIsa) -> CtonResult { + do_postopt(&mut self.func, isa); + self.verify_if(isa)?; + Ok(()) + } + /// Compute the control flow graph. pub fn compute_cfg(&mut self) { self.cfg.compute(&self.func) @@ -189,7 +219,7 @@ impl Context { /// Perform simple GVN on the function. pub fn simple_gvn<'a, FOI: Into>>(&mut self, fisa: FOI) -> CtonResult { - do_simple_gvn(&mut self.func, &mut self.cfg, &mut self.domtree); + do_simple_gvn(&mut self.func, &mut self.domtree); self.verify_if(fisa) } diff --git a/lib/cretonne/src/cursor.rs b/lib/cretonne/src/cursor.rs index ed2528d5be..082889a693 100644 --- a/lib/cretonne/src/cursor.rs +++ b/lib/cretonne/src/cursor.rs @@ -637,7 +637,6 @@ impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> { } } - /// Encoding cursor. /// /// An `EncCursor` can be used to insert instructions that are immediately assigned an encoding. @@ -744,8 +743,9 @@ impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut EncCursor<'f> { if !self.srcloc.is_default() { self.func.srclocs[inst] = self.srcloc; } - // Assign an encoding. + // XXX Is there a way to describe this error to the user? + #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] match self.isa.encode( &self.func.dfg, &self.func.dfg[inst], diff --git a/lib/cretonne/src/dce.rs b/lib/cretonne/src/dce.rs new file mode 100644 index 0000000000..895b922dd7 --- /dev/null +++ b/lib/cretonne/src/dce.rs @@ -0,0 +1,68 @@ +//! A Dead-Code Elimination (DCE) pass. +//! +//! Dead code here means instructions that have no side effects and have no +//! result values used by other instructions. + +use cursor::{Cursor, FuncCursor}; +use dominator_tree::DominatorTree; +use entity::EntityRef; +use ir::instructions::InstructionData; +use ir::{DataFlowGraph, Function, Inst, Opcode}; +use std::vec::Vec; +use timing; + +/// Test whether the given opcode is unsafe to even consider for DCE. +fn trivially_unsafe_for_dce(opcode: Opcode) -> bool { + opcode.is_call() || opcode.is_branch() || opcode.is_terminator() || + opcode.is_return() || opcode.can_trap() || opcode.other_side_effects() || + opcode.can_store() +} + +/// Preserve instructions with used result values. +fn any_inst_results_used(inst: Inst, live: &[bool], dfg: &DataFlowGraph) -> bool { + dfg.inst_results(inst).iter().any(|v| live[v.index()]) +} + +/// Load instructions without the `notrap` flag are defined to trap when +/// operating on inaccessible memory, so we can't DCE them even if the +/// loaded value is unused. +fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool { + if !opcode.can_load() { + return false; + } + match *data { + InstructionData::StackLoad { .. } => false, + InstructionData::Load { flags, .. } => !flags.notrap(), + _ => true, + } +} + +/// Perform DCE on `func`. +pub fn do_dce(func: &mut Function, domtree: &mut DominatorTree) { + let _tt = timing::dce(); + debug_assert!(domtree.is_valid()); + + let mut live = Vec::with_capacity(func.dfg.num_values()); + live.resize(func.dfg.num_values(), false); + + for &ebb in domtree.cfg_postorder().iter() { + let mut pos = FuncCursor::new(func).at_bottom(ebb); + while let Some(inst) = pos.prev_inst() { + { + let data = &pos.func.dfg[inst]; + let opcode = data.opcode(); + if trivially_unsafe_for_dce(opcode) || + is_load_with_defined_trapping(opcode, &data) || + any_inst_results_used(inst, &live, &pos.func.dfg) + { + for arg in pos.func.dfg.inst_args(inst) { + let v = pos.func.dfg.resolve_aliases(*arg); + live[v.index()] = true; + } + continue; + } + } + pos.remove_inst(); + } + } +} diff --git a/lib/cretonne/src/divconst_magic_numbers.rs b/lib/cretonne/src/divconst_magic_numbers.rs index f189e23d1a..8a848711a4 100644 --- a/lib/cretonne/src/divconst_magic_numbers.rs +++ b/lib/cretonne/src/divconst_magic_numbers.rs @@ -1,17 +1,15 @@ //! Compute "magic numbers" for division-by-constants transformations. +//! +//! Math helpers for division by (non-power-of-2) constants. This is based +//! on the presentation in "Hacker's Delight" by Henry Warren, 2003. There +//! are four cases: {unsigned, signed} x {32 bit, 64 bit}. The word size +//! makes little difference, but the signed-vs-unsigned aspect has a large +//! effect. Therefore everything is presented in the order U32 U64 S32 S64 +//! so as to emphasise the similarity of the U32 and U64 cases and the S32 +//! and S64 cases. #![allow(non_snake_case)] -//---------------------------------------------------------------------- -// -// Math helpers for division by (non-power-of-2) constants. This is based -// on the presentation in "Hacker's Delight" by Henry Warren, 2003. There -// are four cases: {unsigned, signed} x {32 bit, 64 bit}. The word size -// makes little difference, but the signed-vs-unsigned aspect has a large -// effect. Therefore everything is presented in the order U32 U64 S32 S64 -// so as to emphasise the similarity of the U32 and U64 cases and the S32 -// and S64 cases. - // Structures to hold the "magic numbers" computed. #[derive(PartialEq, Debug)] @@ -222,8 +220,8 @@ pub fn magicS64(d: i64) -> MS64 { #[cfg(test)] mod tests { - use super::{magicU32, magicU64, magicS32, magicS64}; - use super::{MU32, MU64, MS32, MS64}; + use super::{MS32, MS64, MU32, MU64}; + use super::{magicS32, magicS64, magicU32, magicU64}; fn mkMU32(mulBy: u32, doAdd: bool, shiftBy: i32) -> MU32 { MU32 { diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 823469d081..60ef1e6e42 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -1,38 +1,38 @@ //! A Dominator Tree represented as mappings of Ebbs to their immediate dominator. use entity::EntityMap; -use flowgraph::{ControlFlowGraph, BasicBlock}; -use ir::{Ebb, Inst, Value, Function, Layout, ProgramOrder, ExpandedProgramPoint}; +use flowgraph::{BasicBlock, ControlFlowGraph}; use ir::instructions::BranchInfo; +use ir::{Ebb, ExpandedProgramPoint, Function, Inst, Layout, ProgramOrder, Value}; use packed_option::PackedOption; use std::cmp; -use std::mem; -use timing; use std::cmp::Ordering; +use std::mem; use std::vec::Vec; +use timing; -// RPO numbers are not first assigned in a contiguous way but as multiples of STRIDE, to leave -// room for modifications of the dominator tree. +/// RPO numbers are not first assigned in a contiguous way but as multiples of STRIDE, to leave +/// room for modifications of the dominator tree. const STRIDE: u32 = 4; -// Special RPO numbers used during `compute_postorder`. +/// Special RPO numbers used during `compute_postorder`. const DONE: u32 = 1; const SEEN: u32 = 2; -// Dominator tree node. We keep one of these per EBB. +/// Dominator tree node. We keep one of these per EBB. #[derive(Clone, Default)] struct DomNode { - // Number of this node in a reverse post-order traversal of the CFG, starting from 1. - // This number is monotonic in the reverse postorder but not contiguous, since we leave - // holes for later localized modifications of the dominator tree. - // Unreachable nodes get number 0, all others are positive. + /// Number of this node in a reverse post-order traversal of the CFG, starting from 1. + /// This number is monotonic in the reverse postorder but not contiguous, since we leave + /// holes for later localized modifications of the dominator tree. + /// Unreachable nodes get number 0, all others are positive. rpo_number: u32, - // The immediate dominator of this EBB, represented as the branch or jump instruction at the - // end of the dominating basic block. - // - // This is `None` for unreachable blocks and the entry block which doesn't have an immediate - // dominator. + /// The immediate dominator of this EBB, represented as the branch or jump instruction at the + /// end of the dominating basic block. + /// + /// This is `None` for unreachable blocks and the entry block which doesn't have an immediate + /// dominator. idom: PackedOption, } @@ -40,10 +40,10 @@ struct DomNode { pub struct DominatorTree { nodes: EntityMap, - // CFG post-order of all reachable EBBs. + /// CFG post-order of all reachable EBBs. postorder: Vec, - // Scratch memory used by `compute_postorder()`. + /// Scratch memory used by `compute_postorder()`. stack: Vec, valid: bool, @@ -144,12 +144,12 @@ impl DominatorTree { { let (mut ebb_b, mut inst_b) = match b.into() { ExpandedProgramPoint::Ebb(ebb) => (ebb, None), - ExpandedProgramPoint::Inst(inst) => { - ( - layout.inst_ebb(inst).expect("Instruction not in layout."), - Some(inst), - ) - } + ExpandedProgramPoint::Inst(inst) => ( + layout.inst_ebb(inst).expect( + "Instruction not in layout.", + ), + Some(inst), + ), }; let rpo_a = self.nodes[a].rpo_number; @@ -460,7 +460,6 @@ impl DominatorTree { rpo_number: new_ebb_rpo, idom: Some(split_jump_inst).into(), }; - } // Insert new_ebb just after ebb in the RPO. This function checks @@ -667,12 +666,12 @@ impl DominatorTreePreorder { #[cfg(test)] mod test { + use super::*; use cursor::{Cursor, FuncCursor}; use flowgraph::ControlFlowGraph; use ir::types::*; use ir::{Function, InstBuilder, TrapCode}; use settings; - use super::*; use verifier::verify_context; #[test] diff --git a/lib/cretonne/src/entity/iter.rs b/lib/cretonne/src/entity/iter.rs new file mode 100644 index 0000000000..156021ee86 --- /dev/null +++ b/lib/cretonne/src/entity/iter.rs @@ -0,0 +1,109 @@ +//! A double-ended iterator over entity references and entities. + +use entity::EntityRef; +use std::marker::PhantomData; +use std::slice; + +/// Iterate over all keys in order. +pub struct Iter<'a, K: EntityRef, V> +where + V: 'a, +{ + pos: usize, + iter: slice::Iter<'a, V>, + unused: PhantomData, +} + +impl<'a, K: EntityRef, V> Iter<'a, K, V> { + /// Create an `Iter` iterator that visits the `PrimaryMap` keys and values + /// of `iter`. + pub fn new(key: K, iter: slice::Iter<'a, V>) -> Self { + Self { + pos: key.index(), + iter, + unused: PhantomData, + } + } +} + +impl<'a, K: EntityRef, V> Iterator for Iter<'a, K, V> { + type Item = (K, &'a V); + + fn next(&mut self) -> Option { + if let Some(next) = self.iter.next() { + let pos = self.pos; + self.pos += 1; + Some((K::new(pos), next)) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, K: EntityRef, V> DoubleEndedIterator for Iter<'a, K, V> { + fn next_back(&mut self) -> Option { + if let Some(next_back) = self.iter.next_back() { + Some((K::new(self.pos), next_back)) + } else { + None + } + } +} + +impl<'a, K: EntityRef, V> ExactSizeIterator for Iter<'a, K, V> {} + +/// Iterate over all keys in order. +pub struct IterMut<'a, K: EntityRef, V> +where + V: 'a, +{ + pos: usize, + iter: slice::IterMut<'a, V>, + unused: PhantomData, +} + +impl<'a, K: EntityRef, V> IterMut<'a, K, V> { + /// Create an `IterMut` iterator that visits the `PrimaryMap` keys and values + /// of `iter`. + pub fn new(key: K, iter: slice::IterMut<'a, V>) -> Self { + Self { + pos: key.index(), + iter, + unused: PhantomData, + } + } +} + +impl<'a, K: EntityRef, V> Iterator for IterMut<'a, K, V> { + type Item = (K, &'a mut V); + + fn next(&mut self) -> Option { + if let Some(next) = self.iter.next() { + let pos = self.pos; + self.pos += 1; + Some((K::new(pos), next)) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, K: EntityRef, V> DoubleEndedIterator for IterMut<'a, K, V> { + fn next_back(&mut self) -> Option { + if let Some(next_back) = self.iter.next_back() { + Some((K::new(self.pos), next_back)) + } else { + None + } + } +} + +impl<'a, K: EntityRef, V> ExactSizeIterator for IterMut<'a, K, V> {} diff --git a/lib/cretonne/src/entity/list.rs b/lib/cretonne/src/entity/list.rs index ad03dc6c85..545b1e146b 100644 --- a/lib/cretonne/src/entity/list.rs +++ b/lib/cretonne/src/entity/list.rs @@ -480,9 +480,9 @@ impl EntityList { #[cfg(test)] mod tests { use super::*; - use super::{sclass_size, sclass_for_length}; - use ir::Inst; + use super::{sclass_for_length, sclass_size}; use entity::EntityRef; + use ir::Inst; #[test] fn size_classes() { diff --git a/lib/cretonne/src/entity/map.rs b/lib/cretonne/src/entity/map.rs index f251c2f831..ff402b104f 100644 --- a/lib/cretonne/src/entity/map.rs +++ b/lib/cretonne/src/entity/map.rs @@ -1,8 +1,9 @@ //! Densely numbered entity references as mapping keys. -use entity::{EntityRef, Keys}; +use entity::{EntityRef, Iter, IterMut, Keys}; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; +use std::slice; use std::vec::Vec; /// A mapping `K -> V` for densely indexed entity references. @@ -68,11 +69,31 @@ where self.elems.clear() } + /// Iterate over all the keys and values in this map. + pub fn iter(&self) -> Iter { + Iter::new(K::new(0), self.elems.iter()) + } + + /// Iterate over all the keys and values in this map, mutable edition. + pub fn iter_mut(&mut self) -> IterMut { + IterMut::new(K::new(0), self.elems.iter_mut()) + } + /// Iterate over all the keys in this map. pub fn keys(&self) -> Keys { Keys::new(self.elems.len()) } + /// Iterate over all the keys in this map. + pub fn values(&self) -> slice::Iter { + self.elems.iter() + } + + /// Iterate over all the keys in this map, mutable edition. + pub fn values_mut(&mut self) -> slice::IterMut { + self.elems.iter_mut() + } + /// Resize the map to have `n` entries by adding default entries as needed. pub fn resize(&mut self, n: usize) { self.elems.resize(n, self.default.clone()); diff --git a/lib/cretonne/src/entity/mod.rs b/lib/cretonne/src/entity/mod.rs index 6457e47f56..88c0977cf2 100644 --- a/lib/cretonne/src/entity/mod.rs +++ b/lib/cretonne/src/entity/mod.rs @@ -29,19 +29,21 @@ //! references allocated from an associated memory pool. It has a much smaller footprint than //! `Vec`. +mod iter; mod keys; mod list; mod map; mod primary; -mod sparse; mod set; +mod sparse; +pub use self::iter::{Iter, IterMut}; pub use self::keys::Keys; pub use self::list::{EntityList, ListPool}; pub use self::map::EntityMap; pub use self::primary::PrimaryMap; pub use self::set::EntitySet; -pub use self::sparse::{SparseSet, SparseMap, SparseMapValue}; +pub use self::sparse::{SparseMap, SparseMapValue, SparseSet}; /// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key /// of an `EntityMap` or `SparseMap`. @@ -93,5 +95,5 @@ macro_rules! entity_impl { (self as &::std::fmt::Display).fmt(f) } } - } + }; } diff --git a/lib/cretonne/src/entity/primary.rs b/lib/cretonne/src/entity/primary.rs index c06b818355..5d690a7553 100644 --- a/lib/cretonne/src/entity/primary.rs +++ b/lib/cretonne/src/entity/primary.rs @@ -1,7 +1,8 @@ //! Densely numbered entity references as mapping keys. -use entity::{EntityRef, Keys}; +use entity::{EntityRef, Iter, IterMut, Keys}; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; +use std::slice; use std::vec::Vec; /// A primary mapping `K -> V` allocating dense entity references. @@ -59,6 +60,26 @@ where Keys::new(self.elems.len()) } + /// Iterate over all the values in this map. + pub fn values(&self) -> slice::Iter { + self.elems.iter() + } + + /// Iterate over all the values in this map, mutable edition. + pub fn values_mut(&mut self) -> slice::IterMut { + self.elems.iter_mut() + } + + /// Iterate over all the keys and values in this map. + pub fn iter(&self) -> Iter { + Iter::new(K::new(0), self.elems.iter()) + } + + /// Iterate over all the keys and values in this map, mutable edition. + pub fn iter_mut(&mut self) -> IterMut { + IterMut::new(K::new(0), self.elems.iter_mut()) + } + /// Remove all entries from this map. pub fn clear(&mut self) { self.elems.clear() @@ -133,13 +154,80 @@ mod tests { #[test] fn push() { let mut m = PrimaryMap::new(); - let k1: E = m.push(12); - let k2 = m.push(33); + let k0: E = m.push(12); + let k1 = m.push(33); - assert_eq!(m[k1], 12); - assert_eq!(m[k2], 33); + assert_eq!(m[k0], 12); + assert_eq!(m[k1], 33); let v: Vec = m.keys().collect(); - assert_eq!(v, [k1, k2]); + assert_eq!(v, [k0, k1]); + } + + #[test] + fn iter() { + let mut m: PrimaryMap = PrimaryMap::new(); + m.push(12); + m.push(33); + + let mut i = 0; + for (key, value) in m.iter() { + assert_eq!(key.index(), i); + match i { + 0 => assert_eq!(*value, 12), + 1 => assert_eq!(*value, 33), + _ => panic!(), + } + i += 1; + } + i = 0; + for (key_mut, value_mut) in m.iter_mut() { + assert_eq!(key_mut.index(), i); + match i { + 0 => assert_eq!(*value_mut, 12), + 1 => assert_eq!(*value_mut, 33), + _ => panic!(), + } + i += 1; + } + } + + #[test] + fn keys() { + let mut m: PrimaryMap = PrimaryMap::new(); + m.push(12); + m.push(33); + + let mut i = 0; + for key in m.keys() { + assert_eq!(key.index(), i); + i += 1; + } + } + + #[test] + fn values() { + let mut m: PrimaryMap = PrimaryMap::new(); + m.push(12); + m.push(33); + + let mut i = 0; + for value in m.values() { + match i { + 0 => assert_eq!(*value, 12), + 1 => assert_eq!(*value, 33), + _ => panic!(), + } + i += 1; + } + i = 0; + for value_mut in m.values_mut() { + match i { + 0 => assert_eq!(*value_mut, 12), + 1 => assert_eq!(*value_mut, 33), + _ => panic!(), + } + i += 1; + } } } diff --git a/lib/cretonne/src/entity/sparse.rs b/lib/cretonne/src/entity/sparse.rs index 488fd55393..6894c89498 100644 --- a/lib/cretonne/src/entity/sparse.rs +++ b/lib/cretonne/src/entity/sparse.rs @@ -7,7 +7,7 @@ //! > Briggs, Torczon, *An efficient representation for sparse sets*, //! ACM Letters on Programming Languages and Systems, Volume 2, Issue 1-4, March-Dec. 1993. -use entity::{EntityRef, EntityMap}; +use entity::{EntityMap, EntityRef}; use std::mem; use std::slice; use std::u32; diff --git a/lib/cretonne/src/flowgraph.rs b/lib/cretonne/src/flowgraph.rs index dce60162a4..66f377ce35 100644 --- a/lib/cretonne/src/flowgraph.rs +++ b/lib/cretonne/src/flowgraph.rs @@ -24,9 +24,9 @@ //! and `(Ebb0, jmp Ebb2)` respectively. use bforest; -use ir::{Function, Inst, Ebb}; -use ir::instructions::BranchInfo; use entity::EntityMap; +use ir::instructions::BranchInfo; +use ir::{Ebb, Function, Inst}; use std::mem; use timing; @@ -203,7 +203,7 @@ pub type SuccIter<'a> = bforest::SetIter<'a, Ebb, ()>; mod tests { use super::*; use cursor::{Cursor, FuncCursor}; - use ir::{Function, InstBuilder, types}; + use ir::{types, Function, InstBuilder}; use std::vec::Vec; #[test] diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index a69fd4e181..34f56b9439 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,8 +5,8 @@ use ir; use ir::types; -use ir::{InstructionData, DataFlowGraph}; -use ir::{Opcode, Type, Inst, Value}; +use ir::{DataFlowGraph, InstructionData}; +use ir::{Inst, Opcode, Type, Value}; use isa; /// Base trait for instruction builders. @@ -36,7 +36,7 @@ pub trait InstBuilderBase<'f>: Sized { // // This file defines the `InstBuilder` trait as an extension of `InstBuilderBase` with methods per // instruction format and per opcode. -include!(concat!(env!("OUT_DIR"), "/builder.rs")); +include!(concat!(env!("OUT_DIR"), "/inst_builder.rs")); /// Any type implementing `InstBuilderBase` gets all the `InstBuilder` methods for free. impl<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {} @@ -145,8 +145,9 @@ where } impl<'f, IIB, Array> InstBuilderBase<'f> for InsertReuseBuilder<'f, IIB, Array> - where IIB: InstInserterBase<'f>, - Array: AsRef<[Option]> +where + IIB: InstInserterBase<'f>, + Array: AsRef<[Option]>, { fn data_flow_graph(&self) -> &DataFlowGraph { self.inserter.data_flow_graph() @@ -215,9 +216,9 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { #[cfg(test)] mod tests { use cursor::{Cursor, FuncCursor}; - use ir::{Function, InstBuilder, ValueDef}; - use ir::types::*; use ir::condcodes::*; + use ir::types::*; + use ir::{Function, InstBuilder, ValueDef}; #[test] fn types() { diff --git a/lib/cretonne/src/ir/condcodes.rs b/lib/cretonne/src/ir/condcodes.rs index 11c438b10a..61967a5871 100644 --- a/lib/cretonne/src/ir/condcodes.rs +++ b/lib/cretonne/src/ir/condcodes.rs @@ -13,12 +13,14 @@ pub trait CondCode: Copy { /// /// The inverse condition code produces the opposite result for all comparisons. /// That is, `cmp CC, x, y` is true if and only if `cmp CC.inverse(), x, y` is false. + #[must_use] fn inverse(self) -> Self; /// Get the reversed condition code for `self`. /// /// The reversed condition code produces the same result as swapping `x` and `y` in the /// comparison. That is, `cmp CC, x, y` is the same as `cmp CC.reverse(), y, x`. + #[must_use] fn reverse(self) -> Self; } diff --git a/lib/cretonne/src/ir/dfg.rs b/lib/cretonne/src/ir/dfg.rs index 344483a6d9..24f18ff332 100644 --- a/lib/cretonne/src/ir/dfg.rs +++ b/lib/cretonne/src/ir/dfg.rs @@ -1,20 +1,20 @@ //! Data flow graph tracking Instructions, Values, and EBBs. -use entity::{PrimaryMap, EntityMap}; -use isa::TargetIsa; +use entity::{EntityMap, PrimaryMap}; use ir; use ir::builder::ReplaceBuilder; use ir::extfunc::ExtFuncData; -use ir::instructions::{InstructionData, CallInfo, BranchInfo}; +use ir::instructions::{BranchInfo, CallInfo, InstructionData}; use ir::types; -use ir::{Ebb, Inst, Value, Type, SigRef, Signature, FuncRef, ValueList, ValueListPool}; +use ir::{Ebb, FuncRef, Inst, SigRef, Signature, Type, Value, ValueList, ValueListPool}; +use isa::{Encoding, Legalize, TargetIsa}; use packed_option::ReservedValue; -use write::write_operands; use std::fmt; use std::iter; use std::mem; use std::ops::{Index, IndexMut}; use std::u16; +use write::write_operands; /// A data flow graph defines all instructions and extended basic blocks in a function as well as /// the data flow dependencies between them. The DFG also tracks values which can be either @@ -121,8 +121,9 @@ impl DataFlowGraph { /// Resolve value aliases. /// -/// Find the original SSA value that `value` aliases. -fn resolve_aliases(values: &PrimaryMap, value: Value) -> Value { +/// Find the original SSA value that `value` aliases, or None if an +/// alias cycle is detected. +fn maybe_resolve_aliases(values: &PrimaryMap, value: Value) -> Option { let mut v = value; // Note that values may be empty here. @@ -130,10 +131,22 @@ fn resolve_aliases(values: &PrimaryMap, value: Value) -> Value if let ValueData::Alias { original, .. } = values[v] { v = original; } else { - return v; + return Some(v); } } - panic!("Value alias loop detected for {}", value); + + None +} + +/// Resolve value aliases. +/// +/// Find the original SSA value that `value` aliases. +fn resolve_aliases(values: &PrimaryMap, value: Value) -> Value { + if let Some(v) = maybe_resolve_aliases(values, value) { + v + } else { + panic!("Value alias loop detected for {}", value); + } } /// Handling values. @@ -238,6 +251,7 @@ impl DataFlowGraph { self.value_type(dest), ty ); + debug_assert_ne!(ty, types::VOID); self.values[dest] = ValueData::Alias { ty, original }; } @@ -282,6 +296,7 @@ impl DataFlowGraph { self.value_type(dest), ty ); + debug_assert_ne!(ty, types::VOID); self.values[dest] = ValueData::Alias { ty, original }; } @@ -333,18 +348,18 @@ impl ValueDef { } } -// Internal table storage for extended values. +/// Internal table storage for extended values. #[derive(Clone, Debug)] enum ValueData { - // Value is defined by an instruction. + /// Value is defined by an instruction. Inst { ty: Type, num: u16, inst: Inst }, - // Value is an EBB parameter. + /// Value is an EBB parameter. Param { ty: Type, num: u16, ebb: Ebb }, - // Value is an alias of another value. - // An alias value can't be linked as an instruction result or EBB parameter. It is used as a - // placeholder when the original instruction or EBB has been rewritten or modified. + /// Value is an alias of another value. + /// An alias value can't be linked as an instruction result or EBB parameter. It is used as a + /// placeholder when the original instruction or EBB has been rewritten or modified. Alias { ty: Type, original: Value }, } @@ -645,6 +660,12 @@ impl DataFlowGraph { self.value_type(self.first_result(inst)) } } + + /// Wrapper around `TargetIsa::encode` for encoding an existing instruction + /// in the `DataFlowGraph`. + pub fn encode(&self, inst: Inst, isa: &TargetIsa) -> Result { + isa.encode(&self, &self[inst], self.ctrl_typevar(inst)) + } } /// Allow immutable access to instructions via indexing. @@ -754,7 +775,6 @@ impl DataFlowGraph { } } - /// Append an existing value to `ebb`'s parameters. /// /// The appended value can't already be attached to something else. @@ -808,14 +828,14 @@ impl DataFlowGraph { } } -// Contents of an extended basic block. -// -// Parameters on an extended basic block are values that dominate everything in the EBB. All -// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must -// match the function arguments. +/// Contents of an extended basic block. +/// +/// Parameters on an extended basic block are values that dominate everything in the EBB. All +/// branches to this EBB must provide matching arguments, and the arguments to the entry EBB must +/// match the function arguments. #[derive(Clone)] struct EbbData { - // List of parameters to this EBB. + /// List of parameters to this EBB. params: ValueList, } @@ -842,7 +862,6 @@ impl<'a> fmt::Display for DisplayInst<'a> { write!(f, " = ")?; } - let typevar = dfg.ctrl_typevar(inst); if typevar.is_void() { write!(f, "{}", dfg[inst].opcode())?; @@ -859,8 +878,9 @@ impl DataFlowGraph { /// to create invalid values for index padding which may be reassigned later. #[cold] fn set_value_type_for_parser(&mut self, v: Value, t: Type) { - assert!( - self.value_type(v) == types::VOID, + assert_eq!( + self.value_type(v), + types::VOID, "this function is only for assigning types to previously invalid values" ); match self.values[v] { @@ -920,12 +940,38 @@ impl DataFlowGraph { /// aliases with specific values. #[cold] pub fn make_value_alias_for_parser(&mut self, src: Value, dest: Value) { - let ty = self.value_type(src); + assert_ne!(src, Value::reserved_value()); + assert_ne!(dest, Value::reserved_value()); + let ty = if self.values.is_valid(src) { + self.value_type(src) + } else { + // As a special case, if we can't resolve the aliasee yet, use VOID + // temporarily. It will be resolved later in parsing. + types::VOID + }; let data = ValueData::Alias { ty, original: src }; self.values[dest] = data; } + /// Compute the type of an alias. This is only for use in the parser. + /// Returns false if an alias cycle was encountered. + #[cold] + pub fn set_alias_type_for_parser(&mut self, v: Value) -> bool { + if let Some(resolved) = maybe_resolve_aliases(&self.values, v) { + let old_ty = self.value_type(v); + let new_ty = self.value_type(resolved); + if old_ty == types::VOID { + self.set_value_type_for_parser(v, new_ty); + } else { + assert_eq!(old_ty, new_ty); + } + true + } else { + false + } + } + /// Create an invalid value, to pad the index space. This is only for use by /// the parser to pad out the value index space. #[cold] @@ -936,6 +982,20 @@ impl DataFlowGraph { }; self.make_value(data); } + + /// Check if a value reference is valid, while being aware of aliases which + /// may be unresolved while parsing. + #[cold] + pub fn value_is_valid_for_parser(&self, v: Value) -> bool { + if !self.value_is_valid(v) { + return false; + } + if let ValueData::Alias { ty, .. } = self.values[v] { + ty != types::VOID + } else { + true + } + } } #[cfg(test)] @@ -943,7 +1003,7 @@ mod tests { use super::*; use cursor::{Cursor, FuncCursor}; use ir::types; - use ir::{Function, Opcode, InstructionData, TrapCode}; + use ir::{Function, InstructionData, Opcode, TrapCode}; use std::string::ToString; #[test] diff --git a/lib/cretonne/src/ir/entities.rs b/lib/cretonne/src/ir/entities.rs index 1a4e772be4..d0c627952b 100644 --- a/lib/cretonne/src/ir/entities.rs +++ b/lib/cretonne/src/ir/entities.rs @@ -1,6 +1,6 @@ -//! IL entity references. +//! Cretonne IR entity references. //! -//! Instructions in Cretonne IL need to reference other entities in the function. This can be other +//! Instructions in Cretonne IR need to reference other entities in the function. This can be other //! parts of the function like extended basic blocks or stack slots, or it can be external entities //! that are declared in the function preamble in the text format. //! @@ -16,7 +16,7 @@ //! data structures use the `PackedOption` representation, while function arguments and //! return values prefer the more Rust-like `Option` variant. //! -//! The entity references all implement the `Display` trait in a way that matches the textual IL +//! The entity references all implement the `Display` trait in a way that matches the textual IR //! format. use std::fmt; @@ -261,8 +261,8 @@ impl From for AnyEntity { #[cfg(test)] mod tests { use super::*; - use std::u32; use std::string::ToString; + use std::u32; #[test] fn value_with_number() { @@ -275,8 +275,8 @@ mod tests { #[test] fn memory() { - use std::mem; use packed_option::PackedOption; + use std::mem; // This is the whole point of `PackedOption`. assert_eq!( mem::size_of::(), diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index f8fe767970..45c44c952a 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -5,7 +5,7 @@ //! //! This module declares the data types used to represent external functions and call signatures. -use ir::{Type, ExternalName, SigRef, ArgumentLoc}; +use ir::{ArgumentLoc, ExternalName, SigRef, Type}; use isa::{RegInfo, RegUnit}; use std::cmp; use std::fmt; @@ -343,10 +343,11 @@ impl fmt::Display for ExtFuncData { /// determined by a `(TargetIsa, CallConv)` tuple. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CallConv { - /// The C calling convention. + /// The System V-style calling convention. /// - /// This is the native calling convention that a C compiler would use on the platform. - Native, + /// This is the System V-style calling convention that a C compiler would + /// use on many platforms. + SystemV, /// A JIT-compiled WebAssembly function in the SpiderMonkey VM. SpiderWASM, @@ -356,7 +357,7 @@ impl fmt::Display for CallConv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::CallConv::*; f.write_str(match *self { - Native => "native", + SystemV => "system_v", SpiderWASM => "spiderwasm", }) } @@ -368,7 +369,7 @@ impl FromStr for CallConv { fn from_str(s: &str) -> Result { use self::CallConv::*; match s { - "native" => Ok(Native), + "system_v" => Ok(SystemV), "spiderwasm" => Ok(SpiderWASM), _ => Err(()), } @@ -378,7 +379,7 @@ impl FromStr for CallConv { #[cfg(test)] mod tests { use super::*; - use ir::types::{I32, F32, B8}; + use ir::types::{B8, F32, I32}; use std::string::ToString; #[test] @@ -410,7 +411,7 @@ mod tests { #[test] fn call_conv() { - for &cc in &[CallConv::Native, CallConv::SpiderWASM] { + for &cc in &[CallConv::SystemV, CallConv::SpiderWASM] { assert_eq!(Ok(cc), cc.to_string().parse()) } } diff --git a/lib/cretonne/src/ir/extname.rs b/lib/cretonne/src/ir/extname.rs index b7bcc7d94f..b37e40086c 100644 --- a/lib/cretonne/src/ir/extname.rs +++ b/lib/cretonne/src/ir/extname.rs @@ -16,7 +16,7 @@ const TESTCASE_NAME_LENGTH: usize = 16; /// to keep track of a sy mbol table. /// /// External names are primarily used as keys by code using Cretonne to map -/// from a cretonne::ir::FuncRef or similar to additional associated data. +/// from a `cretonne::ir::FuncRef` or similar to additional associated data. /// /// External names can also serve as a primitive testing and debugging tool. /// In particular, many `.cton` test files use function names to identify diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 91ee7eca75..19baa16b2b 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -4,13 +4,13 @@ //! instructions. use binemit::CodeOffset; -use entity::{PrimaryMap, EntityMap}; +use entity::{EntityMap, PrimaryMap}; use ir; -use ir::{ExternalName, CallConv, Signature, DataFlowGraph, Layout}; -use ir::{InstEncodings, ValueLocations, JumpTables, StackSlots, EbbOffsets, SourceLocs}; -use ir::{Ebb, JumpTableData, JumpTable, StackSlotData, StackSlot, SigRef, ExtFuncData, FuncRef, - GlobalVarData, GlobalVar, HeapData, Heap}; -use isa::{TargetIsa, EncInfo}; +use ir::{CallConv, DataFlowGraph, ExternalName, Layout, Signature}; +use ir::{Ebb, ExtFuncData, FuncRef, GlobalVar, GlobalVarData, Heap, HeapData, JumpTable, + JumpTableData, SigRef, StackSlot, StackSlotData}; +use ir::{EbbOffsets, InstEncodings, JumpTables, SourceLocs, StackSlots, ValueLocations}; +use isa::{EncInfo, Legalize, TargetIsa}; use std::fmt; use write::write_function; @@ -55,7 +55,7 @@ pub struct Function { /// /// This information is only transiently available after the `binemit::relax_branches` function /// computes it, and it can easily be recomputed by calling that function. It is not included - /// in the textual IL format. + /// in the textual IR format. pub offsets: EbbOffsets, /// Source locations. @@ -86,7 +86,7 @@ impl Function { /// Clear all data structures in this function. pub fn clear(&mut self) { - self.signature.clear(ir::CallConv::Native); + self.signature.clear(ir::CallConv::SystemV); self.stack_slots.clear(); self.global_vars.clear(); self.heaps.clear(); @@ -99,9 +99,9 @@ impl Function { self.srclocs.clear(); } - /// Create a new empty, anonymous function with a native calling convention. + /// Create a new empty, anonymous function with a SystemV calling convention. pub fn new() -> Self { - Self::with_name_signature(ExternalName::default(), Signature::new(CallConv::Native)) + Self::with_name_signature(ExternalName::default(), Signature::new(CallConv::SystemV)) } /// Creates a jump table in the function, to be used by `br_table` instructions. @@ -176,6 +176,13 @@ impl Function { iter: self.layout.ebb_insts(ebb), } } + + /// Wrapper around `DataFlowGraph::encode` which assigns `inst` the resulting encoding. + pub fn update_encoding(&mut self, inst: ir::Inst, isa: &TargetIsa) -> Result<(), Legalize> { + self.dfg.encode(inst, isa).map( + |e| { self.encodings[inst] = e; }, + ) + } } /// Wrapper type capable of displaying a `Function` with correct ISA annotations. diff --git a/lib/cretonne/src/ir/globalvar.rs b/lib/cretonne/src/ir/globalvar.rs index 78e1c6cb98..228f691d84 100644 --- a/lib/cretonne/src/ir/globalvar.rs +++ b/lib/cretonne/src/ir/globalvar.rs @@ -1,7 +1,7 @@ //! Global variables. -use ir::{ExternalName, GlobalVar}; use ir::immediates::Offset32; +use ir::{ExternalName, GlobalVar}; use std::fmt; /// Information about a global variable declaration. @@ -17,7 +17,8 @@ pub enum GlobalVarData { /// Variable is part of a struct pointed to by another global variable. /// /// The `base` global variable is assumed to contain a pointer to a struct. This global - /// variable lives at an offset into the struct. + /// variable lives at an offset into the struct. The memory must be accessible, and + /// naturally aligned to hold a pointer value. Deref { /// The base pointer global variable. base: GlobalVar, diff --git a/lib/cretonne/src/ir/heap.rs b/lib/cretonne/src/ir/heap.rs index 7920ec344a..01beb15b35 100644 --- a/lib/cretonne/src/ir/heap.rs +++ b/lib/cretonne/src/ir/heap.rs @@ -1,7 +1,7 @@ //! Heaps. -use ir::immediates::Imm64; use ir::GlobalVar; +use ir::immediates::Imm64; use std::fmt; /// Information about a heap declaration. @@ -25,9 +25,12 @@ pub struct HeapData { #[derive(Clone)] pub enum HeapBase { /// The heap base lives in a reserved register. + /// + /// This feature is not yet implemented. ReservedReg, - /// The heap base is in a global variable. + /// The heap base is in a global variable. The variable must be accessible and naturally + /// aligned for a pointer. GlobalVar(GlobalVar), } @@ -36,7 +39,8 @@ pub enum HeapBase { pub enum HeapStyle { /// A dynamic heap can be relocated to a different base address when it is grown. Dynamic { - /// Global variable holding the current bound of the heap in bytes. + /// Global variable holding the current bound of the heap in bytes. It is + /// required to be accessible and naturally aligned for a pointer-sized integer. bound_gv: GlobalVar, }, diff --git a/lib/cretonne/src/ir/immediates.rs b/lib/cretonne/src/ir/immediates.rs index 2e6339cc01..d56e963bb1 100644 --- a/lib/cretonne/src/ir/immediates.rs +++ b/lib/cretonne/src/ir/immediates.rs @@ -21,6 +21,11 @@ impl Imm64 { pub fn new(x: i64) -> Imm64 { Imm64(x) } + + /// Return self negated. + pub fn wrapping_neg(self) -> Imm64 { + Imm64(self.0.wrapping_neg()) + } } impl Into for Imm64 { @@ -35,12 +40,12 @@ impl From for Imm64 { } } -// Hexadecimal with a multiple of 4 digits and group separators: -// -// 0xfff0 -// 0x0001_ffff -// 0xffff_ffff_fff8_4400 -// +/// Hexadecimal with a multiple of 4 digits and group separators: +/// +/// 0xfff0 +/// 0x0001_ffff +/// 0xffff_ffff_fff8_4400 +/// fn write_hex(x: i64, f: &mut Formatter) -> fmt::Result { let mut pos = (64 - x.leading_zeros() - 1) & 0xf0; write!(f, "0x{:04x}", (x >> pos) & 0xffff)?; @@ -179,7 +184,6 @@ impl Display for Uimm32 { } else { write_hex(i64::from(self.0), f) } - } } @@ -244,7 +248,6 @@ impl Display for Offset32 { } else { write_hex(val, f) } - } } @@ -280,16 +283,16 @@ pub struct Ieee32(u32); #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct Ieee64(u64); -// Format a floating point number in a way that is reasonably human-readable, and that can be -// converted back to binary without any rounding issues. The hexadecimal formatting of normal and -// subnormal numbers is compatible with C99 and the `printf "%a"` format specifier. The NaN and Inf -// formats are not supported by C99. -// -// The encoding parameters are: -// -// w - exponent field width in bits -// t - trailing significand field width in bits -// +/// Format a floating point number in a way that is reasonably human-readable, and that can be +/// converted back to binary without any rounding issues. The hexadecimal formatting of normal and +/// subnormal numbers is compatible with C99 and the `printf "%a"` format specifier. The NaN and Inf +/// formats are not supported by C99. +/// +/// The encoding parameters are: +/// +/// w - exponent field width in bits +/// t - trailing significand field width in bits +/// fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { debug_assert!(w > 0 && w <= 16, "Invalid exponent range"); debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64"); @@ -358,13 +361,13 @@ fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result { } } -// Parse a float using the same format as `format_float` above. -// -// The encoding parameters are: -// -// w - exponent field width in bits -// t - trailing significand field width in bits -// +/// Parse a float using the same format as `format_float` above. +/// +/// The encoding parameters are: +/// +/// w - exponent field width in bits +/// t - trailing significand field width in bits +/// fn parse_float(s: &str, w: u8, t: u8) -> Result { debug_assert!(w > 0 && w <= 16, "Invalid exponent range"); debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64"); @@ -456,7 +459,6 @@ fn parse_float(s: &str, w: u8, t: u8) -> Result { None => return Err("Invalid character"), } } - } } @@ -649,10 +651,10 @@ impl FromStr for Ieee64 { #[cfg(test)] mod tests { use super::*; - use std::{f32, f64}; - use std::str::FromStr; use std::fmt::Display; + use std::str::FromStr; use std::string::ToString; + use std::{f32, f64}; #[test] fn format_imm64() { diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index b4b82c4ea6..aa6a35df3f 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -1,25 +1,23 @@ //! Instruction formats and opcodes. //! //! The `instructions` module contains definitions for instruction formats, opcodes, and the -//! in-memory representation of IL instructions. +//! in-memory representation of IR instructions. //! //! A large part of this module is auto-generated from the instruction descriptions in the meta //! directory. use std::fmt::{self, Display, Formatter}; -use std::str::FromStr; use std::ops::{Deref, DerefMut}; +use std::str::FromStr; use std::vec::Vec; use ir; -use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; -use ir::immediates::{Imm64, Uimm8, Uimm32, Ieee32, Ieee64, Offset32}; -use ir::condcodes::*; use ir::types; -use isa::RegUnit; +use ir::{Ebb, FuncRef, JumpTable, SigRef, Type, Value}; +use isa; -use entity; use bitset::BitSet; +use entity; use ref_slice::{ref_slice, ref_slice_mut}; /// Some instructions use an external list of argument values because there is not enough space in @@ -33,6 +31,7 @@ pub type ValueListPool = entity::ListPool; // Include code generated by `lib/cretonne/meta/gen_instr.py`. This file contains: // // - The `pub enum InstructionFormat` enum with all the instruction formats. +// - The `pub enum InstructionData` enum with all the instruction data fields. // - The `pub enum Opcode` definition with all known opcodes, // - The `const OPCODE_FORMAT: [InstructionFormat; N]` table. // - The private `fn opcode_name(Opcode) -> &'static str` function, and @@ -74,7 +73,7 @@ impl FromStr for Opcode { /// Parse an Opcode name from a string. fn from_str(s: &str) -> Result { - use constant_hash::{Table, simple_hash, probe}; + use constant_hash::{probe, simple_hash, Table}; impl<'a> Table<&'a str> for [Option] { fn len(&self) -> usize { @@ -95,190 +94,6 @@ impl FromStr for Opcode { } } -/// Contents on an instruction. -/// -/// Every variant must contain `opcode` and `ty` fields. An instruction that doesn't produce a -/// value should have its `ty` field set to `VOID`. The size of `InstructionData` should be kept at -/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a -/// `Box` to store the additional information out of line. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -#[allow(missing_docs)] -pub enum InstructionData { - Unary { opcode: Opcode, arg: Value }, - UnaryImm { opcode: Opcode, imm: Imm64 }, - UnaryIeee32 { opcode: Opcode, imm: Ieee32 }, - UnaryIeee64 { opcode: Opcode, imm: Ieee64 }, - UnaryBool { opcode: Opcode, imm: bool }, - UnaryGlobalVar { - opcode: Opcode, - global_var: ir::GlobalVar, - }, - Binary { opcode: Opcode, args: [Value; 2] }, - BinaryImm { - opcode: Opcode, - arg: Value, - imm: Imm64, - }, - Ternary { opcode: Opcode, args: [Value; 3] }, - MultiAry { opcode: Opcode, args: ValueList }, - NullAry { opcode: Opcode }, - InsertLane { - opcode: Opcode, - lane: Uimm8, - args: [Value; 2], - }, - ExtractLane { - opcode: Opcode, - lane: Uimm8, - arg: Value, - }, - IntCompare { - opcode: Opcode, - cond: IntCC, - args: [Value; 2], - }, - IntCompareImm { - opcode: Opcode, - cond: IntCC, - arg: Value, - imm: Imm64, - }, - IntCond { - opcode: Opcode, - cond: IntCC, - arg: Value, - }, - FloatCompare { - opcode: Opcode, - cond: FloatCC, - args: [Value; 2], - }, - FloatCond { - opcode: Opcode, - cond: FloatCC, - arg: Value, - }, - IntSelect { - opcode: Opcode, - cond: IntCC, - args: [Value; 3], - }, - Jump { - opcode: Opcode, - destination: Ebb, - args: ValueList, - }, - Branch { - opcode: Opcode, - destination: Ebb, - args: ValueList, - }, - BranchIcmp { - opcode: Opcode, - cond: IntCC, - destination: Ebb, - args: ValueList, - }, - BranchInt { - opcode: Opcode, - cond: IntCC, - destination: Ebb, - args: ValueList, - }, - BranchFloat { - opcode: Opcode, - cond: FloatCC, - destination: Ebb, - args: ValueList, - }, - BranchTable { - opcode: Opcode, - arg: Value, - table: JumpTable, - }, - Call { - opcode: Opcode, - func_ref: FuncRef, - args: ValueList, - }, - IndirectCall { - opcode: Opcode, - sig_ref: SigRef, - args: ValueList, - }, - FuncAddr { opcode: Opcode, func_ref: FuncRef }, - StackLoad { - opcode: Opcode, - stack_slot: StackSlot, - offset: Offset32, - }, - StackStore { - opcode: Opcode, - arg: Value, - stack_slot: StackSlot, - offset: Offset32, - }, - HeapAddr { - opcode: Opcode, - heap: ir::Heap, - arg: Value, - imm: Uimm32, - }, - Load { - opcode: Opcode, - flags: MemFlags, - arg: Value, - offset: Offset32, - }, - Store { - opcode: Opcode, - flags: MemFlags, - args: [Value; 2], - offset: Offset32, - }, - RegMove { - opcode: Opcode, - arg: Value, - src: RegUnit, - dst: RegUnit, - }, - CopySpecial { - opcode: Opcode, - src: RegUnit, - dst: RegUnit, - }, - RegSpill { - opcode: Opcode, - arg: Value, - src: RegUnit, - dst: StackSlot, - }, - RegFill { - opcode: Opcode, - arg: Value, - src: StackSlot, - dst: RegUnit, - }, - Trap { opcode: Opcode, code: ir::TrapCode }, - CondTrap { - opcode: Opcode, - arg: Value, - code: ir::TrapCode, - }, - IntCondTrap { - opcode: Opcode, - cond: IntCC, - arg: Value, - code: ir::TrapCode, - }, - FloatCondTrap { - opcode: Opcode, - cond: FloatCC, - arg: Value, - code: ir::TrapCode, - }, -} - /// A variable list of `Value` operands used for function call arguments and passing arguments to /// basic blocks. #[derive(Clone, Debug)] @@ -697,16 +512,12 @@ impl OperandConstraint { LaneOf => Bound(ctrl_type.lane_type()), AsBool => Bound(ctrl_type.as_bool()), HalfWidth => Bound(ctrl_type.half_width().expect("invalid type for half_width")), - DoubleWidth => { - Bound(ctrl_type.double_width().expect( - "invalid type for double_width", - )) - } - HalfVector => { - Bound(ctrl_type.half_vector().expect( - "invalid type for half_vector", - )) - } + DoubleWidth => Bound(ctrl_type.double_width().expect( + "invalid type for double_width", + )), + HalfVector => Bound(ctrl_type.half_vector().expect( + "invalid type for half_vector", + )), DoubleVector => Bound(ctrl_type.by(2).expect("invalid type for double_vector")), } } diff --git a/lib/cretonne/src/ir/jumptable.rs b/lib/cretonne/src/ir/jumptable.rs index 1c7bf79157..90b18b1bfe 100644 --- a/lib/cretonne/src/ir/jumptable.rs +++ b/lib/cretonne/src/ir/jumptable.rs @@ -3,11 +3,11 @@ //! Jump tables are declared in the preamble and assigned an `ir::entities::JumpTable` reference. //! The actual table of destinations is stored in a `JumpTableData` struct defined in this module. -use packed_option::PackedOption; use ir::entities::Ebb; +use packed_option::PackedOption; +use std::fmt::{self, Display, Formatter}; use std::iter; use std::slice; -use std::fmt::{self, Display, Formatter}; use std::vec::Vec; /// Contents of a jump table. @@ -140,10 +140,10 @@ impl Display for JumpTableData { #[cfg(test)] mod tests { use super::JumpTableData; - use ir::Ebb; use entity::EntityRef; - use std::vec::Vec; + use ir::Ebb; use std::string::ToString; + use std::vec::Vec; #[test] fn empty() { diff --git a/lib/cretonne/src/ir/layout.rs b/lib/cretonne/src/ir/layout.rs index 0e6e010e7c..2e3ac89903 100644 --- a/lib/cretonne/src/ir/layout.rs +++ b/lib/cretonne/src/ir/layout.rs @@ -4,11 +4,11 @@ //! determined by the `Layout` data structure defined in this module. use entity::EntityMap; +use ir::progpoint::{ExpandedProgramPoint, ProgramOrder}; use ir::{Ebb, Inst}; -use ir::progpoint::{ProgramOrder, ExpandedProgramPoint}; use packed_option::PackedOption; use std::cmp; -use std::iter::{Iterator, IntoIterator}; +use std::iter::{IntoIterator, Iterator}; use timing; /// The `Layout` struct determines the layout of EBBs and instructions in a function. It does not @@ -26,18 +26,18 @@ use timing; /// #[derive(Clone)] pub struct Layout { - // Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in - // both ends by `None`. + /// Linked list nodes for the layout order of EBBs Forms a doubly linked list, terminated in + /// both ends by `None`. ebbs: EntityMap, - // Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, - // terminated in both ends by `None`. + /// Linked list nodes for the layout order of instructions. Forms a double linked list per EBB, + /// terminated in both ends by `None`. insts: EntityMap, - // First EBB in the layout order, or `None` when no EBBs have been laid out. + /// First EBB in the layout order, or `None` when no EBBs have been laid out. first_ebb: Option, - // Last EBB in the layout order, or `None` when no EBBs have been laid out. + /// Last EBB in the layout order, or `None` when no EBBs have been laid out. last_ebb: Option, } @@ -61,32 +61,31 @@ impl Layout { } } -// Sequence numbers. -// -// All instructions and EBBs are given a sequence number that can be used to quickly determine -// their relative position in the layout. The sequence numbers are not contiguous, but are assigned -// like line numbers in BASIC: 10, 20, 30, ... -// -// The EBB sequence numbers are strictly increasing, and so are the instruction sequence numbers -// within an EBB. The instruction sequence numbers are all between the sequence number of their -// containing EBB and the following EBB. -// -// The result is that sequence numbers work like BASIC line numbers for the textual representation -// of the IL. +/// Sequence numbers. +/// +/// All instructions and EBBs are given a sequence number that can be used to quickly determine +/// their relative position in the layout. The sequence numbers are not contiguous, but are assigned +/// like line numbers in BASIC: 10, 20, 30, ... +/// +/// The EBB sequence numbers are strictly increasing, and so are the instruction sequence numbers +/// within an EBB. The instruction sequence numbers are all between the sequence number of their +/// containing EBB and the following EBB. +/// +/// The result is that sequence numbers work like BASIC line numbers for the textual form of the IR. type SequenceNumber = u32; -// Initial stride assigned to new sequence numbers. +/// Initial stride assigned to new sequence numbers. const MAJOR_STRIDE: SequenceNumber = 10; -// Secondary stride used when renumbering locally. +/// Secondary stride used when renumbering locally. const MINOR_STRIDE: SequenceNumber = 2; -// Limit on the sequence number range we'll renumber locally. If this limit is exceeded, we'll -// switch to a full function renumbering. +/// Limit on the sequence number range we'll renumber locally. If this limit is exceeded, we'll +/// switch to a full function renumbering. const LOCAL_LIMIT: SequenceNumber = 100 * MINOR_STRIDE; -// Compute the midpoint between `a` and `b`. -// Return `None` if the midpoint would be equal to either. +/// Compute the midpoint between `a` and `b`. +/// Return `None` if the midpoint would be equal to either. fn midpoint(a: SequenceNumber, b: SequenceNumber) -> Option { debug_assert!(a < b); // Avoid integer overflow. @@ -428,7 +427,7 @@ impl Layout { } /// Return an iterator over all EBBs in layout order. - pub fn ebbs<'f>(&'f self) -> Ebbs<'f> { + pub fn ebbs(&self) -> Ebbs { Ebbs { layout: self, next: self.first_ebb, @@ -611,7 +610,7 @@ impl Layout { } /// Iterate over the instructions in `ebb` in layout order. - pub fn ebb_insts<'f>(&'f self, ebb: Ebb) -> Insts<'f> { + pub fn ebb_insts(&self, ebb: Ebb) -> Insts { Insts { layout: self, head: self.ebbs[ebb].first_inst.into(), @@ -735,11 +734,10 @@ impl<'f> DoubleEndedIterator for Insts<'f> { } } - #[cfg(test)] mod tests { - use cursor::{Cursor, CursorPosition}; use super::Layout; + use cursor::{Cursor, CursorPosition}; use entity::EntityRef; use ir::{Ebb, Inst, ProgramOrder, SourceLoc}; use std::cmp::Ordering; diff --git a/lib/cretonne/src/ir/libcall.rs b/lib/cretonne/src/ir/libcall.rs index 85133611be..6434197247 100644 --- a/lib/cretonne/src/ir/libcall.rs +++ b/lib/cretonne/src/ir/libcall.rs @@ -6,7 +6,7 @@ use std::str::FromStr; /// The name of a runtime library routine. /// -/// Runtime library calls are generated for Cretonne IL instructions that don't have an equivalent +/// Runtime library calls are generated for Cretonne IR instructions that don't have an equivalent /// ISA instruction or an easy macro expansion. A `LibCall` is used as a well-known name to refer to /// the runtime library routine. This way, Cretonne doesn't have to know about the naming /// convention in the embedding VM's runtime library. diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 2144ad6811..ed9771ccad 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -1,50 +1,50 @@ -//! Representation of Cretonne IL functions. +//! Representation of Cretonne IR functions. -pub mod types; -pub mod entities; -pub mod condcodes; -pub mod immediates; -pub mod instructions; -pub mod stackslot; -pub mod jumptable; -pub mod dfg; -pub mod layout; -pub mod function; mod builder; +pub mod condcodes; +pub mod dfg; +pub mod entities; mod extfunc; mod extname; +pub mod function; mod globalvar; mod heap; +pub mod immediates; +pub mod instructions; +pub mod jumptable; +pub mod layout; mod libcall; mod memflags; mod progpoint; mod sourceloc; +pub mod stackslot; mod trapcode; +pub mod types; mod valueloc; -pub use ir::builder::{InstBuilder, InstBuilderBase, InstInserterBase, InsertBuilder}; +pub use ir::builder::{InsertBuilder, InstBuilder, InstBuilderBase, InstInserterBase}; pub use ir::dfg::{DataFlowGraph, ValueDef}; -pub use ir::entities::{Ebb, Inst, Value, StackSlot, GlobalVar, JumpTable, FuncRef, SigRef, Heap}; -pub use ir::extfunc::{Signature, CallConv, AbiParam, ArgumentExtension, ArgumentPurpose, - ExtFuncData}; +pub use ir::entities::{Ebb, FuncRef, GlobalVar, Heap, Inst, JumpTable, SigRef, StackSlot, Value}; +pub use ir::extfunc::{AbiParam, ArgumentExtension, ArgumentPurpose, CallConv, ExtFuncData, + Signature}; pub use ir::extname::ExternalName; pub use ir::function::Function; pub use ir::globalvar::GlobalVarData; -pub use ir::heap::{HeapData, HeapStyle, HeapBase}; -pub use ir::instructions::{Opcode, InstructionData, VariableArgs, ValueList, ValueListPool}; +pub use ir::heap::{HeapBase, HeapData, HeapStyle}; +pub use ir::instructions::{InstructionData, Opcode, ValueList, ValueListPool, VariableArgs}; pub use ir::jumptable::JumpTableData; pub use ir::layout::Layout; pub use ir::libcall::LibCall; pub use ir::memflags::MemFlags; -pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; +pub use ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint}; pub use ir::sourceloc::SourceLoc; -pub use ir::stackslot::{StackSlots, StackSlotKind, StackSlotData}; +pub use ir::stackslot::{StackSlotData, StackSlotKind, StackSlots}; pub use ir::trapcode::TrapCode; pub use ir::types::Type; -pub use ir::valueloc::{ValueLoc, ArgumentLoc}; +pub use ir::valueloc::{ArgumentLoc, ValueLoc}; use binemit; -use entity::{PrimaryMap, EntityMap}; +use entity::{EntityMap, PrimaryMap}; use isa; /// Map of value locations. diff --git a/lib/cretonne/src/ir/progpoint.rs b/lib/cretonne/src/ir/progpoint.rs index ce5b108e54..4a0785aef4 100644 --- a/lib/cretonne/src/ir/progpoint.rs +++ b/lib/cretonne/src/ir/progpoint.rs @@ -2,9 +2,9 @@ use entity::EntityRef; use ir::{Ebb, Inst, ValueDef}; +use std::cmp; use std::fmt; use std::u32; -use std::cmp; /// A `ProgramPoint` represents a position in a function where the live range of an SSA value can /// begin or end. It can be either: @@ -12,7 +12,7 @@ use std::cmp; /// 1. An instruction or /// 2. An EBB header. /// -/// This corresponds more or less to the lines in the textual representation of Cretonne IL. +/// This corresponds more or less to the lines in the textual form of Cretonne IR. #[derive(PartialEq, Eq, Clone, Copy)] pub struct ProgramPoint(u32); @@ -147,7 +147,7 @@ pub trait ProgramOrder { mod tests { use super::*; use entity::EntityRef; - use ir::{Inst, Ebb}; + use ir::{Ebb, Inst}; use std::string::ToString; #[test] diff --git a/lib/cretonne/src/ir/sourceloc.rs b/lib/cretonne/src/ir/sourceloc.rs index 36e5247488..768bbf99d5 100644 --- a/lib/cretonne/src/ir/sourceloc.rs +++ b/lib/cretonne/src/ir/sourceloc.rs @@ -7,7 +7,7 @@ use std::fmt; /// A source location. /// -/// This is an opaque 32-bit number attached to each Cretonne IL instruction. Cretonne does not +/// This is an opaque 32-bit number attached to each Cretonne IR instruction. Cretonne does not /// interpret source locations in any way, they are simply preserved from the input to the output. /// /// The default source location uses the all-ones bit pattern `!0`. It is used for instructions diff --git a/lib/cretonne/src/ir/stackslot.rs b/lib/cretonne/src/ir/stackslot.rs index 5f73fb4b37..9dfcfa7d21 100644 --- a/lib/cretonne/src/ir/stackslot.rs +++ b/lib/cretonne/src/ir/stackslot.rs @@ -3,12 +3,13 @@ //! The `StackSlotData` struct keeps track of a single stack slot in a function. //! -use entity::{PrimaryMap, Keys}; -use ir::{Type, StackSlot}; +use entity::{Iter, IterMut, Keys, PrimaryMap}; +use ir::{StackSlot, Type}; use packed_option::PackedOption; use std::cmp; use std::fmt; use std::ops::{Index, IndexMut}; +use std::slice; use std::str::FromStr; use std::vec::Vec; @@ -208,6 +209,26 @@ impl StackSlots { self.slots[ss].offset = Some(offset); } + /// Get an iterator over all the stack slot keys. + pub fn iter(&self) -> Iter { + self.slots.iter() + } + + /// Get an iterator over all the stack slot keys, mutable edition. + pub fn iter_mut(&mut self) -> IterMut { + self.slots.iter_mut() + } + + /// Get an iterator over all the stack slot records. + pub fn values(&self) -> slice::Iter { + self.slots.values() + } + + /// Get an iterator over all the stack slot records, mutable edition. + pub fn values_mut(&mut self) -> slice::IterMut { + self.slots.values_mut() + } + /// Get an iterator over all the stack slot keys. pub fn keys(&self) -> Keys { self.slots.keys() @@ -317,9 +338,9 @@ impl StackSlots { #[cfg(test)] mod tests { + use super::*; use ir::Function; use ir::types; - use super::*; use std::string::ToString; #[test] diff --git a/lib/cretonne/src/ir/trapcode.rs b/lib/cretonne/src/ir/trapcode.rs index 1487a63aad..fe712b7ffa 100644 --- a/lib/cretonne/src/ir/trapcode.rs +++ b/lib/cretonne/src/ir/trapcode.rs @@ -38,6 +38,10 @@ pub enum TrapCode { /// Failed float-to-int conversion. BadConversionToInteger, + /// Execution has potentially run too long and may be interrupted. + /// This trap is resumable. + Interrupt, + /// A user-defined trap code. User(u16), } @@ -54,6 +58,7 @@ impl Display for TrapCode { IntegerOverflow => "int_ovf", IntegerDivisionByZero => "int_divz", BadConversionToInteger => "bad_toint", + Interrupt => "interrupt", User(x) => return write!(f, "user{}", x), }; f.write_str(identifier) @@ -74,6 +79,7 @@ impl FromStr for TrapCode { "int_ovf" => Ok(IntegerOverflow), "int_divz" => Ok(IntegerDivisionByZero), "bad_toint" => Ok(BadConversionToInteger), + "interrupt" => Ok(Interrupt), _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()), _ => Err(()), } diff --git a/lib/cretonne/src/ir/types.rs b/lib/cretonne/src/ir/types.rs index 07cf97aefa..496312b1c4 100644 --- a/lib/cretonne/src/ir/types.rs +++ b/lib/cretonne/src/ir/types.rs @@ -1,7 +1,7 @@ //! Common types for the Cretonne code generator. use std::default::Default; -use std::fmt::{self, Display, Debug, Formatter}; +use std::fmt::{self, Debug, Display, Formatter}; /// The type of an SSA value. /// @@ -24,10 +24,10 @@ pub struct Type(u8); /// a SIMD vector. pub const VOID: Type = Type(0); -// Start of the lane types. See also `meta/cdsl.types.py`. +/// Start of the lane types. See also `meta/cdsl.types.py`. const LANE_BASE: u8 = 0x70; -// Start of the 2-lane vector types. +/// Start of the 2-lane vector types. const VECTOR_BASE: u8 = LANE_BASE + 16; // Include code generated by `lib/cretonne/meta/gen_types.py`. This file contains constant diff --git a/lib/cretonne/src/ir/valueloc.rs b/lib/cretonne/src/ir/valueloc.rs index 1e3cf20f87..aecf23d0bb 100644 --- a/lib/cretonne/src/ir/valueloc.rs +++ b/lib/cretonne/src/ir/valueloc.rs @@ -3,8 +3,8 @@ //! The register allocator assigns every SSA value to either a register or a stack slot. This //! assignment is represented by a `ValueLoc` object. -use isa::{RegInfo, RegUnit}; use ir::StackSlot; +use isa::{RegInfo, RegUnit}; use std::fmt; /// Value location. diff --git a/lib/cretonne/src/isa/arm32/abi.rs b/lib/cretonne/src/isa/arm32/abi.rs index 47efbc97bf..528a396570 100644 --- a/lib/cretonne/src/isa/arm32/abi.rs +++ b/lib/cretonne/src/isa/arm32/abi.rs @@ -1,10 +1,10 @@ //! ARM ABI implementation. +use super::registers::{D, GPR, Q, S}; use ir; use isa::RegClass; use regalloc::AllocatableSet; use settings as shared_settings; -use super::registers::{S, D, Q, GPR}; /// Legalize `sig`. pub fn legalize_signature( diff --git a/lib/cretonne/src/isa/arm32/binemit.rs b/lib/cretonne/src/isa/arm32/binemit.rs index 78fa2fd657..9f50e63993 100644 --- a/lib/cretonne/src/isa/arm32/binemit.rs +++ b/lib/cretonne/src/isa/arm32/binemit.rs @@ -1,6 +1,6 @@ //! Emitting binary ARM32 machine code. -use binemit::{CodeSink, bad_encoding}; +use binemit::{bad_encoding, CodeSink}; use ir::{Function, Inst}; use regalloc::RegDiversions; diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 02e0d5f1af..350764a436 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -1,20 +1,20 @@ //! ARM 32-bit Instruction Set Architecture. -pub mod settings; mod abi; mod binemit; mod enc_tables; mod registers; +pub mod settings; -use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; -use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; -use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; +use binemit::{emit_function, CodeSink, MemoryCodeSink}; use ir; +use isa::Builder as IsaBuilder; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; +use isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use regalloc; -use std::fmt; use std::boxed::Box; +use std::fmt; #[allow(dead_code)] struct Isa { diff --git a/lib/cretonne/src/isa/arm32/registers.rs b/lib/cretonne/src/isa/arm32/registers.rs index e2c4813bdd..305890b29e 100644 --- a/lib/cretonne/src/isa/arm32/registers.rs +++ b/lib/cretonne/src/isa/arm32/registers.rs @@ -6,7 +6,7 @@ include!(concat!(env!("OUT_DIR"), "/registers-arm32.rs")); #[cfg(test)] mod tests { - use super::{INFO, GPR, S, D}; + use super::{D, GPR, INFO, S}; use isa::RegUnit; use std::string::{String, ToString}; diff --git a/lib/cretonne/src/isa/arm64/abi.rs b/lib/cretonne/src/isa/arm64/abi.rs index 14af59ab18..0540746afa 100644 --- a/lib/cretonne/src/isa/arm64/abi.rs +++ b/lib/cretonne/src/isa/arm64/abi.rs @@ -1,10 +1,10 @@ //! ARM 64 ABI implementation. +use super::registers::{FPR, GPR}; use ir; use isa::RegClass; use regalloc::AllocatableSet; use settings as shared_settings; -use super::registers::{GPR, FPR}; /// Legalize `sig`. pub fn legalize_signature( diff --git a/lib/cretonne/src/isa/arm64/binemit.rs b/lib/cretonne/src/isa/arm64/binemit.rs index a4b79cda55..dd0654067e 100644 --- a/lib/cretonne/src/isa/arm64/binemit.rs +++ b/lib/cretonne/src/isa/arm64/binemit.rs @@ -1,6 +1,6 @@ //! Emitting binary ARM64 machine code. -use binemit::{CodeSink, bad_encoding}; +use binemit::{bad_encoding, CodeSink}; use ir::{Function, Inst}; use regalloc::RegDiversions; diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index 8239cb01e9..3b63d56d54 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -1,20 +1,20 @@ //! ARM 64-bit Instruction Set Architecture. -pub mod settings; mod abi; mod binemit; mod enc_tables; mod registers; +pub mod settings; -use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; -use isa::enc_tables::{lookup_enclist, Encodings}; -use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; +use binemit::{emit_function, CodeSink, MemoryCodeSink}; use ir; +use isa::Builder as IsaBuilder; +use isa::enc_tables::{lookup_enclist, Encodings}; +use isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use regalloc; -use std::fmt; use std::boxed::Box; +use std::fmt; #[allow(dead_code)] struct Isa { diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs index f831c0e187..b08f635ba4 100644 --- a/lib/cretonne/src/isa/constraints.rs +++ b/lib/cretonne/src/isa/constraints.rs @@ -8,8 +8,8 @@ //! are satisfied. use binemit::CodeOffset; +use ir::{Function, Inst, ValueLoc}; use isa::{RegClass, RegUnit}; -use ir::{Function, ValueLoc, Inst}; use regalloc::RegDiversions; /// Register constraint for a single value operand or instruction result. @@ -205,6 +205,5 @@ mod tests { // Backward limit assert!(t1.contains(1000, 748)); assert!(!t1.contains(1000, 746)); - } } diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 7453b7c044..c987606fa6 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -3,8 +3,8 @@ //! This module contains types and functions for working with the encoding tables generated by //! `lib/cretonne/meta/gen_encoding.py`. -use constant_hash::{Table, probe}; -use ir::{Type, Opcode, DataFlowGraph, InstructionData}; +use constant_hash::{probe, Table}; +use ir::{DataFlowGraph, InstructionData, Opcode, Type}; use isa::{Encoding, Legalize}; use settings::PredicateView; use std::ops::Range; diff --git a/lib/cretonne/src/isa/encoding.rs b/lib/cretonne/src/isa/encoding.rs index b82137a6b8..1b850d5c64 100644 --- a/lib/cretonne/src/isa/encoding.rs +++ b/lib/cretonne/src/isa/encoding.rs @@ -1,7 +1,7 @@ //! The `Encoding` struct. use binemit::CodeOffset; -use isa::constraints::{RecipeConstraints, BranchRange}; +use isa::constraints::{BranchRange, RecipeConstraints}; use std::fmt; /// Bits needed to encode an instruction as binary machine code. diff --git a/lib/cretonne/src/isa/intel/abi.rs b/lib/cretonne/src/isa/intel/abi.rs index 0a14566b50..e9019291ef 100644 --- a/lib/cretonne/src/isa/intel/abi.rs +++ b/lib/cretonne/src/isa/intel/abi.rs @@ -1,19 +1,18 @@ //! Intel ABI implementation. +use super::registers::{FPR, GPR, RU}; +use abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion}; +use cursor::{Cursor, CursorPosition, EncCursor}; use ir; +use ir::immediates::Imm64; +use ir::stackslot::{StackOffset, StackSize}; +use ir::{AbiParam, ArgumentExtension, ArgumentLoc, ArgumentPurpose, CallConv, InstBuilder}; use isa::{RegClass, RegUnit, TargetIsa}; use regalloc::AllocatableSet; +use result; use settings as shared_settings; -use super::registers::{GPR, FPR, RU}; -use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; -use ir::{AbiParam, ArgumentPurpose, ArgumentLoc, ArgumentExtension, CallConv, InstBuilder}; -use ir::stackslot::{StackSize, StackOffset}; -use ir::immediates::Imm64; use stack_layout::layout_stack; use std::i32; -use cursor::{Cursor, EncCursor, CursorPosition}; -use result; - /// Argument registers for x86-64 static ARG_GPRS: [RU; 6] = [RU::rdi, RU::rsi, RU::rdx, RU::rcx, RU::r8, RU::r9]; @@ -171,7 +170,7 @@ pub fn callee_saved_registers(flags: &shared_settings::Flags) -> &'static [RU] { pub fn prologue_epilogue(func: &mut ir::Function, isa: &TargetIsa) -> result::CtonResult { match func.signature.call_conv { - ir::CallConv::Native => native_prologue_epilogue(func, isa), + ir::CallConv::SystemV => system_v_prologue_epilogue(func, isa), ir::CallConv::SpiderWASM => spiderwasm_prologue_epilogue(func, isa), } } @@ -194,7 +193,7 @@ pub fn spiderwasm_prologue_epilogue( } /// Insert a System V-compatible prologue and epilogue. -pub fn native_prologue_epilogue(func: &mut ir::Function, isa: &TargetIsa) -> result::CtonResult { +pub fn system_v_prologue_epilogue(func: &mut ir::Function, isa: &TargetIsa) -> result::CtonResult { // The original 32-bit x86 ELF ABI had a 4-byte aligned stack pointer, but // newer versions use a 16-byte aligned stack pointer. let stack_align = 16; @@ -242,17 +241,17 @@ pub fn native_prologue_epilogue(func: &mut ir::Function, isa: &TargetIsa) -> res // Set up the cursor and insert the prologue let entry_ebb = func.layout.entry_block().expect("missing entry block"); let mut pos = EncCursor::new(func, isa).at_first_insertion_point(entry_ebb); - insert_native_prologue(&mut pos, local_stack_size, csr_type, csrs); + insert_system_v_prologue(&mut pos, local_stack_size, csr_type, csrs); // Reset the cursor and insert the epilogue let mut pos = pos.at_position(CursorPosition::Nowhere); - insert_native_epilogues(&mut pos, local_stack_size, csr_type, csrs); + insert_system_v_epilogues(&mut pos, local_stack_size, csr_type, csrs); Ok(()) } /// Insert the prologue for a given function. -fn insert_native_prologue( +fn insert_system_v_prologue( pos: &mut EncCursor, stack_size: i64, csr_type: ir::types::Type, @@ -286,7 +285,7 @@ fn insert_native_prologue( } /// Find all `return` instructions and insert epilogues before them. -fn insert_native_epilogues( +fn insert_system_v_epilogues( pos: &mut EncCursor, stack_size: i64, csr_type: ir::types::Type, @@ -296,14 +295,14 @@ fn insert_native_epilogues( pos.goto_last_inst(ebb); if let Some(inst) = pos.current_inst() { if pos.func.dfg[inst].opcode().is_return() { - insert_native_epilogue(inst, stack_size, pos, csr_type, csrs); + insert_system_v_epilogue(inst, stack_size, pos, csr_type, csrs); } } } } /// Insert an epilogue given a specific `return` instruction. -fn insert_native_epilogue( +fn insert_system_v_epilogue( inst: ir::Inst, stack_size: i64, pos: &mut EncCursor, diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 2d045a0882..da14f80d29 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -1,11 +1,11 @@ //! Emitting binary Intel machine code. -use binemit::{CodeSink, Reloc, bad_encoding}; -use ir::{Function, Inst, Ebb, InstructionData, Opcode}; -use ir::condcodes::{CondCode, IntCC, FloatCC}; -use isa::{RegUnit, StackRef, StackBase, StackBaseMask}; -use regalloc::RegDiversions; use super::registers::RU; +use binemit::{bad_encoding, CodeSink, Reloc}; +use ir::condcodes::{CondCode, FloatCC, IntCC}; +use ir::{Ebb, Function, Inst, InstructionData, Opcode, TrapCode}; +use isa::{RegUnit, StackBase, StackBaseMask, StackRef}; +use regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs")); @@ -257,7 +257,7 @@ fn icc2opc(cond: IntCC) -> u16 { /// Get the low 4 bits of an opcode for a floating point condition code. /// -/// The ucomiss/ucomisd instructions set the EFLAGS bits CF/PF/CF like this: +/// The ucomiss/ucomisd instructions set the FLAGS bits CF/PF/CF like this: /// /// ZPC OSA /// UN 111 000 diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index d3f62adf7c..d847eb6d53 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -1,16 +1,16 @@ //! Encoding tables for Intel ISAs. +use super::registers::*; use bitset::BitSet; use cursor::{Cursor, FuncCursor}; use flowgraph::ControlFlowGraph; -use ir::{self, InstBuilder}; use ir::condcodes::IntCC; +use ir::{self, InstBuilder}; +use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; -use isa; use predicates; -use super::registers::*; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); include!(concat!(env!("OUT_DIR"), "/legalize-intel.rs")); @@ -22,7 +22,6 @@ fn expand_sdivrem( cfg: &mut ControlFlowGraph, isa: &isa::TargetIsa, ) { - let (x, y, is_srem) = match func.dfg[inst] { ir::InstructionData::Binary { opcode: ir::Opcode::Sdiv, @@ -113,7 +112,6 @@ fn expand_udivrem( _cfg: &mut ControlFlowGraph, isa: &isa::TargetIsa, ) { - let (x, y, is_urem) = match func.dfg[inst] { ir::InstructionData::Binary { opcode: ir::Opcode::Udiv, @@ -324,7 +322,7 @@ fn expand_fcvt_to_sint( cfg: &mut ControlFlowGraph, _isa: &isa::TargetIsa, ) { - use ir::condcodes::{IntCC, FloatCC}; + use ir::condcodes::{FloatCC, IntCC}; use ir::immediates::{Ieee32, Ieee64}; let x; @@ -423,7 +421,7 @@ fn expand_fcvt_to_uint( cfg: &mut ControlFlowGraph, _isa: &isa::TargetIsa, ) { - use ir::condcodes::{IntCC, FloatCC}; + use ir::condcodes::{FloatCC, IntCC}; use ir::immediates::{Ieee32, Ieee64}; let x; diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 578dac2d16..91d8325a6b 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -1,22 +1,22 @@ //! Intel Instruction Set Architectures. -pub mod settings; mod abi; mod binemit; mod enc_tables; mod registers; +pub mod settings; -use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; -use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; -use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; +use binemit::{emit_function, CodeSink, MemoryCodeSink}; use ir; +use isa::Builder as IsaBuilder; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; +use isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use regalloc; use result; -use timing; -use std::fmt; use std::boxed::Box; +use std::fmt; +use timing; #[allow(dead_code)] struct Isa { @@ -58,6 +58,10 @@ impl TargetIsa for Isa { &self.shared_flags } + fn uses_cpu_flags(&self) -> bool { + true + } + fn register_info(&self) -> RegInfo { registers::INFO.clone() } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index aefe93a05d..39d7d4eb60 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -40,21 +40,21 @@ //! The configured target ISA trait object is a `Box` which can be used for multiple //! concurrent function compilations. -pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, BranchRange}; -pub use isa::encoding::{Encoding, EncInfo}; -pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; +pub use isa::constraints::{BranchRange, ConstraintKind, OperandConstraint, RecipeConstraints}; +pub use isa::encoding::{EncInfo, Encoding}; +pub use isa::registers::{regs_overlap, RegClass, RegClassIndex, RegInfo, RegUnit}; pub use isa::stack::{StackBase, StackBaseMask, StackRef}; use binemit; use flowgraph; -use settings; use ir; +use isa::enc_tables::Encodings; use regalloc; use result; -use timing; -use isa::enc_tables::Encodings; -use std::fmt; +use settings; use std::boxed::Box; +use std::fmt; +use timing; #[cfg(build_riscv)] mod riscv; @@ -68,28 +68,26 @@ mod arm32; #[cfg(build_arm64)] mod arm64; -pub mod registers; -mod encoding; -mod enc_tables; mod constraints; +mod enc_tables; +mod encoding; +pub mod registers; mod stack; /// Returns a builder that can create a corresponding `TargetIsa` /// or `Err(LookupError::Unsupported)` if not enabled. macro_rules! isa_builder { - ($module:ident, $name:ident) => { - { - #[cfg($name)] - fn $name() -> Result { - Ok($module::isa_builder()) - }; - #[cfg(not($name))] - fn $name() -> Result { - Err(LookupError::Unsupported) - } - $name() + ($module:ident, $name:ident) => {{ + #[cfg($name)] + fn $name() -> Result { + Ok($module::isa_builder()) + }; + #[cfg(not($name))] + fn $name() -> Result { + Err(LookupError::Unsupported) } - }; + $name() + }}; } /// Look for a supported ISA with the given `name`. @@ -158,6 +156,11 @@ pub trait TargetIsa: fmt::Display { /// Get the ISA-independent flags that were used to make this trait object. fn flags(&self) -> &settings::Flags; + /// Does the CPU implement scalar comparisons using a CPU flags register? + fn uses_cpu_flags(&self) -> bool { + false + } + /// Get a data structure describing the registers in this ISA. fn register_info(&self) -> RegInfo; @@ -243,8 +246,8 @@ pub trait TargetIsa: fmt::Display { fn prologue_epilogue(&self, func: &mut ir::Function) -> result::CtonResult { let _tt = timing::prologue_epilogue(); // This default implementation is unlikely to be good enough. + use ir::stackslot::{StackOffset, StackSize}; use stack_layout::layout_stack; - use ir::stackslot::{StackSize, StackOffset}; let word_size = if self.flags().is_64bit() { 8 } else { 4 }; diff --git a/lib/cretonne/src/isa/riscv/abi.rs b/lib/cretonne/src/isa/riscv/abi.rs index 1db377b0ef..0782fa5e53 100644 --- a/lib/cretonne/src/isa/riscv/abi.rs +++ b/lib/cretonne/src/isa/riscv/abi.rs @@ -5,13 +5,13 @@ //! //! This doesn't support the soft-float ABI at the moment. -use abi::{ArgAction, ValueConversion, ArgAssigner, legalize_args}; -use ir::{self, Type, AbiParam, ArgumentLoc, ArgumentExtension, ArgumentPurpose}; +use super::registers::{FPR, GPR}; +use super::settings; +use abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion}; +use ir::{self, AbiParam, ArgumentExtension, ArgumentLoc, ArgumentPurpose, Type}; use isa::RegClass; use regalloc::AllocatableSet; use settings as shared_settings; -use super::registers::{GPR, FPR}; -use super::settings; use std::i32; struct Args { diff --git a/lib/cretonne/src/isa/riscv/binemit.rs b/lib/cretonne/src/isa/riscv/binemit.rs index 8cc1ef17d1..120324d1be 100644 --- a/lib/cretonne/src/isa/riscv/binemit.rs +++ b/lib/cretonne/src/isa/riscv/binemit.rs @@ -1,8 +1,8 @@ //! Emitting binary RISC-V machine code. -use binemit::{CodeSink, Reloc, bad_encoding}; +use binemit::{bad_encoding, CodeSink, Reloc}; use ir::{Function, Inst, InstructionData}; -use isa::{RegUnit, StackRef, StackBaseMask}; +use isa::{RegUnit, StackBaseMask, StackRef}; use predicates::is_signed_int; use regalloc::RegDiversions; use std::u32; diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index 1f0f2e132d..46b2458b49 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -1,12 +1,12 @@ //! Encoding tables for RISC-V. +use super::registers::*; use ir; use isa; use isa::constraints::*; use isa::enc_tables::*; use isa::encoding::RecipeSizing; use predicates; -use super::registers::*; // Include the generated encoding tables: // - `LEVEL1_RV32` diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 6fc976686b..9c3a498d5e 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -1,20 +1,20 @@ //! RISC-V Instruction Set Architecture. -pub mod settings; mod abi; mod binemit; mod enc_tables; mod registers; +pub mod settings; use super::super::settings as shared_settings; -use binemit::{CodeSink, MemoryCodeSink, emit_function}; -use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; -use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, RegClass, EncInfo}; +use binemit::{emit_function, CodeSink, MemoryCodeSink}; use ir; +use isa::Builder as IsaBuilder; +use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; +use isa::{EncInfo, RegClass, RegInfo, TargetIsa}; use regalloc; -use std::fmt; use std::boxed::Box; +use std::fmt; #[allow(dead_code)] struct Isa { @@ -113,10 +113,10 @@ impl TargetIsa for Isa { #[cfg(test)] mod tests { - use settings::{self, Configurable}; - use isa; use ir::{DataFlowGraph, InstructionData, Opcode}; - use ir::{types, immediates}; + use ir::{immediates, types}; + use isa; + use settings::{self, Configurable}; use std::string::{String, ToString}; fn encstr(isa: &isa::TargetIsa, enc: Result) -> String { diff --git a/lib/cretonne/src/isa/riscv/registers.rs b/lib/cretonne/src/isa/riscv/registers.rs index e2073899b6..d6254fe38e 100644 --- a/lib/cretonne/src/isa/riscv/registers.rs +++ b/lib/cretonne/src/isa/riscv/registers.rs @@ -6,7 +6,7 @@ include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs")); #[cfg(test)] mod tests { - use super::{INFO, GPR, FPR}; + use super::{FPR, GPR, INFO}; use isa::RegUnit; use std::string::{String, ToString}; diff --git a/lib/cretonne/src/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs index 2f0f6822a9..27cba43f2f 100644 --- a/lib/cretonne/src/isa/riscv/settings.rs +++ b/lib/cretonne/src/isa/riscv/settings.rs @@ -22,12 +22,12 @@ mod tests { assert_eq!( f.to_string(), "[riscv]\n\ - supports_m = false\n\ - supports_a = false\n\ - supports_f = false\n\ - supports_d = false\n\ - enable_m = true\n\ - enable_e = false\n" + supports_m = false\n\ + supports_a = false\n\ + supports_f = false\n\ + supports_d = false\n\ + enable_m = true\n\ + enable_e = false\n" ); // Predicates are not part of the Display output. assert_eq!(f.full_float(), false); diff --git a/lib/cretonne/src/isa/stack.rs b/lib/cretonne/src/isa/stack.rs index e8a9b6981b..0db6279d3f 100644 --- a/lib/cretonne/src/isa/stack.rs +++ b/lib/cretonne/src/isa/stack.rs @@ -4,8 +4,8 @@ //! defined in this module expresses the low-level details of accessing a stack slot from an //! encoded instruction. -use ir::stackslot::{StackSlots, StackOffset, StackSlotKind}; use ir::StackSlot; +use ir::stackslot::{StackOffset, StackSlotKind, StackSlots}; /// A method for referencing a stack slot in the current stack frame. /// @@ -68,6 +68,8 @@ pub enum StackBase { FP = 1, /// Use an explicit zone pointer in a general-purpose register. + /// + /// This feature is not yet implemented. Zone = 2, } diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 0c5479650c..61f07884ca 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -20,9 +20,9 @@ use abi::{legalize_abi_value, ValueConversion}; use cursor::{Cursor, FuncCursor}; use flowgraph::ControlFlowGraph; -use ir::{Function, DataFlowGraph, Inst, InstBuilder, Ebb, Type, Value, Signature, SigRef, - AbiParam, ArgumentPurpose, ArgumentLoc, ValueLoc}; use ir::instructions::CallInfo; +use ir::{AbiParam, ArgumentLoc, ArgumentPurpose, DataFlowGraph, Ebb, Function, Inst, InstBuilder, + SigRef, Signature, Type, Value, ValueLoc}; use isa::TargetIsa; use legalizer::split::{isplit, vsplit}; use std::vec::Vec; @@ -35,9 +35,9 @@ use std::vec::Vec; pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) { isa.legalize_signature(&mut func.signature, true); func.signature.compute_argument_bytes(); - for sig in func.dfg.signatures.keys() { - isa.legalize_signature(&mut func.dfg.signatures[sig], false); - func.dfg.signatures[sig].compute_argument_bytes(); + for sig_data in func.dfg.signatures.values_mut() { + isa.legalize_signature(sig_data, false); + sig_data.compute_argument_bytes(); } if let Some(entry) = func.layout.entry_block() { diff --git a/lib/cretonne/src/legalizer/globalvar.rs b/lib/cretonne/src/legalizer/globalvar.rs index 5f6427822b..27494bfd87 100644 --- a/lib/cretonne/src/legalizer/globalvar.rs +++ b/lib/cretonne/src/legalizer/globalvar.rs @@ -45,15 +45,18 @@ fn vmctx_addr(inst: ir::Inst, func: &mut ir::Function, offset: i64) { /// Expand a `global_addr` instruction for a deref global. fn deref_addr(inst: ir::Inst, func: &mut ir::Function, base: ir::GlobalVar, offset: i64) { // We need to load a pointer from the `base` global variable, so insert a new `global_addr` - // instruction. This depends on the iterative legalization loop. Note that the IL verifier + // instruction. This depends on the iterative legalization loop. Note that the IR verifier // detects any cycles in the `deref` globals. let ptr_ty = func.dfg.value_type(func.dfg.first_result(inst)); let mut pos = FuncCursor::new(func).at_inst(inst); pos.use_srcloc(inst); let base_addr = pos.ins().global_addr(ptr_ty, base); - // TODO: We could probably set both `notrap` and `aligned` on this load instruction. - let base_ptr = pos.ins().load(ptr_ty, ir::MemFlags::new(), base_addr, 0); + let mut mflags = ir::MemFlags::new(); + // Deref globals are required to be accessible and aligned. + mflags.set_notrap(); + mflags.set_aligned(); + let base_ptr = pos.ins().load(ptr_ty, mflags, base_addr, 0); pos.func.dfg.replace(inst).iadd_imm(base_ptr, offset); } diff --git a/lib/cretonne/src/legalizer/heap.rs b/lib/cretonne/src/legalizer/heap.rs index 37ce226351..b3a595833f 100644 --- a/lib/cretonne/src/legalizer/heap.rs +++ b/lib/cretonne/src/legalizer/heap.rs @@ -5,8 +5,8 @@ use cursor::{Cursor, FuncCursor}; use flowgraph::ControlFlowGraph; -use ir::{self, InstBuilder, MemFlags}; use ir::condcodes::IntCC; +use ir::{self, InstBuilder, MemFlags}; use isa::TargetIsa; /// Expand a `heap_addr` instruction according to the definition of the heap. @@ -58,7 +58,11 @@ fn dynamic_addr( // Start with the bounds check. Trap if `offset + size > bound`. let bound_addr = pos.ins().global_addr(addr_ty, bound_gv); - let bound = pos.ins().load(offset_ty, MemFlags::new(), bound_addr, 0); + let mut mflags = MemFlags::new(); + // The bound variable is requied to be accessible and aligned. + mflags.set_notrap(); + mflags.set_aligned(); + let bound = pos.ins().load(offset_ty, mflags, bound_addr, 0); let oob; if size == 1 { @@ -175,7 +179,11 @@ fn offset_addr( ir::HeapBase::ReservedReg => unimplemented!(), ir::HeapBase::GlobalVar(base_gv) => { let base_addr = pos.ins().global_addr(addr_ty, base_gv); - let base = pos.ins().load(addr_ty, MemFlags::new(), base_addr, 0); + let mut mflags = MemFlags::new(); + // The base address variable is requied to be accessible and aligned. + mflags.set_notrap(); + mflags.set_aligned(); + let base = pos.ins().load(addr_ty, mflags, base_addr, 0); pos.func.dfg.replace(inst).iadd(base, offset); } } diff --git a/lib/cretonne/src/legalizer/libcall.rs b/lib/cretonne/src/legalizer/libcall.rs index cdf6a763a4..1a3da4a9a1 100644 --- a/lib/cretonne/src/legalizer/libcall.rs +++ b/lib/cretonne/src/legalizer/libcall.rs @@ -29,8 +29,8 @@ pub fn expand_as_libcall(inst: ir::Inst, func: &mut ir::Function) -> bool { fn find_funcref(libcall: ir::LibCall, func: &ir::Function) -> Option { // We're assuming that all libcall function decls are at the end. // If we get this wrong, worst case we'll have duplicate libcall decls which is harmless. - for fref in func.dfg.ext_funcs.keys().rev() { - match func.dfg.ext_funcs[fref].name { + for (fref, func_data) in func.dfg.ext_funcs.iter().rev() { + match func_data.name { ir::ExternalName::LibCall(lc) => { if lc == libcall { return Some(fref); @@ -44,8 +44,8 @@ fn find_funcref(libcall: ir::LibCall, func: &ir::Function) -> Option ir::FuncRef { - // Start with a native calling convention. We'll give the ISA a chance to change it. - let mut sig = ir::Signature::new(ir::CallConv::Native); + // Start with a system_v calling convention. We'll give the ISA a chance to change it. + let mut sig = ir::Signature::new(ir::CallConv::SystemV); for &v in func.dfg.inst_args(inst) { sig.params.push(ir::AbiParam::new(func.dfg.value_type(v))); } diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index 51198d05a0..23b3f56c89 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -13,11 +13,11 @@ //! The legalizer does not deal with register allocation constraints. These constraints are derived //! from the encoding recipes, and solved later by the register allocator. +use bitset::BitSet; use cursor::{Cursor, FuncCursor}; use flowgraph::ControlFlowGraph; use ir::{self, InstBuilder}; use isa::TargetIsa; -use bitset::BitSet; use timing; mod boundary; @@ -56,28 +56,24 @@ pub fn legalize_function(func: &mut ir::Function, cfg: &mut ControlFlowGraph, is let opcode = pos.func.dfg[inst].opcode(); // Check for ABI boundaries that need to be converted to the legalized signature. - if opcode.is_call() && boundary::handle_call_abi(inst, pos.func, cfg) { - // Go back and legalize the inserted argument conversion instructions. - pos.set_position(prev_pos); - continue; - } - - if opcode.is_return() && boundary::handle_return_abi(inst, pos.func, cfg) { - // Go back and legalize the inserted return value conversion instructions. - pos.set_position(prev_pos); - continue; - } - - if opcode.is_branch() { + if opcode.is_call() { + if boundary::handle_call_abi(inst, pos.func, cfg) { + // Go back and legalize the inserted argument conversion instructions. + pos.set_position(prev_pos); + continue; + } + } else if opcode.is_return() { + if boundary::handle_return_abi(inst, pos.func, cfg) { + // Go back and legalize the inserted return value conversion instructions. + pos.set_position(prev_pos); + continue; + } + } else if opcode.is_branch() { split::simplify_branch_arguments(&mut pos.func.dfg, inst); } - match isa.encode( - &pos.func.dfg, - &pos.func.dfg[inst], - pos.func.dfg.ctrl_typevar(inst), - ) { - Ok(encoding) => pos.func.encodings[inst] = encoding, + match pos.func.update_encoding(inst, isa) { + Ok(()) => {} Err(action) => { // We should transform the instruction into legal equivalents. let changed = action(inst, pos.func, cfg, isa); @@ -239,7 +235,6 @@ fn expand_select( cfg.recompute_ebb(pos.func, old_ebb); } - /// Expand illegal `f32const` and `f64const` instructions. fn expand_fconst( inst: ir::Inst, diff --git a/lib/cretonne/src/legalizer/split.rs b/lib/cretonne/src/legalizer/split.rs index cdc60974e0..8f82166db8 100644 --- a/lib/cretonne/src/legalizer/split.rs +++ b/lib/cretonne/src/legalizer/split.rs @@ -66,7 +66,7 @@ use cursor::{Cursor, CursorPosition, FuncCursor}; use flowgraph::ControlFlowGraph; -use ir::{self, Ebb, Inst, Value, Type, Opcode, ValueDef, InstructionData, InstBuilder}; +use ir::{self, Ebb, Inst, InstBuilder, InstructionData, Opcode, Type, Value, ValueDef}; use std::iter; use std::vec::Vec; @@ -229,7 +229,6 @@ fn split_value( let hi = pos.func.dfg.append_ebb_param(ebb, split_type); reuse = Some((lo, hi)); - // Now the original value is dangling. Insert a concatenation instruction that can // compute it from the two new parameters. This also serves as a record of what we // did so a future call to this function doesn't have to redo the work. diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index 8511951b50..cbdd0bae1d 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -1,8 +1,34 @@ //! Cretonne code generation library. -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))] +#![cfg_attr(feature="cargo-clippy", allow( +// Rustfmt 0.9.0 is at odds with this lint: + block_in_if_condition_stmt, +// Produces only a false positive: + while_let_loop, +// Produces many false positives, but did produce some valid lints, now fixed: + needless_lifetimes, +// Generated code makes some style transgressions, but readability doesn't suffer much: + many_single_char_names, + identity_op, + needless_borrow, + cast_lossless, + unreadable_literal, + assign_op_pattern, + empty_line_after_outer_attr, +// Hard to avoid in generated code: + cyclomatic_complexity, + too_many_arguments, +// Code generator doesn't have a way to collapse identical arms: + match_same_arms, +// These are relatively minor style issues, but would be easy to fix: + new_without_default, + new_without_default_derive, + should_implement_trait, + redundant_field_names, + useless_let_if_seq, + len_without_is_empty))] // Turns on no_std and alloc features if std is not available. #![cfg_attr(not(feature = "std"), no_std)] @@ -25,7 +51,7 @@ pub use verifier::verify_function; pub use write::write_function; /// Version number of the cretonne crate. -pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); #[macro_use] pub mod dbg; @@ -34,6 +60,7 @@ pub mod entity; pub mod bforest; pub mod binemit; +pub mod cfg_printer; pub mod cursor; pub mod dominator_tree; pub mod flowgraph; @@ -41,6 +68,7 @@ pub mod ir; pub mod isa; pub mod loop_analysis; pub mod packed_option; +pub mod print_errors; pub mod result; pub mod settings; pub mod timing; @@ -50,11 +78,13 @@ mod abi; mod bitset; mod constant_hash; mod context; +mod dce; mod divconst_magic_numbers; mod iterators; mod legalizer; mod licm; mod partition_slice; +mod postopt; mod predicates; mod preopt; mod ref_slice; diff --git a/lib/cretonne/src/licm.rs b/lib/cretonne/src/licm.rs index e767fc838b..b5a8f5ed68 100644 --- a/lib/cretonne/src/licm.rs +++ b/lib/cretonne/src/licm.rs @@ -1,14 +1,14 @@ //! A Loop Invariant Code Motion optimization pass use cursor::{Cursor, FuncCursor}; -use ir::{Function, Ebb, Inst, Value, Type, InstBuilder, Layout}; -use flowgraph::ControlFlowGraph; -use std::collections::HashSet; use dominator_tree::DominatorTree; use entity::{EntityList, ListPool}; +use flowgraph::ControlFlowGraph; +use ir::{DataFlowGraph, Ebb, Function, Inst, InstBuilder, Layout, Opcode, Type, Value}; use loop_analysis::{Loop, LoopAnalysis}; -use timing; +use std::collections::HashSet; use std::vec::Vec; +use timing; /// Performs the LICM pass by detecting loops within the CFG and moving /// loop-invariant instructions out of them. @@ -27,10 +27,10 @@ pub fn do_licm( for lp in loop_analysis.loops() { // For each loop that we want to optimize we determine the set of loop-invariant // instructions - let invariant_inst = remove_loop_invariant_instructions(lp, func, cfg, loop_analysis); + let invariant_insts = remove_loop_invariant_instructions(lp, func, cfg, loop_analysis); // Then we create the loop's pre-header and fill it with the invariant instructions // Then we remove the invariant instructions from the loop body - if !invariant_inst.is_empty() { + if !invariant_insts.is_empty() { // If the loop has a natural pre-header we use it, otherwise we create it. let mut pos; match has_pre_header(&func.layout, cfg, domtree, loop_analysis.loop_header(lp)) { @@ -47,7 +47,7 @@ pub fn do_licm( }; // The last instruction of the pre-header is the termination instruction (usually // a jump) so we need to insert just before this. - for inst in invariant_inst { + for inst in invariant_insts { pos.insert_inst(inst); } } @@ -121,7 +121,6 @@ fn has_pre_header( result } - // Change the destination of a jump or branch instruction. Does nothing if called with a non-jump // or non-branch instruction. fn change_branch_jump_destination(inst: Inst, new_ebb: Ebb, func: &mut Function) { @@ -131,6 +130,29 @@ fn change_branch_jump_destination(inst: Inst, new_ebb: Ebb, func: &mut Function) } } +/// Test whether the given opcode is unsafe to even consider for LICM. +fn trivially_unsafe_for_licm(opcode: Opcode) -> bool { + opcode.can_load() || opcode.can_store() || opcode.is_call() || opcode.is_branch() || + opcode.is_terminator() || opcode.is_return() || + opcode.can_trap() || opcode.other_side_effects() || opcode.writes_cpu_flags() +} + +/// Test whether the given instruction is loop-invariant. +fn is_loop_invariant(inst: Inst, dfg: &DataFlowGraph, loop_values: &HashSet) -> bool { + if trivially_unsafe_for_licm(dfg[inst].opcode()) { + return false; + } + + let inst_args = dfg.inst_args(inst); + for arg in inst_args { + let arg = dfg.resolve_aliases(*arg); + if loop_values.contains(&arg) { + return false; + } + } + return true; +} + // Traverses a loop in reverse post-order from a header EBB and identify loop-invariant // instructions. These loop-invariant instructions are then removed from the code and returned // (in reverse post-order) for later use. @@ -141,7 +163,7 @@ fn remove_loop_invariant_instructions( loop_analysis: &LoopAnalysis, ) -> Vec { let mut loop_values: HashSet = HashSet::new(); - let mut invariant_inst: Vec = Vec::new(); + let mut invariant_insts: Vec = Vec::new(); let mut pos = FuncCursor::new(func); // We traverse the loop EBB in reverse post-order. for ebb in postorder_ebbs_loop(loop_analysis, cfg, lp).iter().rev() { @@ -150,15 +172,12 @@ fn remove_loop_invariant_instructions( loop_values.insert(*val); } pos.goto_top(*ebb); - while let Some(inst) = pos.next_inst() { - if pos.func.dfg.has_results(inst) && - pos.func.dfg.inst_args(inst).into_iter().all(|arg| { - !loop_values.contains(arg) - }) - { + #[cfg_attr(feature = "cargo-clippy", allow(block_in_if_condition_stmt))] + 'next_inst: while let Some(inst) = pos.next_inst() { + if is_loop_invariant(inst, &pos.func.dfg, &loop_values) { // If all the instruction's argument are defined outside the loop // then this instruction is loop-invariant - invariant_inst.push(inst); + invariant_insts.push(inst); // We remove it from the loop pos.remove_inst_and_step_back(); } else { @@ -170,7 +189,7 @@ fn remove_loop_invariant_instructions( } } } - invariant_inst + invariant_insts } /// Return ebbs from a loop in post-order, starting from an entry point in the block. diff --git a/lib/cretonne/src/loop_analysis.rs b/lib/cretonne/src/loop_analysis.rs index 431496b6e4..e75f5f26fe 100644 --- a/lib/cretonne/src/loop_analysis.rs +++ b/lib/cretonne/src/loop_analysis.rs @@ -2,13 +2,13 @@ //! and parent in the loop tree. use dominator_tree::DominatorTree; -use entity::{PrimaryMap, Keys}; use entity::EntityMap; +use entity::{Keys, PrimaryMap}; use flowgraph::ControlFlowGraph; -use ir::{Function, Ebb, Layout}; +use ir::{Ebb, Function, Layout}; use packed_option::PackedOption; -use timing; use std::vec::Vec; +use timing; /// A opaque reference to a code loop. #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -218,7 +218,6 @@ impl LoopAnalysis { } } } - } } } @@ -227,10 +226,10 @@ impl LoopAnalysis { mod test { use cursor::{Cursor, FuncCursor}; - use ir::{Function, InstBuilder, types}; - use loop_analysis::{Loop, LoopAnalysis}; - use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; + use flowgraph::ControlFlowGraph; + use ir::{types, Function, InstBuilder}; + use loop_analysis::{Loop, LoopAnalysis}; use std::vec::Vec; #[test] @@ -257,7 +256,6 @@ mod test { cur.insert_ebb(ebb3); cur.ins().brnz(cond, ebb0, &[]); - } let mut loop_analysis = LoopAnalysis::new(); @@ -317,7 +315,6 @@ mod test { cur.insert_ebb(ebb5); cur.ins().brnz(cond, ebb0, &[]); - } let mut loop_analysis = LoopAnalysis::new(); diff --git a/lib/cretonne/src/partition_slice.rs b/lib/cretonne/src/partition_slice.rs index 7a94a9fe0d..dd0aff5da6 100644 --- a/lib/cretonne/src/partition_slice.rs +++ b/lib/cretonne/src/partition_slice.rs @@ -6,7 +6,7 @@ /// The order of elements is not preserved, unless the slice is already partitioned. /// /// Returns the number of elements where `p(t)` is true. -pub fn partition_slice<'a, T: 'a, F>(s: &'a mut [T], mut p: F) -> usize +pub fn partition_slice(s: &mut [T], mut p: F) -> usize where F: FnMut(&T) -> bool, { diff --git a/lib/cretonne/src/postopt.rs b/lib/cretonne/src/postopt.rs new file mode 100644 index 0000000000..18f467a68a --- /dev/null +++ b/lib/cretonne/src/postopt.rs @@ -0,0 +1,203 @@ +//! A post-legalization rewriting pass. + +#![allow(non_snake_case)] + +use cursor::{Cursor, EncCursor}; +use ir::condcodes::{CondCode, FloatCC, IntCC}; +use ir::dfg::ValueDef; +use ir::immediates::Imm64; +use ir::instructions::{Opcode, ValueList}; +use ir::{Ebb, Function, Inst, InstBuilder, InstructionData, Value}; +use isa::TargetIsa; +use timing; + +/// Information collected about a compare+branch sequence. +struct CmpBrInfo { + /// The branch instruction. + br_inst: Inst, + /// The icmp, icmp_imm, or fcmp instruction. + cmp_inst: Inst, + /// The destination of the branch. + destination: Ebb, + /// The arguments of the branch. + args: ValueList, + /// The first argument to the comparison. The second is in the `kind` field. + cmp_arg: Value, + /// If the branch is `brz` rather than `brnz`, we need to invert the condition + /// before the branch. + invert_branch_cond: bool, + /// The kind of comparison, and the second argument. + kind: CmpBrKind, +} + +enum CmpBrKind { + Icmp { cond: IntCC, arg: Value }, + IcmpImm { cond: IntCC, imm: Imm64 }, + Fcmp { cond: FloatCC, arg: Value }, +} + +/// Optimize comparisons to use flags values, to avoid materializing conditions +/// in integer registers. +/// +/// For example, optimize icmp/fcmp brz/brnz sequences into ifcmp/ffcmp brif/brff +/// sequences. +fn optimize_cpu_flags( + pos: &mut EncCursor, + inst: Inst, + last_flags_clobber: Option, + isa: &TargetIsa, +) { + // Look for compare and branch patterns. + // This code could be considerably simplified with non-lexical lifetimes. + let info = match pos.func.dfg[inst] { + InstructionData::Branch { + opcode, + destination, + ref args, + } => { + let first_arg = args.first(&pos.func.dfg.value_lists).unwrap(); + let invert_branch_cond = match opcode { + Opcode::Brz => true, + Opcode::Brnz => false, + _ => panic!(), + }; + if let ValueDef::Result(cond_inst, _) = pos.func.dfg.value_def(first_arg) { + match pos.func.dfg[cond_inst] { + InstructionData::IntCompare { + cond, + args: cmp_args, + .. + } => CmpBrInfo { + br_inst: inst, + cmp_inst: cond_inst, + destination, + args: args.clone(), + cmp_arg: cmp_args[0], + invert_branch_cond, + kind: CmpBrKind::Icmp { + cond, + arg: cmp_args[1], + }, + }, + InstructionData::IntCompareImm { + cond, + arg: cmp_arg, + imm: cmp_imm, + .. + } => CmpBrInfo { + br_inst: inst, + cmp_inst: cond_inst, + destination, + args: args.clone(), + cmp_arg, + invert_branch_cond, + kind: CmpBrKind::IcmpImm { cond, imm: cmp_imm }, + }, + InstructionData::FloatCompare { + cond, + args: cmp_args, + .. + } => CmpBrInfo { + br_inst: inst, + cmp_inst: cond_inst, + destination, + args: args.clone(), + cmp_arg: cmp_args[0], + invert_branch_cond, + kind: CmpBrKind::Fcmp { + cond, + arg: cmp_args[1], + }, + }, + _ => return, + } + } else { + return; + } + } + // TODO: trapif, trueif, selectif, and their ff counterparts. + _ => return, + }; + + // If any instructions clobber the flags between the comparison and the branch, + // don't optimize them. + if last_flags_clobber != Some(info.cmp_inst) { + return; + } + + // We found a compare+branch pattern. Transform it to use flags. + let args = info.args.as_slice(&pos.func.dfg.value_lists)[1..].to_vec(); + pos.goto_inst(info.cmp_inst); + match info.kind { + CmpBrKind::Icmp { mut cond, arg } => { + let flags = pos.ins().ifcmp(info.cmp_arg, arg); + pos.func.dfg.replace(info.cmp_inst).trueif(cond, flags); + if info.invert_branch_cond { + cond = cond.inverse(); + } + pos.func.dfg.replace(info.br_inst).brif( + cond, + flags, + info.destination, + &args, + ); + } + CmpBrKind::IcmpImm { mut cond, imm } => { + let flags = pos.ins().ifcmp_imm(info.cmp_arg, imm); + pos.func.dfg.replace(info.cmp_inst).trueif(cond, flags); + if info.invert_branch_cond { + cond = cond.inverse(); + } + pos.func.dfg.replace(info.br_inst).brif( + cond, + flags, + info.destination, + &args, + ); + } + CmpBrKind::Fcmp { mut cond, arg } => { + let flags = pos.ins().ffcmp(info.cmp_arg, arg); + pos.func.dfg.replace(info.cmp_inst).trueff(cond, flags); + if info.invert_branch_cond { + cond = cond.inverse(); + } + pos.func.dfg.replace(info.br_inst).brff( + cond, + flags, + info.destination, + &args, + ); + } + } + pos.func.update_encoding(info.cmp_inst, isa).is_ok(); + pos.func.update_encoding(info.br_inst, isa).is_ok(); +} + +//---------------------------------------------------------------------- +// +// The main post-opt pass. + +pub fn do_postopt(func: &mut Function, isa: &TargetIsa) { + let _tt = timing::postopt(); + let mut pos = EncCursor::new(func, isa); + while let Some(_ebb) = pos.next_ebb() { + let mut last_flags_clobber = None; + while let Some(inst) = pos.next_inst() { + if isa.uses_cpu_flags() { + // Optimize instructions to make use of flags. + optimize_cpu_flags(&mut pos, inst, last_flags_clobber, isa); + + // Track the most recent seen instruction that clobbers the flags. + if let Some(constraints) = + isa.encoding_info().operand_constraints( + pos.func.encodings[inst], + ) + { + if constraints.clobbers_flags { + last_flags_clobber = Some(inst) + } + } + } + } + } +} diff --git a/lib/cretonne/src/predicates.rs b/lib/cretonne/src/predicates.rs index 63d2e79af3..618369291d 100644 --- a/lib/cretonne/src/predicates.rs +++ b/lib/cretonne/src/predicates.rs @@ -7,7 +7,7 @@ //! bound is implemented by all the native integer types as well as `Imm64`. //! //! Some of these predicates may be unused in certain ISA configurations, so we suppress the -//! dead_code warning. +//! dead code warning. /// Check that `x` is the same as `y`. #[allow(dead_code)] diff --git a/lib/cretonne/src/preopt.rs b/lib/cretonne/src/preopt.rs index 8e4bef8da3..bdd1c445eb 100644 --- a/lib/cretonne/src/preopt.rs +++ b/lib/cretonne/src/preopt.rs @@ -3,24 +3,23 @@ #![allow(non_snake_case)] use cursor::{Cursor, FuncCursor}; -use ir::dfg::ValueDef; -use ir::{Function, InstructionData, Value, DataFlowGraph, InstBuilder, Type}; +use divconst_magic_numbers::{MS32, MS64, MU32, MU64}; +use divconst_magic_numbers::{magicS32, magicS64, magicU32, magicU64}; use ir::Inst; -use ir::types::{I32, I64}; +use ir::dfg::ValueDef; use ir::instructions::Opcode; -use divconst_magic_numbers::{MU32, MU64, MS32, MS64}; -use divconst_magic_numbers::{magicU32, magicU64, magicS32, magicS64}; +use ir::types::{I32, I64}; +use ir::{DataFlowGraph, Function, InstBuilder, InstructionData, Type, Value}; use timing; - //---------------------------------------------------------------------- // // Pattern-match helpers and transformation for div and rem by constants. // Simple math helpers -// if `x` is a power of two, or the negation thereof, return the power along -// with a boolean that indicates whether `x` is negative. Else return None. +/// if `x` is a power of two, or the negation thereof, return the power along +/// with a boolean that indicates whether `x` is negative. Else return None. #[inline] fn isPowerOf2_S32(x: i32) -> Option<(bool, u32)> { // We have to special-case this because abs(x) isn't representable. @@ -34,7 +33,7 @@ fn isPowerOf2_S32(x: i32) -> Option<(bool, u32)> { None } -// Same comments as for isPowerOf2_S64 apply. +/// Same comments as for isPowerOf2_S64 apply. #[inline] fn isPowerOf2_S64(x: i64) -> Option<(bool, u32)> { // We have to special-case this because abs(x) isn't representable. @@ -60,9 +59,9 @@ enum DivRemByConstInfo { RemS64(Value, i64), } -// Possibly create a DivRemByConstInfo from the given components, by -// figuring out which, if any, of the 8 cases apply, and also taking care to -// sanity-check the immediate. +/// Possibly create a DivRemByConstInfo from the given components, by +/// figuring out which, if any, of the 8 cases apply, and also taking care to +/// sanity-check the immediate. fn package_up_divrem_info( argL: Value, argL_ty: Type, @@ -108,13 +107,13 @@ fn package_up_divrem_info( None } -// Examine `idata` to see if it is a div or rem by a constant, and if so -// return the operands, signedness, operation size and div-vs-rem-ness in a -// handy bundle. +/// Examine `idata` to see if it is a div or rem by a constant, and if so +/// return the operands, signedness, operation size and div-vs-rem-ness in a +/// handy bundle. fn get_div_info(inst: Inst, dfg: &DataFlowGraph) -> Option { let idata: &InstructionData = &dfg[inst]; - if let &InstructionData::BinaryImm { opcode, arg, imm } = idata { + if let InstructionData::BinaryImm { opcode, arg, imm } = *idata { let (isSigned, isRem) = match opcode { Opcode::UdivImm => (false, false), Opcode::UremImm => (false, true), @@ -127,37 +126,15 @@ fn get_div_info(inst: Inst, dfg: &DataFlowGraph) -> Option { return package_up_divrem_info(arg, argL_ty, imm.into(), isSigned, isRem); } - // TODO: should we actually bother to do this (that is, manually match - // the case that the second argument is an iconst)? Or should we assume - // that some previous constant propagation pass has pushed all such - // immediates to their use points, creating BinaryImm instructions - // instead? For now we take the conservative approach. - if let &InstructionData::Binary { opcode, args } = idata { - let (isSigned, isRem) = match opcode { - Opcode::Udiv => (false, false), - Opcode::Urem => (false, true), - Opcode::Sdiv => (true, false), - Opcode::Srem => (true, true), - _other => return None, - }; - let argR: Value = args[1]; - if let Some(simm64) = get_const(argR, dfg) { - let argL: Value = args[0]; - // Pull the operation size (type) from the left arg - let argL_ty = dfg.value_type(argL); - return package_up_divrem_info(argL, argL_ty, simm64, isSigned, isRem); - } - } - None } -// Actually do the transformation given a bundle containing the relevant -// information. `divrem_info` describes a div or rem by a constant, that -// `pos` currently points at, and `inst` is the associated instruction. -// `inst` is replaced by a sequence of other operations that calculate the -// same result. Note that there are various `divrem_info` cases where we -// cannot do any transformation, in which case `inst` is left unchanged. +/// Actually do the transformation given a bundle containing the relevant +/// information. `divrem_info` describes a div or rem by a constant, that +/// `pos` currently points at, and `inst` is the associated instruction. +/// `inst` is replaced by a sequence of other operations that calculate the +/// same result. Note that there are various `divrem_info` cases where we +/// cannot do any transformation, in which case `inst` is left unchanged. fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCursor, inst: Inst) { let isRem = match *divrem_info { DivRemByConstInfo::DivU32(_, _) | @@ -170,18 +147,17 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso DivRemByConstInfo::RemS64(_, _) => true, }; - match divrem_info { - + match *divrem_info { // -------------------- U32 -------------------- // U32 div, rem by zero: ignore - &DivRemByConstInfo::DivU32(_n1, 0) | - &DivRemByConstInfo::RemU32(_n1, 0) => {} + DivRemByConstInfo::DivU32(_n1, 0) | + DivRemByConstInfo::RemU32(_n1, 0) => {} // U32 div by 1: identity // U32 rem by 1: zero - &DivRemByConstInfo::DivU32(n1, 1) | - &DivRemByConstInfo::RemU32(n1, 1) => { + DivRemByConstInfo::DivU32(n1, 1) | + DivRemByConstInfo::RemU32(n1, 1) => { if isRem { pos.func.dfg.replace(inst).iconst(I32, 0); } else { @@ -190,8 +166,8 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } // U32 div, rem by a power-of-2 - &DivRemByConstInfo::DivU32(n1, d) | - &DivRemByConstInfo::RemU32(n1, d) if d.is_power_of_two() => { + DivRemByConstInfo::DivU32(n1, d) | + DivRemByConstInfo::RemU32(n1, d) if d.is_power_of_two() => { debug_assert!(d >= 2); // compute k where d == 2^k let k = d.trailing_zeros(); @@ -205,8 +181,8 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } // U32 div, rem by non-power-of-2 - &DivRemByConstInfo::DivU32(n1, d) | - &DivRemByConstInfo::RemU32(n1, d) => { + DivRemByConstInfo::DivU32(n1, d) | + DivRemByConstInfo::RemU32(n1, d) => { debug_assert!(d >= 3); let MU32 { mulBy, @@ -223,7 +199,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso let t3 = pos.ins().iadd(t2, q1); // I never found any case where shiftBy == 1 here. // So there's no attempt to fold out a zero shift. - debug_assert!(shiftBy != 1); + debug_assert_ne!(shiftBy, 1); qf = pos.ins().ushr_imm(t3, (shiftBy - 1) as i64); } else { debug_assert!(shiftBy >= 0 && shiftBy <= 31); @@ -247,13 +223,13 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso // -------------------- U64 -------------------- // U64 div, rem by zero: ignore - &DivRemByConstInfo::DivU64(_n1, 0) | - &DivRemByConstInfo::RemU64(_n1, 0) => {} + DivRemByConstInfo::DivU64(_n1, 0) | + DivRemByConstInfo::RemU64(_n1, 0) => {} // U64 div by 1: identity // U64 rem by 1: zero - &DivRemByConstInfo::DivU64(n1, 1) | - &DivRemByConstInfo::RemU64(n1, 1) => { + DivRemByConstInfo::DivU64(n1, 1) | + DivRemByConstInfo::RemU64(n1, 1) => { if isRem { pos.func.dfg.replace(inst).iconst(I64, 0); } else { @@ -262,8 +238,8 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } // U64 div, rem by a power-of-2 - &DivRemByConstInfo::DivU64(n1, d) | - &DivRemByConstInfo::RemU64(n1, d) if d.is_power_of_two() => { + DivRemByConstInfo::DivU64(n1, d) | + DivRemByConstInfo::RemU64(n1, d) if d.is_power_of_two() => { debug_assert!(d >= 2); // compute k where d == 2^k let k = d.trailing_zeros(); @@ -277,8 +253,8 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } // U64 div, rem by non-power-of-2 - &DivRemByConstInfo::DivU64(n1, d) | - &DivRemByConstInfo::RemU64(n1, d) => { + DivRemByConstInfo::DivU64(n1, d) | + DivRemByConstInfo::RemU64(n1, d) => { debug_assert!(d >= 3); let MU64 { mulBy, @@ -295,7 +271,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso let t3 = pos.ins().iadd(t2, q1); // I never found any case where shiftBy == 1 here. // So there's no attempt to fold out a zero shift. - debug_assert!(shiftBy != 1); + debug_assert_ne!(shiftBy, 1); qf = pos.ins().ushr_imm(t3, (shiftBy - 1) as i64); } else { debug_assert!(shiftBy >= 0 && shiftBy <= 63); @@ -319,15 +295,15 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso // -------------------- S32 -------------------- // S32 div, rem by zero or -1: ignore - &DivRemByConstInfo::DivS32(_n1, -1) | - &DivRemByConstInfo::RemS32(_n1, -1) | - &DivRemByConstInfo::DivS32(_n1, 0) | - &DivRemByConstInfo::RemS32(_n1, 0) => {} + DivRemByConstInfo::DivS32(_n1, -1) | + DivRemByConstInfo::RemS32(_n1, -1) | + DivRemByConstInfo::DivS32(_n1, 0) | + DivRemByConstInfo::RemS32(_n1, 0) => {} // S32 div by 1: identity // S32 rem by 1: zero - &DivRemByConstInfo::DivS32(n1, 1) | - &DivRemByConstInfo::RemS32(n1, 1) => { + DivRemByConstInfo::DivS32(n1, 1) | + DivRemByConstInfo::RemS32(n1, 1) => { if isRem { pos.func.dfg.replace(inst).iconst(I32, 0); } else { @@ -335,8 +311,8 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } } - &DivRemByConstInfo::DivS32(n1, d) | - &DivRemByConstInfo::RemS32(n1, d) => { + DivRemByConstInfo::DivS32(n1, d) | + DivRemByConstInfo::RemS32(n1, d) => { if let Some((isNeg, k)) = isPowerOf2_S32(d) { // k can be 31 only in the case that d is -2^31. debug_assert!(k >= 1 && k <= 31); @@ -396,15 +372,15 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso // -------------------- S64 -------------------- // S64 div, rem by zero or -1: ignore - &DivRemByConstInfo::DivS64(_n1, -1) | - &DivRemByConstInfo::RemS64(_n1, -1) | - &DivRemByConstInfo::DivS64(_n1, 0) | - &DivRemByConstInfo::RemS64(_n1, 0) => {} + DivRemByConstInfo::DivS64(_n1, -1) | + DivRemByConstInfo::RemS64(_n1, -1) | + DivRemByConstInfo::DivS64(_n1, 0) | + DivRemByConstInfo::RemS64(_n1, 0) => {} // S64 div by 1: identity // S64 rem by 1: zero - &DivRemByConstInfo::DivS64(n1, 1) | - &DivRemByConstInfo::RemS64(n1, 1) => { + DivRemByConstInfo::DivS64(n1, 1) | + DivRemByConstInfo::RemS64(n1, 1) => { if isRem { pos.func.dfg.replace(inst).iconst(I64, 0); } else { @@ -412,8 +388,8 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } } - &DivRemByConstInfo::DivS64(n1, d) | - &DivRemByConstInfo::RemS64(n1, d) => { + DivRemByConstInfo::DivS64(n1, d) | + DivRemByConstInfo::RemS64(n1, d) => { if let Some((isNeg, k)) = isPowerOf2_S64(d) { // k can be 63 only in the case that d is -2^63. debug_assert!(k >= 1 && k <= 63); @@ -469,43 +445,120 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso } } } - } } - -//---------------------------------------------------------------------- -// -// General pattern-match helpers. - -// Find out if `value` actually resolves to a constant, and if so what its -// value is. -fn get_const(value: Value, dfg: &DataFlowGraph) -> Option { - match dfg.value_def(value) { - ValueDef::Result(definingInst, resultNo) => { - let definingIData: &InstructionData = &dfg[definingInst]; - if let &InstructionData::UnaryImm { opcode, imm } = definingIData { - if opcode == Opcode::Iconst && resultNo == 0 { - return Some(imm.into()); +/// Apply basic simplifications. +/// +/// This folds constants with arithmetic to form `_imm` instructions, and other +/// minor simplifications. +fn simplify(pos: &mut FuncCursor, inst: Inst) { + match pos.func.dfg[inst] { + InstructionData::Binary { opcode, args } => { + if let ValueDef::Result(iconst_inst, _) = pos.func.dfg.value_def(args[1]) { + if let InstructionData::UnaryImm { + opcode: Opcode::Iconst, + mut imm, + } = pos.func.dfg[iconst_inst] + { + let new_opcode = match opcode { + Opcode::Iadd => Opcode::IaddImm, + Opcode::Imul => Opcode::ImulImm, + Opcode::Sdiv => Opcode::SdivImm, + Opcode::Udiv => Opcode::UdivImm, + Opcode::Srem => Opcode::SremImm, + Opcode::Urem => Opcode::UremImm, + Opcode::Band => Opcode::BandImm, + Opcode::Bor => Opcode::BorImm, + Opcode::Bxor => Opcode::BxorImm, + Opcode::Rotl => Opcode::RotlImm, + Opcode::Rotr => Opcode::RotrImm, + Opcode::Ishl => Opcode::IshlImm, + Opcode::Ushr => Opcode::UshrImm, + Opcode::Sshr => Opcode::SshrImm, + Opcode::Isub => { + imm = imm.wrapping_neg(); + Opcode::IaddImm + } + _ => return, + }; + let ty = pos.func.dfg.ctrl_typevar(inst); + pos.func.dfg.replace(inst).BinaryImm( + new_opcode, + ty, + imm, + args[0], + ); + } + } else if let ValueDef::Result(iconst_inst, _) = pos.func.dfg.value_def(args[0]) { + if let InstructionData::UnaryImm { + opcode: Opcode::Iconst, + mut imm, + } = pos.func.dfg[iconst_inst] + { + let new_opcode = match opcode { + Opcode::Isub => Opcode::IrsubImm, + _ => return, + }; + let ty = pos.func.dfg.ctrl_typevar(inst); + pos.func.dfg.replace(inst).BinaryImm( + new_opcode, + ty, + imm, + args[0], + ); } } - None } - ValueDef::Param(_definingEbb, _paramNo) => None, + InstructionData::IntCompare { opcode, cond, args } => { + debug_assert_eq!(opcode, Opcode::Icmp); + if let ValueDef::Result(iconst_inst, _) = pos.func.dfg.value_def(args[1]) { + if let InstructionData::UnaryImm { + opcode: Opcode::Iconst, + imm, + } = pos.func.dfg[iconst_inst] + { + pos.func.dfg.replace(inst).icmp_imm(cond, args[0], imm); + } + } + } + InstructionData::CondTrap { .. } | + InstructionData::Branch { .. } | + InstructionData::Ternary { opcode: Opcode::Select, .. } => { + // Fold away a redundant `bint`. + let maybe = { + let args = pos.func.dfg.inst_args(inst); + if let ValueDef::Result(def_inst, _) = pos.func.dfg.value_def(args[0]) { + if let InstructionData::Unary { + opcode: Opcode::Bint, + arg: bool_val, + } = pos.func.dfg[def_inst] + { + Some(bool_val) + } else { + None + } + } else { + None + } + }; + if let Some(bool_val) = maybe { + let args = pos.func.dfg.inst_args_mut(inst); + args[0] = bool_val; + } + } + _ => {} } } - -//---------------------------------------------------------------------- -// -// The main pre-opt pass. - +/// The main pre-opt pass. pub fn do_preopt(func: &mut Function) { let _tt = timing::preopt(); let mut pos = FuncCursor::new(func); while let Some(_ebb) = pos.next_ebb() { - while let Some(inst) = pos.next_inst() { + // Apply basic simplifications. + simplify(&mut pos, inst); //-- BEGIN -- division by constants ---------------- diff --git a/lib/cretonne/src/print_errors.rs b/lib/cretonne/src/print_errors.rs new file mode 100644 index 0000000000..ffc8bab4ec --- /dev/null +++ b/lib/cretonne/src/print_errors.rs @@ -0,0 +1,34 @@ +//! Utility routines for pretty-printing error messages. + +use ir; +use isa::TargetIsa; +use result::CtonError; +use std::fmt::Write; +use std::string::{String, ToString}; +use verifier; + +/// Pretty-print a verifier error. +pub fn pretty_verifier_error( + func: &ir::Function, + isa: Option<&TargetIsa>, + err: &verifier::Error, +) -> String { + let mut msg = err.to_string(); + match err.location { + ir::entities::AnyEntity::Inst(inst) => { + write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap() + } + _ => msg.push('\n'), + } + write!(msg, "{}", func.display(isa)).unwrap(); + msg +} + +/// Pretty-print a Cretonne error. +pub fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String { + if let CtonError::Verifier(e) = err { + pretty_verifier_error(func, isa, &e) + } else { + err.to_string() + } +} diff --git a/lib/cretonne/src/ref_slice.rs b/lib/cretonne/src/ref_slice.rs index b9dbd55dd1..251b601d00 100644 --- a/lib/cretonne/src/ref_slice.rs +++ b/lib/cretonne/src/ref_slice.rs @@ -1,6 +1,6 @@ //! Functions for converting a reference into a singleton slice. //! -//! See also the ref_slice crate on crates.io. +//! See also the [`ref_slice` crate](https://crates.io/crates/ref_slice). //! //! We define the functions here to avoid external dependencies, and to ensure that they are //! inlined in this crate. diff --git a/lib/cretonne/src/regalloc/affinity.rs b/lib/cretonne/src/regalloc/affinity.rs index 7d85ae8721..e0078579a7 100644 --- a/lib/cretonne/src/regalloc/affinity.rs +++ b/lib/cretonne/src/regalloc/affinity.rs @@ -8,9 +8,9 @@ //! subclass. This is just a hint, and the register allocator is allowed to pick a register from a //! larger register class instead. -use std::fmt; use ir::{AbiParam, ArgumentLoc}; -use isa::{TargetIsa, RegInfo, RegClassIndex, OperandConstraint, ConstraintKind}; +use isa::{ConstraintKind, OperandConstraint, RegClassIndex, RegInfo, TargetIsa}; +use std::fmt; /// Preferred register allocation for an SSA value. #[derive(Clone, Copy, Debug)] diff --git a/lib/cretonne/src/regalloc/allocatable_set.rs b/lib/cretonne/src/regalloc/allocatable_set.rs index e0abf76d37..63b7a297d7 100644 --- a/lib/cretonne/src/regalloc/allocatable_set.rs +++ b/lib/cretonne/src/regalloc/allocatable_set.rs @@ -5,7 +5,7 @@ //! "register unit" abstraction. Every register contains one or more register units. Registers that //! share a register unit can't be in use at the same time. -use isa::registers::{RegInfo, RegUnit, RegUnitMask, RegClass}; +use isa::registers::{RegClass, RegInfo, RegUnit, RegUnitMask}; use std::char; use std::fmt; use std::iter::ExactSizeIterator; @@ -197,9 +197,9 @@ impl<'a> fmt::Display for DisplayAllocatableSet<'a> { "{}", bank.names .get(offset as usize) - .and_then(|name| name.chars().skip(1).next()) - .unwrap_or( - char::from_digit(u32::from(offset % 10), 10).unwrap(), + .and_then(|name| name.chars().nth(1)) + .unwrap_or_else( + || char::from_digit(u32::from(offset % 10), 10).unwrap(), ) )?; } diff --git a/lib/cretonne/src/regalloc/coalescing.rs b/lib/cretonne/src/regalloc/coalescing.rs index 2437fd1a03..b77488fec7 100644 --- a/lib/cretonne/src/regalloc/coalescing.rs +++ b/lib/cretonne/src/regalloc/coalescing.rs @@ -10,16 +10,16 @@ use dbg::DisplayList; use dominator_tree::{DominatorTree, DominatorTreePreorder}; use flowgraph::ControlFlowGraph; use ir::{self, InstBuilder, ProgramOrder}; -use ir::{Function, Ebb, Inst, Value, ExpandedProgramPoint}; +use ir::{Ebb, ExpandedProgramPoint, Function, Inst, Value}; +use isa::{EncInfo, TargetIsa}; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; use regalloc::virtregs::{VirtReg, VirtRegs}; use std::cmp; -use std::iter; use std::fmt; +use std::iter; use std::slice; use std::vec::Vec; -use isa::{TargetIsa, EncInfo}; use timing; // # Implementation @@ -92,7 +92,6 @@ impl Coalescing { predecessors: Vec::new(), backedges: Vec::new(), } - } /// Clear all data structures in this coalescing pass. @@ -907,7 +906,7 @@ impl VirtualCopies { self.filter.clear(); } - /// Initialise virtual copies from the (interfering) values in a union-find virtual register + /// Initialize virtual copies from the (interfering) values in a union-find virtual register /// that is going to be broken up and reassembled iteratively. /// /// The values are assumed to be in domtree pre-order. diff --git a/lib/cretonne/src/regalloc/coloring.rs b/lib/cretonne/src/regalloc/coloring.rs index d7ab5dd0f3..b9b1512220 100644 --- a/lib/cretonne/src/regalloc/coloring.rs +++ b/lib/cretonne/src/regalloc/coloring.rs @@ -44,10 +44,10 @@ use cursor::{Cursor, EncCursor}; use dominator_tree::DominatorTree; -use ir::{Ebb, Inst, Value, Function, Layout, ValueLoc, SigRef}; -use ir::{InstBuilder, AbiParam, ArgumentLoc, ValueDef}; -use isa::{RegUnit, RegClass, RegInfo, regs_overlap}; -use isa::{TargetIsa, EncInfo, RecipeConstraints, OperandConstraint, ConstraintKind}; +use ir::{AbiParam, ArgumentLoc, InstBuilder, ValueDef}; +use ir::{Ebb, Function, Inst, Layout, SigRef, Value, ValueLoc}; +use isa::{ConstraintKind, EncInfo, OperandConstraint, RecipeConstraints, TargetIsa}; +use isa::{regs_overlap, RegClass, RegInfo, RegUnit}; use packed_option::PackedOption; use regalloc::RegDiversions; use regalloc::affinity::Affinity; @@ -59,7 +59,6 @@ use regalloc::solver::{Solver, SolverError}; use std::mem; use timing; - /// Data structures for the coloring pass. /// /// These are scratch space data structures that can be reused between invocations. @@ -268,7 +267,6 @@ impl<'a> Context<'a> { abi.display(&self.reginfo) ); } - } // The spiller will have assigned an incoming stack slot already. Affinity::Stack => debug_assert!(abi.location.is_stack()), @@ -426,7 +424,6 @@ impl<'a> Context<'a> { self.iterate_solution(throughs, ®s.global, &mut replace_global_defines) }); - // The solution and/or fixed input constraints may require us to shuffle the set of live // registers around. self.shuffle_inputs(&mut regs.input); @@ -722,7 +719,6 @@ impl<'a> Context<'a> { ConstraintKind::Reg | ConstraintKind::Tied(_) | ConstraintKind::Stack => {} - } } } @@ -869,7 +865,6 @@ impl<'a> Context<'a> { self.solver.clear_all_global_flags(); } }; - } } diff --git a/lib/cretonne/src/regalloc/context.rs b/lib/cretonne/src/regalloc/context.rs index a8284a9579..cb29963e89 100644 --- a/lib/cretonne/src/regalloc/context.rs +++ b/lib/cretonne/src/regalloc/context.rs @@ -18,7 +18,7 @@ use regalloc::virtregs::VirtRegs; use result::CtonResult; use timing; use topo_order::TopoOrder; -use verifier::{verify_context, verify_liveness, verify_cssa, verify_locations}; +use verifier::{verify_context, verify_cssa, verify_liveness, verify_locations}; /// Persistent memory allocations for register allocation. pub struct Context { @@ -106,7 +106,6 @@ impl Context { verify_cssa(func, cfg, domtree, &self.liveness, &self.virtregs)?; } - // Pass: Spilling. self.spilling.run( isa, diff --git a/lib/cretonne/src/regalloc/diversion.rs b/lib/cretonne/src/regalloc/diversion.rs index 16eb0e9b50..b848a879c9 100644 --- a/lib/cretonne/src/regalloc/diversion.rs +++ b/lib/cretonne/src/regalloc/diversion.rs @@ -7,15 +7,15 @@ //! These register diversions are local to an EBB. No values can be diverted when entering a new //! EBB. -use ir::{Value, ValueLoc, ValueLocations, StackSlot}; use ir::{InstructionData, Opcode}; -use isa::{RegUnit, RegInfo}; +use ir::{StackSlot, Value, ValueLoc, ValueLocations}; +use isa::{RegInfo, RegUnit}; use std::fmt; use std::vec::Vec; /// A diversion of a value from its original location to a new register or stack location. /// -/// In IL, a diversion is represented by a `regmove` instruction, possibly a chain of them for the +/// In IR, a diversion is represented by a `regmove` instruction, possibly a chain of them for the /// same value. /// /// When tracking diversions, the `from` field is the original assigned value location, and `to` is @@ -187,8 +187,8 @@ impl<'a> fmt::Display for DisplayDiversions<'a> { #[cfg(test)] mod tests { use super::*; - use ir::Value; use entity::EntityRef; + use ir::Value; #[test] fn inserts() { diff --git a/lib/cretonne/src/regalloc/live_value_tracker.rs b/lib/cretonne/src/regalloc/live_value_tracker.rs index bedd0480e5..27a76deb5c 100644 --- a/lib/cretonne/src/regalloc/live_value_tracker.rs +++ b/lib/cretonne/src/regalloc/live_value_tracker.rs @@ -6,8 +6,7 @@ use dominator_tree::DominatorTree; use entity::{EntityList, ListPool}; -use ir::instructions::BranchInfo; -use ir::{Inst, Ebb, Value, DataFlowGraph, Layout, ExpandedProgramPoint}; +use ir::{DataFlowGraph, Ebb, ExpandedProgramPoint, Inst, Layout, Value}; use partition_slice::partition_slice; use regalloc::affinity::Affinity; use regalloc::liveness::Liveness; @@ -33,6 +32,7 @@ pub struct LiveValueTracker { } /// Information about a value that is live at the current program point. +#[derive(Debug)] pub struct LiveValue { /// The live value. pub value: Value, @@ -261,9 +261,8 @@ impl LiveValueTracker { ) -> (&[LiveValue], &[LiveValue], &[LiveValue]) { // Save a copy of the live values before any branches or jumps that could be somebody's // immediate dominator. - match dfg.analyze_branch(inst) { - BranchInfo::NotABranch => {} - _ => self.save_idom_live_set(inst), + if dfg[inst].opcode().is_branch() { + self.save_idom_live_set(inst); } // Move killed values to the end of the vector. diff --git a/lib/cretonne/src/regalloc/liveness.rs b/lib/cretonne/src/regalloc/liveness.rs index 9a76cb5899..1b82d98bf8 100644 --- a/lib/cretonne/src/regalloc/liveness.rs +++ b/lib/cretonne/src/regalloc/liveness.rs @@ -178,10 +178,10 @@ use entity::SparseMap; use flowgraph::ControlFlowGraph; use ir::dfg::ValueDef; -use ir::{Function, Value, Inst, Ebb, Layout, ProgramPoint}; -use isa::{TargetIsa, EncInfo}; +use ir::{Ebb, Function, Inst, Layout, ProgramPoint, Value}; +use isa::{EncInfo, TargetIsa}; use regalloc::affinity::Affinity; -use regalloc::liverange::{LiveRange, LiveRangeForest, LiveRangeContext}; +use regalloc::liverange::{LiveRange, LiveRangeContext, LiveRangeForest}; use std::mem; use std::ops::Index; use std::vec::Vec; @@ -378,7 +378,6 @@ impl Liveness { mem::replace(&mut lr.affinity, Affinity::Stack) } - /// Compute the live ranges of all SSA values used in `func`. /// This clears out any existing analysis stored in this data structure. pub fn compute(&mut self, isa: &TargetIsa, func: &mut Function, cfg: &ControlFlowGraph) { diff --git a/lib/cretonne/src/regalloc/liverange.rs b/lib/cretonne/src/regalloc/liverange.rs index dbde52f05d..18118e706d 100644 --- a/lib/cretonne/src/regalloc/liverange.rs +++ b/lib/cretonne/src/regalloc/liverange.rs @@ -109,7 +109,7 @@ use bforest; use entity::SparseMapValue; -use ir::{Inst, Ebb, Value, Layout, ProgramPoint, ExpandedProgramPoint, ProgramOrder}; +use ir::{Ebb, ExpandedProgramPoint, Inst, Layout, ProgramOrder, ProgramPoint, Value}; use regalloc::affinity::Affinity; use std::cmp::Ordering; @@ -276,33 +276,31 @@ impl GenLiveRange { } else { return first_time_livein; } - } else { + } else if let Some((_, end)) = c.prev() { // There's no interval beginning at `ebb`, but we could still be live-in at `ebb` with // a coalesced interval that begins before and ends after. - if let Some((_, end)) = c.prev() { - if order.cmp(end, ebb) == Ordering::Greater { - // Yep, the previous interval overlaps `ebb`. - first_time_livein = false; - if order.cmp(end, to) == Ordering::Less { - *c.value_mut().unwrap() = to; - } else { - return first_time_livein; - } + if order.cmp(end, ebb) == Ordering::Greater { + // Yep, the previous interval overlaps `ebb`. + first_time_livein = false; + if order.cmp(end, to) == Ordering::Less { + *c.value_mut().unwrap() = to; } else { - first_time_livein = true; - // The current interval does not overlap `ebb`, but it may still be possible to - // coalesce with it. - if order.is_ebb_gap(end, ebb) { - *c.value_mut().unwrap() = to; - } else { - c.insert(ebb, to); - } + return first_time_livein; } } else { - // There is no existing interval before `ebb`. first_time_livein = true; - c.insert(ebb, to); + // The current interval does not overlap `ebb`, but it may still be possible to + // coalesce with it. + if order.is_ebb_gap(end, ebb) { + *c.value_mut().unwrap() = to; + } else { + c.insert(ebb, to); + } } + } else { + // There is no existing interval before `ebb`. + first_time_livein = true; + c.insert(ebb, to); } // Now `c` to left pointing at an interval that ends in `to`. @@ -459,9 +457,9 @@ impl SparseMapValue for GenLiveRange { mod tests { use super::{GenLiveRange, LiveRangeContext}; use bforest; - use ir::{Inst, Ebb, Value}; use entity::EntityRef; - use ir::{ProgramOrder, ExpandedProgramPoint}; + use ir::{Ebb, Inst, Value}; + use ir::{ExpandedProgramPoint, ProgramOrder}; use std::cmp::Ordering; use std::vec::Vec; @@ -545,7 +543,6 @@ mod tests { // Save for next round. prev_end = Some(end); } - } } diff --git a/lib/cretonne/src/regalloc/mod.rs b/lib/cretonne/src/regalloc/mod.rs index 6ae93c67b6..6868c0a235 100644 --- a/lib/cretonne/src/regalloc/mod.rs +++ b/lib/cretonne/src/regalloc/mod.rs @@ -2,11 +2,11 @@ //! //! This module contains data structures and algorithms used for register allocation. -pub mod liverange; -pub mod liveness; pub mod allocatable_set; -pub mod live_value_tracker; pub mod coloring; +pub mod live_value_tracker; +pub mod liveness; +pub mod liverange; pub mod virtregs; mod affinity; diff --git a/lib/cretonne/src/regalloc/pressure.rs b/lib/cretonne/src/regalloc/pressure.rs index 67e0e99b70..14978f48b8 100644 --- a/lib/cretonne/src/regalloc/pressure.rs +++ b/lib/cretonne/src/regalloc/pressure.rs @@ -36,7 +36,7 @@ // Remove once we're using the pressure tracker. #![allow(dead_code)] -use isa::registers::{RegInfo, MAX_TRACKED_TOPRCS, RegClass, RegClassMask}; +use isa::registers::{RegClass, RegClassMask, RegInfo, MAX_TRACKED_TOPRCS}; use regalloc::AllocatableSet; use std::cmp::min; use std::fmt; @@ -135,7 +135,7 @@ impl Pressure { /// `can_take()` to check again. fn check_avail(&self, rc: RegClass) -> RegClassMask { let entry = match self.toprc.get(rc.toprc as usize) { - None => return 0, // Not a pressure tracked bank. + None => return 0, // Not a pressure tracked bank. Some(e) => e, }; let mask = 1 << rc.toprc; @@ -269,16 +269,16 @@ impl fmt::Display for Pressure { #[cfg(test)] #[cfg(build_arm32)] mod tests { - use isa::{TargetIsa, RegClass}; + use super::Pressure; + use isa::{RegClass, TargetIsa}; use regalloc::AllocatableSet; use std::borrow::Borrow; - use super::Pressure; use std::boxed::Box; // Make an arm32 `TargetIsa`, if possible. fn arm32() -> Option> { - use settings; use isa; + use settings; let shared_builder = settings::builder(); let shared_flags = settings::Flags::new(&shared_builder); diff --git a/lib/cretonne/src/regalloc/reload.rs b/lib/cretonne/src/regalloc/reload.rs index 63e7354453..df527fdbca 100644 --- a/lib/cretonne/src/regalloc/reload.rs +++ b/lib/cretonne/src/regalloc/reload.rs @@ -12,16 +12,16 @@ use cursor::{Cursor, EncCursor}; use dominator_tree::DominatorTree; use entity::{SparseMap, SparseMapValue}; -use ir::{Ebb, Inst, Value, Function}; -use ir::{InstBuilder, AbiParam, ArgumentLoc}; +use ir::{AbiParam, ArgumentLoc, InstBuilder}; +use ir::{Ebb, Function, Inst, Value}; use isa::RegClass; -use isa::{TargetIsa, Encoding, EncInfo, RecipeConstraints, ConstraintKind}; +use isa::{ConstraintKind, EncInfo, Encoding, RecipeConstraints, TargetIsa}; use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; +use std::vec::Vec; use timing; use topo_order::TopoOrder; -use std::vec::Vec; /// Reusable data structures for the reload pass. pub struct Reload { @@ -306,14 +306,12 @@ impl<'a> Context<'a> { let args = self.cur.func.dfg.inst_args(inst); for (argidx, (op, &arg)) in constraints.ins.iter().zip(args).enumerate() { - if op.kind != ConstraintKind::Stack { - if self.liveness[arg].affinity.is_stack() { - self.candidates.push(ReloadCandidate { - argidx, - value: arg, - regclass: op.regclass, - }) - } + if op.kind != ConstraintKind::Stack && self.liveness[arg].affinity.is_stack() { + self.candidates.push(ReloadCandidate { + argidx, + value: arg, + regclass: op.regclass, + }) } } diff --git a/lib/cretonne/src/regalloc/solver.rs b/lib/cretonne/src/regalloc/solver.rs index 7824444eec..4bac41aa99 100644 --- a/lib/cretonne/src/regalloc/solver.rs +++ b/lib/cretonne/src/regalloc/solver.rs @@ -98,6 +98,7 @@ //! appropriate candidate among the set of live register values, add it as a variable and start //! over. +use super::AllocatableSet; use dbg::DisplayList; use entity::{SparseMap, SparseMapValue}; use ir::Value; @@ -106,7 +107,6 @@ use regalloc::allocatable_set::RegSetIter; use std::cmp; use std::fmt; use std::mem; -use super::AllocatableSet; use std::u16; use std::vec::Vec; @@ -299,6 +299,7 @@ impl Move { } /// Get the "from" register and register class, if possible. + #[cfg_attr(feature = "cargo-clippy", allow(wrong_self_convention))] fn from_reg(&self) -> Option<(RegClass, RegUnit)> { match *self { Move::Reg { rc, from, .. } | @@ -349,7 +350,6 @@ impl Move { } } - /// Get the value being moved. fn value(&self) -> Value { match *self { @@ -1158,17 +1158,17 @@ impl fmt::Display for Solver { #[cfg(test)] #[cfg(build_arm32)] mod tests { + use super::{Move, Solver}; use entity::EntityRef; use ir::Value; - use isa::{TargetIsa, RegClass, RegUnit, RegInfo}; + use isa::{RegClass, RegInfo, RegUnit, TargetIsa}; use regalloc::AllocatableSet; - use super::{Solver, Move}; use std::boxed::Box; // Make an arm32 `TargetIsa`, if possible. fn arm32() -> Option> { - use settings; use isa; + use settings; let shared_builder = settings::builder(); let shared_flags = settings::Flags::new(&shared_builder); @@ -1395,7 +1395,7 @@ mod tests { mov(v15, gpr, r5, r3), mov(v14, gpr, r4, r5), mov(v13, gpr, r1, r4), - fill(v10, gpr, 0, r1), // Finally complete cycle 1. + fill(v10, gpr, 0, r1) // Finally complete cycle 1. ] ); } diff --git a/lib/cretonne/src/regalloc/spilling.rs b/lib/cretonne/src/regalloc/spilling.rs index 3c3f805cbf..5a695eeb8b 100644 --- a/lib/cretonne/src/regalloc/spilling.rs +++ b/lib/cretonne/src/regalloc/spilling.rs @@ -17,9 +17,9 @@ use cursor::{Cursor, EncCursor}; use dominator_tree::DominatorTree; -use ir::{InstBuilder, Function, Ebb, Inst, Value, ValueLoc, SigRef}; -use isa::registers::{RegClassMask, RegClassIndex}; -use isa::{TargetIsa, RegInfo, EncInfo, RecipeConstraints, ConstraintKind}; +use ir::{Ebb, Function, Inst, InstBuilder, SigRef, Value, ValueLoc}; +use isa::registers::{RegClassIndex, RegClassMask}; +use isa::{ConstraintKind, EncInfo, RecipeConstraints, RegInfo, TargetIsa}; use regalloc::affinity::Affinity; use regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use regalloc::liveness::Liveness; @@ -359,12 +359,10 @@ impl<'a> Context<'a> { if abi.location.is_reg() { let (rci, spilled) = match self.liveness[arg].affinity { Affinity::Reg(rci) => (rci, false), - Affinity::Stack => { - ( - self.cur.isa.regclass_for_abi_type(abi.value_type).into(), - true, - ) - } + Affinity::Stack => ( + self.cur.isa.regclass_for_abi_type(abi.value_type).into(), + true, + ), Affinity::None => panic!("Missing affinity for {}", arg), }; let mut reguse = RegUse::new(arg, fixed_args + idx, rci); @@ -548,8 +546,8 @@ impl<'a> Context<'a> { } } -// Struct representing a register use of a value. -// Used to detect multiple uses of the same value with incompatible register constraints. +/// Struct representing a register use of a value. +/// Used to detect multiple uses of the same value with incompatible register constraints. #[derive(Clone, Copy)] struct RegUse { value: Value, diff --git a/lib/cretonne/src/regalloc/virtregs.rs b/lib/cretonne/src/regalloc/virtregs.rs index 097a66cccd..215e5efac2 100644 --- a/lib/cretonne/src/regalloc/virtregs.rs +++ b/lib/cretonne/src/regalloc/virtregs.rs @@ -13,10 +13,10 @@ use dbg::DisplayList; use dominator_tree::DominatorTreePreorder; -use entity::{EntityList, ListPool}; -use entity::{PrimaryMap, EntityMap, Keys}; use entity::EntityRef; -use ir::{Value, Function}; +use entity::{EntityList, ListPool}; +use entity::{EntityMap, Keys, PrimaryMap}; +use ir::{Function, Value}; use packed_option::PackedOption; use ref_slice::ref_slice; use std::cmp::Ordering; @@ -101,8 +101,10 @@ impl VirtRegs { where 'a: 'b, { - self.get(*value).map(|vr| self.values(vr)).unwrap_or( - ref_slice(value), + self.get(*value).map(|vr| self.values(vr)).unwrap_or_else( + || { + ref_slice(value) + }, ) } @@ -371,7 +373,7 @@ impl VirtRegs { let vreg = self.get(leader).unwrap_or_else(|| { // Allocate a vreg for `leader`, but leave it empty. let vr = self.alloc(); - if let &mut Some(ref mut vec) = &mut new_vregs { + if let Some(ref mut vec) = new_vregs { vec.push(vr); } self.value_vregs[leader] = vr.into(); diff --git a/lib/cretonne/src/result.rs b/lib/cretonne/src/result.rs index fcff7a6e65..2807202013 100644 --- a/lib/cretonne/src/result.rs +++ b/lib/cretonne/src/result.rs @@ -14,9 +14,9 @@ pub enum CtonError { #[fail(display = "Invalid input code")] InvalidInput, - /// An IL verifier error. + /// An IR verifier error. /// - /// This always represents a bug, either in the code that generated IL for Cretonne, or a bug + /// This always represents a bug, either in the code that generated IR for Cretonne, or a bug /// in Cretonne itself. #[fail(display = "Verifier error: {}", _0)] Verifier( diff --git a/lib/cretonne/src/scoped_hash_map.rs b/lib/cretonne/src/scoped_hash_map.rs index d5acf2ecb6..4acc7087f0 100644 --- a/lib/cretonne/src/scoped_hash_map.rs +++ b/lib/cretonne/src/scoped_hash_map.rs @@ -1,10 +1,10 @@ -//! ScopedHashMap +//! `ScopedHashMap` //! //! This module defines a struct `ScopedHashMap` which defines a `HashMap`-like //! container that has a concept of scopes that can be entered and exited, such that //! values inserted while inside a scope aren't visible outside the scope. -use std::collections::{HashMap, hash_map}; +use std::collections::{hash_map, HashMap}; use std::hash::Hash; use std::mem; diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 9a93b4a00c..aee441f3ca 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -190,8 +190,8 @@ impl<'a> PredicateView<'a> { /// This module holds definitions that need to be public so the can be instantiated by generated /// code in other modules. pub mod detail { - use std::fmt; use constant_hash; + use std::fmt; /// An instruction group template. pub struct Template { @@ -303,8 +303,8 @@ pub mod detail { /// Check if a detail is a Detail::Preset. Useful because the Descriptor /// offset field has a different meaning when the detail is a preset. pub fn is_preset(&self) -> bool { - match self { - &Detail::Preset => true, + match *self { + Detail::Preset => true, _ => false, } } @@ -345,9 +345,9 @@ impl<'a> From<&'a TargetIsa> for FlagsOrIsa<'a> { #[cfg(test)] mod tests { - use super::{builder, Flags}; - use super::Error::*; use super::Configurable; + use super::Error::*; + use super::{builder, Flags}; use std::string::ToString; #[test] @@ -357,18 +357,18 @@ mod tests { assert_eq!( f.to_string(), "[shared]\n\ - opt_level = \"default\"\n\ - enable_verifier = true\n\ - is_64bit = false\n\ - is_pic = false\n\ - return_at_end = false\n\ - avoid_div_traps = false\n\ - is_compressed = false\n\ - enable_float = true\n\ - enable_simd = true\n\ - enable_atomics = true\n\ - spiderwasm_prologue_words = 0\n\ - allones_funcaddrs = false\n" + opt_level = \"default\"\n\ + enable_verifier = true\n\ + is_64bit = false\n\ + is_pic = false\n\ + return_at_end = false\n\ + avoid_div_traps = false\n\ + is_compressed = false\n\ + enable_float = true\n\ + enable_simd = true\n\ + enable_atomics = true\n\ + spiderwasm_prologue_words = 0\n\ + allones_funcaddrs = false\n" ); assert_eq!(f.opt_level(), super::OptLevel::Default); assert_eq!(f.enable_simd(), true); diff --git a/lib/cretonne/src/simple_gvn.rs b/lib/cretonne/src/simple_gvn.rs index dd0fb3b1aa..ae8dd387d6 100644 --- a/lib/cretonne/src/simple_gvn.rs +++ b/lib/cretonne/src/simple_gvn.rs @@ -1,12 +1,11 @@ //! A simple GVN pass. use cursor::{Cursor, FuncCursor}; -use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; -use ir::{InstructionData, Function, Inst, Opcode, Type}; +use ir::{Function, Inst, InstructionData, Opcode, Type}; use scoped_hash_map::ScopedHashMap; -use timing; use std::vec::Vec; +use timing; /// Test whether the given opcode is unsafe to even consider for GVN. fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool { @@ -17,9 +16,8 @@ fn trivially_unsafe_for_gvn(opcode: Opcode) -> bool { /// Perform simple GVN on `func`. /// -pub fn do_simple_gvn(func: &mut Function, cfg: &mut ControlFlowGraph, domtree: &mut DominatorTree) { +pub fn do_simple_gvn(func: &mut Function, domtree: &mut DominatorTree) { let _tt = timing::gvn(); - debug_assert!(cfg.is_valid()); debug_assert!(domtree.is_valid()); let mut visible_values: ScopedHashMap<(InstructionData, Type), Inst> = ScopedHashMap::new(); diff --git a/lib/cretonne/src/stack_layout.rs b/lib/cretonne/src/stack_layout.rs index f1cf35f150..3b07ee154a 100644 --- a/lib/cretonne/src/stack_layout.rs +++ b/lib/cretonne/src/stack_layout.rs @@ -1,9 +1,9 @@ //! Computing stack layout. use ir::StackSlots; -use ir::stackslot::{StackSize, StackOffset, StackSlotKind}; +use ir::stackslot::{StackOffset, StackSize, StackSlotKind}; use result::CtonError; -use std::cmp::{min, max}; +use std::cmp::{max, min}; /// Compute the stack frame layout. /// @@ -39,9 +39,7 @@ pub fn layout_stack(frame: &mut StackSlots, alignment: StackSize) -> Result max_size { return Err(CtonError::ImplLimitExceeded); } @@ -72,9 +70,7 @@ pub fn layout_stack(frame: &mut StackSlots, alignment: StackSize) -> Result Result Result { #[allow(non_camel_case_types)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] - enum $enum { $($pass,)+ NoPass } + enum $enum { $($pass,)+ None} - const $num_passes: usize = $enum::NoPass as usize; + const $num_passes: usize = $enum::None as usize; const $descriptions: [&str; $num_passes] = [ $($desc),+ ]; @@ -41,11 +41,11 @@ define_passes!{ Pass, NUM_PASSES, DESCRIPTIONS; process_file: "Processing test file", - parse_text: "Parsing textual Cretonne IL", + parse_text: "Parsing textual Cretonne IR", wasm_translate_module: "Translate WASM module", wasm_translate_function: "Translate WASM function", - verifier: "Verify Cretonne IL", + verifier: "Verify Cretonne IR", verify_cssa: "Verify CSSA", verify_liveness: "Verify live ranges", verify_locations: "Verify value locations", @@ -55,7 +55,9 @@ define_passes!{ flowgraph: "Control flow graph", domtree: "Dominator tree", loop_analysis: "Loop analysis", + postopt: "Post-legalization rewriting", preopt: "Pre-legalization rewriting", + dce: "Dead code elimination", legalize: "Legalization", gvn: "Global value numbering", licm: "Loop invariant code motion", @@ -95,11 +97,11 @@ impl fmt::Display for Pass { /// `TimingToken` and `PassTimes` types and `take_current`, `add_to_current`, and `start_pass` funcs #[cfg(feature = "std")] mod details { - use super::{Pass, NUM_PASSES, DESCRIPTIONS}; + use super::{Pass, DESCRIPTIONS, NUM_PASSES}; use std::cell::{Cell, RefCell}; use std::fmt; use std::mem; - use std::time::{Instant, Duration}; + use std::time::{Duration, Instant}; /// A timing token is responsible for timing the currently running pass. Timing starts when it /// is created and ends when it is dropped. @@ -164,7 +166,7 @@ mod details { /// Information about passes in a single thread. thread_local!{ - static CURRENT_PASS: Cell = Cell::new(Pass::NoPass); + static CURRENT_PASS: Cell = Cell::new(Pass::None); static PASS_TIME: RefCell = RefCell::new(Default::default()); } @@ -204,7 +206,7 @@ mod details { } /// Add `timings` to the accumulated timings for the current thread. - pub fn add_to_current(times: PassTimes) { + pub fn add_to_current(times: &PassTimes) { PASS_TIME.with(|rc| for (a, b) in rc.borrow_mut().pass.iter_mut().zip( ×.pass, ) @@ -243,7 +245,7 @@ mod test { #[test] fn display() { - assert_eq!(Pass::NoPass.to_string(), ""); + assert_eq!(Pass::None.to_string(), ""); assert_eq!(Pass::regalloc.to_string(), "Register allocation"); } } diff --git a/lib/cretonne/src/topo_order.rs b/lib/cretonne/src/topo_order.rs index 373ab69e0d..a43d0fcc93 100644 --- a/lib/cretonne/src/topo_order.rs +++ b/lib/cretonne/src/topo_order.rs @@ -1,7 +1,7 @@ //! Topological order of EBBs, according to the dominator tree. -use entity::SparseSet; use dominator_tree::DominatorTree; +use entity::SparseSet; use ir::{Ebb, Layout}; use std::vec::Vec; @@ -89,12 +89,12 @@ impl TopoOrder { #[cfg(test)] mod test { + use super::*; use cursor::{Cursor, FuncCursor}; - use flowgraph::ControlFlowGraph; use dominator_tree::DominatorTree; + use flowgraph::ControlFlowGraph; use ir::{Function, InstBuilder}; use std::iter; - use super::*; #[test] fn empty() { diff --git a/lib/cretonne/src/verifier/cssa.rs b/lib/cretonne/src/verifier/cssa.rs index c508ed8d49..4982b797f7 100644 --- a/lib/cretonne/src/verifier/cssa.rs +++ b/lib/cretonne/src/verifier/cssa.rs @@ -3,7 +3,7 @@ use dbg::DisplayList; use dominator_tree::{DominatorTree, DominatorTreePreorder}; use flowgraph::ControlFlowGraph; -use ir::{Function, ExpandedProgramPoint}; +use ir::{ExpandedProgramPoint, Function}; use regalloc::liveness::Liveness; use regalloc::virtregs::VirtRegs; use timing; diff --git a/lib/cretonne/src/verifier/flags.rs b/lib/cretonne/src/verifier/flags.rs index da47fdfcf8..286c169517 100644 --- a/lib/cretonne/src/verifier/flags.rs +++ b/lib/cretonne/src/verifier/flags.rs @@ -2,13 +2,13 @@ use entity::{EntityMap, SparseSet}; use flowgraph::ControlFlowGraph; -use ir::instructions::BranchInfo; use ir; +use ir::instructions::BranchInfo; use isa; use packed_option::PackedOption; use std::result; -use verifier::{Result, Error}; use timing; +use verifier::{Error, Result}; /// Verify that CPU flags are used correctly. /// diff --git a/lib/cretonne/src/verifier/liveness.rs b/lib/cretonne/src/verifier/liveness.rs index 95691625c8..cb57de9a4f 100644 --- a/lib/cretonne/src/verifier/liveness.rs +++ b/lib/cretonne/src/verifier/liveness.rs @@ -1,14 +1,14 @@ //! Liveness verifier. use flowgraph::ControlFlowGraph; -use ir::{Function, Inst, Value, ProgramOrder, ProgramPoint, ExpandedProgramPoint}; use ir::entities::AnyEntity; +use ir::{ExpandedProgramPoint, Function, Inst, ProgramOrder, ProgramPoint, Value}; use isa::TargetIsa; use regalloc::liveness::Liveness; use regalloc::liverange::LiveRange; use std::cmp::Ordering; -use verifier::Result; use timing; +use verifier::Result; /// Verify liveness information for `func`. /// @@ -86,16 +86,14 @@ impl<'a> LivenessVerifier<'a> { self.isa.encoding_info().display(encoding) ); } - } else { + } else if !lr.affinity.is_none() { // A non-encoded instruction can only define ghost values. - if !lr.affinity.is_none() { - return err!( - inst, - "{} is a real {} value defined by a ghost instruction", - val, - lr.affinity.display(&self.isa.register_info()) - ); - } + return err!( + inst, + "{} is a real {} value defined by a ghost instruction", + val, + lr.affinity.display(&self.isa.register_info()) + ); } } @@ -109,16 +107,14 @@ impl<'a> LivenessVerifier<'a> { return err!(inst, "{} is not live at this use", val); } - if encoding.is_legal() { - // A legal instruction is not allowed to depend on ghost values. - if lr.affinity.is_none() { - return err!( - inst, - "{} is a ghost value used by a real [{}] instruction", - val, - self.isa.encoding_info().display(encoding) - ); - } + // A legal instruction is not allowed to depend on ghost values. + if encoding.is_legal() && lr.affinity.is_none() { + return err!( + inst, + "{} is a ghost value used by a real [{}] instruction", + val, + self.isa.encoding_info().display(encoding) + ); } } } diff --git a/lib/cretonne/src/verifier/locations.rs b/lib/cretonne/src/verifier/locations.rs index ae3c5cfb24..7fdfcf96ce 100644 --- a/lib/cretonne/src/verifier/locations.rs +++ b/lib/cretonne/src/verifier/locations.rs @@ -4,8 +4,8 @@ use ir; use isa; use regalloc::RegDiversions; use regalloc::liveness::Liveness; -use verifier::Result; use timing; +use verifier::Result; /// Verify value locations for `func`. /// @@ -69,10 +69,10 @@ impl<'a> LocationVerifier<'a> { let opcode = dfg[inst].opcode(); if opcode.is_return() { self.check_return_abi(inst, &divert)?; - } - - if opcode.is_branch() && !divert.is_empty() { - self.check_cfg_edges(inst, &divert)?; + } else if opcode.is_branch() { + if !divert.is_empty() { + self.check_cfg_edges(inst, &divert)?; + } } self.update_diversions(inst, &mut divert)?; diff --git a/lib/cretonne/src/verifier/mod.rs b/lib/cretonne/src/verifier/mod.rs index b2ad18c8e8..1dcd2b4377 100644 --- a/lib/cretonne/src/verifier/mod.rs +++ b/lib/cretonne/src/verifier/mod.rs @@ -34,7 +34,7 @@ //! For polymorphic opcodes, determine the controlling type variable first. //! - Branches and jumps must pass arguments to destination EBBs that match the //! expected types exactly. The number of arguments must match. -//! - All EBBs in a jump_table must take no arguments. +//! - All EBBs in a jump table must take no arguments. //! - Function calls are type checked against their signature. //! - The entry block must take arguments that match the signature of the current //! function. @@ -55,25 +55,25 @@ //! - Swizzle and shuffle instructions take a variable number of lane arguments. The number //! of arguments must match the destination type, and the lane indexes must be in range. +use self::flags::verify_flags; use dbg::DisplayList; use dominator_tree::DominatorTree; use entity::SparseSet; use flowgraph::ControlFlowGraph; -use ir::entities::AnyEntity; -use ir::instructions::{InstructionFormat, BranchInfo, ResolvedConstraint, CallInfo}; -use ir::{types, Function, ValueDef, Ebb, Inst, SigRef, FuncRef, ValueList, JumpTable, StackSlot, - StackSlotKind, GlobalVar, Value, Type, Opcode, ValueLoc, ArgumentLoc}; use ir; +use ir::entities::AnyEntity; +use ir::instructions::{BranchInfo, CallInfo, InstructionFormat, ResolvedConstraint}; +use ir::{types, ArgumentLoc, Ebb, FuncRef, Function, GlobalVar, Inst, JumpTable, Opcode, SigRef, + StackSlot, StackSlotKind, Type, Value, ValueDef, ValueList, ValueLoc}; use isa::TargetIsa; use iterators::IteratorExtras; -use self::flags::verify_flags; use settings::{Flags, FlagsOrIsa}; use std::cmp::Ordering; use std::collections::BTreeSet; use std::fmt::{self, Display, Formatter, Write}; use std::result; -use std::vec::Vec; use std::string::String; +use std::vec::Vec; use timing; pub use self::cssa::verify_cssa; @@ -188,7 +188,6 @@ impl<'a> Verifier<'a> { } fn ebb_integrity(&self, ebb: Ebb, inst: Inst) -> Result { - let is_terminator = self.func.dfg[inst].opcode().is_terminator(); let is_last_inst = self.func.layout.last_inst(ebb) == Some(inst); @@ -261,7 +260,7 @@ impl<'a> Verifier<'a> { use ir::instructions::InstructionData::*; for &arg in self.func.dfg.inst_args(inst) { - self.verify_value(inst, arg)?; + self.verify_inst_arg(inst, arg)?; // All used values must be attached to something. let original = self.func.dfg.resolve_aliases(arg); @@ -271,7 +270,7 @@ impl<'a> Verifier<'a> { } for &res in self.func.dfg.inst_results(inst) { - self.verify_value(inst, res)?; + self.verify_inst_result(inst, res)?; } match self.func.dfg[inst] { @@ -439,8 +438,16 @@ impl<'a> Verifier<'a> { fn verify_value(&self, loc_inst: Inst, v: Value) -> Result { let dfg = &self.func.dfg; if !dfg.value_is_valid(v) { - return err!(loc_inst, "invalid value reference {}", v); + err!(loc_inst, "invalid value reference {}", v) + } else { + Ok(()) } + } + + fn verify_inst_arg(&self, loc_inst: Inst, v: Value) -> Result { + self.verify_value(loc_inst, v)?; + + let dfg = &self.func.dfg; let loc_ebb = self.func.layout.pp_ebb(loc_inst); let is_reachable = self.expected_domtree.is_reachable(loc_ebb); @@ -466,14 +473,23 @@ impl<'a> Verifier<'a> { ); } // Defining instruction dominates the instruction that uses the value. - if is_reachable && - !self.expected_domtree.dominates( + if is_reachable { + if !self.expected_domtree.dominates( def_inst, loc_inst, &self.func.layout, ) - { - return err!(loc_inst, "uses value from non-dominating {}", def_inst); + { + return err!(loc_inst, "uses value from non-dominating {}", def_inst); + } + if def_inst == loc_inst { + return err!( + loc_inst, + "uses value from itself {}, {}", + def_inst, + loc_inst + ); + } } } ValueDef::Param(ebb, _) => { @@ -505,6 +521,31 @@ impl<'a> Verifier<'a> { Ok(()) } + fn verify_inst_result(&self, loc_inst: Inst, v: Value) -> Result { + self.verify_value(loc_inst, v)?; + + match self.func.dfg.value_def(v) { + ValueDef::Result(def_inst, _) => { + if def_inst != loc_inst { + err!( + loc_inst, + "instruction result {} is not defined by the instruction", + v + ) + } else { + Ok(()) + } + } + ValueDef::Param(_, _) => { + err!( + loc_inst, + "instruction result {} is not defined by the instruction", + v + ) + } + } + } + fn domtree_integrity(&self, domtree: &DominatorTree) -> Result { // We consider two `DominatorTree`s to be equal if they return the same immediate // dominator for each EBB. Therefore the current domtree is valid if it matches the freshly @@ -864,50 +905,47 @@ impl<'a> Verifier<'a> { // Check special-purpose type constraints that can't be expressed in the normal opcode // constraints. fn typecheck_special(&self, inst: Inst, ctrl_type: Type) -> Result { - match self.func.dfg[inst] { - ir::InstructionData::Unary { opcode, arg } => { - let arg_type = self.func.dfg.value_type(arg); - match opcode { - Opcode::Bextend | Opcode::Uextend | Opcode::Sextend | Opcode::Fpromote => { - if arg_type.lane_count() != ctrl_type.lane_count() { - return err!( - inst, - "input {} and output {} must have same number of lanes", - arg_type, - ctrl_type - ); - } - if arg_type.lane_bits() >= ctrl_type.lane_bits() { - return err!( - inst, - "input {} must be smaller than output {}", - arg_type, - ctrl_type - ); - } + if let ir::InstructionData::Unary { opcode, arg } = self.func.dfg[inst] { + let arg_type = self.func.dfg.value_type(arg); + match opcode { + Opcode::Bextend | Opcode::Uextend | Opcode::Sextend | Opcode::Fpromote => { + if arg_type.lane_count() != ctrl_type.lane_count() { + return err!( + inst, + "input {} and output {} must have same number of lanes", + arg_type, + ctrl_type + ); } - Opcode::Breduce | Opcode::Ireduce | Opcode::Fdemote => { - if arg_type.lane_count() != ctrl_type.lane_count() { - return err!( - inst, - "input {} and output {} must have same number of lanes", - arg_type, - ctrl_type - ); - } - if arg_type.lane_bits() <= ctrl_type.lane_bits() { - return err!( - inst, - "input {} must be larger than output {}", - arg_type, - ctrl_type - ); - } + if arg_type.lane_bits() >= ctrl_type.lane_bits() { + return err!( + inst, + "input {} must be smaller than output {}", + arg_type, + ctrl_type + ); } - _ => {} } + Opcode::Breduce | Opcode::Ireduce | Opcode::Fdemote => { + if arg_type.lane_count() != ctrl_type.lane_count() { + return err!( + inst, + "input {} and output {} must have same number of lanes", + arg_type, + ctrl_type + ); + } + if arg_type.lane_bits() <= ctrl_type.lane_bits() { + return err!( + inst, + "input {} must be larger than output {}", + arg_type, + ctrl_type + ); + } + } + _ => {} } - _ => {} } Ok(()) } @@ -1054,11 +1092,7 @@ impl<'a> Verifier<'a> { if let Some(text) = needs_enc { // This instruction needs an encoding, so generate an error. // Provide the ISA default encoding as a hint. - match isa.encode( - &self.func.dfg, - &self.func.dfg[inst], - self.func.dfg.ctrl_typevar(inst), - ) { + match self.func.dfg.encode(inst, isa) { Ok(enc) => { return err!( inst, @@ -1113,26 +1147,29 @@ impl<'a> Verifier<'a> { #[cfg(test)] mod tests { - use super::{Verifier, Error}; + use super::{Error, Verifier}; + use entity::EntityList; use ir::Function; use ir::instructions::{InstructionData, Opcode}; - use entity::EntityList; use settings; macro_rules! assert_err_with_msg { - ($e:expr, $msg:expr) => ( + ($e:expr, $msg:expr) => { match $e { - Ok(_) => { panic!("Expected an error!") }, - Err(Error { message, .. } ) => { + Ok(_) => panic!("Expected an error!"), + Err(Error { message, .. }) => { if !message.contains($msg) { - #[cfg(feature = "std")] - panic!(format!("'{}' did not contain the substring '{}'", message, $msg)); - #[cfg(not(feature = "std"))] - panic!("error message did not contain the expected substring"); + #[cfg(feature = "std")] + panic!(format!( + "'{}' did not contain the substring '{}'", + message, $msg + )); + #[cfg(not(feature = "std"))] + panic!("error message did not contain the expected substring"); } } } - ) + }; } #[test] diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index 50acbbf553..2ed8da87e9 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -1,14 +1,13 @@ -//! Converting Cretonne IL to text. +//! Converting Cretonne IR to text. //! -//! The `write` module provides the `write_function` function which converts an IL `Function` to an -//! equivalent textual representation. This textual representation can be read back by the -//! `cretonne-reader` crate. +//! The `write` module provides the `write_function` function which converts an IR `Function` to an +//! equivalent textual form. This textual form can be read back by the `cretonne-reader` crate. -use ir::{Function, DataFlowGraph, Ebb, Inst, Value, ValueDef, Type, SigRef}; -use isa::{TargetIsa, RegInfo}; -use std::fmt::{self, Result, Error, Write}; -use std::result; +use ir::{DataFlowGraph, Ebb, Function, Inst, SigRef, Type, Value, ValueDef}; +use isa::{RegInfo, TargetIsa}; use packed_option::ReservedValue; +use std::fmt::{self, Error, Result, Write}; +use std::result; use std::string::String; /// Write `func` to `w` as equivalent text. @@ -30,11 +29,9 @@ pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) - writeln!(w, "}}") } -// ====--------------------------------------------------------------------------------------====// +//---------------------------------------------------------------------- // // Function spec. -// -// ====--------------------------------------------------------------------------------------====// fn write_spec(w: &mut Write, func: &Function, regs: Option<&RegInfo>) -> Result { write!(w, "function {}{}", func.name, func.signature.display(regs)) @@ -47,54 +44,46 @@ fn write_preamble( ) -> result::Result { let mut any = false; - for ss in func.stack_slots.keys() { + for (ss, slot) in func.stack_slots.iter() { any = true; - writeln!(w, " {} = {}", ss, func.stack_slots[ss])?; + writeln!(w, " {} = {}", ss, slot)?; } - for gv in func.global_vars.keys() { + for (gv, gv_data) in func.global_vars.iter() { any = true; - writeln!(w, " {} = {}", gv, func.global_vars[gv])?; + writeln!(w, " {} = {}", gv, gv_data)?; } - for heap in func.heaps.keys() { + for (heap, heap_data) in func.heaps.iter() { any = true; - writeln!(w, " {} = {}", heap, func.heaps[heap])?; + writeln!(w, " {} = {}", heap, heap_data)?; } // Write out all signatures before functions since function declarations can refer to // signatures. - for sig in func.dfg.signatures.keys() { + for (sig, sig_data) in func.dfg.signatures.iter() { any = true; - writeln!( - w, - " {} = {}", - sig, - func.dfg.signatures[sig].display(regs) - )?; + writeln!(w, " {} = {}", sig, sig_data.display(regs))?; } - for fnref in func.dfg.ext_funcs.keys() { + for (fnref, ext_func) in func.dfg.ext_funcs.iter() { any = true; - let ext_func = &func.dfg.ext_funcs[fnref]; if ext_func.signature != SigRef::reserved_value() { writeln!(w, " {} = {}", fnref, ext_func)?; } } - for jt in func.jump_tables.keys() { + for (jt, jt_data) in func.jump_tables.iter() { any = true; - writeln!(w, " {} = {}", jt, func.jump_tables[jt])?; + writeln!(w, " {} = {}", jt, jt_data)?; } Ok(any) } -// ====--------------------------------------------------------------------------------------====// +//---------------------------------------------------------------------- // // Basic blocks -// -// ====--------------------------------------------------------------------------------------====// pub fn write_arg(w: &mut Write, func: &Function, regs: Option<&RegInfo>, arg: Value) -> Result { write!(w, "{}: {}", arg, func.dfg.value_type(arg))?; @@ -157,12 +146,9 @@ pub fn write_ebb(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, ebb: E Ok(()) } - -// ====--------------------------------------------------------------------------------------====// +//---------------------------------------------------------------------- // // Instructions -// -// ====--------------------------------------------------------------------------------------====// // Should `inst` be printed with a type suffix? // @@ -465,41 +451,41 @@ impl<'a> fmt::Display for DisplayValues<'a> { #[cfg(test)] mod tests { - use ir::{Function, ExternalName, StackSlotData, StackSlotKind}; use ir::types; + use ir::{ExternalName, Function, StackSlotData, StackSlotKind}; use std::string::ToString; #[test] fn basic() { let mut f = Function::new(); - assert_eq!(f.to_string(), "function u0:0() native {\n}\n"); + assert_eq!(f.to_string(), "function u0:0() system_v {\n}\n"); f.name = ExternalName::testcase("foo"); - assert_eq!(f.to_string(), "function %foo() native {\n}\n"); + assert_eq!(f.to_string(), "function %foo() system_v {\n}\n"); f.create_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4)); assert_eq!( f.to_string(), - "function %foo() native {\n ss0 = explicit_slot 4\n}\n" + "function %foo() system_v {\n ss0 = explicit_slot 4\n}\n" ); let ebb = f.dfg.make_ebb(); f.layout.append_ebb(ebb); assert_eq!( f.to_string(), - "function %foo() native {\n ss0 = explicit_slot 4\n\nebb0:\n}\n" + "function %foo() system_v {\n ss0 = explicit_slot 4\n\nebb0:\n}\n" ); f.dfg.append_ebb_param(ebb, types::I8); assert_eq!( f.to_string(), - "function %foo() native {\n ss0 = explicit_slot 4\n\nebb0(v0: i8):\n}\n" + "function %foo() system_v {\n ss0 = explicit_slot 4\n\nebb0(v0: i8):\n}\n" ); f.dfg.append_ebb_param(ebb, types::F32.by(4).unwrap()); assert_eq!( f.to_string(), - "function %foo() native {\n ss0 = explicit_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n" + "function %foo() system_v {\n ss0 = explicit_slot 4\n\nebb0(v0: i8, v1: f32x4):\n}\n" ); } } diff --git a/lib/filecheck/Cargo.toml b/lib/filecheck/Cargo.toml deleted file mode 100644 index b29eaa5c53..0000000000 --- a/lib/filecheck/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -authors = ["The Cretonne Project Developers"] -name = "filecheck" -version = "0.1.0" -description = "Library for matching test outputs against filecheck directives" -license = "Apache-2.0" -repository = "https://github.com/Cretonne/cretonne" -documentation = "https://docs.rs/filecheck" - -[lib] -name = "filecheck" - -[dependencies] -regex = "0.2.6" -failure = "0.1.1" -failure_derive = "0.1.1" diff --git a/lib/filecheck/src/checker.rs b/lib/filecheck/src/checker.rs deleted file mode 100644 index b3a68fba4c..0000000000 --- a/lib/filecheck/src/checker.rs +++ /dev/null @@ -1,456 +0,0 @@ -use error::{Error, Result}; -use variable::{VariableMap, Value, varname_prefix}; -use pattern::Pattern; -use regex::{Regex, Captures}; -use std::borrow::Cow; -use std::collections::HashMap; -use std::cmp::max; -use std::fmt::{self, Display, Formatter}; -use std::mem; -use MatchRange; -use explain::{Recorder, Explainer}; - -// The different kinds of directives we support. -enum Directive { - Check(Pattern), - SameLn(Pattern), - NextLn(Pattern), - Unordered(Pattern), - Not(Pattern), - Regex(String, String), -} - -// Regular expression matching a directive. -// The match groups are: -// -// 1. Keyword. -// 2. Rest of line / pattern. -// -const DIRECTIVE_RX: &str = r"\b(check|sameln|nextln|unordered|not|regex):\s+(.*)"; - -impl Directive { - /// Create a new directive from a `DIRECTIVE_RX` match. - fn new(caps: Captures) -> Result { - let cmd = caps.get(1).map(|m| m.as_str()).expect("group 1 must match"); - let rest = caps.get(2).map(|m| m.as_str()).expect("group 2 must match"); - - if cmd == "regex" { - return Directive::regex(rest); - } - - // All other commands are followed by a pattern. - let pat = rest.parse()?; - - match cmd { - "check" => Ok(Directive::Check(pat)), - "sameln" => Ok(Directive::SameLn(pat)), - "nextln" => Ok(Directive::NextLn(pat)), - "unordered" => Ok(Directive::Unordered(pat)), - "not" => { - if !pat.defs().is_empty() { - let msg = format!( - "can't define variables '$({}=...' in not: {}", - pat.defs()[0], - rest - ); - Err(Error::DuplicateDef(msg)) - } else { - Ok(Directive::Not(pat)) - } - } - _ => panic!("unexpected command {} in regex match", cmd), - } - } - - /// Create a `regex:` directive from a `VAR=...` string. - fn regex(rest: &str) -> Result { - let varlen = varname_prefix(rest); - if varlen == 0 { - return Err(Error::Syntax( - format!("invalid variable name in regex: {}", rest), - )); - } - let var = rest[0..varlen].to_string(); - if !rest[varlen..].starts_with('=') { - return Err(Error::Syntax(format!( - "expected '=' after variable '{}' in regex: {}", - var, - rest - ))); - } - // Ignore trailing white space in the regex, including CR. - Ok(Directive::Regex( - var, - rest[varlen + 1..].trim_right().to_string(), - )) - } -} - - -/// Builder for constructing a `Checker` instance. -pub struct CheckerBuilder { - directives: Vec, - linerx: Regex, -} - -impl CheckerBuilder { - /// Create a new, blank `CheckerBuilder`. - pub fn new() -> Self { - Self { - directives: Vec::new(), - linerx: Regex::new(DIRECTIVE_RX).unwrap(), - } - } - - /// Add a potential directive line. - /// - /// Returns true if this is a directive with one of the known prefixes. - /// Returns false if no known directive was found. - /// Returns an error if there is a problem with the directive. - pub fn directive(&mut self, l: &str) -> Result { - match self.linerx.captures(l) { - Some(caps) => { - self.directives.push(Directive::new(caps)?); - Ok(true) - } - None => Ok(false), - } - } - - /// Add multiple directives. - /// - /// The text is split into lines that are added individually as potential directives. - /// This method can be used to parse a whole test file containing multiple directives. - pub fn text(&mut self, t: &str) -> Result<&mut Self> { - for caps in self.linerx.captures_iter(t) { - self.directives.push(Directive::new(caps)?); - } - Ok(self) - } - - /// Get the finished `Checker`. - pub fn finish(&mut self) -> Checker { - // Move directives into the new checker, leaving `self.directives` empty and ready for - // building a new checker. - let new_directives = mem::replace(&mut self.directives, Vec::new()); - Checker::new(new_directives) - } -} - -/// Verify a list of directives against a test input. -/// -/// Use a `CheckerBuilder` to construct a `Checker`. Then use the `test` method to verify the list -/// of directives against a test input. -pub struct Checker { - directives: Vec, -} - -impl Checker { - fn new(directives: Vec) -> Self { - Self { directives: directives } - } - - /// An empty checker contains no directives, and will match any input string. - pub fn is_empty(&self) -> bool { - self.directives.is_empty() - } - - /// Verify directives against the input text. - /// - /// This returns `true` if the text matches all the directives, `false` if it doesn't. - /// An error is only returned if there is a problem with the directives. - pub fn check(&self, text: &str, vars: &VariableMap) -> Result { - self.run(text, vars, &mut ()) - } - - /// Explain how directives are matched against the input text. - pub fn explain(&self, text: &str, vars: &VariableMap) -> Result<(bool, String)> { - let mut expl = Explainer::new(text); - let success = self.run(text, vars, &mut expl)?; - expl.finish(); - Ok((success, expl.to_string())) - } - - fn run(&self, text: &str, vars: &VariableMap, recorder: &mut Recorder) -> Result { - let mut state = State::new(text, vars, recorder); - - // For each pending `not:` check, store (begin-offset, regex). - let mut nots = Vec::new(); - - for (dct_idx, dct) in self.directives.iter().enumerate() { - let (pat, range) = match *dct { - Directive::Check(ref pat) => (pat, state.check()), - Directive::SameLn(ref pat) => (pat, state.sameln()), - Directive::NextLn(ref pat) => (pat, state.nextln()), - Directive::Unordered(ref pat) => (pat, state.unordered(pat)), - Directive::Not(ref pat) => { - // Resolve `not:` directives immediately to get the right variable values, but - // don't match it until we know the end of the range. - // - // The `not:` directives test the same range as `unordered:` directives. In - // particular, if they refer to defined variables, their range is restricted to - // the text following the match that defined the variable. - nots.push((dct_idx, state.unordered_begin(pat), pat.resolve(&state)?)); - continue; - } - Directive::Regex(ref var, ref rx) => { - state.vars.insert( - var.clone(), - VarDef { - value: Value::Regex(Cow::Borrowed(rx)), - offset: 0, - }, - ); - continue; - } - }; - // Check if `pat` matches in `range`. - state.recorder.directive(dct_idx); - if let Some((match_begin, match_end)) = state.match_positive(pat, range)? { - if let Directive::Unordered(_) = *dct { - // This was an unordered match. - // Keep track of the largest matched position, but leave `last_ordered` alone. - state.max_match = max(state.max_match, match_end); - } else { - // Ordered match. - state.last_ordered = match_end; - state.max_match = match_end; - - // Verify any pending `not:` directives now that we know their range. - for (not_idx, not_begin, rx) in nots.drain(..) { - state.recorder.directive(not_idx); - if let Some(mat) = rx.find(&text[not_begin..match_begin]) { - // Matched `not:` pattern. - state.recorder.matched_not(rx.as_str(), ( - not_begin + mat.start(), - not_begin + mat.end(), - )); - return Ok(false); - } else { - state.recorder.missed_not( - rx.as_str(), - (not_begin, match_begin), - ); - } - } - } - } else { - // No match! - return Ok(false); - } - } - - // Verify any pending `not:` directives after the last ordered directive. - for (not_idx, not_begin, rx) in nots.drain(..) { - state.recorder.directive(not_idx); - if rx.find(&text[not_begin..]).is_some() { - // Matched `not:` pattern. - // TODO: Use matched range for an error message. - return Ok(false); - } - } - - Ok(true) - } -} - -/// A local definition of a variable. -pub struct VarDef<'a> { - /// The value given to the variable. - value: Value<'a>, - /// Offset in input text from where the variable is available. - offset: usize, -} - -struct State<'a> { - text: &'a str, - env_vars: &'a VariableMap, - recorder: &'a mut Recorder, - - vars: HashMap>, - // Offset after the last ordered match. This does not include recent unordered matches. - last_ordered: usize, - // Largest offset following a positive match, including unordered matches. - max_match: usize, -} - -impl<'a> State<'a> { - fn new(text: &'a str, env_vars: &'a VariableMap, recorder: &'a mut Recorder) -> State<'a> { - State { - text, - env_vars, - recorder, - vars: HashMap::new(), - last_ordered: 0, - max_match: 0, - } - } - - // Get the offset following the match that defined `var`, or 0 if var is an environment - // variable or unknown. - fn def_offset(&self, var: &str) -> usize { - self.vars - .get(var) - .map(|&VarDef { offset, .. }| offset) - .unwrap_or(0) - } - - // Get the offset of the beginning of the next line after `pos`. - fn bol(&self, pos: usize) -> usize { - if let Some(offset) = self.text[pos..].find('\n') { - pos + offset + 1 - } else { - self.text.len() - } - } - - // Get the range in text to be matched by a `check:`. - fn check(&self) -> MatchRange { - (self.max_match, self.text.len()) - } - - // Get the range in text to be matched by a `sameln:`. - fn sameln(&self) -> MatchRange { - let b = self.max_match; - let e = self.bol(b); - (b, e) - } - - // Get the range in text to be matched by a `nextln:`. - fn nextln(&self) -> MatchRange { - let b = self.bol(self.max_match); - let e = self.bol(b); - (b, e) - } - - // Get the beginning of the range in text to be matched by a `unordered:` or `not:` directive. - // The unordered directive must match after the directives that define the variables used. - fn unordered_begin(&self, pat: &Pattern) -> usize { - pat.parts() - .iter() - .filter_map(|part| part.ref_var()) - .map(|var| self.def_offset(var)) - .fold(self.last_ordered, max) - } - - // Get the range in text to be matched by a `unordered:` directive. - fn unordered(&self, pat: &Pattern) -> MatchRange { - (self.unordered_begin(pat), self.text.len()) - } - - // Search for `pat` in `range`, return the range matched. - // After a positive match, update variable definitions, if any. - fn match_positive(&mut self, pat: &Pattern, range: MatchRange) -> Result> { - let rx = pat.resolve(self)?; - let txt = &self.text[range.0..range.1]; - let defs = pat.defs(); - let matched_range = if defs.is_empty() { - // Pattern defines no variables. Fastest search is `find`. - rx.find(txt) - } else { - // We need the captures to define variables. - rx.captures(txt).map(|caps| { - let matched_range = caps.get(0).expect("whole expression must match"); - for var in defs { - let txtval = caps.name(var).map(|mat| mat.as_str()).unwrap_or(""); - self.recorder.defined_var(var, txtval); - let vardef = VarDef { - value: Value::Text(Cow::Borrowed(txtval)), - // This offset is the end of the whole matched pattern, not just the text - // defining the variable. - offset: range.0 + matched_range.end(), - }; - self.vars.insert(var.clone(), vardef); - } - matched_range - }) - }; - Ok(if let Some(mat) = matched_range { - let r = (range.0 + mat.start(), range.0 + mat.end()); - self.recorder.matched_check(rx.as_str(), r); - Some(r) - } else { - self.recorder.missed_check(rx.as_str(), range); - None - }) - } -} - -impl<'a> VariableMap for State<'a> { - fn lookup(&self, varname: &str) -> Option { - // First look for a local define. - if let Some(&VarDef { ref value, .. }) = self.vars.get(varname) { - Some(value.clone()) - } else { - // No local, maybe an environment variable? - self.env_vars.lookup(varname) - } - } -} - -impl Display for Directive { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use self::Directive::*; - match *self { - Check(ref pat) => writeln!(f, "check: {}", pat), - SameLn(ref pat) => writeln!(f, "sameln: {}", pat), - NextLn(ref pat) => writeln!(f, "nextln: {}", pat), - Unordered(ref pat) => writeln!(f, "unordered: {}", pat), - Not(ref pat) => writeln!(f, "not: {}", pat), - Regex(ref var, ref rx) => writeln!(f, "regex: {}={}", var, rx), - } - } -} - -impl Display for Checker { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for (idx, dir) in self.directives.iter().enumerate() { - write!(f, "#{} {}", idx, dir)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::CheckerBuilder; - use error::Error; - - fn e2s(e: Error) -> String { - e.to_string() - } - - #[test] - fn directive() { - let mut b = CheckerBuilder::new(); - - assert_eq!(b.directive("not here: more text").map_err(e2s), Ok(false)); - assert_eq!( - b.directive("not here: regex: X=more text").map_err(e2s), - Ok(true) - ); - assert_eq!( - b.directive("regex: X = tommy").map_err(e2s), - Err( - "expected '=' after variable 'X' in regex: X = tommy".to_string(), - ) - ); - assert_eq!( - b.directive("[arm]not: patt $x $(y) here").map_err(e2s), - Ok(true) - ); - assert_eq!( - b.directive("[x86]sameln: $x $(y=[^]]*) there").map_err(e2s), - Ok(true) - ); - // Windows line ending sneaking in. - assert_eq!(b.directive("regex: Y=foo\r").map_err(e2s), Ok(true)); - - let c = b.finish(); - assert_eq!( - c.to_string(), - "#0 regex: X=more text\n#1 not: patt $(x) $(y) here\n#2 sameln: $(x) \ - $(y=[^]]*) there\n#3 regex: Y=foo\n" - ); - } -} diff --git a/lib/filecheck/src/error.rs b/lib/filecheck/src/error.rs deleted file mode 100644 index d0736e682d..0000000000 --- a/lib/filecheck/src/error.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::result; -use std::convert::From; -use regex; - -/// A result from the filecheck library. -pub type Result = result::Result; - -/// A filecheck error. -#[derive(Fail, Debug)] -pub enum Error { - /// A syntax error in a check line. - #[fail(display = "{}", _0)] - Syntax(String), - /// A check refers to an undefined variable. - /// - /// The pattern contains `$foo` where the `foo` variable has not yet been defined. - /// Use `$$` to match a literal dollar sign. - #[fail(display = "{}", _0)] - UndefVariable(String), - /// A pattern contains a back-reference to a variable that was defined in the same pattern. - /// - /// For example, `check: Hello $(world=.*) $world`. Backreferences are not supported. Often the - /// desired effect can be achieved with the `sameln` check: - /// - /// ```text - /// check: Hello $(world=[^ ]*) - /// sameln: $world - /// ``` - #[fail(display = "{}", _0)] - Backref(String), - /// A pattern contains multiple definitions of the same variable. - #[fail(display = "{}", _0)] - DuplicateDef(String), - /// An error in a regular expression. - /// - /// Use `cause()` to get the underlying `Regex` library error. - #[fail(display = "{}", _0)] - Regex( - #[cause] - regex::Error - ), -} - -impl From for Error { - fn from(e: regex::Error) -> Error { - Error::Regex(e) - } -} diff --git a/lib/filecheck/src/explain.rs b/lib/filecheck/src/explain.rs deleted file mode 100644 index 137d360f89..0000000000 --- a/lib/filecheck/src/explain.rs +++ /dev/null @@ -1,202 +0,0 @@ -//! Explaining how *filecheck* matched or failed to match a file. - -use MatchRange; -use std::fmt::{self, Display, Formatter}; -use std::cmp::min; - -/// Record events during matching. -pub trait Recorder { - /// Set the directive we're talking about now. - fn directive(&mut self, dct: usize); - - /// Matched a positive check directive (check/sameln/nextln/unordered). - fn matched_check(&mut self, regex: &str, matched: MatchRange); - - /// Matched a `not:` directive. This means the match will fail. - fn matched_not(&mut self, regex: &str, matched: MatchRange); - - /// Missed a positive check directive. The range given is the range searched for a match. - fn missed_check(&mut self, regex: &str, searched: MatchRange); - - /// Missed `not:` directive (as intended). - fn missed_not(&mut self, regex: &str, searched: MatchRange); - - /// The directive defined a variable. - fn defined_var(&mut self, varname: &str, value: &str); -} - -/// The null recorder just doesn't listen to anything you say. -impl Recorder for () { - fn directive(&mut self, _: usize) {} - fn matched_check(&mut self, _: &str, _: MatchRange) {} - fn matched_not(&mut self, _: &str, _: MatchRange) {} - fn defined_var(&mut self, _: &str, _: &str) {} - fn missed_check(&mut self, _: &str, _: MatchRange) {} - fn missed_not(&mut self, _: &str, _: MatchRange) {} -} - -struct Match { - directive: usize, - is_match: bool, - is_not: bool, - regex: String, - range: MatchRange, -} - -struct VarDef { - directive: usize, - varname: String, - value: String, -} - -/// Record an explanation for the matching process, success or failure. -pub struct Explainer<'a> { - text: &'a str, - directive: usize, - matches: Vec, - vardefs: Vec, -} - -impl<'a> Explainer<'a> { - pub fn new(text: &'a str) -> Explainer { - Explainer { - text, - directive: 0, - matches: Vec::new(), - vardefs: Vec::new(), - } - } - - /// Finish up after recording all events in a match. - pub fn finish(&mut self) { - self.matches.sort_by_key(|m| (m.range, m.directive)); - self.vardefs.sort_by_key(|v| v.directive); - } -} - -impl<'a> Display for Explainer<'a> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - // Offset of beginning of the last line printed. - let mut curln = 0; - // Offset of beginning of the next line to be printed. - let mut nextln = 0; - - for m in &self.matches { - // Emit lines until m.range.0 is visible. - while nextln <= m.range.0 && nextln < self.text.len() { - let newln = self.text[nextln..] - .find('\n') - .map(|d| nextln + d + 1) - .unwrap_or(self.text.len()); - assert!(newln > nextln); - writeln!(f, "> {}", &self.text[nextln..newln - 1])?; - curln = nextln; - nextln = newln; - } - - // Emit ~~~ under the part of the match in curln. - if m.is_match { - write!(f, " ")?; - let mend = min(m.range.1, nextln - 1); - for pos in curln..mend { - if pos < m.range.0 { - write!(f, " ") - } else if pos == m.range.0 { - write!(f, "^") - } else { - write!(f, "~") - }?; - } - writeln!(f, "")?; - } - - // Emit the match message itself. - writeln!( - f, - "{} #{}{}: {}", - if m.is_match { "Matched" } else { "Missed" }, - m.directive, - if m.is_not { " not" } else { "" }, - m.regex - )?; - - // Emit any variable definitions. - if let Ok(found) = self.vardefs.binary_search_by_key( - &m.directive, - |v| v.directive, - ) - { - let mut first = found; - while first > 0 && self.vardefs[first - 1].directive == m.directive { - first -= 1; - } - for d in &self.vardefs[first..] { - if d.directive != m.directive { - break; - } - writeln!(f, "Define {}={}", d.varname, d.value)?; - } - } - } - - // Emit trailing lines. - for line in self.text[nextln..].lines() { - writeln!(f, "> {}", line)?; - } - Ok(()) - } -} - -impl<'a> Recorder for Explainer<'a> { - fn directive(&mut self, dct: usize) { - self.directive = dct; - } - - fn matched_check(&mut self, regex: &str, matched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: false, - regex: regex.to_owned(), - range: matched, - }); - } - - fn matched_not(&mut self, regex: &str, matched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: true, - is_not: true, - regex: regex.to_owned(), - range: matched, - }); - } - - fn missed_check(&mut self, regex: &str, searched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: false, - regex: regex.to_owned(), - range: searched, - }); - } - - fn missed_not(&mut self, regex: &str, searched: MatchRange) { - self.matches.push(Match { - directive: self.directive, - is_match: false, - is_not: true, - regex: regex.to_owned(), - range: searched, - }); - } - - fn defined_var(&mut self, varname: &str, value: &str) { - self.vardefs.push(VarDef { - directive: self.directive, - varname: varname.to_owned(), - value: value.to_owned(), - }); - } -} diff --git a/lib/filecheck/src/lib.rs b/lib/filecheck/src/lib.rs deleted file mode 100644 index 40b7d04d40..0000000000 --- a/lib/filecheck/src/lib.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! This crate provides a text pattern matching library with functionality similar to the LLVM -//! project's [FileCheck command](https://llvm.org/docs/CommandGuide/FileCheck.html). -//! -//! A list of directives is typically extracted from a file containing a test case. The test case -//! is then run through the program under test, and its output matched against the directives. -//! -//! See the [`CheckerBuilder`](struct.CheckerBuilder.html) and [`Checker`](struct.Checker.html) -//! types for the main library API. -//! -//! # Directives -//! -//! These are the directives recognized by *filecheck*: -//! -//!
-//! check: <pattern>
-//! sameln: <pattern>
-//! nextln: <pattern>
-//! unordered: <pattern>
-//! not: <pattern>
-//! regex: <variable>=<regex>
-//! 
-//! -//! Each directive is described in more detail below. -//! -//! ## Example -//! -//! The Rust program below prints the primes less than 100. It has *filecheck* directives embedded -//! in comments: -//! -//! ```rust -//! fn is_prime(x: u32) -> bool { -//! (2..x).all(|d| x % d != 0) -//! } -//! -//! // Check that we get the primes and nothing else: -//! // regex: NUM=\d+ -//! // not: $NUM -//! // check: 2 -//! // nextln: 3 -//! // check: 89 -//! // nextln: 97 -//! // not: $NUM -//! fn main() { -//! for p in (2..10).filter(|&x| is_prime(x)) { -//! println!("{}", p); -//! } -//! } -//! ``` -//! -//! A test driver compiles and runs the program, then pipes the output through *filecheck*: -//! -//! ```sh -//! $ rustc primes.rs -//! $ ./primes | cton-util filecheck -v -//! #0 regex: NUM=\d+ -//! #1 not: $NUM -//! #2 check: 2 -//! #3 nextln: 3 -//! #4 check: 89 -//! #5 nextln: 97 -//! #6 not: $NUM -//! no match #1: \d+ -//! > 2 -//! ~ -//! match #2: \b2\b -//! > 3 -//! ~ -//! match #3: \b3\b -//! > 5 -//! > 7 -//! ... -//! > 79 -//! > 83 -//! > 89 -//! ~~ -//! match #4: \b89\b -//! > 97 -//! ~~ -//! match #5: \b97\b -//! no match #6: \d+ -//! OK -//! ``` -//! -//! ## The `check:` directive -//! -//! Match patterns non-overlapping and in order: -//! -//! ```sh -//! #0 check: one -//! #1 check: two -//! ``` -//! -//! These directives will match the string `"one two"`, but not `"two one"`. The second directive -//! must match after the first one, and it can't overlap. -//! -//! ## The `sameln:` directive -//! -//! Match a pattern in the same line as the previous match. -//! -//! ```sh -//! #0 check: one -//! #1 sameln: two -//! ``` -//! -//! These directives will match the string `"one two"`, but not `"one\ntwo"`. The second match must -//! be in the same line as the first. Like the `check:` directive, the match must also follow the -//! first match, so `"two one" would not be matched. -//! -//! If there is no previous match, `sameln:` matches on the first line of the input. -//! -//! ## The `nextln:` directive -//! -//! Match a pattern in the next line after the previous match. -//! -//! ```sh -//! #0 check: one -//! #1 nextln: two -//! ``` -//! -//! These directives will match the string `"one\ntwo"`, but not `"one two"` or `"one\n\ntwo"`. -//! -//! If there is no previous match, `nextln:` matches on the second line of the input as if there -//! were a previous match on the first line. -//! -//! ## The `unordered:` directive -//! -//! Match patterns in any order, and possibly overlapping each other. -//! -//! ```sh -//! #0 unordered: one -//! #1 unordered: two -//! ``` -//! -//! These directives will match the string `"one two"` *and* the string `"two one"`. -//! -//! When a normal ordered match is inserted into a sequence of `unordered:` directives, it acts as -//! a barrier: -//! -//! ```sh -//! #0 unordered: one -//! #1 unordered: two -//! #2 check: three -//! #3 unordered: four -//! #4 unordered: five -//! ``` -//! -//! These directives will match `"two one three four five"`, but not `"two three one four five"`. -//! The `unordered:` matches are not allowed to cross the ordered `check:` directive. -//! -//! When `unordered:` matches define and use variables, a topological order is enforced. This means -//! that a match referencing a variable must follow the match where the variable was defined: -//! -//! ```sh -//! #0 regex: V=\bv\d+\b -//! #1 unordered: $(va=$V) = load -//! #2 unordered: $(vb=$V) = iadd $va -//! #3 unordered: $(vc=$V) = load -//! #4 unordered: iadd $va, $vc -//! ``` -//! -//! In the above directives, #2 must match after #1, and #4 must match after both #1 and #3, but -//! otherwise they can match in any order. -//! -//! ## The `not:` directive -//! -//! Check that a pattern *does not* appear between matches. -//! -//! ```sh -//! #0 check: one -//! #1 not: two -//! #2 check: three -//! ``` -//! -//! The directives above will match `"one five three"`, but not `"one two three"`. -//! -//! The pattern in a `not:` directive can't define any variables. Since it never matches anything, -//! the variables would not get a value. -//! -//! ## The `regex:` directive -//! -//! Define a shorthand name for a regular expression. -//! -//! ```sh -//! #0 regex: ID=\b[_a-zA-Z][_0-9a-zA-Z]*\b -//! #1 check: $ID + $ID -//! ``` -//! -//! The `regex:` directive gives a name to a regular expression which can then be used as part of a -//! pattern to match. Patterns are otherwise just plain text strings to match, so this is not -//! simple macro expansion. -//! -//! See [the Rust regex crate](../regex/index.html#syntax) for the regular expression syntax. -//! -//! # Patterns and variables -//! -//! Patterns are plain text strings to be matched in the input file. The dollar sign is used as an -//! escape character to expand variables. The following escape sequences are recognized: -//! -//!
-//! $$                Match single dollar sign.
-//! $()               Match the empty string.
-//! $(=<regex>)       Match regular expression <regex>.
-//! $<var>            Match contents of variable <var>.
-//! $(<var>)          Match contents of variable <var>.
-//! $(<var>=<regex>)  Match <regex>, then
-//!                   define <var> as the matched text.
-//! $(<var>=$<rxvar>) Match regex in <rxvar>, then
-//!                   define <var> as the matched text.
-//! 
-//! -//! Variables can contain either plain text or regular expressions. Plain text variables are -//! defined with the `$(var=...)` syntax in a previous directive. They match the same text again. -//! Backreferences within the same pattern are not allowed. When a variable is defined in a -//! pattern, it can't be referenced again in the same pattern. -//! -//! Regular expression variables are defined with the `regex:` directive. They match the regular -//! expression each time they are used, so the matches don't need to be identical. -//! -//! ## Word boundaries -//! -//! If a pattern begins or ends with a (plain text) letter or number, it will only match on a word -//! boundary. Use the `$()` empty string match to prevent this: -//! -//! ```sh -//! check: one$() -//! ``` -//! -//! This will match `"one"` and `"onetwo"`, but not `"zeroone"`. -//! -//! The empty match syntax can also be used to require leading or trailing whitespace: -//! -//! ```sh -//! check: one, $() -//! ``` -//! -//! This will match `"one, two"` , but not `"one,two"`. Without the `$()`, trailing whitespace -//! would be trimmed from the pattern. - -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] - -pub use error::{Error, Result}; -pub use variable::{VariableMap, Value, NO_VARIABLES}; -pub use checker::{Checker, CheckerBuilder}; - -extern crate regex; -extern crate failure; -#[macro_use] -extern crate failure_derive; - -mod error; -mod variable; -mod pattern; -mod checker; -mod explain; - -/// The range of a match in the input text. -pub type MatchRange = (usize, usize); diff --git a/lib/filecheck/src/pattern.rs b/lib/filecheck/src/pattern.rs deleted file mode 100644 index 97977b191c..0000000000 --- a/lib/filecheck/src/pattern.rs +++ /dev/null @@ -1,593 +0,0 @@ -//! Pattern matching for a single directive. - -use error::{Error, Result}; -use variable::{varname_prefix, VariableMap, Value}; -use std::str::FromStr; -use std::fmt::{self, Display, Formatter, Write}; -use regex::{Regex, RegexBuilder, escape}; - -/// A pattern to match as specified in a directive. -/// -/// Each pattern is broken into a sequence of parts that must match in order. The kinds of parts -/// are: -/// -/// 1. Plain text match. -/// 2. Variable match, `$FOO` or `$(FOO)`. The variable `FOO` may expand to plain text or a regex. -/// 3. Variable definition from literal regex, `$(foo=.*)`. Match the regex and assign matching text -/// to variable `foo`. -/// 4. Variable definition from regex variable, `$(foo=$RX)`. Lookup variable `RX` which should -/// expand to a regex, match the regex, and assign matching text to variable `foo`. -/// -pub struct Pattern { - parts: Vec, - // Variables defined by this pattern. - defs: Vec, -} - -/// One atomic part of a pattern. -#[derive(Debug, PartialEq, Eq)] -pub enum Part { - /// Match a plain string. - Text(String), - /// Match a regular expression. The regex has already been wrapped in a non-capturing group if - /// necessary, so it is safe to concatenate. - Regex(String), - /// Match the contents of a variable, which can be plain text or regex. - Var(String), - /// Match literal regex, then assign match to variable. - /// The regex has already been wrapped in a named capture group. - DefLit { def: usize, regex: String }, - /// Lookup variable `var`, match resulting regex, assign matching text to variable `defs[def]`. - DefVar { def: usize, var: String }, -} - -impl Part { - /// Get the variable referenced by this part, if any. - pub fn ref_var(&self) -> Option<&str> { - match *self { - Part::Var(ref var) | - Part::DefVar { ref var, .. } => Some(var), - _ => None, - } - } -} - -impl Pattern { - /// Create a new blank pattern. Use the `FromStr` trait to generate Patterns with content. - fn new() -> Self { - Self { - parts: Vec::new(), - defs: Vec::new(), - } - } - - /// Check if the variable `v` is defined by this pattern. - pub fn defines_var(&self, v: &str) -> bool { - self.defs.iter().any(|d| d == v) - } - - /// Add a definition of a new variable. - /// Return the allocated def number. - fn add_def(&mut self, v: &str) -> Result { - if self.defines_var(v) { - Err(Error::DuplicateDef( - format!("duplicate definition of ${} in same pattern", v), - )) - } else { - let idx = self.defs.len(); - self.defs.push(v.to_string()); - Ok(idx) - } - } - - /// Parse a `Part` from a prefix of `s`. - /// Return the part and the number of bytes consumed from `s`. - /// Adds defined variables to `self.defs`. - fn parse_part(&mut self, s: &str) -> Result<(Part, usize)> { - let dollar = s.find('$'); - if dollar != Some(0) { - // String doesn't begin with a dollar sign, so match plain text up to the dollar sign. - let end = dollar.unwrap_or(s.len()); - return Ok((Part::Text(s[0..end].to_string()), end)); - } - - // String starts with a dollar sign. Look for these possibilities: - // - // 1. `$$`. - // 2. `$var`. - // 3. `$(var)`. - // 4. `$(var=regex)`. Where `regex` is a regular expression possibly containing matching - // braces. - // 5. `$(var=$VAR)`. - - // A doubled dollar sign matches a single dollar sign. - if s.starts_with("$$") { - return Ok((Part::Text("$".to_string()), 2)); - } - - // Look for `$var`. - let varname_end = 1 + varname_prefix(&s[1..]); - if varname_end != 1 { - return Ok((Part::Var(s[1..varname_end].to_string()), varname_end)); - } - - // All remaining possibilities start with `$(`. - if s.len() < 2 || !s.starts_with("$(") { - return Err(Error::Syntax( - "pattern syntax error, use $$ to match a single $" - .to_string(), - )); - } - - // Match the variable name, allowing for an empty varname in `$()`, or `$(=...)`. - let varname_end = 2 + varname_prefix(&s[2..]); - let varname = s[2..varname_end].to_string(); - - match s[varname_end..].chars().next() { - None => { - return Err(Error::Syntax(format!("unterminated $({}...", varname))); - } - Some(')') => { - let part = if varname.is_empty() { - // Match `$()`, turn it into an empty text match. - Part::Text(varname) - } else { - // Match `$(var)`. - Part::Var(varname) - }; - return Ok((part, varname_end + 1)); - } - Some('=') => { - // Variable definition. Fall through. - } - Some(ch) => { - return Err(Error::Syntax( - format!("syntax error in $({}... '{}'", varname, ch), - )); - } - } - - // This is a variable definition of the form `$(var=...`. - - // Allocate a definition index. - let def = if varname.is_empty() { - None - } else { - Some(self.add_def(&varname)?) - }; - - // Match `$(var=$PAT)`. - if s[varname_end + 1..].starts_with('$') { - let refname_begin = varname_end + 2; - let refname_end = refname_begin + varname_prefix(&s[refname_begin..]); - if refname_begin == refname_end { - return Err(Error::Syntax( - format!("expected variable name in $({}=$...", varname), - )); - } - if !s[refname_end..].starts_with(')') { - return Err(Error::Syntax(format!( - "expected ')' after $({}=${}...", - varname, - &s[refname_begin..refname_end] - ))); - } - let refname = s[refname_begin..refname_end].to_string(); - return if let Some(defidx) = def { - Ok(( - Part::DefVar { - def: defidx, - var: refname, - }, - refname_end + 1, - )) - } else { - Err(Error::Syntax( - format!("expected variable name in $(=${})", refname), - )) - }; - } - - // Last case: `$(var=...)` where `...` is a regular expression, possibly containing matched - // parentheses. - let rx_begin = varname_end + 1; - let rx_end = rx_begin + regex_prefix(&s[rx_begin..]); - if s[rx_end..].starts_with(')') { - let part = if let Some(defidx) = def { - // Wrap the regex in a named capture group. - Part::DefLit { - def: defidx, - regex: format!("(?P<{}>{})", varname, &s[rx_begin..rx_end]), - } - } else { - // When the varname is empty just match the regex, don't capture any variables. - // This is `$(=[a-z])`. - // Wrap the regex in a non-capturing group to make it concatenation-safe. - Part::Regex(format!("(?:{})", &s[rx_begin..rx_end])) - }; - Ok((part, rx_end + 1)) - } else { - Err(Error::Syntax(format!( - "missing ')' after regex in $({}={}", - varname, - &s[rx_begin..rx_end] - ))) - } - } -} - -/// Compute the length of a regular expression terminated by `)` or `}`. -/// Handle nested and escaped parentheses in the rx, but don't actually parse it. -/// Return the position of the terminating brace or the length of the string. -fn regex_prefix(s: &str) -> usize { - // The previous char was a backslash. - let mut escape = false; - // State around parsing charsets. - enum State { - Normal, // Outside any charset. - Curly, // Inside curly braces. - CSFirst, // Immediately after opening `[`. - CSNeg, // Immediately after `[^`. - CSBody, // Inside `[...`. - } - let mut state = State::Normal; - - // Current nesting level of parens. - let mut nest = 0usize; - - for (idx, ch) in s.char_indices() { - if escape { - escape = false; - continue; - } else if ch == '\\' { - escape = true; - continue; - } - match state { - State::Normal => { - match ch { - '[' => state = State::CSFirst, - '{' => state = State::Curly, - '(' => nest += 1, - ')' if nest > 0 => nest -= 1, - ')' | '}' => return idx, - _ => {} - } - } - State::Curly => { - if ch == '}' { - state = State::Normal; - } - } - State::CSFirst => { - state = match ch { - '^' => State::CSNeg, - _ => State::CSBody, - } - } - State::CSNeg => state = State::CSBody, - State::CSBody => { - if ch == ']' { - state = State::Normal; - } - } - } - } - s.len() -} - -impl FromStr for Pattern { - type Err = Error; - - fn from_str(s: &str) -> Result { - // Always remove leading and trailing whitespace. - // Use `$()` to actually include that in a match. - let s = s.trim(); - let mut pat = Pattern::new(); - let mut pos = 0; - while pos < s.len() { - let (part, len) = pat.parse_part(&s[pos..])?; - if let Some(v) = part.ref_var() { - if pat.defines_var(v) { - return Err(Error::Backref(format!( - "unsupported back-reference to '${}' \ - defined in same pattern", - v - ))); - } - } - pat.parts.push(part); - pos += len; - } - Ok(pat) - } -} - -impl Pattern { - /// Get a list of parts in this pattern. - pub fn parts(&self) -> &[Part] { - &self.parts - } - - /// Get a list of variable names defined when this pattern matches. - pub fn defs(&self) -> &[String] { - &self.defs - } - - /// Resolve all variable references in this pattern, turning it into a regular expression. - pub fn resolve(&self, vmap: &VariableMap) -> Result { - let mut out = String::new(); - - // Add a word boundary check `\b` to the beginning of the regex, but only if the first part - // is a plain text match that starts with a word character. - // - // This behavior can be disabled by starting the pattern with `$()`. - if let Some(&Part::Text(ref s)) = self.parts.first() { - if s.starts_with(char::is_alphanumeric) { - out.push_str(r"\b"); - } - } - - for part in &self.parts { - match *part { - Part::Text(ref s) => { - out.push_str(&escape(s)); - } - Part::Regex(ref rx) => out.push_str(rx), - Part::Var(ref var) => { - // Resolve the variable. We can handle a plain text expansion. - match vmap.lookup(var) { - None => { - return Err(Error::UndefVariable(format!("undefined variable ${}", var))) - } - Some(Value::Text(s)) => out.push_str(&escape(&s)), - // Wrap regex in non-capturing group for safe concatenation. - Some(Value::Regex(rx)) => write!(out, "(?:{})", rx).unwrap(), - } - } - Part::DefLit { ref regex, .. } => out.push_str(regex), - Part::DefVar { def, ref var } => { - // Wrap regex in a named capture group. - write!(out, "(?P<{}>", self.defs[def]).unwrap(); - match vmap.lookup(var) { - None => { - return Err(Error::UndefVariable(format!("undefined variable ${}", var))) - } - Some(Value::Text(s)) => write!(out, "{})", escape(&s[..])).unwrap(), - Some(Value::Regex(rx)) => write!(out, "{})", rx).unwrap(), - } - } - } - - } - - // Add a word boundary check `\b` to the end of the regex, but only if the final part - // is a plain text match that ends with a word character. - // - // This behavior can be disabled by ending the pattern with `$()`. - if let Some(&Part::Text(ref s)) = self.parts.last() { - if s.ends_with(char::is_alphanumeric) { - out.push_str(r"\b"); - } - } - - Ok(RegexBuilder::new(&out).multi_line(true).build()?) - } -} - -impl Display for Pattern { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - for part in &self.parts { - use self::Part::*; - match *part { - Text(ref txt) if txt == "" => write!(f, "$()"), - Text(ref txt) if txt == "$" => write!(f, "$$"), - Text(ref txt) => write!(f, "{}", txt), - Regex(ref rx) => write!(f, "$(={})", rx), - Var(ref var) => write!(f, "$({})", var), - DefLit { def, ref regex } => { - let defvar = &self.defs[def]; - // (?P...). - let litrx = ®ex[5 + defvar.len()..regex.len() - 1]; - write!(f, "$({}={})", defvar, litrx) - } - DefVar { def, ref var } => write!(f, "$({}=${})", self.defs[def], var), - }?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn regex() { - use super::regex_prefix; - - assert_eq!(regex_prefix(""), 0); - assert_eq!(regex_prefix(")"), 0); - assert_eq!(regex_prefix(")c"), 0); - assert_eq!(regex_prefix("x"), 1); - assert_eq!(regex_prefix("x)x"), 1); - - assert_eq!(regex_prefix("x(c))x"), 4); - assert_eq!(regex_prefix("()x(c))x"), 6); - assert_eq!(regex_prefix("()x(c)"), 6); - - assert_eq!(regex_prefix("x([)]))x"), 6); - assert_eq!(regex_prefix("x[)])x"), 4); - assert_eq!(regex_prefix("x[^)])x"), 5); - assert_eq!(regex_prefix("x[^])x"), 6); - } - - #[test] - fn part() { - use super::{Pattern, Part}; - let mut pat = Pattern::new(); - - // This is dubious, should we panic instead? - assert_eq!(pat.parse_part("").unwrap(), (Part::Text("".to_string()), 0)); - - assert_eq!( - pat.parse_part("x").unwrap(), - (Part::Text("x".to_string()), 1) - ); - assert_eq!(pat.parse_part("x2").unwrap(), ( - Part::Text("x2".to_string()), - 2, - )); - assert_eq!(pat.parse_part("x$").unwrap(), ( - Part::Text("x".to_string()), - 1, - )); - assert_eq!(pat.parse_part("x$$").unwrap(), ( - Part::Text("x".to_string()), - 1, - )); - - assert_eq!( - pat.parse_part("$").unwrap_err().to_string(), - "pattern syntax error, use $$ to match a single $" - ); - - assert_eq!(pat.parse_part("$$").unwrap(), ( - Part::Text("$".to_string()), - 2, - )); - assert_eq!(pat.parse_part("$$ ").unwrap(), ( - Part::Text("$".to_string()), - 2, - )); - - assert_eq!( - pat.parse_part("$0").unwrap(), - (Part::Var("0".to_string()), 2) - ); - assert_eq!(pat.parse_part("$xx=").unwrap(), ( - Part::Var("xx".to_string()), - 3, - )); - assert_eq!(pat.parse_part("$xx$").unwrap(), ( - Part::Var("xx".to_string()), - 3, - )); - - assert_eq!(pat.parse_part("$(0)").unwrap(), ( - Part::Var("0".to_string()), - 4, - )); - assert_eq!(pat.parse_part("$()").unwrap(), ( - Part::Text("".to_string()), - 3, - )); - - assert_eq!( - pat.parse_part("$(0").unwrap_err().to_string(), - ("unterminated $(0...") - ); - assert_eq!( - pat.parse_part("$(foo:").unwrap_err().to_string(), - ("syntax error in $(foo... ':'") - ); - assert_eq!( - pat.parse_part("$(foo =").unwrap_err().to_string(), - ("syntax error in $(foo... ' '") - ); - assert_eq!( - pat.parse_part("$(eo0=$bar").unwrap_err().to_string(), - ("expected ')' after $(eo0=$bar...") - ); - assert_eq!( - pat.parse_part("$(eo1=$bar}").unwrap_err().to_string(), - ("expected ')' after $(eo1=$bar...") - ); - assert_eq!( - pat.parse_part("$(eo2=$)").unwrap_err().to_string(), - ("expected variable name in $(eo2=$...") - ); - assert_eq!( - pat.parse_part("$(eo3=$-)").unwrap_err().to_string(), - ("expected variable name in $(eo3=$...") - ); - } - - #[test] - fn partdefs() { - use super::{Pattern, Part}; - let mut pat = Pattern::new(); - - assert_eq!(pat.parse_part("$(foo=$bar)").unwrap(), ( - Part::DefVar { - def: 0, - var: "bar".to_string(), - }, - 11, - )); - assert_eq!( - pat.parse_part("$(foo=$bar)").unwrap_err().to_string(), - "duplicate definition of $foo in same pattern" - ); - - assert_eq!(pat.parse_part("$(fxo=$bar)x").unwrap(), ( - Part::DefVar { - def: 1, - var: "bar".to_string(), - }, - 11, - )); - - assert_eq!(pat.parse_part("$(fo2=[a-z])").unwrap(), ( - Part::DefLit { - def: 2, - regex: "(?P[a-z])".to_string(), - }, - 12, - )); - assert_eq!(pat.parse_part("$(fo3=[a-)])").unwrap(), ( - Part::DefLit { - def: 3, - regex: "(?P[a-)])".to_string(), - }, - 12, - )); - assert_eq!(pat.parse_part("$(fo4=)").unwrap(), ( - Part::DefLit { - def: 4, - regex: "(?P)".to_string(), - }, - 7, - )); - - assert_eq!(pat.parse_part("$(=.*)").unwrap(), ( - Part::Regex( - "(?:.*)".to_string(), - ), - 6, - )); - - assert_eq!(pat.parse_part("$(=)").unwrap(), ( - Part::Regex( - "(?:)".to_string(), - ), - 4, - )); - assert_eq!(pat.parse_part("$()").unwrap(), ( - Part::Text("".to_string()), - 3, - )); - } - - #[test] - fn pattern() { - use super::Pattern; - - let p: Pattern = " Hello world! ".parse().unwrap(); - assert_eq!(format!("{:?}", p.parts), "[Text(\"Hello world!\")]"); - - let p: Pattern = " $foo=$(bar) ".parse().unwrap(); - assert_eq!( - format!("{:?}", p.parts), - "[Var(\"foo\"), Text(\"=\"), Var(\"bar\")]" - ); - } -} diff --git a/lib/filecheck/src/variable.rs b/lib/filecheck/src/variable.rs deleted file mode 100644 index 5977f06354..0000000000 --- a/lib/filecheck/src/variable.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::borrow::Cow; - -/// A variable name is one or more ASCII alphanumerical characters, including underscore. -/// Note that numerical variable names like `$45` are allowed too. -/// -/// Try to parse a variable name from the beginning of `s`. -/// Return the index of the character following the varname. -/// This returns 0 if `s` doesn't have a prefix that is a variable name. -pub fn varname_prefix(s: &str) -> usize { - for (idx, ch) in s.char_indices() { - match ch { - 'a'...'z' | 'A'...'Z' | '0'...'9' | '_' => {} - _ => return idx, - } - } - s.len() -} - -/// A variable can contain either a regular expression or plain text. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Value<'a> { - /// Verbatim text. - Text(Cow<'a, str>), - /// Regular expression. - Regex(Cow<'a, str>), -} - -/// Resolve variables by name. -pub trait VariableMap { - /// Get the value of the variable `varname`, or return `None` for an unknown variable name. - fn lookup(&self, varname: &str) -> Option; -} - -impl VariableMap for () { - fn lookup(&self, _: &str) -> Option { - None - } -} - -/// An empty variable map. -pub const NO_VARIABLES: &'static VariableMap = &(); - -#[cfg(test)] -mod tests { - #[test] - fn varname() { - use super::varname_prefix; - - assert_eq!(varname_prefix(""), 0); - assert_eq!(varname_prefix("\0"), 0); - assert_eq!(varname_prefix("_"), 1); - assert_eq!(varname_prefix("0"), 1); - assert_eq!(varname_prefix("01"), 2); - assert_eq!(varname_prefix("b"), 1); - assert_eq!(varname_prefix("C"), 1); - assert_eq!(varname_prefix("."), 0); - assert_eq!(varname_prefix(".s"), 0); - assert_eq!(varname_prefix("0."), 1); - assert_eq!(varname_prefix("01="), 2); - assert_eq!(varname_prefix("0a)"), 2); - } -} diff --git a/lib/filecheck/tests/basic.rs b/lib/filecheck/tests/basic.rs deleted file mode 100644 index 1ccc5991a5..0000000000 --- a/lib/filecheck/tests/basic.rs +++ /dev/null @@ -1,402 +0,0 @@ -extern crate filecheck; - -use filecheck::{CheckerBuilder, NO_VARIABLES, Error as FcError}; - -fn e2s(e: FcError) -> String { - e.to_string() -} - -#[test] -fn empty() { - let c = CheckerBuilder::new().finish(); - assert!(c.is_empty()); - - // An empty checker matches anything. - assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn no_directives() { - let c = CheckerBuilder::new().text("nothing here").unwrap().finish(); - assert!(c.is_empty()); - - // An empty checker matches anything. - assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn no_matches() { - let c = CheckerBuilder::new() - .text("regex: FOO=bar") - .unwrap() - .finish(); - assert!(!c.is_empty()); - - // An empty checker matches anything. - assert_eq!(c.check("", NO_VARIABLES).map_err(e2s), Ok(true)); - assert_eq!(c.check("hello", NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn simple() { - let c = CheckerBuilder::new() - .text( - " - check: one - check: two - ", - ) - .unwrap() - .finish(); - - let t = " - zero - one - and a half - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = " - zero - and a half - two - one - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); -} - -#[test] -fn sameln() { - let c = CheckerBuilder::new() - .text( - " - check: one - sameln: two - ", - ) - .unwrap() - .finish(); - - let t = " - zero - one - and a half - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn nextln() { - let c = CheckerBuilder::new() - .text( - " - check: one - nextln: two - ", - ) - .unwrap() - .finish(); - - let t = " - zero - one - and a half - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = " - zero - one two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = " - zero - one - two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn leading_nextln() { - // A leading nextln directive should match from line 2. - // This is somewhat arbitrary, but consistent with a preceding 'check: $()' directive. - let c = CheckerBuilder::new() - .text( - " - nextln: one - nextln: two - ", - ) - .unwrap() - .finish(); - - let t = "zero - one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "one - two - three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); -} - -#[test] -fn leading_sameln() { - // A leading sameln directive should match from line 1. - let c = CheckerBuilder::new() - .text( - " - sameln: one - sameln: two - ", - ) - .unwrap() - .finish(); - - let t = "zero - one two three - "; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "zero one two three"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "zero one - two three"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); -} - -#[test] -fn not() { - let c = CheckerBuilder::new() - .text( - " - check: one$() - not: $()eat$() - check: $()two - ", - ) - .unwrap() - .finish(); - - let t = "onetwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "one eat two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneeattwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneatwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn notnot() { - let c = CheckerBuilder::new() - .text( - " - check: one$() - not: $()eat$() - not: half - check: $()two - ", - ) - .unwrap() - .finish(); - - let t = "onetwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); - - let t = "one eat two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "one half two"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneeattwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - // The `not: half` pattern only matches whole words, but the bracketing matches are considered - // word boundaries, so it does match in this case. - let t = "onehalftwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(false)); - - let t = "oneatwo"; - assert_eq!(c.check(t, NO_VARIABLES).map_err(e2s), Ok(true)); -} - -#[test] -fn unordered() { - let c = CheckerBuilder::new() - .text( - " - check: one - unordered: two - unordered: three - check: four - ", - ) - .unwrap() - .finish(); - - assert_eq!( - c.check("one two three four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three two four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - - assert_eq!( - c.check("one two four three four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - assert_eq!( - c.check("one three four two four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - - assert_eq!( - c.check("one two four three", NO_VARIABLES).map_err(e2s), - Ok(false) - ); - assert_eq!( - c.check("one three four two", NO_VARIABLES).map_err(e2s), - Ok(false) - ); -} - -#[test] -fn leading_unordered() { - let c = CheckerBuilder::new() - .text( - " - unordered: two - unordered: three - check: four - ", - ) - .unwrap() - .finish(); - - assert_eq!( - c.check("one two three four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three two four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - - assert_eq!( - c.check("one two four three four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - assert_eq!( - c.check("one three four two four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - - assert_eq!( - c.check("one two four three", NO_VARIABLES).map_err(e2s), - Ok(false) - ); - assert_eq!( - c.check("one three four two", NO_VARIABLES).map_err(e2s), - Ok(false) - ); -} - -#[test] -fn trailing_unordered() { - let c = CheckerBuilder::new() - .text( - " - check: one - unordered: two - unordered: three - ", - ) - .unwrap() - .finish(); - - assert_eq!( - c.check("one two three four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three two four", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - - assert_eq!( - c.check("one two four three four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - assert_eq!( - c.check("one three four two four", NO_VARIABLES).map_err( - e2s, - ), - Ok(true) - ); - - assert_eq!( - c.check("one two four three", NO_VARIABLES).map_err(e2s), - Ok(true) - ); - assert_eq!( - c.check("one three four two", NO_VARIABLES).map_err(e2s), - Ok(true) - ); -} diff --git a/lib/filetests/Cargo.toml b/lib/filetests/Cargo.toml new file mode 100644 index 0000000000..0e37c1ef8d --- /dev/null +++ b/lib/filetests/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cretonne-filetests" +authors = ["The Cretonne Project Developers"] +version = "0.4.1" +description = "Test driver and implementations of the filetest commands" +license = "Apache-2.0" +documentation = "http://cretonne.readthedocs.io/en/latest/testing.html#file-tests" +repository = "https://github.com/Cretonne/cretonne" +publish = false + +[lib] +name = "cton_filetests" + +[dependencies] +cretonne = { path = "../cretonne", version = "0.4.1" } +cretonne-reader = { path = "../reader", version = "0.4.1" } +filecheck = "0.3.0" +num_cpus = "1.8.0" diff --git a/cranelift/src/filetest/concurrent.rs b/lib/filetests/src/concurrent.rs similarity index 91% rename from cranelift/src/filetest/concurrent.rs rename to lib/filetests/src/concurrent.rs index a651c14071..7ca4b01d89 100644 --- a/cranelift/src/filetest/concurrent.rs +++ b/lib/filetests/src/concurrent.rs @@ -4,16 +4,16 @@ //! concurrently. use cretonne::timing; +use num_cpus; use std::panic::catch_unwind; use std::path::{Path, PathBuf}; -use std::sync::mpsc::{channel, Sender, Receiver}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use num_cpus; -use filetest::{TestResult, runone}; +use {runone, TestResult}; -// Request sent to worker threads contains jobid and path. +/// Request sent to worker threads contains jobid and path. struct Request(usize, PathBuf); /// Reply from worker thread, @@ -25,13 +25,13 @@ pub enum Reply { /// Manage threads that run test jobs concurrently. pub struct ConcurrentRunner { - // Channel for sending requests to the worker threads. - // The workers are sharing the receiver with an `Arc>`. - // This is `None` when shutting down. + /// Channel for sending requests to the worker threads. + /// The workers are sharing the receiver with an `Arc>`. + /// This is `None` when shutting down. request_tx: Option>, - // Channel for receiving replies from the workers. - // Workers have their own `Sender`. + /// Channel for receiving replies from the workers. + /// Workers have their own `Sender`. reply_rx: Receiver, handles: Vec>, @@ -70,7 +70,7 @@ impl ConcurrentRunner { assert!(self.request_tx.is_none(), "must shutdown before join"); for h in self.handles.drain(..) { match h.join() { - Ok(t) => timing::add_to_current(t), + Ok(t) => timing::add_to_current(&t), Err(e) => println!("worker panicked: {:?}", e), } } diff --git a/lib/filetests/src/lib.rs b/lib/filetests/src/lib.rs new file mode 100644 index 0000000000..718502eb53 --- /dev/null +++ b/lib/filetests/src/lib.rs @@ -0,0 +1,90 @@ +//! File tests. +//! +//! This crate contains the main test driver as well as implementations of the +//! available filetest commands. + +#![cfg_attr(feature="cargo-clippy", allow( + type_complexity, +// Rustfmt 0.9.0 is at odds with this lint: + block_in_if_condition_stmt))] + +#[macro_use(dbg)] +extern crate cretonne; +extern crate cton_reader; +extern crate filecheck; +extern crate num_cpus; + +use cton_reader::TestCommand; +use runner::TestRunner; +use std::path::Path; +use std::time; + +mod concurrent; +mod match_directive; +mod runner; +mod runone; +mod subtest; + +mod test_binemit; +mod test_cat; +mod test_compile; +mod test_dce; +mod test_domtree; +mod test_legalizer; +mod test_licm; +mod test_postopt; +mod test_preopt; +mod test_print_cfg; +mod test_regalloc; +mod test_simple_gvn; +mod test_verifier; + +/// The result of running the test in a file. +type TestResult = Result; + +/// Main entry point for `cton-util test`. +/// +/// Take a list of filenames which can be either `.cton` files or directories. +/// +/// Files are interpreted as test cases and executed immediately. +/// +/// Directories are scanned recursively for test cases ending in `.cton`. These test cases are +/// executed on background threads. +/// +pub fn run(verbose: bool, files: &[String]) -> TestResult { + let mut runner = TestRunner::new(verbose); + + for path in files.iter().map(Path::new) { + if path.is_file() { + runner.push_test(path); + } else { + runner.push_dir(path); + } + } + + runner.start_threads(); + runner.run() +} + +/// Create a new subcommand trait object to match `parsed.command`. +/// +/// This function knows how to create all of the possible `test ` commands that can appear in +/// a `.cton` test file. +fn new_subtest(parsed: &TestCommand) -> subtest::Result> { + match parsed.command { + "binemit" => test_binemit::subtest(parsed), + "cat" => test_cat::subtest(parsed), + "compile" => test_compile::subtest(parsed), + "dce" => test_dce::subtest(parsed), + "domtree" => test_domtree::subtest(parsed), + "legalizer" => test_legalizer::subtest(parsed), + "licm" => test_licm::subtest(parsed), + "postopt" => test_postopt::subtest(parsed), + "preopt" => test_preopt::subtest(parsed), + "print-cfg" => test_print_cfg::subtest(parsed), + "regalloc" => test_regalloc::subtest(parsed), + "simple-gvn" => test_simple_gvn::subtest(parsed), + "verifier" => test_verifier::subtest(parsed), + _ => Err(format!("unknown test command '{}'", parsed.command)), + } +} diff --git a/lib/filetests/src/match_directive.rs b/lib/filetests/src/match_directive.rs new file mode 100644 index 0000000000..9a8d94a4df --- /dev/null +++ b/lib/filetests/src/match_directive.rs @@ -0,0 +1,27 @@ +/// Look for a directive in a comment string. +/// The directive is of the form "foo:" and should follow the leading `;` in the comment: +/// +/// ; dominates: ebb3 ebb4 +/// +/// Return the comment text following the directive. +pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> { + assert!( + directive.ends_with(':'), + "Directive must include trailing colon" + ); + let text = comment.trim_left_matches(';').trim_left(); + if text.starts_with(directive) { + Some(text[directive.len()..].trim()) + } else { + None + } +} + +#[test] +fn test_match_directive() { + assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar")); + assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar")); + assert_eq!(match_directive("foo:bar", "foo:"), Some("bar")); + assert_eq!(match_directive(";x foo: bar", "foo:"), None); + assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar")); +} diff --git a/cranelift/src/filetest/runner.rs b/lib/filetests/src/runner.rs similarity index 95% rename from cranelift/src/filetest/runner.rs rename to lib/filetests/src/runner.rs index 09ec3a19a9..d4865b92e9 100644 --- a/cranelift/src/filetest/runner.rs +++ b/lib/filetests/src/runner.rs @@ -3,18 +3,18 @@ //! This module implements the `TestRunner` struct which manages executing tests as well as //! scanning directories for tests. +use concurrent::{ConcurrentRunner, Reply}; use std::error::Error; -use std::fmt::{self, Display}; use std::ffi::OsStr; +use std::fmt::{self, Display}; use std::path::{Path, PathBuf}; -use filetest::{TestResult, runone}; -use filetest::concurrent::{ConcurrentRunner, Reply}; -use CommandResult; +use std::time; +use {runone, TestResult}; -// Timeout in seconds when we're not making progress. +/// Timeout in seconds when we're not making progress. const TIMEOUT_PANIC: usize = 10; -// Timeout for reporting slow tests without panicking. +/// Timeout for reporting slow tests without panicking. const TIMEOUT_SLOW: usize = 3; struct QueueEntry { @@ -45,7 +45,7 @@ impl Display for QueueEntry { f, "{}.{:03} {}", dur.as_secs(), - dur.subsec_nanos() / 1000000, + dur.subsec_nanos() / 1_000_000, p ) } @@ -135,7 +135,7 @@ impl TestRunner { // This lets us skip spurious extensionless files without statting everything // needlessly. if !dir.is_file() { - self.path_error(dir, err); + self.path_error(&dir, &err); } } Ok(entries) => { @@ -149,7 +149,7 @@ impl TestRunner { // libstd/sys/unix/fs.rs seems to suggest that breaking now would // be a good idea, or the iterator could keep returning the same // error forever. - self.path_error(dir, err); + self.path_error(&dir, &err); break; } Ok(entry) => { @@ -172,7 +172,7 @@ impl TestRunner { } /// Report an error related to a path. - fn path_error(&mut self, path: PathBuf, err: E) { + fn path_error(&mut self, path: &PathBuf, err: &E) { self.errors += 1; println!("{}: {}", path.to_string_lossy(), err); } @@ -302,7 +302,6 @@ impl TestRunner { // Inter-quartile range. let iqr = q3 - q1; - // Cut-off for what we consider a 'slow' test: 3 IQR from the 75% quartile. // // Q3 + 1.5 IQR are the data points that would be plotted as outliers outside a box plot, @@ -319,18 +318,18 @@ impl TestRunner { { println!("slow: {}", t) } - } /// Scan pushed directories for tests and run them. - pub fn run(&mut self) -> CommandResult { + pub fn run(&mut self) -> TestResult { + let started = time::Instant::now(); self.scan_dirs(); self.schedule_jobs(); self.drain_threads(); self.report_slow_tests(); println!("{} tests", self.tests.len()); match self.errors { - 0 => Ok(()), + 0 => Ok(started.elapsed()), 1 => Err("1 failure".to_string()), n => Err(format!("{} failures", n)), } diff --git a/cranelift/src/filetest/runone.rs b/lib/filetests/src/runone.rs similarity index 90% rename from cranelift/src/filetest/runone.rs rename to lib/filetests/src/runone.rs index 1dc8eb9849..8cdec2d626 100644 --- a/cranelift/src/filetest/runone.rs +++ b/lib/filetests/src/runone.rs @@ -1,18 +1,28 @@ //! Run the tests in a single test file. -use std::borrow::Cow; -use std::path::Path; -use std::time; use cretonne::ir::Function; use cretonne::isa::TargetIsa; +use cretonne::print_errors::pretty_verifier_error; use cretonne::settings::Flags; use cretonne::timing; use cretonne::verify_function; -use cton_reader::parse_test; use cton_reader::IsaSpec; -use utils::{read_to_string, pretty_verifier_error}; -use filetest::{TestResult, new_subtest}; -use filetest::subtest::{SubTest, Context, Result}; +use cton_reader::parse_test; +use std::borrow::Cow; +use std::fs; +use std::io::{self, Read}; +use std::path::Path; +use std::time; +use subtest::{Context, Result, SubTest}; +use {new_subtest, TestResult}; + +/// Read an entire file into a string. +fn read_to_string>(path: P) -> io::Result { + let mut file = fs::File::open(path)?; + let mut buffer = String::new(); + file.read_to_string(&mut buffer)?; + Ok(buffer) +} /// Load `path` and run the test in it. /// @@ -72,7 +82,6 @@ pub fn run(path: &Path) -> TestResult { run_one_test(last_tuple, Cow::Owned(func), &mut context)?; } - Ok(started.elapsed()) } @@ -122,7 +131,7 @@ fn run_one_test<'a>( if !context.verified && test.needs_verifier() { verify_function(&func, context.flags_or_isa()).map_err( |e| { - pretty_verifier_error(&func, isa, e) + pretty_verifier_error(&func, isa, &e) }, )?; context.verified = true; diff --git a/cranelift/src/filetest/subtest.rs b/lib/filetests/src/subtest.rs similarity index 96% rename from cranelift/src/filetest/subtest.rs rename to lib/filetests/src/subtest.rs index a9cf657d8e..ba94f87e78 100644 --- a/cranelift/src/filetest/subtest.rs +++ b/lib/filetests/src/subtest.rs @@ -1,12 +1,12 @@ -//! SubTest trait. +//! `SubTest` trait. -use std::result; -use std::borrow::Cow; use cretonne::ir::Function; use cretonne::isa::TargetIsa; use cretonne::settings::{Flags, FlagsOrIsa}; -use cton_reader::{Details, Comment}; -use filecheck::{CheckerBuilder, Checker, NO_VARIABLES}; +use cton_reader::{Comment, Details}; +use filecheck::{Checker, CheckerBuilder, NO_VARIABLES}; +use std::borrow::Cow; +use std::result; pub type Result = result::Result; diff --git a/cranelift/src/filetest/binemit.rs b/lib/filetests/src/test_binemit.rs similarity index 92% rename from cranelift/src/filetest/binemit.rs rename to lib/filetests/src/test_binemit.rs index 3c52a63b2b..775e014600 100644 --- a/cranelift/src/filetest/binemit.rs +++ b/lib/filetests/src/test_binemit.rs @@ -3,17 +3,18 @@ //! The `binemit` test command generates binary machine code for every instruction in the input //! functions and compares the results to the expected output. -use std::borrow::Cow; -use std::collections::HashMap; -use std::fmt::Write; use cretonne::binemit; +use cretonne::binemit::RegDiversions; use cretonne::dbg::DisplayList; use cretonne::ir; use cretonne::ir::entities::AnyEntity; -use cretonne::binemit::RegDiversions; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result}; -use utils::{match_directive, pretty_error}; +use match_directive::match_directive; +use std::borrow::Cow; +use std::collections::HashMap; +use std::fmt::Write; +use subtest::{Context, Result, SubTest}; struct TestBinEmit; @@ -26,7 +27,7 @@ pub fn subtest(parsed: &TestCommand) -> Result> { } } -// Code sink that generates text. +/// Code sink that generates text. struct TextSink { offset: binemit::CodeOffset, text: String, @@ -42,8 +43,6 @@ impl TextSink { } } - - impl binemit::CodeSink for TextSink { fn offset(&self) -> binemit::CodeOffset { self.offset @@ -79,28 +78,20 @@ impl binemit::CodeSink for TextSink { name: &ir::ExternalName, addend: binemit::Addend, ) { - write!( - self.text, - "{}({}", - reloc, - name, - ).unwrap(); + write!(self.text, "{}({}", reloc, name,).unwrap(); if addend != 0 { - write!( - self.text, - "{:+}", - addend, - ).unwrap(); + write!(self.text, "{:+}", addend,).unwrap(); } - write!( - self.text, - ") ", - ).unwrap(); + write!(self.text, ") ",).unwrap(); } fn reloc_jt(&mut self, reloc: binemit::Reloc, jt: ir::JumpTable) { write!(self.text, "{}({}) ", reloc, jt).unwrap(); } + + fn trap(&mut self, code: ir::TrapCode, _srcloc: ir::SourceLoc) { + write!(self.text, "{} ", code).unwrap(); + } } impl SubTest for TestBinEmit { @@ -125,8 +116,8 @@ impl SubTest for TestBinEmit { // Fix the stack frame layout so we can test spill/fill encodings. let min_offset = func.stack_slots - .keys() - .map(|ss| func.stack_slots[ss].offset.unwrap()) + .values() + .map(|slot| slot.offset.unwrap()) .min(); func.stack_slots.frame_size = min_offset.map(|off| (-off) as u32); @@ -273,10 +264,10 @@ impl SubTest for TestBinEmit { )); } return Err(format!( - "No matching encodings for {} in {}", - func.dfg.display_inst(inst, isa), - DisplayList(&encodings), - )); + "No matching encodings for {} in {}", + func.dfg.display_inst(inst, isa), + DisplayList(&encodings), + )); } let have = sink.text.trim(); if have != want { diff --git a/lib/filetests/src/test_cat.rs b/lib/filetests/src/test_cat.rs new file mode 100644 index 0000000000..f8f145834c --- /dev/null +++ b/lib/filetests/src/test_cat.rs @@ -0,0 +1,37 @@ +//! The `cat` subtest. + +use cretonne::ir::Function; +use cton_reader::TestCommand; +use std::borrow::Cow; +use subtest::{self, Context, Result as STResult, SubTest}; + +/// Object implementing the `test cat` sub-test. +/// +/// This command is used for testing the parser and function printer. It simply parses a function +/// and prints it out again. +/// +/// The result is verified by filecheck. +struct TestCat; + +pub fn subtest(parsed: &TestCommand) -> STResult> { + assert_eq!(parsed.command, "cat"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestCat)) + } +} + +impl SubTest for TestCat { + fn name(&self) -> Cow { + Cow::from("cat") + } + + fn needs_verifier(&self) -> bool { + false + } + + fn run(&self, func: Cow, context: &Context) -> STResult<()> { + subtest::run_filecheck(&func.display(context.isa).to_string(), context) + } +} diff --git a/cranelift/src/filetest/compile.rs b/lib/filetests/src/test_compile.rs similarity index 93% rename from cranelift/src/filetest/compile.rs rename to lib/filetests/src/test_compile.rs index 8c8db46591..a924eee799 100644 --- a/cranelift/src/filetest/compile.rs +++ b/lib/filetests/src/test_compile.rs @@ -2,14 +2,14 @@ //! //! The `compile` test command runs each function through the full code generator pipeline +use cretonne; use cretonne::binemit; use cretonne::ir; -use cretonne; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; use std::fmt::Write; -use utils::pretty_error; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestCompile; @@ -76,7 +76,7 @@ impl SubTest for TestCompile { } } -// Code sink that simply counts bytes. +/// Code sink that simply counts bytes. struct SizeSink { offset: binemit::CodeOffset, } @@ -111,4 +111,5 @@ impl binemit::CodeSink for SizeSink { ) { } fn reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable) {} + fn trap(&mut self, _code: ir::TrapCode, _srcloc: ir::SourceLoc) {} } diff --git a/lib/filetests/src/test_dce.rs b/lib/filetests/src/test_dce.rs new file mode 100644 index 0000000000..d96214182b --- /dev/null +++ b/lib/filetests/src/test_dce.rs @@ -0,0 +1,53 @@ +//! Test command for testing the DCE pass. +//! +//! The `dce` test command runs each function through the DCE pass after ensuring +//! that all instructions are legal for the target. +//! +//! The resulting function is sent to `filecheck`. + +use cretonne; +use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; +use cton_reader::TestCommand; +use std::borrow::Cow; +use std::fmt::Write; +use subtest::{run_filecheck, Context, Result, SubTest}; + +struct TestDCE; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "dce"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestDCE)) + } +} + +impl SubTest for TestDCE { + fn name(&self) -> Cow { + Cow::from("dce") + } + + fn is_mutating(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + comp_ctx.flowgraph(); + comp_ctx.compute_loop_analysis(); + comp_ctx.dce(context.flags_or_isa()).map_err(|e| { + pretty_error(&comp_ctx.func, context.isa, Into::into(e)) + })?; + + let mut text = String::new(); + write!(&mut text, "{}", &comp_ctx.func).map_err( + |e| e.to_string(), + )?; + run_filecheck(&text, context) + } +} diff --git a/cranelift/src/filetest/domtree.rs b/lib/filetests/src/test_domtree.rs similarity index 94% rename from cranelift/src/filetest/domtree.rs rename to lib/filetests/src/test_domtree.rs index 9c0fe5a841..da5a926f3d 100644 --- a/cranelift/src/filetest/domtree.rs +++ b/lib/filetests/src/test_domtree.rs @@ -2,7 +2,9 @@ //! //! The `test domtree` test command looks for annotations on instructions like this: //! +//! ```cton //! jump ebb3 ; dominates: ebb3 +//! ``` //! //! This annotation means that the jump instruction is expected to be the immediate dominator of //! `ebb3`. @@ -15,12 +17,12 @@ use cretonne::flowgraph::ControlFlowGraph; use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use match_directive::match_directive; use std::borrow::{Borrow, Cow}; use std::collections::HashMap; use std::fmt::{self, Write}; use std::result; -use utils::match_directive; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestDomtree; @@ -74,7 +76,7 @@ impl SubTest for TestDomtree { Some(got_inst) if got_inst != inst => { return Err(format!( "mismatching idoms for {}:\n\ - want: {}, got: {}", + want: {}, got: {}", src_ebb, inst, got_inst @@ -83,7 +85,7 @@ impl SubTest for TestDomtree { None => { return Err(format!( "mismatching idoms for {}:\n\ - want: {}, got: unreachable", + want: {}, got: unreachable", src_ebb, inst )); @@ -103,7 +105,7 @@ impl SubTest for TestDomtree { if let Some(got_inst) = domtree.idom(ebb) { return Err(format!( "mismatching idoms for renumbered {}:\n\ - want: unrechable, got: {}", + want: unrechable, got: {}", ebb, got_inst )); diff --git a/cranelift/src/filetest/legalizer.rs b/lib/filetests/src/test_legalizer.rs similarity index 90% rename from cranelift/src/filetest/legalizer.rs rename to lib/filetests/src/test_legalizer.rs index e79aada799..0794081e51 100644 --- a/cranelift/src/filetest/legalizer.rs +++ b/lib/filetests/src/test_legalizer.rs @@ -1,15 +1,15 @@ -//! Test command for checking the IL legalizer. +//! Test command for checking the IR legalizer. //! //! The `test legalizer` test command runs each function through `legalize_function()` and sends //! the result to filecheck. -use std::borrow::Cow; use cretonne; use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; +use std::borrow::Cow; use std::fmt::Write; -use utils::pretty_error; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestLegalizer; diff --git a/cranelift/src/filetest/licm.rs b/lib/filetests/src/test_licm.rs similarity index 93% rename from cranelift/src/filetest/licm.rs rename to lib/filetests/src/test_licm.rs index 32be450476..c59dee1cba 100644 --- a/cranelift/src/filetest/licm.rs +++ b/lib/filetests/src/test_licm.rs @@ -5,13 +5,13 @@ //! //! The resulting function is sent to `filecheck`. -use cretonne::ir::Function; use cretonne; +use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; use std::fmt::Write; -use utils::pretty_error; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestLICM; diff --git a/lib/filetests/src/test_postopt.rs b/lib/filetests/src/test_postopt.rs new file mode 100644 index 0000000000..c89fb33ea5 --- /dev/null +++ b/lib/filetests/src/test_postopt.rs @@ -0,0 +1,50 @@ +//! Test command for testing the postopt pass. +//! +//! The resulting function is sent to `filecheck`. + +use cretonne; +use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; +use cton_reader::TestCommand; +use std::borrow::Cow; +use std::fmt::Write; +use subtest::{run_filecheck, Context, Result, SubTest}; + +struct TestPostopt; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "postopt"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestPostopt)) + } +} + +impl SubTest for TestPostopt { + fn name(&self) -> Cow { + Cow::from("postopt") + } + + fn is_mutating(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + let isa = context.isa.expect("postopt needs an ISA"); + + comp_ctx.flowgraph(); + comp_ctx.postopt(isa).map_err(|e| { + pretty_error(&comp_ctx.func, context.isa, Into::into(e)) + })?; + + let mut text = String::new(); + write!(&mut text, "{}", &comp_ctx.func).map_err( + |e| e.to_string(), + )?; + run_filecheck(&text, context) + } +} diff --git a/cranelift/src/filetest/preopt.rs b/lib/filetests/src/test_preopt.rs similarity index 92% rename from cranelift/src/filetest/preopt.rs rename to lib/filetests/src/test_preopt.rs index 60d03f8207..e2fc9819c9 100644 --- a/cranelift/src/filetest/preopt.rs +++ b/lib/filetests/src/test_preopt.rs @@ -2,13 +2,13 @@ //! //! The resulting function is sent to `filecheck`. -use cretonne::ir::Function; use cretonne; +use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; use std::fmt::Write; -use utils::pretty_error; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestPreopt; diff --git a/lib/filetests/src/test_print_cfg.rs b/lib/filetests/src/test_print_cfg.rs new file mode 100644 index 0000000000..7092126cd7 --- /dev/null +++ b/lib/filetests/src/test_print_cfg.rs @@ -0,0 +1,37 @@ +//! The `print-cfg` sub-command. +//! +//! Read a series of Cretonne IR files and print their control flow graphs +//! in graphviz format. + +use std::borrow::Cow; + +use cretonne::cfg_printer::CFGPrinter; +use cretonne::ir::Function; +use cton_reader::TestCommand; +use subtest::{self, Context, Result as STResult, SubTest}; + +/// Object implementing the `test print-cfg` sub-test. +struct TestPrintCfg; + +pub fn subtest(parsed: &TestCommand) -> STResult> { + assert_eq!(parsed.command, "print-cfg"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestPrintCfg)) + } +} + +impl SubTest for TestPrintCfg { + fn name(&self) -> Cow { + Cow::from("print-cfg") + } + + fn needs_verifier(&self) -> bool { + false + } + + fn run(&self, func: Cow, context: &Context) -> STResult<()> { + subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context) + } +} diff --git a/cranelift/src/filetest/regalloc.rs b/lib/filetests/src/test_regalloc.rs similarity index 94% rename from cranelift/src/filetest/regalloc.rs rename to lib/filetests/src/test_regalloc.rs index 48160796a8..b2ab98c3a4 100644 --- a/cranelift/src/filetest/regalloc.rs +++ b/lib/filetests/src/test_regalloc.rs @@ -5,13 +5,13 @@ //! //! The resulting function is sent to `filecheck`. -use cretonne::ir::Function; use cretonne; +use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; use std::fmt::Write; -use utils::pretty_error; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestRegalloc; diff --git a/cranelift/src/filetest/simple_gvn.rs b/lib/filetests/src/test_simple_gvn.rs similarity index 93% rename from cranelift/src/filetest/simple_gvn.rs rename to lib/filetests/src/test_simple_gvn.rs index a1da0e42a4..62f5a2adae 100644 --- a/cranelift/src/filetest/simple_gvn.rs +++ b/lib/filetests/src/test_simple_gvn.rs @@ -5,13 +5,13 @@ //! //! The resulting function is sent to `filecheck`. -use cretonne::ir::Function; use cretonne; +use cretonne::ir::Function; +use cretonne::print_errors::pretty_error; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result, run_filecheck}; use std::borrow::Cow; use std::fmt::Write; -use utils::pretty_error; +use subtest::{run_filecheck, Context, Result, SubTest}; struct TestSimpleGVN; diff --git a/cranelift/src/filetest/verifier.rs b/lib/filetests/src/test_verifier.rs similarity index 94% rename from cranelift/src/filetest/verifier.rs rename to lib/filetests/src/test_verifier.rs index 7834fa778d..a4ec799ee3 100644 --- a/cranelift/src/filetest/verifier.rs +++ b/lib/filetests/src/test_verifier.rs @@ -1,18 +1,20 @@ -//! Test command for checking the IL verifier. +//! Test command for checking the IR verifier. //! //! The `test verifier` test command looks for annotations on instructions like this: //! +//! ```cton //! jump ebb3 ; error: jump to non-existent EBB +//! ``` //! //! This annotation means that the verifier is expected to given an error for the jump instruction //! containing the substring "jump to non-existent EBB". -use std::borrow::{Borrow, Cow}; -use cretonne::verify_function; use cretonne::ir::Function; +use cretonne::verify_function; use cton_reader::TestCommand; -use filetest::subtest::{SubTest, Context, Result}; -use utils::match_directive; +use match_directive::match_directive; +use std::borrow::{Borrow, Cow}; +use subtest::{Context, Result, SubTest}; struct TestVerifier; diff --git a/lib/frontend/Cargo.toml b/lib/frontend/Cargo.toml index 75822af7ce..57ee26d8c0 100644 --- a/lib/frontend/Cargo.toml +++ b/lib/frontend/Cargo.toml @@ -1,8 +1,8 @@ [package] authors = ["The Cretonne Project Developers"] name = "cretonne-frontend" -version = "0.3.4" -description = "Cretonne IL builder helper" +version = "0.4.1" +description = "Cretonne IR builder helper" license = "Apache-2.0" documentation = "https://cretonne.readthedocs.io/" repository = "https://github.com/Cretonne/cretonne" @@ -12,9 +12,13 @@ readme = "README.md" name = "cton_frontend" [dependencies] -cretonne = { path = "../cretonne", version = "0.3.4", default-features = false } +cretonne = { path = "../cretonne", version = "0.4.1", default-features = false } [features] default = ["std"] std = ["cretonne/std"] core = ["cretonne/core"] + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "Cretonne/cretonne" } diff --git a/lib/frontend/README.md b/lib/frontend/README.md index 53248b68ab..cca2b12ab5 100644 --- a/lib/frontend/README.md +++ b/lib/frontend/README.md @@ -1,5 +1,5 @@ This crate provides a straightforward way to create a -[Cretonne](https://crates.io/crates/cretonne) IL function and fill it with +[Cretonne](https://crates.io/crates/cretonne) IR function and fill it with instructions translated from another language. It contains an SSA construction module that provides convenient methods for translating non-SSA variables into -SSA Cretonne IL values via `use_var` and `def_var` calls. +SSA Cretonne IR values via `use_var` and `def_var` calls. diff --git a/lib/frontend/src/frontend.rs b/lib/frontend/src/frontend.rs index 6802884b6a..4396c4edac 100644 --- a/lib/frontend/src/frontend.rs +++ b/lib/frontend/src/frontend.rs @@ -1,25 +1,25 @@ -//! A frontend for building Cretonne IL from other languages. +//! A frontend for building Cretonne IR from other languages. use cretonne::cursor::{Cursor, FuncCursor}; +use cretonne::entity::{EntityMap, EntityRef, EntitySet}; use cretonne::ir; -use cretonne::ir::{Ebb, Type, Value, Function, Inst, JumpTable, StackSlot, JumpTableData, - StackSlotData, DataFlowGraph, InstructionData, ExtFuncData, FuncRef, SigRef, - Signature, InstBuilderBase, GlobalVarData, GlobalVar, HeapData, Heap}; use cretonne::ir::function::DisplayFunction; +use cretonne::ir::{DataFlowGraph, Ebb, ExtFuncData, FuncRef, Function, GlobalVar, GlobalVarData, + Heap, HeapData, Inst, InstBuilderBase, InstructionData, JumpTable, + JumpTableData, SigRef, Signature, StackSlot, StackSlotData, Type, Value}; use cretonne::isa::TargetIsa; -use ssa::{SSABuilder, SideEffects, Block}; -use cretonne::entity::{EntityRef, EntityMap, EntitySet}; use cretonne::packed_option::PackedOption; +use ssa::{Block, SSABuilder, SideEffects}; -/// Structure used for translating a series of functions into Cretonne IL. +/// Structure used for translating a series of functions into Cretonne IR. /// /// In order to reduce memory reallocations when compiling multiple functions, -/// `ILBuilder` holds various data structures which are cleared between +/// `FunctionBuilderContext` holds various data structures which are cleared between /// functions, rather than dropped, preserving the underlying allocations. /// /// The `Variable` parameter can be any index-like type that can be made to /// implement `EntityRef`. For frontends that don't have an obvious type to /// use here, `variable::Variable` can be used. -pub struct ILBuilder +pub struct FunctionBuilderContext where Variable: EntityRef, { @@ -28,8 +28,7 @@ where types: EntityMap, } - -/// Temporary object used to build a single Cretonne IL `Function`. +/// Temporary object used to build a single Cretonne IR `Function`. pub struct FunctionBuilder<'a, Variable: 'a> where Variable: EntityRef, @@ -41,7 +40,7 @@ where /// Source location to assign to all new instructions. srcloc: ir::SourceLoc, - builder: &'a mut ILBuilder, + func_ctx: &'a mut FunctionBuilderContext, position: Position, } @@ -77,12 +76,12 @@ impl Position { } } -impl ILBuilder +impl FunctionBuilderContext where Variable: EntityRef, { - /// Creates a ILBuilder structure. The structure is automatically cleared after each - /// [`FunctionBuilder`](struct.FunctionBuilder.html) completes translating a function. + /// Creates a FunctionBuilderContext structure. The structure is automatically cleared after + /// each [`FunctionBuilder`](struct.FunctionBuilder.html) completes translating a function. pub fn new() -> Self { Self { ssa: SSABuilder::new(), @@ -103,7 +102,7 @@ where } /// Implementation of the [`InstBuilder`](../cretonne/ir/builder/trait.InstBuilder.html) that has -/// one convenience method per Cretonne IL instruction. +/// one convenience method per Cretonne IR instruction. pub struct FuncInstBuilder<'short, 'long: 'short, Variable: 'long> where Variable: EntityRef, @@ -125,7 +124,8 @@ where } impl<'short, 'long, Variable> InstBuilderBase<'short> for FuncInstBuilder<'short, 'long, Variable> - where Variable: EntityRef +where + Variable: EntityRef, { fn data_flow_graph(&self) -> &DataFlowGraph { &self.builder.func.dfg @@ -165,14 +165,15 @@ impl<'short, 'long, Variable> InstBuilderBase<'short> for FuncInstBuilder<'short // multiple times, so we must deduplicate. let mut unique = EntitySet::::new(); for dest_ebb in self.builder - .func - .jump_tables - .get(table) - .expect("you are referencing an undeclared jump table") - .entries() - .map(|(_, ebb)| ebb) - .filter(|dest_ebb| unique.insert(*dest_ebb)) { - self.builder.builder.ssa.declare_ebb_predecessor( + .func + .jump_tables + .get(table) + .expect("you are referencing an undeclared jump table") + .entries() + .map(|(_, ebb)| ebb) + .filter(|dest_ebb| unique.insert(*dest_ebb)) + { + self.builder.func_ctx.ssa.declare_ebb_predecessor( dest_ebb, self.builder.position.basic_block.unwrap(), inst, @@ -191,7 +192,7 @@ impl<'short, 'long, Variable> InstBuilderBase<'short> for FuncInstBuilder<'short } } -/// This module allows you to create a function in Cretonne IL in a straightforward way, hiding +/// This module allows you to create a function in Cretonne IR in a straightforward way, hiding /// all the complexity of its internal representation. /// /// The module is parametrized by one type which is the representation of variables in your @@ -203,23 +204,24 @@ impl<'short, 'long, Variable> InstBuilderBase<'short> for FuncInstBuilder<'short /// - the last instruction of each block is a terminator instruction which has no natural successor, /// and those instructions can only appear at the end of extended blocks. /// -/// The parameters of Cretonne IL instructions are Cretonne IL values, which can only be created -/// as results of other Cretonne IL instructions. To be able to create variables redefined multiple +/// The parameters of Cretonne IR instructions are Cretonne IR values, which can only be created +/// as results of other Cretonne IR instructions. To be able to create variables redefined multiple /// times in your program, use the `def_var` and `use_var` command, that will maintain the -/// correspondence between your variables and Cretonne IL SSA values. +/// correspondence between your variables and Cretonne IR SSA values. /// /// The first block for which you call `switch_to_block` will be assumed to be the beginning of /// the function. /// /// At creation, a `FunctionBuilder` instance borrows an already allocated `Function` which it /// modifies with the information stored in the mutable borrowed -/// [`ILBuilder`](struct.ILBuilder.html). The function passed in argument should be newly created -/// with [`Function::with_name_signature()`](../function/struct.Function.html), whereas the -/// `ILBuilder` can be kept as is between two function translations. +/// [`FunctionBuilderContext`](struct.FunctionBuilderContext.html). The function passed in +/// argument should be newly created with +/// [`Function::with_name_signature()`](../function/struct.Function.html), whereas the +/// `FunctionBuilderContext` can be kept as is between two function translations. /// /// # Errors /// -/// The functions below will panic in debug mode whenever you try to modify the Cretonne IL +/// The functions below will panic in debug mode whenever you try to modify the Cretonne IR /// function in a way that violate the coherence of the code. For instance: switching to a new /// `Ebb` when you haven't filled the current one with a terminator instruction, inserting a /// return instruction with arguments that don't match the function's signature. @@ -228,16 +230,16 @@ where Variable: EntityRef, { /// Creates a new FunctionBuilder structure that will operate on a `Function` using a - /// `IlBuilder`. + /// `FunctionBuilderContext`. pub fn new( func: &'a mut Function, - builder: &'a mut ILBuilder, + func_ctx: &'a mut FunctionBuilderContext, ) -> FunctionBuilder<'a, Variable> { - debug_assert!(builder.is_empty()); + debug_assert!(func_ctx.is_empty()); FunctionBuilder { func: func, srcloc: Default::default(), - builder: builder, + func_ctx: func_ctx, position: Position::default(), } } @@ -250,8 +252,8 @@ where /// Creates a new `Ebb` and returns its reference. pub fn create_ebb(&mut self) -> Ebb { let ebb = self.func.dfg.make_ebb(); - self.builder.ssa.declare_ebb_header_block(ebb); - self.builder.ebbs[ebb] = EbbData { + self.func_ctx.ssa.declare_ebb_header_block(ebb); + self.func_ctx.ebbs[ebb] = EbbData { filled: false, pristine: true, user_param_count: 0, @@ -275,11 +277,11 @@ where ); // We cannot switch to a filled block debug_assert!( - !self.builder.ebbs[ebb].filled, + !self.func_ctx.ebbs[ebb].filled, "you cannot switch to a block which is already filled" ); - let basic_block = self.builder.ssa.header_block(ebb); + let basic_block = self.func_ctx.ssa.header_block(ebb); // Then we change the cursor position. self.position = Position::at(ebb, basic_block); } @@ -290,7 +292,7 @@ where /// created. Forgetting to call this method on every block will cause inconsistencies in the /// produced functions. pub fn seal_block(&mut self, ebb: Ebb) { - let side_effects = self.builder.ssa.seal_ebb_header_block(ebb, self.func); + let side_effects = self.func_ctx.ssa.seal_ebb_header_block(ebb, self.func); self.handle_ssa_side_effects(side_effects); } @@ -301,22 +303,22 @@ where /// function can be used at the end of translating all blocks to ensure /// that everything is sealed. pub fn seal_all_blocks(&mut self) { - let side_effects = self.builder.ssa.seal_all_ebb_header_blocks(self.func); + let side_effects = self.func_ctx.ssa.seal_all_ebb_header_blocks(self.func); self.handle_ssa_side_effects(side_effects); } /// In order to use a variable in a `use_var`, you need to declare its type with this method. pub fn declare_var(&mut self, var: Variable, ty: Type) { - self.builder.types[var] = ty; + self.func_ctx.types[var] = ty; } - /// Returns the Cretonne IL value corresponding to the utilization at the current program + /// Returns the Cretonne IR value corresponding to the utilization at the current program /// position of a previously defined user variable. pub fn use_var(&mut self, var: Variable) -> Value { - let ty = *self.builder.types.get(var).expect( + let ty = *self.func_ctx.types.get(var).expect( "this variable is used but its type has not been declared", ); - let (val, side_effects) = self.builder.ssa.use_var( + let (val, side_effects) = self.func_ctx.ssa.use_var( self.func, var, ty, @@ -329,7 +331,7 @@ where /// Register a new definition of a user variable. Panics if the type of the value is not the /// same as the type registered for the variable. pub fn def_var(&mut self, var: Variable, val: Value) { - self.builder.ssa.def_var( + self.func_ctx.ssa.def_var( var, val, self.position.basic_block.unwrap(), @@ -382,14 +384,14 @@ where /// Make sure that the current EBB is inserted in the layout. pub fn ensure_inserted_ebb(&mut self) { let ebb = self.position.ebb.unwrap(); - if self.builder.ebbs[ebb].pristine { + if self.func_ctx.ebbs[ebb].pristine { if !self.func.layout.is_ebb_inserted(ebb) { self.func.layout.append_ebb(ebb); } - self.builder.ebbs[ebb].pristine = false; + self.func_ctx.ebbs[ebb].pristine = false; } else { debug_assert!( - !self.builder.ebbs[ebb].filled, + !self.func_ctx.ebbs[ebb].filled, "you cannot add an instruction to a block already filled" ); } @@ -399,7 +401,7 @@ where /// /// This can be used to insert SSA code that doesn't need to access locals and that doesn't /// need to know about `FunctionBuilder` at all. - pub fn cursor<'f>(&'f mut self) -> FuncCursor<'f> { + pub fn cursor(&mut self) -> FuncCursor { self.ensure_inserted_ebb(); FuncCursor::new(self.func) .with_srcloc(self.srcloc) @@ -412,7 +414,7 @@ where pub fn append_ebb_params_for_function_params(&mut self, ebb: Ebb) { // These parameters count as "user" parameters here because they aren't // inserted by the SSABuilder. - let user_param_count = &mut self.builder.ebbs[ebb].user_param_count; + let user_param_count = &mut self.func_ctx.ebbs[ebb].user_param_count; for argtyp in &self.func.signature.params { *user_param_count += 1; self.func.dfg.append_ebb_param(ebb, argtyp.value_type); @@ -425,7 +427,7 @@ where pub fn append_ebb_params_for_function_returns(&mut self, ebb: Ebb) { // These parameters count as "user" parameters here because they aren't // inserted by the SSABuilder. - let user_param_count = &mut self.builder.ebbs[ebb].user_param_count; + let user_param_count = &mut self.func_ctx.ebbs[ebb].user_param_count; for argtyp in &self.func.signature.returns { *user_param_count += 1; self.func.dfg.append_ebb_param(ebb, argtyp.value_type); @@ -438,21 +440,21 @@ where pub fn finalize(&mut self) { // Check that all the `Ebb`s are filled and sealed. debug_assert!( - self.builder.ebbs.keys().all(|ebb| { - self.builder.ebbs[ebb].pristine || self.builder.ssa.is_sealed(ebb) + self.func_ctx.ebbs.iter().all(|(ebb, ebb_data)| { + ebb_data.pristine || self.func_ctx.ssa.is_sealed(ebb) }), "all blocks should be sealed before dropping a FunctionBuilder" ); debug_assert!( - self.builder.ebbs.keys().all(|ebb| { - self.builder.ebbs[ebb].pristine || self.builder.ebbs[ebb].filled + self.func_ctx.ebbs.values().all(|ebb_data| { + ebb_data.pristine || ebb_data.filled }), "all blocks should be filled before dropping a FunctionBuilder" ); // Clear the state (but preserve the allocated buffers) in preparation // for translation another function. - self.builder.clear(); + self.func_ctx.clear(); // Reset srcloc and position to initial states. self.srcloc = Default::default(); @@ -461,8 +463,8 @@ where } /// All the functions documented in the previous block are write-only and help you build a valid -/// Cretonne IL functions via multiple debug asserts. However, you might need to improve the -/// performance of your translation perform more complex transformations to your Cretonne IL +/// Cretonne IR functions via multiple debug asserts. However, you might need to improve the +/// performance of your translation perform more complex transformations to your Cretonne IR /// function. The functions below help you inspect the function you're creating and modify it /// in ways that can be unsafe if used incorrectly. impl<'a, Variable> FunctionBuilder<'a, Variable> @@ -486,12 +488,12 @@ where /// **Note:** this function has to be called at the creation of the `Ebb` before adding /// instructions to it, otherwise this could interfere with SSA construction. pub fn append_ebb_param(&mut self, ebb: Ebb, ty: Type) -> Value { - debug_assert!(self.builder.ebbs[ebb].pristine); + debug_assert!(self.func_ctx.ebbs[ebb].pristine); debug_assert_eq!( - self.builder.ebbs[ebb].user_param_count, + self.func_ctx.ebbs[ebb].user_param_count, self.func.dfg.num_ebb_params(ebb) ); - self.builder.ebbs[ebb].user_param_count += 1; + self.func_ctx.ebbs[ebb].user_param_count += 1; self.func.dfg.append_ebb_param(ebb, ty) } @@ -508,9 +510,9 @@ where let old_dest = self.func.dfg[inst].branch_destination_mut().expect( "you want to change the jump destination of a non-jump instruction", ); - let pred = self.builder.ssa.remove_ebb_predecessor(*old_dest, inst); + let pred = self.func_ctx.ssa.remove_ebb_predecessor(*old_dest, inst); *old_dest = new_dest; - self.builder.ssa.declare_ebb_predecessor( + self.func_ctx.ssa.declare_ebb_predecessor( new_dest, pred, inst, @@ -525,8 +527,8 @@ where None => false, Some(entry) => self.position.ebb.unwrap() == entry, }; - !is_entry && self.builder.ssa.is_sealed(self.position.ebb.unwrap()) && - self.builder + !is_entry && self.func_ctx.ssa.is_sealed(self.position.ebb.unwrap()) && + self.func_ctx .ssa .predecessors(self.position.ebb.unwrap()) .is_empty() @@ -535,18 +537,20 @@ where /// Returns `true` if and only if no instructions have been added since the last call to /// `switch_to_block`. pub fn is_pristine(&self) -> bool { - self.builder.ebbs[self.position.ebb.unwrap()].pristine + self.func_ctx.ebbs[self.position.ebb.unwrap()].pristine } /// Returns `true` if and only if a terminator instruction has been inserted since the /// last call to `switch_to_block`. pub fn is_filled(&self) -> bool { - self.builder.ebbs[self.position.ebb.unwrap()].filled + self.func_ctx.ebbs[self.position.ebb.unwrap()].filled } /// Returns a displayable object for the function as it is. /// /// Useful for debug purposes. Use it with `None` for standard printing. + // Clippy thinks the lifetime that follows is needless, but rustc needs it + #[cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))] pub fn display<'b, I: Into>>(&'b self, isa: I) -> DisplayFunction { self.func.display(isa) } @@ -558,17 +562,17 @@ where Variable: EntityRef, { fn move_to_next_basic_block(&mut self) { - self.position.basic_block = PackedOption::from(self.builder.ssa.declare_ebb_body_block( + self.position.basic_block = PackedOption::from(self.func_ctx.ssa.declare_ebb_body_block( self.position.basic_block.unwrap(), )); } fn fill_current_block(&mut self) { - self.builder.ebbs[self.position.ebb.unwrap()].filled = true; + self.func_ctx.ebbs[self.position.ebb.unwrap()].filled = true; } fn declare_successor(&mut self, dest_ebb: Ebb, jump_inst: Inst) { - self.builder.ssa.declare_ebb_predecessor( + self.func_ctx.ssa.declare_ebb_predecessor( dest_ebb, self.position.basic_block.unwrap(), jump_inst, @@ -577,10 +581,10 @@ where fn handle_ssa_side_effects(&mut self, side_effects: SideEffects) { for split_ebb in side_effects.split_ebbs_created { - self.builder.ebbs[split_ebb].filled = true + self.func_ctx.ebbs[split_ebb].filled = true } for modified_ebb in side_effects.instructions_added_to_ebbs { - self.builder.ebbs[modified_ebb].pristine = false + self.func_ctx.ebbs[modified_ebb].pristine = false } } } @@ -588,23 +592,23 @@ where #[cfg(test)] mod tests { - use cretonne::entity::EntityRef; - use cretonne::ir::{ExternalName, Function, CallConv, Signature, AbiParam, InstBuilder}; - use cretonne::ir::types::*; - use frontend::{ILBuilder, FunctionBuilder}; - use cretonne::verifier::verify_function; - use cretonne::settings; use Variable; + use cretonne::entity::EntityRef; + use cretonne::ir::types::*; + use cretonne::ir::{AbiParam, CallConv, ExternalName, Function, InstBuilder, Signature}; + use cretonne::settings; + use cretonne::verifier::verify_function; + use frontend::{FunctionBuilder, FunctionBuilderContext}; fn sample_function(lazy_seal: bool) { - let mut sig = Signature::new(CallConv::Native); + let mut sig = Signature::new(CallConv::SystemV); sig.returns.push(AbiParam::new(I32)); sig.params.push(AbiParam::new(I32)); - let mut il_builder = ILBuilder::::new(); + let mut fn_ctx = FunctionBuilderContext::::new(); let mut func = Function::with_name_signature(ExternalName::testcase("sample"), sig); { - let mut builder = FunctionBuilder::::new(&mut func, &mut il_builder); + let mut builder = FunctionBuilder::::new(&mut func, &mut fn_ctx); let block0 = builder.create_ebb(); let block1 = builder.create_ebb(); diff --git a/lib/frontend/src/lib.rs b/lib/frontend/src/lib.rs index a44214cf14..6e04fd190a 100644 --- a/lib/frontend/src/lib.rs +++ b/lib/frontend/src/lib.rs @@ -1,15 +1,15 @@ -//! Cretonne IL builder library. +//! Cretonne IR builder library. //! -//! Provides a straightforward way to create a Cretonne IL function and fill it with instructions +//! Provides a straightforward way to create a Cretonne IR function and fill it with instructions //! translated from another language. Contains an SSA construction module that lets you translate -//! your non-SSA variables into SSA Cretonne IL values via `use_var` and `def_var` calls. +//! your non-SSA variables into SSA Cretonne IR values via `use_var` and `def_var` calls. //! -//! To get started, create an [`IlBuilder`](struct.ILBuilder.html) and pass it as an argument -//! to a [`FunctionBuilder`](struct.FunctionBuilder.html). +//! To get started, create an [`FunctionBuilderContext`](struct.FunctionBuilderContext.html) and +//! pass it as an argument to a [`FunctionBuilder`](struct.FunctionBuilder.html). //! //! # Example //! -//! Here is a pseudo-program we want to transform into Cretonne IL: +//! Here is a pseudo-program we want to transform into Cretonne IR: //! //! ```cton //! function(x) { @@ -29,7 +29,7 @@ //! } //! ``` //! -//! Here is how you build the corresponding Cretonne IL function using `ILBuilder`: +//! Here is how you build the corresponding Cretonne IR function using `FunctionBuilderContext`: //! //! ```rust //! extern crate cretonne; @@ -39,17 +39,17 @@ //! use cretonne::ir::{ExternalName, CallConv, Function, Signature, AbiParam, InstBuilder}; //! use cretonne::ir::types::*; //! use cretonne::settings; -//! use cton_frontend::{ILBuilder, FunctionBuilder, Variable}; +//! use cton_frontend::{FunctionBuilderContext, FunctionBuilder, Variable}; //! use cretonne::verifier::verify_function; //! //! fn main() { -//! let mut sig = Signature::new(CallConv::Native); +//! let mut sig = Signature::new(CallConv::SystemV); //! sig.returns.push(AbiParam::new(I32)); //! sig.params.push(AbiParam::new(I32)); -//! let mut il_builder = ILBuilder::::new(); +//! let mut fn_builder_ctx = FunctionBuilderContext::::new(); //! let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig); //! { -//! let mut builder = FunctionBuilder::::new(&mut func, &mut il_builder); +//! let mut builder = FunctionBuilder::::new(&mut func, &mut fn_builder_ctx); //! //! let block0 = builder.create_ebb(); //! let block1 = builder.create_ebb(); @@ -127,9 +127,8 @@ //! } //! ``` -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![cfg_attr(feature = "cargo-clippy", allow(new_without_default, redundant_field_names))] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(alloc))] @@ -139,7 +138,7 @@ extern crate cretonne; #[cfg(not(feature = "std"))] extern crate alloc; -pub use frontend::{ILBuilder, FunctionBuilder}; +pub use frontend::{FunctionBuilder, FunctionBuilderContext}; pub use variable::Variable; mod frontend; diff --git a/lib/frontend/src/ssa.rs b/lib/frontend/src/ssa.rs index 37e4fd8751..6c1579228b 100644 --- a/lib/frontend/src/ssa.rs +++ b/lib/frontend/src/ssa.rs @@ -6,15 +6,15 @@ //! Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg use cretonne::cursor::{Cursor, FuncCursor}; -use cretonne::ir::{Ebb, Value, Inst, Type, Function, InstBuilder}; +use cretonne::entity::{EntityMap, EntityRef, PrimaryMap}; +use cretonne::ir::immediates::{Ieee32, Ieee64}; use cretonne::ir::instructions::BranchInfo; -use cretonne::entity::{EntityRef, PrimaryMap, EntityMap}; +use cretonne::ir::types::{F32, F64}; +use cretonne::ir::{Ebb, Function, Inst, InstBuilder, Type, Value}; use cretonne::packed_option::PackedOption; use cretonne::packed_option::ReservedValue; -use std::u32; -use cretonne::ir::types::{F32, F64}; -use cretonne::ir::immediates::{Ieee32, Ieee64}; use std::mem; +use std::u32; use std::vec::Vec; /// Structure containing the data relevant the construction of SSA for a given function. @@ -77,12 +77,12 @@ impl SideEffects { } } -// Describes the current position of a basic block in the control flow graph. +/// Describes the current position of a basic block in the control flow graph. enum BlockData { - // A block at the top of an `Ebb`. + /// A block at the top of an `Ebb`. EbbHeader(EbbHeaderBlockData), - // A block inside an `Ebb` with an unique other block as its predecessor. - // The block is implicitly sealed at creation. + /// A block inside an `Ebb` with an unique other block as its predecessor. + /// The block is implicitly sealed at creation. EbbBody { predecessor: Block }, } @@ -179,7 +179,7 @@ where } } -// Small enum used for clarity in some functions. +/// Small enum used for clarity in some functions. #[derive(Debug)] enum ZeroOneOrMore { Zero(), @@ -194,7 +194,7 @@ enum UseVarCases { SealedMultiplePredecessors(Value, Ebb), } -// States for the `use_var`/`predecessors_lookup` state machine. +/// States for the `use_var`/`predecessors_lookup` state machine. enum Call { UseVar(Block), FinishSealedOnePredecessor(Block), @@ -231,7 +231,7 @@ fn emit_zero(ty: Type, mut cur: FuncCursor) -> Value { } } /// The following methods are the API of the SSA builder. Here is how it should be used when -/// translating to Cretonne IL: +/// translating to Cretonne IR: /// /// - for each sequence of contiguous instructions (with no branches), create a corresponding /// basic block with `declare_ebb_body_block` or `declare_ebb_header_block` depending on the @@ -713,15 +713,15 @@ where #[cfg(test)] mod tests { + use Variable; use cretonne::cursor::{Cursor, FuncCursor}; use cretonne::entity::EntityRef; - use cretonne::ir::{Function, InstBuilder, Inst, JumpTableData, Opcode}; - use cretonne::ir::types::*; - use cretonne::verify_function; use cretonne::ir::instructions::BranchInfo; + use cretonne::ir::types::*; + use cretonne::ir::{Function, Inst, InstBuilder, JumpTableData, Opcode}; use cretonne::settings; + use cretonne::verify_function; use ssa::SSABuilder; - use Variable; #[test] fn simple_block() { @@ -960,7 +960,6 @@ mod tests { assert_eq!(func.dfg.ebb_params(ebb1)[0], z2); assert_eq!(func.dfg.ebb_params(ebb1)[1], y3); assert_eq!(func.dfg.resolve_aliases(x3), x1); - } #[test] diff --git a/lib/frontend/src/variable.rs b/lib/frontend/src/variable.rs index b69a63afeb..b4e3c75da2 100644 --- a/lib/frontend/src/variable.rs +++ b/lib/frontend/src/variable.rs @@ -1,6 +1,6 @@ //! A basic `Variable` implementation. //! -//! `ILBuilder`, `FunctionBuilder`, and related types have a `Variable` +//! `FunctionBuilderContext`, `FunctionBuilder`, and related types have a `Variable` //! type parameter, to allow frontends that identify variables with //! their own index types to use them directly. Frontends which don't //! can use the `Variable` defined here. diff --git a/lib/native/Cargo.toml b/lib/native/Cargo.toml index 817f12c73d..34e910de8f 100644 --- a/lib/native/Cargo.toml +++ b/lib/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cretonne-native" -version = "0.3.4" +version = "0.4.1" authors = ["The Cretonne Project Developers"] description = "Support for targeting the host with Cretonne" repository = "https://github.com/Cretonne/cretonne" @@ -11,7 +11,7 @@ readme = "README.md" name = "cton_native" [dependencies] -cretonne = { path = "../cretonne", version = "0.3.4", default-features = false } +cretonne = { path = "../cretonne", version = "0.4.1", default-features = false } [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] raw-cpuid = "3.0.0" @@ -20,3 +20,7 @@ raw-cpuid = "3.0.0" default = ["std"] std = ["cretonne/std"] core = ["cretonne/core"] + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "Cretonne/cretonne" } diff --git a/lib/native/src/lib.rs b/lib/native/src/lib.rs index 66c4cdec4a..d09a0b04f2 100644 --- a/lib/native/src/lib.rs +++ b/lib/native/src/lib.rs @@ -1,9 +1,7 @@ //! Performs autodetection of the host for the purposes of running //! Cretonne to generate code to run on the same machine. -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/lib/reader/Cargo.toml b/lib/reader/Cargo.toml index 0d2061e361..d8259a2027 100644 --- a/lib/reader/Cargo.toml +++ b/lib/reader/Cargo.toml @@ -1,8 +1,8 @@ [package] authors = ["The Cretonne Project Developers"] name = "cretonne-reader" -version = "0.3.4" -description = "Cretonne textual IL reader" +version = "0.4.1" +description = "Cretonne textual IR reader" license = "Apache-2.0" documentation = "https://cretonne.readthedocs.io/" repository = "https://github.com/Cretonne/cretonne" @@ -12,4 +12,8 @@ readme = "README.md" name = "cton_reader" [dependencies] -cretonne = { path = "../cretonne", version = "0.3.4" } +cretonne = { path = "../cretonne", version = "0.4.1" } + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "Cretonne/cretonne" } diff --git a/lib/reader/src/isaspec.rs b/lib/reader/src/isaspec.rs index 2b1e8fae6d..e87b2b6771 100644 --- a/lib/reader/src/isaspec.rs +++ b/lib/reader/src/isaspec.rs @@ -6,9 +6,9 @@ //! If a test case file contains `isa` commands, the tests will only be run against the specified //! ISAs. If the file contains no `isa` commands, the tests will be run against all supported ISAs. -use cretonne::settings::{Flags, Configurable, Error as SetError}; use cretonne::isa::TargetIsa; -use error::{Result, Location}; +use cretonne::settings::{Configurable, Error as SetError, Flags}; +use error::{Location, Result}; use testcommand::TestOption; /// The ISA specifications in a `.cton` file. diff --git a/lib/reader/src/lexer.rs b/lib/reader/src/lexer.rs index ca315d3b58..a2fde86b5a 100644 --- a/lib/reader/src/lexer.rs +++ b/lib/reader/src/lexer.rs @@ -1,12 +1,12 @@ //! Lexical analysis for .cton files. -use std::str::CharIndices; -use std::u16; +use cretonne::ir::types; +use cretonne::ir::{Ebb, Value}; +use error::Location; #[allow(unused_imports)] use std::ascii::AsciiExt; -use cretonne::ir::types; -use cretonne::ir::{Value, Ebb}; -use error::Location; +use std::str::CharIndices; +use std::u16; /// A Token returned from the `Lexer`. /// @@ -53,7 +53,7 @@ pub struct LocatedToken<'a> { } /// Wrap up a `Token` with the given location. -fn token<'a>(token: Token<'a>, loc: Location) -> Result, LocatedError> { +fn token(token: Token, loc: Location) -> Result { Ok(LocatedToken { token, location: loc, @@ -458,7 +458,7 @@ mod tests { use super::trailing_digits; use super::*; use cretonne::ir::types; - use cretonne::ir::{Value, Ebb}; + use cretonne::ir::{Ebb, Value}; use error::Location; #[test] @@ -556,7 +556,7 @@ mod tests { fn lex_identifiers() { let mut lex = Lexer::new( "v0 v00 vx01 ebb1234567890 ebb5234567890 v1x vx1 vxvx4 \ - function0 function b1 i32x4 f32x5 \ + function0 function b1 i32x4 f32x5 \ iflags fflags iflagss", ); assert_eq!( diff --git a/lib/reader/src/lib.rs b/lib/reader/src/lib.rs index 85ea048950..e012876c1c 100644 --- a/lib/reader/src/lib.rs +++ b/lib/reader/src/lib.rs @@ -1,25 +1,23 @@ //! Cretonne file reader library. //! -//! The cton_reader library supports reading .cton files. This functionality is needed for testing +//! The `cton_reader` library supports reading .cton files. This functionality is needed for testing //! Cretonne, but is not essential for a JIT compiler. -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] extern crate cretonne; -pub use error::{Location, Result, Error}; +pub use error::{Error, Location, Result}; +pub use isaspec::{parse_options, IsaSpec}; pub use parser::{parse_functions, parse_test}; -pub use testcommand::{TestCommand, TestOption}; -pub use testfile::{TestFile, Details, Comment}; -pub use isaspec::{IsaSpec, parse_options}; pub use sourcemap::SourceMap; +pub use testcommand::{TestCommand, TestOption}; +pub use testfile::{Comment, Details, TestFile}; mod error; +mod isaspec; mod lexer; mod parser; -mod testcommand; -mod isaspec; -mod testfile; mod sourcemap; +mod testcommand; +mod testfile; diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index a9568942ed..f23f644a0e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -1,28 +1,27 @@ //! Parser for .cton files. +use cretonne::entity::EntityRef; +use cretonne::ir; +use cretonne::ir::entities::AnyEntity; +use cretonne::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32, Uimm32}; +use cretonne::ir::instructions::{InstructionData, InstructionFormat, VariableArgs}; +use cretonne::ir::types::VOID; +use cretonne::ir::{AbiParam, ArgumentExtension, ArgumentLoc, CallConv, Ebb, ExtFuncData, + ExternalName, FuncRef, Function, GlobalVar, GlobalVarData, Heap, HeapBase, + HeapData, HeapStyle, JumpTable, JumpTableData, MemFlags, Opcode, SigRef, + Signature, StackSlot, StackSlotData, StackSlotKind, Type, Value, ValueLoc}; +use cretonne::isa::{self, Encoding, RegUnit, TargetIsa}; +use cretonne::packed_option::ReservedValue; +use cretonne::{settings, timing}; +use error::{Error, Location, Result}; +use isaspec; +use lexer::{self, Lexer, Token}; +use sourcemap::SourceMap; +use std::mem; use std::str::FromStr; use std::{u16, u32}; -use std::mem; -use cretonne::ir::{Function, Ebb, Opcode, Value, Type, ExternalName, CallConv, StackSlotData, - StackSlotKind, JumpTable, JumpTableData, Signature, AbiParam, - ArgumentExtension, ExtFuncData, SigRef, FuncRef, StackSlot, ValueLoc, - ArgumentLoc, MemFlags, GlobalVar, GlobalVarData, Heap, HeapData, HeapStyle, - HeapBase}; -use cretonne::ir; -use cretonne::ir::types::VOID; -use cretonne::ir::immediates::{Imm64, Uimm32, Offset32, Ieee32, Ieee64}; -use cretonne::ir::entities::AnyEntity; -use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArgs}; -use cretonne::isa::{self, TargetIsa, Encoding, RegUnit}; -use cretonne::{settings, timing}; -use cretonne::entity::EntityRef; -use cretonne::packed_option::ReservedValue; -use testfile::{TestFile, Details, Comment}; -use error::{Location, Error, Result}; -use lexer::{self, Lexer, Token}; use testcommand::TestCommand; -use isaspec; -use sourcemap::SourceMap; +use testfile::{Comment, Details, TestFile}; /// Parse the entire `text` into a list of functions. /// @@ -37,7 +36,7 @@ pub fn parse_functions(text: &str) -> Result> { /// Parse the entire `text` as a test case file. /// /// The returned `TestFile` contains direct references to substrings of `text`. -pub fn parse_test<'a>(text: &'a str) -> Result> { +pub fn parse_test(text: &str) -> Result { let _tt = timing::parse_text(); let mut parser = Parser::new(text); // Gather the preamble comments. @@ -65,31 +64,34 @@ pub struct Parser<'a> { lex_error: Option, - // Current lookahead token. + /// Current lookahead token. lookahead: Option>, - // Location of lookahead. + /// Location of lookahead. loc: Location, - // Are we gathering any comments that we encounter? + /// Are we gathering any comments that we encounter? gathering_comments: bool, - // The gathered comments; claim them with `claim_gathered_comments`. + /// The gathered comments; claim them with `claim_gathered_comments`. gathered_comments: Vec<&'a str>, - // Comments collected so far. + /// Comments collected so far. comments: Vec>, } -// Context for resolving references when parsing a single function. +/// Context for resolving references when parsing a single function. struct Context<'a> { function: Function, map: SourceMap, - // Reference to the unique_isa for things like parsing ISA-specific instruction encoding - // information. This is only `Some` if exactly one set of `isa` directives were found in the - // prologue (it is valid to have directives for multiple different ISAs, but in that case we - // couldn't know which ISA the provided encodings are intended for) + /// Aliases to resolve once value definitions are known. + aliases: Vec, + + /// Reference to the unique_isa for things like parsing ISA-specific instruction encoding + /// information. This is only `Some` if exactly one set of `isa` directives were found in the + /// prologue (it is valid to have directives for multiple different ISAs, but in that case we + /// couldn't know which ISA the provided encodings are intended for) unique_isa: Option<&'a TargetIsa>, } @@ -99,6 +101,7 @@ impl<'a> Context<'a> { function: f, map: SourceMap::new(), unique_isa, + aliases: Vec::new(), } } @@ -183,7 +186,7 @@ impl<'a> Context<'a> { fn add_sig(&mut self, sig: SigRef, data: Signature, loc: &Location) -> Result<()> { while self.function.dfg.signatures.next_key().index() <= sig.index() { self.function.import_signature( - Signature::new(CallConv::Native), + Signature::new(CallConv::SystemV), ); } self.function.dfg.signatures[sig] = data; @@ -277,6 +280,10 @@ impl<'a> Parser<'a> { // Get the current lookahead token, after making sure there is one. fn token(&mut self) -> Option> { + // clippy says self.lookahead is immutable so this loop is either infinite or never + // running. I don't think this is true - self.lookahead is mutated in the loop body - so + // maybe this is a clippy bug? Either way, disable clippy for this. + #[cfg_attr(feature = "cargo-clippy", allow(while_immutable_condition))] while self.lookahead == None { match self.lex.next() { Some(Ok(lexer::LocatedToken { token, location })) => { @@ -863,8 +870,8 @@ impl<'a> Parser<'a> { // signature ::= * "(" [paramlist] ")" ["->" retlist] [callconv] // fn parse_signature(&mut self, unique_isa: Option<&TargetIsa>) -> Result { - // Calling convention defaults to `native`, but can be changed. - let mut sig = Signature::new(CallConv::Native); + // Calling convention defaults to `system_v`, but can be changed. + let mut sig = Signature::new(CallConv::SystemV); self.match_token( Token::LPar, @@ -957,7 +964,7 @@ impl<'a> Parser<'a> { isa.register_info() .parse_regunit(name) .map(ArgumentLoc::Reg) - .ok_or(self.error("invalid register name")) + .ok_or_else(|| self.error("invalid register name")) } else { err!(self.loc, "argument location requires exactly one isa") } @@ -1181,12 +1188,12 @@ impl<'a> Parser<'a> { } "bound" => { data.style = match style_name { - "dynamic" => { - HeapStyle::Dynamic { bound_gv: self.match_gv("expected gv bound")? } - } - "static" => { - HeapStyle::Static { bound: self.match_imm64("expected integer bound")? } - } + "dynamic" => HeapStyle::Dynamic { + bound_gv: self.match_gv("expected gv bound")?, + }, + "static" => HeapStyle::Static { + bound: self.match_imm64("expected integer bound")?, + }, t => return err!(self.loc, "unknown heap style '{}'", t), }; } @@ -1337,6 +1344,30 @@ impl<'a> Parser<'a> { while self.token() != Some(Token::RBrace) { self.parse_extended_basic_block(ctx)?; } + + // Now that we've seen all defined values in the function, ensure that + // all references refer to a definition. + for ebb in &ctx.function.layout { + for inst in ctx.function.layout.ebb_insts(ebb) { + for value in ctx.function.dfg.inst_args(inst) { + if !ctx.map.contains_value(*value) { + return err!( + ctx.map.location(AnyEntity::Inst(inst)).unwrap(), + "undefined operand value {}", + value + ); + } + } + } + } + + for alias in &ctx.aliases { + if !ctx.function.dfg.set_alias_type_for_parser(*alias) { + let loc = ctx.map.location(AnyEntity::Value(*alias)).unwrap(); + return err!(loc, "alias cycle involving {}", alias); + } + } + Ok(()) } @@ -1392,12 +1423,12 @@ impl<'a> Parser<'a> { match self.token() { Some(Token::Arrow) => { self.consume(); - self.parse_value_alias(results, ctx)?; + self.parse_value_alias(&results, ctx)?; } Some(Token::Equal) => { self.consume(); self.parse_instruction( - results, + &results, srcloc, encoding, result_locations, @@ -1408,7 +1439,7 @@ impl<'a> Parser<'a> { _ if !results.is_empty() => return err!(self.loc, "expected -> or ="), _ => { self.parse_instruction( - results, + &results, srcloc, encoding, result_locations, @@ -1512,7 +1543,7 @@ impl<'a> Parser<'a> { isa.register_info() .parse_regunit(name) .map(ValueLoc::Reg) - .ok_or(self.error("invalid register value location")) + .ok_or_else(|| self.error("invalid register value location")) } else { err!(self.loc, "value location requires exactly one isa") } @@ -1601,7 +1632,7 @@ impl<'a> Parser<'a> { // // value_alias ::= [inst-results] "->" Value(v) // - fn parse_value_alias(&mut self, results: Vec, ctx: &mut Context) -> Result<()> { + fn parse_value_alias(&mut self, results: &[Value], ctx: &mut Context) -> Result<()> { if results.len() != 1 { return err!(self.loc, "wrong number of aliases"); } @@ -1612,6 +1643,7 @@ impl<'a> Parser<'a> { results[0], ); ctx.map.def_value(results[0], &self.loc)?; + ctx.aliases.push(results[0]); Ok(()) } @@ -1621,7 +1653,7 @@ impl<'a> Parser<'a> { // fn parse_instruction( &mut self, - results: Vec, + results: &[Value], srcloc: ir::SourceLoc, encoding: Option, result_locations: Option>, @@ -1629,7 +1661,7 @@ impl<'a> Parser<'a> { ebb: Ebb, ) -> Result<()> { // Define the result values. - for val in &results { + for val in results { ctx.map.def_value(*val, &self.loc)?; } @@ -1674,7 +1706,7 @@ impl<'a> Parser<'a> { let num_results = ctx.function.dfg.make_inst_results_for_parser( inst, ctrl_typevar, - &results, + results, ); ctx.function.layout.append_inst(inst, ebb); ctx.map.def_entity(inst.into(), &opcode_loc).expect( @@ -1703,7 +1735,7 @@ impl<'a> Parser<'a> { return err!( self.loc, "instruction produces {} result values, but {} locations were \ - specified", + specified", results.len(), result_locations.len() ); @@ -1749,11 +1781,31 @@ impl<'a> Parser<'a> { // explicit type specified. Look up `ctrl_value` to see if it was defined // already. // TBD: If it is defined in another block, the type should have been - // specified explicitly. It is unfortunate that the correctness of IL + // specified explicitly. It is unfortunate that the correctness of IR // depends on the layout of the blocks. let ctrl_src_value = inst_data .typevar_operand(&ctx.function.dfg.value_lists) .expect("Constraints <-> Format inconsistency"); + if !ctx.map.contains_value(ctrl_src_value) { + return err!( + self.loc, + "type variable required for polymorphic opcode, e.g. '{}.{}'; \ + can't infer from {} which is not yet defined", + opcode, + constraints.ctrl_typeset().unwrap().example(), + ctrl_src_value + ); + } + if !ctx.function.dfg.value_is_valid_for_parser(ctrl_src_value) { + return err!( + self.loc, + "type variable required for polymorphic opcode, e.g. '{}.{}'; \ + can't infer from {} which is not yet resolved", + opcode, + constraints.ctrl_typeset().unwrap().example(), + ctrl_src_value + ); + } ctx.function.dfg.value_type(ctrl_src_value) } else if constraints.is_polymorphic() { // This opcode does not support type inference, so the explicit type @@ -1784,11 +1836,9 @@ impl<'a> Parser<'a> { opcode ); } - } else { - // Treat it as a syntax error to speficy a typevar on a non-polymorphic opcode. - if ctrl_type != VOID { - return err!(self.loc, "{} does not take a typevar", opcode); - } + // Treat it as a syntax error to speficy a typevar on a non-polymorphic opcode. + } else if ctrl_type != VOID { + return err!(self.loc, "{} does not take a typevar", opcode); } Ok(ctrl_type) @@ -1839,36 +1889,26 @@ impl<'a> Parser<'a> { opcode: Opcode, ) -> Result { let idata = match opcode.format() { - InstructionFormat::Unary => { - InstructionData::Unary { - opcode, - arg: self.match_value("expected SSA value operand")?, - } - } - InstructionFormat::UnaryImm => { - InstructionData::UnaryImm { - opcode, - imm: self.match_imm64("expected immediate integer operand")?, - } - } - InstructionFormat::UnaryIeee32 => { - InstructionData::UnaryIeee32 { - opcode, - imm: self.match_ieee32("expected immediate 32-bit float operand")?, - } - } - InstructionFormat::UnaryIeee64 => { - InstructionData::UnaryIeee64 { - opcode, - imm: self.match_ieee64("expected immediate 64-bit float operand")?, - } - } - InstructionFormat::UnaryBool => { - InstructionData::UnaryBool { - opcode, - imm: self.match_bool("expected immediate boolean operand")?, - } - } + InstructionFormat::Unary => InstructionData::Unary { + opcode, + arg: self.match_value("expected SSA value operand")?, + }, + InstructionFormat::UnaryImm => InstructionData::UnaryImm { + opcode, + imm: self.match_imm64("expected immediate integer operand")?, + }, + InstructionFormat::UnaryIeee32 => InstructionData::UnaryIeee32 { + opcode, + imm: self.match_ieee32("expected immediate 32-bit float operand")?, + }, + InstructionFormat::UnaryIeee64 => InstructionData::UnaryIeee64 { + opcode, + imm: self.match_ieee64("expected immediate 64-bit float operand")?, + }, + InstructionFormat::UnaryBool => InstructionData::UnaryBool { + opcode, + imm: self.match_bool("expected immediate boolean operand")?, + }, InstructionFormat::UnaryGlobalVar => { let gv = self.match_gv("expected global variable")?; ctx.check_gv(gv, &self.loc)?; @@ -2355,13 +2395,13 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use cretonne::ir::{CallConv, ArgumentExtension, ArgumentPurpose}; - use cretonne::ir::types; use cretonne::ir::StackSlotKind; use cretonne::ir::entities::AnyEntity; - use testfile::{Details, Comment}; - use isaspec::IsaSpec; + use cretonne::ir::types; + use cretonne::ir::{ArgumentExtension, ArgumentPurpose, CallConv}; use error::Error; + use isaspec::IsaSpec; + use testfile::{Comment, Details}; #[test] fn argument_type() { @@ -2378,7 +2418,7 @@ mod tests { #[test] fn aliases() { let (func, details) = Parser::new( - "function %qux() native { + "function %qux() system_v { ebb0: v4 = iconst.i8 6 v3 -> v4 @@ -2402,10 +2442,10 @@ mod tests { #[test] fn signature() { - let sig = Parser::new("()native").parse_signature(None).unwrap(); + let sig = Parser::new("()system_v").parse_signature(None).unwrap(); assert_eq!(sig.params.len(), 0); assert_eq!(sig.returns.len(), 0); - assert_eq!(sig.call_conv, CallConv::Native); + assert_eq!(sig.call_conv, CallConv::SystemV); let sig2 = Parser::new("(i8 uext, f32, f64, i32 sret) -> i32 sext, f64 spiderwasm") .parse_signature(None) @@ -2419,7 +2459,7 @@ mod tests { // Old-style signature without a calling convention. assert_eq!( Parser::new("()").parse_signature(None).unwrap().to_string(), - "() native" + "() system_v" ); assert_eq!( Parser::new("() notacc") @@ -2456,7 +2496,7 @@ mod tests { #[test] fn stack_slot_decl() { let (func, _) = Parser::new( - "function %foo() native { + "function %foo() system_v { ss3 = incoming_arg 13 ss1 = spill_slot 1 }", @@ -2479,7 +2519,7 @@ mod tests { // Catch duplicate definitions. assert_eq!( Parser::new( - "function %bar() native { + "function %bar() system_v { ss1 = spill_slot 13 ss1 = spill_slot 1 }", @@ -2493,7 +2533,7 @@ mod tests { #[test] fn ebb_header() { let (func, _) = Parser::new( - "function %ebbs() native { + "function %ebbs() system_v { ebb0: ebb4(v3: i32): }", @@ -2516,7 +2556,7 @@ mod tests { fn comments() { let (func, Details { comments, .. }) = Parser::new( "; before - function %comment() native { ; decl + function %comment() system_v { ; decl ss10 = outgoing_arg 13 ; stackslot. ; Still stackslot. jt10 = jump_table ebb0 @@ -2559,7 +2599,7 @@ mod tests { test verify set enable_float=false ; still preamble - function %comment() native {}", + function %comment() system_v {}", ).unwrap(); assert_eq!(tf.commands.len(), 2); assert_eq!(tf.commands[0].command, "cfg"); @@ -2584,7 +2624,7 @@ mod tests { assert!( parse_test( "isa - function %foo() native {}", + function %foo() system_v {}", ).is_err() ); @@ -2592,14 +2632,14 @@ mod tests { parse_test( "isa riscv set enable_float=false - function %foo() native {}", + function %foo() system_v {}", ).is_err() ); match parse_test( "set enable_float=false isa riscv - function %foo() native {}", + function %foo() system_v {}", ).unwrap() .isa_spec { IsaSpec::None(_) => panic!("Expected some ISA"), @@ -2614,7 +2654,7 @@ mod tests { fn user_function_name() { // Valid characters in the name: let func = Parser::new( - "function u1:2() native { + "function u1:2() system_v { ebb0: trap int_divz }", @@ -2625,7 +2665,7 @@ mod tests { // Invalid characters in the name: let mut parser = Parser::new( - "function u123:abc() native { + "function u123:abc() system_v { ebb0: trap stk_ovf }", @@ -2634,7 +2674,7 @@ mod tests { // Incomplete function names should not be valid: let mut parser = Parser::new( - "function u() native { + "function u() system_v { ebb0: trap int_ovf }", @@ -2642,7 +2682,7 @@ mod tests { assert!(parser.parse_function(None).is_err()); let mut parser = Parser::new( - "function u0() native { + "function u0() system_v { ebb0: trap int_ovf }", @@ -2650,7 +2690,7 @@ mod tests { assert!(parser.parse_function(None).is_err()); let mut parser = Parser::new( - "function u0:() native { + "function u0:() system_v { ebb0: trap int_ovf }", diff --git a/lib/reader/src/sourcemap.rs b/lib/reader/src/sourcemap.rs index 704c097855..b30b172768 100644 --- a/lib/reader/src/sourcemap.rs +++ b/lib/reader/src/sourcemap.rs @@ -7,13 +7,13 @@ //! to parser clients. use cretonne::ir::entities::AnyEntity; -use cretonne::ir::{StackSlot, GlobalVar, Heap, JumpTable, Ebb, Value, SigRef, FuncRef}; -use error::{Result, Location}; +use cretonne::ir::{Ebb, FuncRef, GlobalVar, Heap, JumpTable, SigRef, StackSlot, Value}; +use error::{Location, Result}; use lexer::split_entity_name; use std::collections::HashMap; /// Mapping from entity names to source locations. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct SourceMap { // Store locations for entities, including instructions. locations: HashMap, diff --git a/lib/reader/src/testfile.rs b/lib/reader/src/testfile.rs index 8ec5271a07..7218f8cb72 100644 --- a/lib/reader/src/testfile.rs +++ b/lib/reader/src/testfile.rs @@ -6,10 +6,10 @@ use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; -use testcommand::TestCommand; +use error::Location; use isaspec::IsaSpec; use sourcemap::SourceMap; -use error::Location; +use testcommand::TestCommand; /// A parsed test case. /// diff --git a/lib/wasm/Cargo.toml b/lib/wasm/Cargo.toml index 27177521ee..4af0ab68ef 100644 --- a/lib/wasm/Cargo.toml +++ b/lib/wasm/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "cretonne-wasm" -version = "0.3.4" +version = "0.4.1" authors = ["The Cretonne Project Developers"] -description = "Translator from WebAssembly to Cretonne IL" +description = "Translator from WebAssembly to Cretonne IR" repository = "https://github.com/Cretonne/cretonne" license = "Apache-2.0" readme = "README.md" -keywords = [ "webassembly", "wasm" ] +keywords = ["webassembly", "wasm"] [lib] name = "cton_wasm" [dependencies] -wasmparser = "0.14.1" -cretonne = { path = "../cretonne", version = "0.3.4", default_features = false } -cretonne-frontend = { path = "../frontend", version = "0.3.4", default_features = false } +wasmparser = "0.15.1" +cretonne = { path = "../cretonne", version = "0.4.1", default_features = false } +cretonne-frontend = { path = "../frontend", version = "0.4.1", default_features = false } [dependencies.hashmap_core] version = "0.1.1" @@ -27,3 +27,7 @@ tempdir = "0.3.5" default = ["std"] std = ["cretonne/std", "cretonne-frontend/std"] core = ["hashmap_core", "cretonne/core", "cretonne-frontend/core"] + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "Cretonne/cretonne" } diff --git a/lib/wasm/README.md b/lib/wasm/README.md index f12f00d46a..946a41f26d 100644 --- a/lib/wasm/README.md +++ b/lib/wasm/README.md @@ -1,3 +1,2 @@ This crate performs the translation from a wasm module in binary format to the -in-memory representation of the [Cretonne](https://crates.io/crates/cretonne) -IL. +in-memory form of the [Cretonne](https://crates.io/crates/cretonne) IR. diff --git a/lib/wasm/src/code_translator.rs b/lib/wasm/src/code_translator.rs index c9b8732989..9dbace8556 100644 --- a/lib/wasm/src/code_translator.rs +++ b/lib/wasm/src/code_translator.rs @@ -1,5 +1,5 @@ //! This module contains the bulk of the interesting code performing the translation between -//! WebAssembly and Cretonne IL. +//! WebAssembly and Cretonne IR. //! //! The translation is done in one pass, opcode by opcode. Two main data structures are used during //! code translations: the value stack and the control stack. The value stack mimics the execution @@ -22,21 +22,23 @@ //! //! That is why `translate_function_body` takes an object having the `WasmRuntime` trait as //! argument. -use cretonne::ir::{self, InstBuilder, MemFlags, JumpTableData}; +use cretonne::ir::condcodes::{FloatCC, IntCC}; use cretonne::ir::types::*; -use cretonne::ir::condcodes::{IntCC, FloatCC}; +use cretonne::ir::{self, InstBuilder, JumpTableData, MemFlags}; use cretonne::packed_option::ReservedValue; use cton_frontend::{FunctionBuilder, Variable}; -use wasmparser::{Operator, MemoryImmediate}; -use translation_utils::{f32_translation, f64_translation, type_to_type, num_return_values}; -use translation_utils::{TableIndex, SignatureIndex, FunctionIndex, MemoryIndex}; -use state::{TranslationState, ControlStackFrame}; -use std::collections::{HashMap, hash_map}; use environ::{FuncEnvironment, GlobalValue}; -use std::{i32, u32}; +use state::{ControlStackFrame, TranslationState}; +use std::collections::{hash_map, HashMap}; use std::vec::Vec; +use std::{i32, u32}; +use translation_utils::{FunctionIndex, MemoryIndex, SignatureIndex, TableIndex}; +use translation_utils::{num_return_values, type_to_type, f32_translation, f64_translation}; +use wasmparser::{MemoryImmediate, Operator}; -/// Translates wasm operators into Cretonne IL instructions. Returns `true` if it inserted +// Clippy warns about "flags: _" but its important to document that the flags field is ignored +#[cfg_attr(feature = "cargo-clippy", allow(unneeded_field_pattern))] +/// Translates wasm operators into Cretonne IR instructions. Returns `true` if it inserted /// a return. pub fn translate_operator( op: Operator, @@ -45,7 +47,7 @@ pub fn translate_operator( environ: &mut FE, ) { if !state.reachable { - return translate_unreachable_operator(op, builder, state); + return translate_unreachable_operator(&op, builder, state); } // This big match treats all Wasm code operators. @@ -73,8 +75,9 @@ pub fn translate_operator( GlobalValue::Const(val) => val, GlobalValue::Memory { gv, ty } => { let addr = builder.ins().global_addr(environ.native_pointer(), gv); - // TODO: It is likely safe to set `aligned notrap` flags on a global load. - let flags = ir::MemFlags::new(); + let mut flags = ir::MemFlags::new(); + flags.set_notrap(); + flags.set_aligned(); builder.ins().load(ty, flags, addr, 0) } }; @@ -85,8 +88,9 @@ pub fn translate_operator( GlobalValue::Const(_) => panic!("global #{} is a constant", global_index), GlobalValue::Memory { gv, .. } => { let addr = builder.ins().global_addr(environ.native_pointer(), gv); - // TODO: It is likely safe to set `aligned notrap` flags on a global store. - let flags = ir::MemFlags::new(); + let mut flags = ir::MemFlags::new(); + flags.set_notrap(); + flags.set_aligned(); let val = state.pop1(); builder.ins().store(flags, val, addr, 0); } @@ -138,6 +142,7 @@ pub fn translate_operator( builder.ins().jump(loop_body, &[]); state.push_loop(loop_body, next, num_return_values(ty)); builder.switch_to_block(loop_body); + environ.translate_loop_header(builder.cursor()); } Operator::If { ty } => { let val = state.pop1(); @@ -187,8 +192,8 @@ pub fn translate_operator( } Operator::End => { let frame = state.control_stack.pop().unwrap(); - let return_count = frame.num_return_values(); if !builder.is_unreachable() || !builder.is_pristine() { + let return_count = frame.num_return_values(); builder.ins().jump( frame.following_code(), state.peekn(return_count), @@ -197,9 +202,8 @@ pub fn translate_operator( builder.switch_to_block(frame.following_code()); builder.seal_block(frame.following_code()); // If it is a loop we also have to seal the body loop block - match frame { - ControlStackFrame::Loop { header, .. } => builder.seal_block(header), - _ => {} + if let ControlStackFrame::Loop { header, .. } = frame { + builder.seal_block(header) } state.stack.truncate(frame.original_stack_size()); state.stack.extend_from_slice( @@ -247,27 +251,7 @@ pub fn translate_operator( state.popn(return_count); state.reachable = false; } - Operator::BrIf { relative_depth } => { - let val = state.pop1(); - let i = state.control_stack.len() - 1 - (relative_depth as usize); - let (return_count, br_destination) = { - let frame = &mut state.control_stack[i]; - // The values returned by the branch are still available for the reachable - // code that comes after it - frame.set_branched_to_exit(); - let return_count = if frame.is_loop() { - 0 - } else { - frame.num_return_values() - }; - (return_count, frame.br_destination()) - }; - builder.ins().brnz( - val, - br_destination, - state.peekn(return_count), - ); - } + Operator::BrIf { relative_depth } => translate_br_if(relative_depth, builder, state), Operator::BrTable { table } => { let (depths, default) = table.read_table(); let mut min_depth = default; @@ -765,101 +749,45 @@ pub fn translate_operator( } /**************************** Comparison Operators **********************************/ Operator::I32LtS | Operator::I64LtS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::SignedLessThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::SignedLessThan, builder, state) } Operator::I32LtU | Operator::I64LtU => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::UnsignedLessThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::UnsignedLessThan, builder, state) } Operator::I32LeS | Operator::I64LeS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::SignedLessThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::SignedLessThanOrEqual, builder, state) } Operator::I32LeU | Operator::I64LeU => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp( - IntCC::UnsignedLessThanOrEqual, - arg1, - arg2, - ); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::UnsignedLessThanOrEqual, builder, state) } Operator::I32GtS | Operator::I64GtS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::SignedGreaterThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::SignedGreaterThan, builder, state) } Operator::I32GtU | Operator::I64GtU => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::UnsignedGreaterThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::UnsignedGreaterThan, builder, state) } Operator::I32GeS | Operator::I64GeS => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp( - IntCC::SignedGreaterThanOrEqual, - arg1, - arg2, - ); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::SignedGreaterThanOrEqual, builder, state) } Operator::I32GeU | Operator::I64GeU => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp( - IntCC::UnsignedGreaterThanOrEqual, - arg1, - arg2, - ); - state.push1(builder.ins().bint(I32, val)); + translate_icmp(IntCC::UnsignedGreaterThanOrEqual, builder, state) } Operator::I32Eqz | Operator::I64Eqz => { let arg = state.pop1(); let val = builder.ins().icmp_imm(IntCC::Equal, arg, 0); state.push1(builder.ins().bint(I32, val)); } - Operator::I32Eq | Operator::I64Eq => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::Equal, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Operator::F32Eq | Operator::F64Eq => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::Equal, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Operator::I32Ne | Operator::I64Ne => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().icmp(IntCC::NotEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Operator::F32Ne | Operator::F64Ne => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::NotEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Operator::F32Gt | Operator::F64Gt => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::GreaterThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } + Operator::I32Eq | Operator::I64Eq => translate_icmp(IntCC::Equal, builder, state), + Operator::F32Eq | Operator::F64Eq => translate_fcmp(FloatCC::Equal, builder, state), + Operator::I32Ne | Operator::I64Ne => translate_icmp(IntCC::NotEqual, builder, state), + Operator::F32Ne | Operator::F64Ne => translate_fcmp(FloatCC::NotEqual, builder, state), + Operator::F32Gt | Operator::F64Gt => translate_fcmp(FloatCC::GreaterThan, builder, state), Operator::F32Ge | Operator::F64Ge => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::GreaterThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); - } - Operator::F32Lt | Operator::F64Lt => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::LessThan, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_fcmp(FloatCC::GreaterThanOrEqual, builder, state) } + Operator::F32Lt | Operator::F64Lt => translate_fcmp(FloatCC::LessThan, builder, state), Operator::F32Le | Operator::F64Le => { - let (arg1, arg2) = state.pop2(); - let val = builder.ins().fcmp(FloatCC::LessThanOrEqual, arg1, arg2); - state.push1(builder.ins().bint(I32, val)); + translate_fcmp(FloatCC::LessThanOrEqual, builder, state) } Operator::Wake { .. } | Operator::I32Wait { .. } | @@ -932,15 +860,17 @@ pub fn translate_operator( } } +// Clippy warns us of some fields we are deliberately ignoring +#[cfg_attr(feature = "cargo-clippy", allow(unneeded_field_pattern))] /// Deals with a Wasm instruction located in an unreachable portion of the code. Most of them /// are dropped but special ones like `End` or `Else` signal the potential end of the unreachable /// portion so the translation state muts be updated accordingly. fn translate_unreachable_operator( - op: Operator, + op: &Operator, builder: &mut FunctionBuilder, state: &mut TranslationState, ) { - match op { + match *op { Operator::If { ty: _ } => { // Push a placeholder control stack entry. The if isn't reachable, // so we don't have any branches anywhere. @@ -952,27 +882,25 @@ fn translate_unreachable_operator( } Operator::Else => { let i = state.control_stack.len() - 1; - match state.control_stack[i] { - ControlStackFrame::If { - branch_inst, - ref mut reachable_from_top, - .. - } => { - if *reachable_from_top { - // We have a branch from the top of the if to the else. - state.reachable = true; - // And because there's an else, there can no longer be a - // branch from the top directly to the end. - *reachable_from_top = false; + if let ControlStackFrame::If { + branch_inst, + ref mut reachable_from_top, + .. + } = state.control_stack[i] + { + if *reachable_from_top { + // We have a branch from the top of the if to the else. + state.reachable = true; + // And because there's an else, there can no longer be a + // branch from the top directly to the end. + *reachable_from_top = false; - // We change the target of the branch instruction - let else_ebb = builder.create_ebb(); - builder.change_jump_destination(branch_inst, else_ebb); - builder.seal_block(else_ebb); - builder.switch_to_block(else_ebb); - } + // We change the target of the branch instruction + let else_ebb = builder.create_ebb(); + builder.change_jump_destination(branch_inst, else_ebb); + builder.seal_block(else_ebb); + builder.switch_to_block(else_ebb); } - _ => {} } } Operator::End => { @@ -1016,7 +944,7 @@ fn translate_unreachable_operator( } } -// Get the address+offset to use for a heap access. +/// Get the address+offset to use for a heap access. fn get_heap_addr( heap: ir::Heap, addr32: ir::Value, @@ -1053,7 +981,7 @@ fn get_heap_addr( } } -// Translate a load instruction. +/// Translate a load instruction. fn translate_load( offset: u32, opcode: ir::Opcode, @@ -1066,6 +994,9 @@ fn translate_load( // We don't yet support multiple linear memories. let heap = state.get_heap(builder.func, 0, environ); let (base, offset) = get_heap_addr(heap, addr32, offset, environ.native_pointer(), builder); + // Note that we don't set `is_aligned` here, even if the load instruction's + // alignment immediate says it's aligned, because WebAssembly's immediate + // field is just a hint, while Cretonne's aligned flag needs a guarantee. let flags = MemFlags::new(); let (load, dfg) = builder.ins().Load( opcode, @@ -1077,7 +1008,7 @@ fn translate_load( state.push1(dfg.first_result(load)); } -// Translate a store instruction. +/// Translate a store instruction. fn translate_store( offset: u32, opcode: ir::Opcode, @@ -1091,6 +1022,7 @@ fn translate_store( // We don't yet support multiple linear memories. let heap = state.get_heap(builder.func, 0, environ); let (base, offset) = get_heap_addr(heap, addr32, offset, environ.native_pointer(), builder); + // See the comments in `translate_load` about the flags. let flags = MemFlags::new(); builder.ins().Store( opcode, @@ -1101,3 +1033,54 @@ fn translate_store( base, ); } + +fn translate_icmp( + cc: IntCC, + builder: &mut FunctionBuilder, + state: &mut TranslationState, +) { + let (arg0, arg1) = state.pop2(); + let val = builder.ins().icmp(cc, arg0, arg1); + state.push1(builder.ins().bint(I32, val)); +} + +fn translate_fcmp( + cc: FloatCC, + builder: &mut FunctionBuilder, + state: &mut TranslationState, +) { + let (arg0, arg1) = state.pop2(); + let val = builder.ins().fcmp(cc, arg0, arg1); + state.push1(builder.ins().bint(I32, val)); +} + +fn translate_br_if( + relative_depth: u32, + builder: &mut FunctionBuilder, + state: &mut TranslationState, +) { + let val = state.pop1(); + let (br_destination, inputs) = translate_br_if_args(relative_depth, state); + builder.ins().brnz(val, br_destination, inputs); +} + +fn translate_br_if_args( + relative_depth: u32, + state: &mut TranslationState, +) -> (ir::Ebb, &[ir::Value]) { + let i = state.control_stack.len() - 1 - (relative_depth as usize); + let (return_count, br_destination) = { + let frame = &mut state.control_stack[i]; + // The values returned by the branch are still available for the reachable + // code that comes after it + frame.set_branched_to_exit(); + let return_count = if frame.is_loop() { + 0 + } else { + frame.num_return_values() + }; + (return_count, frame.br_destination()) + }; + let inputs = state.peekn(return_count); + (br_destination, inputs) +} diff --git a/lib/wasm/src/environ/dummy.rs b/lib/wasm/src/environ/dummy.rs index 102e83ef79..49386fe2bb 100644 --- a/lib/wasm/src/environ/dummy.rs +++ b/lib/wasm/src/environ/dummy.rs @@ -1,16 +1,16 @@ //! "Dummy" environment for testing wasm translation. -use environ::{FuncEnvironment, GlobalValue, ModuleEnvironment}; -use translation_utils::{Global, Memory, Table, GlobalIndex, TableIndex, SignatureIndex, - FunctionIndex, MemoryIndex}; -use func_translator::FuncTranslator; -use cretonne::ir::{self, InstBuilder}; -use cretonne::ir::types::*; use cretonne::cursor::FuncCursor; +use cretonne::ir::types::*; +use cretonne::ir::{self, InstBuilder}; use cretonne::settings; -use wasmparser; -use std::vec::Vec; +use environ::{FuncEnvironment, GlobalValue, ModuleEnvironment}; +use func_translator::FuncTranslator; use std::string::String; +use std::vec::Vec; +use translation_utils::{FunctionIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, + Table, TableIndex}; +use wasmparser; /// Compute a `ir::ExternalName` for a given wasm function index. fn get_func_name(func_index: FunctionIndex) -> ir::ExternalName { @@ -120,7 +120,7 @@ impl DummyEnvironment { } } -/// The FuncEnvironment implementation for use by the `DummyEnvironment`. +/// The `FuncEnvironment` implementation for use by the `DummyEnvironment`. pub struct DummyFuncEnvironment<'dummy_environment> { pub mod_info: &'dummy_environment DummyModuleInfo, } @@ -208,7 +208,10 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ let ext = pos.ins().uextend(I64, callee); pos.ins().imul_imm(ext, 4) }; - let func_ptr = pos.ins().load(ptr, ir::MemFlags::new(), callee_offset, 0); + let mut mflags = ir::MemFlags::new(); + mflags.set_notrap(); + mflags.set_aligned(); + let func_ptr = pos.ins().load(ptr, mflags, callee_offset, 0); // Build a value list for the indirect call instruction containing the callee, call_args, // and the vmctx parameter. diff --git a/lib/wasm/src/environ/mod.rs b/lib/wasm/src/environ/mod.rs index f45dbd3161..e89995d8f8 100644 --- a/lib/wasm/src/environ/mod.rs +++ b/lib/wasm/src/environ/mod.rs @@ -1,7 +1,7 @@ //! Support for configurable wasm translation. -mod spec; mod dummy; +mod spec; -pub use environ::spec::{ModuleEnvironment, FuncEnvironment, GlobalValue}; pub use environ::dummy::DummyEnvironment; +pub use environ::spec::{FuncEnvironment, GlobalValue, ModuleEnvironment}; diff --git a/lib/wasm/src/environ/spec.rs b/lib/wasm/src/environ/spec.rs index 73c1d3ff7f..8c313f0ef3 100644 --- a/lib/wasm/src/environ/spec.rs +++ b/lib/wasm/src/environ/spec.rs @@ -1,12 +1,12 @@ //! All the runtime support necessary for the wasm to cretonne translation is formalized by the //! traits `FunctionEnvironment` and `ModuleEnvironment`. -use cretonne::ir::{self, InstBuilder}; use cretonne::cursor::FuncCursor; +use cretonne::ir::{self, InstBuilder}; use cretonne::settings::Flags; -use translation_utils::{SignatureIndex, FunctionIndex, TableIndex, GlobalIndex, MemoryIndex, - Global, Table, Memory}; -use std::vec::Vec; use std::string::String; +use std::vec::Vec; +use translation_utils::{FunctionIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, + Table, TableIndex}; /// The value of a WebAssembly global variable. #[derive(Clone, Copy)] @@ -26,7 +26,7 @@ pub enum GlobalValue { /// Environment affecting the translation of a single WebAssembly function. /// /// A `FuncEnvironment` trait object is required to translate a WebAssembly function to Cretonne -/// IL. The function environment provides information about the WebAssembly module as well as the +/// IR. The function environment provides information about the WebAssembly module as well as the /// runtime environment. pub trait FuncEnvironment { /// Get the flags for the current compilation. @@ -146,6 +146,14 @@ pub trait FuncEnvironment { index: MemoryIndex, heap: ir::Heap, ) -> ir::Value; + + /// Emit code at the beginning of every wasm loop. + /// + /// This can be used to insert explicit interrupt or safepoint checking at + /// the beginnings of loops. + fn translate_loop_header(&mut self, _pos: FuncCursor) { + // By default, don't emit anything. + } } /// An object satisfying the `ModuleEnvironment` trait can be passed as argument to the diff --git a/lib/wasm/src/func_translator.rs b/lib/wasm/src/func_translator.rs index cef629c7dc..c89fa21266 100644 --- a/lib/wasm/src/func_translator.rs +++ b/lib/wasm/src/func_translator.rs @@ -1,26 +1,26 @@ -//! Stand-alone WebAssembly to Cretonne IL translator. +//! Stand-alone WebAssembly to Cretonne IR translator. //! //! This module defines the `FuncTranslator` type which can translate a single WebAssembly -//! function to Cretonne IL guided by a `FuncEnvironment` which provides information about the +//! function to Cretonne IR guided by a `FuncEnvironment` which provides information about the //! WebAssembly module and the runtime environment. use code_translator::translate_operator; use cretonne::entity::EntityRef; -use cretonne::ir::{self, InstBuilder, Ebb}; -use cretonne::result::{CtonResult, CtonError}; +use cretonne::ir::{self, Ebb, InstBuilder}; +use cretonne::result::{CtonError, CtonResult}; use cretonne::timing; -use cton_frontend::{ILBuilder, FunctionBuilder, Variable}; +use cton_frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; use environ::FuncEnvironment; use state::TranslationState; use wasmparser::{self, BinaryReader}; -/// WebAssembly to Cretonne IL function translator. +/// WebAssembly to Cretonne IR function translator. /// -/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cretonne IL guided +/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cretonne IR guided /// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple /// functions which will reduce heap allocation traffic. pub struct FuncTranslator { - il_builder: ILBuilder, + func_ctx: FunctionBuilderContext, state: TranslationState, } @@ -28,7 +28,7 @@ impl FuncTranslator { /// Create a new translator. pub fn new() -> Self { Self { - il_builder: ILBuilder::new(), + func_ctx: FunctionBuilderContext::new(), state: TranslationState::new(), } } @@ -77,8 +77,8 @@ impl FuncTranslator { debug_assert_eq!(func.dfg.num_ebbs(), 0, "Function must be empty"); debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty"); - // This clears the `ILBuilder`. - let mut builder = FunctionBuilder::new(func, &mut self.il_builder); + // This clears the `FunctionBuilderContext`. + let mut builder = FunctionBuilder::new(func, &mut self.func_ctx); let entry_block = builder.create_ebb(); builder.append_ebb_params_for_function_params(entry_block); builder.switch_to_block(entry_block); // This also creates values for the arguments. @@ -232,10 +232,10 @@ fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc { #[cfg(test)] mod tests { - use cretonne::{ir, Context}; - use cretonne::ir::types::I32; - use environ::{DummyEnvironment, FuncEnvironment}; use super::FuncTranslator; + use cretonne::ir::types::I32; + use cretonne::{ir, Context}; + use environ::{DummyEnvironment, FuncEnvironment}; #[test] fn small1() { diff --git a/lib/wasm/src/lib.rs b/lib/wasm/src/lib.rs index d791a14846..d89072b5e4 100644 --- a/lib/wasm/src/lib.rs +++ b/lib/wasm/src/lib.rs @@ -1,5 +1,5 @@ -//! Performs the translation from a wasm module in binary format to the in-memory representation -//! of the Cretonne IL. More particularly, it translates the code of all the functions bodies and +//! Performs translation from a wasm module in binary format to the in-memory form +//! of Cretonne IR. More particularly, it translates the code of all the functions bodies and //! interacts with an environment implementing the //! [`ModuleEnvironment`](trait.ModuleEnvironment.html) //! trait to deal with tables, globals and linear memory. @@ -9,9 +9,9 @@ //! //! The main function of this module is [`translate_module`](fn.translate_module.html). -#![deny(missing_docs, - trivial_numeric_casts, - unused_extern_crates)] +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))] +#![cfg_attr(feature = "cargo-clippy", allow(new_without_default, redundant_field_names))] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(alloc))] @@ -24,24 +24,24 @@ extern crate alloc; #[cfg(not(feature = "std"))] extern crate hashmap_core; -extern crate wasmparser; -extern crate cton_frontend; #[macro_use(dbg)] extern crate cretonne; +extern crate cton_frontend; +extern crate wasmparser; mod code_translator; +mod environ; mod func_translator; mod module_translator; -mod environ; mod sections_translator; mod state; mod translation_utils; +pub use environ::{DummyEnvironment, FuncEnvironment, GlobalValue, ModuleEnvironment}; pub use func_translator::FuncTranslator; pub use module_translator::translate_module; -pub use environ::{FuncEnvironment, ModuleEnvironment, DummyEnvironment, GlobalValue}; -pub use translation_utils::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, SignatureIndex, - Global, GlobalInit, Table, Memory}; +pub use translation_utils::{FunctionIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex, + SignatureIndex, Table, TableIndex}; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/wasm/src/module_translator.rs b/lib/wasm/src/module_translator.rs index bb74db4276..e41712a6d6 100644 --- a/lib/wasm/src/module_translator.rs +++ b/lib/wasm/src/module_translator.rs @@ -1,16 +1,16 @@ //! Translation skeleton that traverses the whole WebAssembly module and call helper functions //! to deal with each part of it. use cretonne::timing; -use wasmparser::{ParserState, SectionCode, ParserInput, Parser, WasmDecoder, BinaryReaderError}; -use sections_translator::{SectionParsingError, parse_function_signatures, parse_import_section, - parse_function_section, parse_export_section, parse_start_section, - parse_memory_section, parse_global_section, parse_table_section, - parse_elements_section, parse_data_section}; use environ::ModuleEnvironment; +use sections_translator::{parse_data_section, parse_elements_section, parse_export_section, + parse_function_section, parse_function_signatures, parse_global_section, + parse_import_section, parse_memory_section, parse_start_section, + parse_table_section, SectionParsingError}; +use wasmparser::{BinaryReaderError, Parser, ParserInput, ParserState, SectionCode, WasmDecoder}; use std::string::String; -/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cretonne IL +/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cretonne IR /// [`Function`](../cretonne/ir/function/struct.Function.html). /// Returns the functions and also the mappings for imported functions and signature between the /// indexes in the wasm module and the indexes inside each functions. diff --git a/lib/wasm/src/sections_translator.rs b/lib/wasm/src/sections_translator.rs index 1bf7c9441c..ce5ed05995 100644 --- a/lib/wasm/src/sections_translator.rs +++ b/lib/wasm/src/sections_translator.rs @@ -7,17 +7,17 @@ //! The special case of the initialize expressions for table elements offsets or global variables //! is handled, according to the semantics of WebAssembly, to only specific expressions that are //! interpreted on the fly. -use translation_utils::{type_to_type, TableIndex, FunctionIndex, GlobalIndex, SignatureIndex, - MemoryIndex, Global, GlobalInit, Table, TableElementType, Memory}; -use cretonne::ir::{Signature, AbiParam, CallConv}; use cretonne; -use wasmparser::{Parser, ParserState, FuncType, ImportSectionEntryType, ExternalKind, WasmDecoder, - MemoryType, Operator}; -use wasmparser; -use std::str::from_utf8; +use cretonne::ir::{AbiParam, CallConv, Signature}; use environ::ModuleEnvironment; -use std::vec::Vec; +use std::str::from_utf8; use std::string::String; +use std::vec::Vec; +use translation_utils::{type_to_type, FunctionIndex, Global, GlobalIndex, GlobalInit, Memory, + MemoryIndex, SignatureIndex, Table, TableElementType, TableIndex}; +use wasmparser; +use wasmparser::{ExternalKind, FuncType, ImportSectionEntryType, MemoryType, Operator, Parser, + ParserState, WasmDecoder}; pub enum SectionParsingError { WrongSectionContent(String), @@ -36,7 +36,7 @@ pub fn parse_function_signatures( ref params, ref returns, }) => { - let mut sig = Signature::new(CallConv::Native); + let mut sig = Signature::new(CallConv::SystemV); sig.params.extend(params.iter().map(|ty| { let cret_arg: cretonne::ir::Type = type_to_type(ty).expect( "only numeric types are supported in function signatures", diff --git a/lib/wasm/src/state.rs b/lib/wasm/src/state.rs index 5ced6d6056..f95619d52b 100644 --- a/lib/wasm/src/state.rs +++ b/lib/wasm/src/state.rs @@ -6,8 +6,8 @@ use cretonne::ir::{self, Ebb, Inst, Value}; use environ::{FuncEnvironment, GlobalValue}; use std::collections::HashMap; -use translation_utils::{GlobalIndex, MemoryIndex, SignatureIndex, FunctionIndex}; use std::vec::Vec; +use translation_utils::{FunctionIndex, GlobalIndex, MemoryIndex, SignatureIndex}; /// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following /// fields: diff --git a/lib/wasm/src/translation_utils.rs b/lib/wasm/src/translation_utils.rs index 44c03fb6dd..bac50be7a6 100644 --- a/lib/wasm/src/translation_utils.rs +++ b/lib/wasm/src/translation_utils.rs @@ -1,7 +1,7 @@ //! Helper functions and structures for the translation. -use wasmparser; use cretonne; use std::u32; +use wasmparser; /// Index of a function (imported or defined) inside the WebAssembly module. pub type FunctionIndex = usize; diff --git a/lib/wasm/tests/wasm_testsuite.rs b/lib/wasm/tests/wasm_testsuite.rs index c551dbf783..273818f470 100644 --- a/lib/wasm/tests/wasm_testsuite.rs +++ b/lib/wasm/tests/wasm_testsuite.rs @@ -1,21 +1,19 @@ -extern crate cton_wasm; extern crate cretonne; +extern crate cton_wasm; extern crate tempdir; -use cton_wasm::{translate_module, DummyEnvironment}; -use std::path::PathBuf; -use std::fs::File; -use std::error::Error; -use std::io; -use std::str; -use std::io::prelude::*; -use std::process::Command; -use std::fs; -use cretonne::ir; -use cretonne::ir::entities::AnyEntity; -use cretonne::isa::TargetIsa; +use cretonne::print_errors::pretty_verifier_error; use cretonne::settings::{self, Configurable, Flags}; use cretonne::verifier; +use cton_wasm::{translate_module, DummyEnvironment}; +use std::error::Error; +use std::fs; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::path::PathBuf; +use std::process::Command; +use std::str; use tempdir::TempDir; #[test] @@ -27,7 +25,7 @@ fn testsuite() { // Ignore files starting with `.`, which could be editor temporary files if let Some(stem) = p.path().file_stem() { if let Some(stemstr) = stem.to_str() { - return !stemstr.starts_with("."); + return !stemstr.starts_with('.'); } } false @@ -37,7 +35,7 @@ fn testsuite() { let flags = Flags::new(&settings::builder()); for path in paths { let path = path.path(); - handle_module(path, &flags); + handle_module(&path, &flags); } } @@ -46,7 +44,7 @@ fn return_at_end() { let mut flag_builder = settings::builder(); flag_builder.enable("return_at_end").unwrap(); let flags = Flags::new(&flag_builder); - handle_module(PathBuf::from("../../wasmtests/return_at_end.wat"), &flags); + handle_module(&PathBuf::from("../../wasmtests/return_at_end.wat"), &flags); } fn read_wasm_file(path: PathBuf) -> Result, io::Error> { @@ -56,7 +54,7 @@ fn read_wasm_file(path: PathBuf) -> Result, io::Error> { Ok(buf) } -fn handle_module(path: PathBuf, flags: &Flags) { +fn handle_module(path: &PathBuf, flags: &Flags) { let data = match path.extension() { None => { panic!("the file extension is not wasm or wat"); @@ -105,29 +103,7 @@ fn handle_module(path: PathBuf, flags: &Flags) { translate_module(&data, &mut dummy_environ).unwrap(); for func in &dummy_environ.info.function_bodies { verifier::verify_function(func, flags) - .map_err(|err| panic!(pretty_verifier_error(func, None, err))) + .map_err(|err| panic!(pretty_verifier_error(func, None, &err))) .unwrap(); } } - - -/// Pretty-print a verifier error. -pub fn pretty_verifier_error( - func: &ir::Function, - isa: Option<&TargetIsa>, - err: verifier::Error, -) -> String { - let msg = err.to_string(); - let str1 = match err.location { - AnyEntity::Inst(inst) => { - format!( - "{}\n{}: {}\n\n", - msg, - inst, - func.dfg.display_inst(inst, isa) - ) - } - _ => String::from(format!("{}\n", msg)), - }; - format!("{}{}", str1, func.display(isa)) -} diff --git a/misc/vim/syntax/cton.vim b/misc/vim/syntax/cton.vim index 6ccacae7fc..4862c38ec8 100644 --- a/misc/vim/syntax/cton.vim +++ b/misc/vim/syntax/cton.vim @@ -14,7 +14,7 @@ endif syn spell notoplevel syn keyword ctonHeader test isa set -syn keyword ctonDecl function jump_table incoming_arg outgoing_arg spill_slot local emergency_slot +syn keyword ctonDecl function jump_table incoming_arg outgoing_arg spill_slot explicit_slot emergency_slot syn keyword ctonFilecheck check sameln nextln unordered not regex contained syn match ctonType /\<\([bif]\d\+\(x\d\+\)\?\)\|[if]flags\>/